Skip to main content
The com.phosra.sdk Kotlin library provides on-device child safety enforcement for Android using UsageStatsManager, DevicePolicyManager, VpnService, and AccessibilityService.

Overview

The SDK has a two-layer architecture:
  1. API Client — Communicates with the Phosra API to register devices, fetch compiled policies, submit enforcement reports, and acknowledge policy versions.
  2. Enforcement Engine — Translates the compiled policy document into native Android API calls to enforce content filters, screen time limits, web filtering, purchase controls, and more.

API Client

Handles device registration, policy sync, and reporting via the Phosra REST API.

Enforcement Engine

Maps policy rules to UsageStatsManager, DevicePolicyManager, VpnService, and AccessibilityService.

Requirements

RequirementVersion
Android8.0+ (API 26)
Kotlin1.9+
Gradle8+
compileSdk35
targetSdk35

Installation

The com.phosra:sdk Android library is in private preview — it is not published to Maven Central or Google’s Maven repo yet. Email developers@phosra.com for the AAR / private Maven URL and access credentials. Once you have access, add the private repository and the dependency to your Gradle build:
settings.gradle.kts
// Private preview: repo URL + credentials provided by Phosra
dependencyResolutionManagement {
    repositories {
        maven {
            url = uri("<private maven URL provided by Phosra>")
            credentials {
                username = providers.gradleProperty("phosraMavenUser").get()
                password = providers.gradleProperty("phosraMavenToken").get()
            }
        }
    }
}
Then add the dependency to your module-level build.gradle.kts:
dependencies {
    implementation("com.phosra:sdk:1.0.0")
}
The SDK pulls in the following transitive dependencies:
  • kotlinx-coroutines-android — Async operations
  • kotlinx-serialization-json — JSON parsing
  • okhttp3 — HTTP networking
  • androidx.work:work-runtime-ktx — Background sync
  • androidx.security:security-crypto — Encrypted key storage

Quick Start

1
Request Required Permissions
2
Android requires several special permissions for parental control enforcement. Guide the user through each:
3
import com.phosra.sdk.permissions.PermissionManager

val permissionManager = PermissionManager(context)
val missing = permissionManager.getMissingPermissions()

for (permission in missing) {
    permissionManager.requestPermission(activity, permission)
}
4
Register the Device
5
The parent initiates device registration from their authenticated session:
6
import com.phosra.sdk.PhosraClient
import com.phosra.sdk.PhosraConfiguration
import com.phosra.sdk.models.RegisterDeviceRequest

val config = PhosraConfiguration(
    parentToken = parentJWT,
    childId = childUUID
)
val client = PhosraClient(config)

val request = RegisterDeviceRequest(
    deviceName = "Emma's Pixel",
    deviceModel = android.os.Build.MODEL,
    osVersion = android.os.Build.VERSION.RELEASE,
    appVersion = BuildConfig.VERSION_NAME,
    capabilities = listOf("UsageStats", "DeviceAdmin", "VPN", "Accessibility")
)

val response = client.registerDevice(request)
7
Store the API Key
8
The API key is returned exactly once during registration. Store it in EncryptedSharedPreferences:
9
import com.phosra.sdk.storage.KeystoreHelper

KeystoreHelper.saveDeviceKey(context, response.apiKey)
10
Schedule Background Sync
11
Set up periodic policy sync using WorkManager:
12
import com.phosra.sdk.sync.PolicySyncWorker
import androidx.work.*
import java.util.concurrent.TimeUnit

val syncRequest = PeriodicWorkRequestBuilder<PolicySyncWorker>(
    15, TimeUnit.MINUTES
).setConstraints(
    Constraints.Builder()
        .setRequiredNetworkType(NetworkType.CONNECTED)
        .build()
).build()

WorkManager.getInstance(context).enqueueUniquePeriodicWork(
    "phosra_policy_sync",
    ExistingPeriodicWorkPolicy.KEEP,
    syncRequest
)
13
Fetch and Apply the Policy
14
val deviceKey = KeystoreHelper.loadDeviceKey(context)!!
val deviceConfig = PhosraConfiguration(deviceKey = deviceKey)
val deviceClient = PhosraClient(deviceConfig)

val policy = deviceClient.fetchPolicy()
println("Applying policy v${policy.version} for ${policy.ageGroup}")

// Apply via enforcement engine
val engine = EnforcementEngine(context)
val results = engine.apply(policy)

// Report results back to Phosra
deviceClient.submitReport(DeviceReport(
    reportType = ReportType.ENFORCEMENT_STATUS,
    payload = EnforcementStatusPayload(
        policyVersion = policy.version,
        results = results
    )
))
deviceClient.ackPolicyVersion(policy.version)

Device Registration

