All projects

Soundmind

Mohani Child Smartphone Management

A service where parents' control commands are delivered via push. Pushes get lost, and children turn off permissions. My job was to keep control intact in both situations.

Role
Server, child app, parent app
Period
2025.07 ~ Present
Stack
Spring BootReact NativeAndroidSamsung Knox SDKFCMRedis
App / clientNativeServerWorkerStorageExternal
Commands travel over push, but push is never trusted: the device polls a settings version and catches up on its own, and a watchdog keeps blocking even with accessibility off.Hover a block to highlight its flows. Drag to pan.

Outcome

  • Version counter

    Recovery mechanism that lets devices catch up on their own when a push is lost

  • Dual-layer

    A structure that keeps blocking active even when accessibility permission is turned off

01The problem

When a parent sets an app block or a sleep schedule, that command goes to the child's device via push. But push delivery has no guarantee. If it gets lost, the parent's screen shows the block as active while nothing has happened on the device. There was another axis of failure too. If the child turns off accessibility permission, the blocking itself is neutralized. And push loss is not bad luck: a screen-off device sinks into doze, which delays or batches push delivery and suppresses background polling for the same reason. Control had to be designed on top of a channel with no delivery guarantee against doze.

02What I decided

I changed the design to stop trusting push. Every time a setting changes, a per-child version number is atomically incremented in the DB, and the device periodically compares that number and fetches the latest settings on its own if it has fallen behind. Push is just a signal that brings that check forward. For the permission problem, I added redundancy with a separate watchdog service: if accessibility is turned off, it periodically checks usage records and keeps blocking disallowed apps.

  • Atomically increment a settings version counter, with devices comparing it and self-recovering
  • Assume polling is suppressed in doze: version checks and policy re-evaluation piggyback on every wake moment, such as screen-on, app switches, boot, and push receipt, so the device catches up without constant polling
  • When accessibility is off, a watchdog service keeps blocking based on usage records
  • For outdated bundles, the server identifies the generation via a response header and prompts a forced update
  • Deployment set up as a pipeline where pushing code automatically flows through to traffic switching

03The first day is a gate

Control here does not begin when the app is installed. Policy only takes hold once the child receives the device and finishes onboarding, and that window is the loosest moment there is. Skip a single permission and blocking is half-blind; leave the vendor management license inactive and the strong instruments, firewall rules and app disabling, drop out entirely. So onboarding became a gate the device cannot get past rather than a walkthrough.

  • Permission requests are split into stages, and each stage asks the system again whether the grant actually happened before advancing. Having moved past a screen and holding a permission are not the same fact
  • If onboarding completes while the management license is still inactive, a full-screen overlay locks the device until it is reactivated. Rather than watching constantly, it re-evaluates only on wake moments: screen-on, app switches, boot, and license results
  • That cover must never block calls or messages. While the phone app is in front or a call is ringing, the cover drops immediately so emergency contact is guaranteed
  • Some capabilities require per-vendor approval, so filing and obtaining that allowance from the manufacturer became part of the rollout process

04The bypass routes that open after setup

Once it is live, children find the gaps quickly. Rather than breaking blocking head-on, they remove the footing it stands on, and that footing sits entirely on Android's permission model and vendor policy. So whenever a bypass was reported, the first step was identifying which Android behavior it exploited, then finding a countermeasure at that same layer. Patching over it in the app layer just produces the next bypass.

attempt, and the layer that answers it

Each bypass is answered at the layer it came from.

  1. Turning off accessibility permission

    Blocking decisions ride on accessibility events, so revoking the permission neutralizes everything. A separate foreground service acts as a dead man's switch: once accessibility drops, it sweeps usage records every second after a grace period and keeps stopping disallowed apps, including cases where more than one thing is in front, such as split screen, pop-ups, and picture-in-picture

  2. Encrypted DNS in the browser

    Domain blocking depends on DNS lookups, and I measured Chromium-based browsers resolving over an encrypted path and slipping past it. A global blocklist, kept separate from the per-child domain list, now ships from the server, and browser apps themselves came under control

  3. In-app browsers

    Blocked sites could be opened in another app's web view, so decisions are also made on what is actually on screen rather than on the app alone

  4. Rolling back the clock

    Sleep-hour policy keys off device time, so changing the time released it. Vendor policy now locks time changes themselves

  5. Going through the settings app

    The routes into permission and app management screens are sealed by vendor settings restrictions

  6. Force-stopping the app

    The gap between the kill and the next launch is the target, so the login token is mirrored into native storage and policy is restored immediately on restart; critical remote commands bypass the app's screen code and run natively, so they land even while the app is dead

  7. Package spoofing and reinstalls

    Apps that differ only in name are grouped into one protected cluster, so decisions apply per cluster instead of one package at a time

05Outcome

Even when a push is lost, the device catches up on its own, so the problem of the parent's screen and the device state staying out of sync is gone. The path of dodging blocks by turning off permissions is closed too, and server state and logs are checked in one place through monitoring.