The registration flow requires parent authentication and produces a device-specific API key:
Parent App                    Phosra API                  Child Device
    |                             |                           |
    |-- POST /children/{id}/devices ------------------------> |
    |   (Authorization: Bearer parentJWT)                     |
    |                             |                           |
    |   <-- { device, api_key } --|                           |
    |                             |                           |
    |   -- Transfer api_key ------------------------------>   |
    |                             |                           | EncryptedSharedPrefs
    |                             |                           |
The RegisterDeviceRequest includes device metadata and capability declarations:
RegisterDeviceRequest(
    deviceName = "Emma's Pixel",
    deviceModel = "Pixel 8 Pro",
    osVersion = "14",
    appVersion = "1.0.0",
    capabilities = listOf(
        "UsageStats",        // UsageStatsManager access
        "DeviceAdmin",       // DevicePolicyManager enrollment
        "VPN",               // Local VPN for DNS filtering
        "Accessibility",     // Foreground app detection
        "NotificationListener" // Notification filtering
    )
)

Policy Sync

WorkManager-Based Sync

The SDK uses WorkManager for reliable background policy synchronization:
class PolicySyncWorker(
    context: Context,
    params: WorkerParameters
) : CoroutineWorker(context, params) {

    override suspend fun doWork(): Result {
        val deviceKey = KeystoreHelper.loadDeviceKey(applicationContext) ?: return Result.failure()
        val client = PhosraClient(PhosraConfiguration(deviceKey = deviceKey))

        return try {
            val currentVersion = PreferenceManager.getCurrentPolicyVersion(applicationContext)
            val policy = client.fetchPolicy(sinceVersion = currentVersion) ?: return Result.success()

            val engine = EnforcementEngine(applicationContext)
            val results = engine.apply(policy)

            client.submitReport(DeviceReport(
                reportType = ReportType.ENFORCEMENT_STATUS,
                payload = EnforcementStatusPayload(
                    policyVersion = policy.version,
                    results = results
                )
            ))
            client.ackPolicyVersion(policy.version)

            PreferenceManager.setCurrentPolicyVersion(applicationContext, policy.version)
            Result.success()
        } catch (e: Exception) {
            Result.retry()
        }
    }
}

Firebase Cloud Messaging (FCM)

For immediate policy refresh when a parent modifies rules, handle FCM data messages:
class PhosraMessagingService : FirebaseMessagingService() {

    override fun onMessageReceived(message: RemoteMessage) {
        val event = message.data["phosra_event"] ?: return
        val version = message.data["phosra_version"]?.toIntOrNull() ?: return

        if (event == "policy.updated") {
            val currentVersion = PreferenceManager.getCurrentPolicyVersion(this)
            if (version > currentVersion) {
                // Enqueue immediate one-time sync
                val syncRequest = OneTimeWorkRequestBuilder<PolicySyncWorker>()
                    .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
                    .build()
                WorkManager.getInstance(this).enqueue(syncRequest)
            }
        }
    }
}

Enforcement Engine

Each enforcer handles a specific policy section and maps it to the appropriate Android API.

Content Filter Enforcer

Monitors the foreground app using UsageStatsManager and AccessibilityService. Blocks restricted apps by displaying a full-screen overlay.
Policy FieldAndroid APIMechanism
contentFilter.blockedAppsUsageStatsManager + OverlayDetect foreground, show blocking overlay
contentFilter.ageRatingPackageManagerCheck app age rating metadata
contentFilter.allowlistModeUsageStatsManager + OverlayBlock all apps not in allowedApps
// Detect foreground app and block if restricted
val usageStatsManager = context.getSystemService(UsageStatsManager::class.java)
val events = usageStatsManager.queryEvents(startTime, System.currentTimeMillis())

val event = UsageEvents.Event()
while (events.hasNextEvent()) {
    events.getNextEvent(event)
    if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) {
        if (event.packageName in policy.contentFilter.blockedApps) {
            showBlockingOverlay(event.packageName)
        }
    }
}

Screen Time Enforcer

Tracks cumulative daily usage and enforces time limits.
Policy FieldAndroid APIMechanism
screenTime.dailyLimitMinutesUsageStatsManagerAggregate daily usage, lock when exceeded
screenTime.perAppLimitsUsageStatsManagerTrack per-package usage
screenTime.downtimeWindowsAlarmManagerSchedule device lock during downtime
screenTime.scheduleAlarmManagerEnforce allowed-hours windows

Web Filter Enforcer

Runs a local VPN service that intercepts DNS queries and blocks restricted domains.
Policy FieldAndroid APIMechanism
webFilter.blockedDomainsVpnServiceDNS interception, return NXDOMAIN
webFilter.safeSearchVpnServiceRedirect search DNS to safe search IPs
webFilter.blockedCategoriesVpnServiceDNS-based category blocking
webFilter.levelVpnServicePreset domain blocklists
class PhosraVpnService : VpnService() {
    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val builder = Builder()
            .addAddress("10.0.0.2", 32)
            .addDnsServer("10.0.0.1")
            .setSession("Phosra Web Filter")

        val tunnel = builder.establish()
        // DNS query interception loop
        // Block domains matching policy.webFilter.blockedDomains
        return START_STICKY
    }
}

Purchase Enforcer

Policy FieldAndroid APIMechanism
purchases.requireApprovalDevicePolicyManagerRestrict Play Store access
purchases.blockIAPDevicePolicyManagerDisable in-app purchase flows
purchases.spendingCapUSDNoneTracked in-app via Play Billing observer

Social Enforcer

Policy FieldAndroid APIMechanism
social.chatModeOverlay + UsageStatsBlock messaging apps based on mode
social.dmRestrictionOverlay + UsageStatsBlock DM-capable apps
social.multiplayerModeOverlay + UsageStatsBlock multiplayer gaming apps

Notification Enforcer

Policy FieldAndroid APIMechanism
notifications.curfewStart/EndNotificationListenerServiceSuppress notifications during curfew
notifications.usageTimerMinutesAlarmManagerFire reminder notification at interval

Permission Management

Android requires several special permissions. Each must be granted by the parent through system settings.
Required for screen time tracking and foreground app detection.
val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
startActivity(intent)
Check: AppOpsManager.checkOpNoThrow(AppOpsManager.OPSTR_GET_USAGE_STATS, ...)

Reporting

The SDK submits two types of reports to the Phosra API:

Enforcement Status Report

Sent after applying a policy, contains per-category results:
DeviceReport(
    reportType = ReportType.ENFORCEMENT_STATUS,
    payload = EnforcementStatusPayload(
        policyVersion = 3,
        results = listOf(
            CategoryEnforcementResult(
                category = "content_rating",
                status = "enforced",   // "enforced", "partial", "failed", "unsupported"
                framework = "UsageStatsManager"
            ),
            CategoryEnforcementResult(
                category = "web_filter_level",
                status = "enforced",
                framework = "VpnService"
            )
        )
    )
)

Screen Time Report

Aggregated daily usage data:
DeviceReport(
    reportType = ReportType.SCREEN_TIME,
    payload = ScreenTimePayload(
        byCategory = mapOf("social-media" to 45, "gaming" to 30, "education" to 60),
        topApps = mapOf("com.instagram.android" to 25, "com.google.android.youtube" to 20),
        totalMinutes = 135,
        date = "2026-02-23"
    )
)

CompiledPolicy Structure

The compiled policy is fetched from GET /device/policy and contains all enforcement rules organized by section:
{
  "version": 3,
  "child_id": "uuid",
  "child_age": 10,
  "age_group": "tween",
  "policy_id": "uuid",
  "status": "active",
  "generated_at": "2026-02-23T14:30:00Z",
  "content_filter": {
    "age_rating": "E10+",
    "max_ratings": { "esrb": "E10+", "mpaa": "PG" },
    "blocked_apps": ["com.instagram.android"],
    "allowed_apps": [],
    "allowlist_mode": false
  },
  "screen_time": {
    "daily_limit_minutes": 120,
    "per_app_limits": [
      { "bundle_id": "com.google.android.youtube", "daily_minutes": 30 }
    ],
    "downtime_windows": [
      { "days_of_week": ["monday","tuesday","wednesday","thursday","friday"],
        "start_time": "21:00", "end_time": "07:00" }
    ],
    "always_allowed_apps": ["com.android.dialer", "com.android.mms", "com.google.android.apps.maps"]
  },
  "purchases": {
    "require_approval": true,
    "block_iap": false,
    "spending_cap_usd": 10.00
  },
  "privacy": {
    "location_sharing_enabled": true,
    "profile_visibility": "private",
    "account_creation_approval": true,
    "data_sharing_restricted": true
  },
  "social": {
    "chat_mode": "friends_only",
    "dm_restriction": "contacts_only",
    "multiplayer_mode": "friends_only"
  },
  "notifications": {
    "curfew_start": "21:00",
    "curfew_end": "07:00",
    "usage_timer_minutes": 30
  },
  "web_filter": {
    "level": "moderate",
    "safe_search": true,
    "blocked_domains": ["reddit.com"],
    "allowed_domains": ["khanacademy.org"],
    "blocked_categories": ["gambling", "adult"]
  }
}

Supported Rule Categories

The 45 OCSS rule categories the Android SDK maps to native framework primitives (of the 123 categories in the OCSS rule registry; the remainder are policy-only or census-enforced and carry no client-side Android verb):

Content Rules

CategoryAndroid APIMechanism
content_ratingPackageManagerApp age rating metadata check
content_block_titleUsageStatsManager + OverlayBlock by package name
content_allow_titleUsageStatsManager + OverlayAllow only listed packages
content_allowlist_modeUsageStatsManager + OverlayBlock all unlisted apps
content_descriptor_blockPackageManagerContent descriptor filtering

Time Rules

CategoryAndroid APIMechanism
time_daily_limitUsageStatsManagerAggregate usage, lock at limit
time_scheduled_hoursAlarmManagerAllow usage only during scheduled hours
time_per_app_limitUsageStatsManagerPer-package usage tracking
time_downtimeAlarmManagerLock device during downtime windows

Purchase Rules

CategoryAndroid APIMechanism
purchase_approvalDevicePolicyManagerRestrict Play Store access
purchase_block_iapDevicePolicyManagerBlock in-app purchase flows
purchase_spending_capNonePlay Billing observer (in-app)

Social Rules

CategoryAndroid APIMechanism
social_contactsContactsContractRestrict contact access
social_chat_controlUsageStatsManager + OverlayBlock messaging apps
social_multiplayerUsageStatsManager + OverlayBlock multiplayer gaming apps

Web Rules

CategoryAndroid APIMechanism
web_safesearchVpnServiceDNS redirect to safe search
web_category_blockVpnServiceDNS-based category blocking
web_custom_allowlistVpnServiceAllow only listed domains
web_custom_blocklistVpnServiceBlock listed domains
web_filter_levelVpnServicePreset blocklist by level

Privacy Rules

CategoryAndroid APIMechanism
privacy_locationLocationManagerApp-level location permission management
privacy_profile_visibilityNoneApp-level enforcement
privacy_data_sharingNoneApp-level enforcement
privacy_account_creationNoneIn-app parental gate

Monitoring Rules

CategoryAndroid APIMechanism
monitoring_activityUsageStatsManagerQuery and report usage data
monitoring_alertsUsageStatsManager + AlarmManagerThreshold-based alerts

Engagement Rules

CategoryAndroid APIMechanism
algo_feed_controlUsageStatsManager + OverlayBlock/limit social apps
addictive_design_controlUsageStatsManager + OverlayBlock offending apps

Notification Rules

CategoryAndroid APIMechanism
notification_curfewNotificationListenerServiceSuppress during curfew
usage_timer_notificationAlarmManagerPeriodic usage reminder

Legislation-Driven Rules

CategoryAndroid APIMechanism
targeted_ad_blockNoneApp-level (no system API)
dm_restrictionUsageStatsManager + OverlayBlock messaging apps
age_gateNoneIn-app enforcement
data_deletion_requestNoneApp-level support flow
geolocation_opt_inLocationManagerPer-app permission management

Compliance Rules

CategoryAndroid APIMechanism
csam_reportingNoneServer-side detection
library_filter_complianceVpnServiceDNS-based web filtering
ai_minor_interactionUsageStatsManager + OverlayBlock AI apps
social_media_min_ageUsageStatsManager + OverlayBlock social apps for underage
image_rights_minorNoneApp-level enforcement

Parental / Legislative Rules

CategoryAndroid APIMechanism
parental_consent_gateNoneIn-app parental auth flow
parental_event_notificationAlarmManager + FCMPush to parent on flagged events
screen_time_reportUsageStatsManagerGenerate and submit usage summary
commercial_data_banNoneApp/server-level policy
algorithmic_auditNonePlatform transparency requirement

Google Play Compliance

Apps using UsageStatsManager, AccessibilityService, VpnService, and Device Admin require additional review by Google Play. Plan for a longer review cycle.

Required Declarations

  1. Permissions Declaration Form: Submit in Play Console for QUERY_ALL_PACKAGES, PACKAGE_USAGE_STATS, and Accessibility Service usage.
  2. Data Safety section: Declare all data collected from child devices. Phosra collects:
    • App usage statistics (aggregated)
    • Web activity (domain-level, via DNS)
    • Device metadata (model, OS version)
  3. VPN usage: Declare that the VPN is used exclusively for local DNS filtering, not for routing traffic through external servers.
  4. Target audience: Set to “Parents” — the app is a parental control tool, not a child-facing app.
  5. Families Policy: If your app appears in the Play Store’s Family section, ensure compliance with Google’s Families Policy.

Tips for Approval

  • Provide a detailed app description explaining the parental control use case
  • Include a demo video showing the parent setup flow and enforcement in action
  • Reference applicable child safety legislation (COPPA, KOSA, etc.)
  • Respond promptly to any review team questions about permission usage