{ "intent": "Let the Device Owner sweep hide the preinstalled apps it exists to block -- YouTube and Chrome ship with FLAG_SYSTEM and were skipped entirely -- without exposing the rest of the platform to being hidden.", "scope": [ "python_pkg/focus_policy/export.py", "python_pkg/focus_policy/tests/test_export.py", "focus_owner/android/app/src/main/kotlin/com/kuhy/focus_owner/FocusPolicy.kt", "focus_owner/android/app/src/main/kotlin/com/kuhy/focus_owner/EnforcementRunner.kt", "focus_owner/assets/policy.json", "Non-goal: Device Owner provisioning itself, which still needs the factory reset" ], "changes": [ "Added BLOCKABLE_SYSTEM_PACKAGES to the exporter, rendered as a new blockable_system_packages field: chrome, vending, youtube, youtube.music, videos. com.android.vending is included deliberately -- leaving Play reachable makes every other removal a one-tap undo.", "The exporter subtracts anything already allowed, so an asset can never declare a package both allowed and blockable.", "EnforcementRunner.installedThirdPartyPackages became sweepablePackages(policy): third-party apps as before, plus system apps the policy names. System apps stay default-deny.", "FocusPolicy gained blockableSystemPackages, parsed with a new optionalStringSet so an asset predating the field reads as the empty set instead of throwing -- a throw would make decide() skip the pass and silently disable enforcement." ], "verification": [ { "command": "measured blast radius before changing anything: pm list packages -s vs the policy", "result": "pass", "evidence": "320 system packages on the device, of which 243 match no allowlist entry and no never_disable_prefix -- including com.android.cellbroadcastreceiver (emergency alerts), com.android.credentialmanager and com.android.devicelockcontroller. This is why the filter was not simply deleted." }, { "command": "profile-owner harness (user 10), tap 'Run enforcement now' with YouTube installed", "result": "pass", "evidence": "'setApplicationHidden(com.google.android.youtube,true)=true' then 'applied LOCATION_UNKNOWN: hid 1, restored 0'; dumpsys confirms hidden=true. Before this change the package was invisible to the sweep." }, { "command": "same harness with YouTube temporarily added to the allowlist", "result": "pass", "evidence": "'setApplicationHidden(com.google.android.youtube,false)=true', 'restored 1', hidden=false. Both directions of the system-app path work." }, { "command": "dumpsys on safety-critical system packages after the sweep", "result": "pass", "evidence": "com.android.cellbroadcastreceiver, com.android.credentialmanager, com.android.devicelockcontroller and com.android.settings all installed=true hidden=false. All five blockable packages are hidden=true. Default-deny holds." }, { "command": "pytest python_pkg/focus_policy/ ; gradlew :app:testDebugUnitTest ; flutter build apk --debug", "result": "pass", "evidence": "125 Python tests pass, 100% branch coverage on focus_policy. Kotlin unit tests BUILD SUCCESSFUL. APK builds." } ], "risks": [ "Hiding com.android.vending under Device Owner removes the user's ability to install or update ANY app from Play, not just to reinstall YouTube. That is the intent, but it is a large blast radius and should be applied late, after the release path has been re-tested -- consistent with the wipe checklist's 'one restriction at a time'.", "BLOCKABLE_SYSTEM_PACKAGES is a hardcoded list in the exporter rather than something derived from config.sh. Adding a new preinstalled distraction means editing Python, not the shell config. Chosen deliberately: this list is a safety boundary, and a typo in config.sh should not be able to widen it.", "Verified under a profile owner, not a device owner. The DPM calls are identical but the package set differs: a work profile does not provision every system app (talkback, for example, is installed=false there), so the real user-0 sweep will see more packages than this harness did.", "An intermediate `comm` on visible-vs-all package lists made it look like the sweep had hidden ~60 system packages. It had not -- those were absent from the profile, not hidden. Distinguishing installed= from hidden= is what settled it; the app logged exactly one setApplicationHidden call." ], "rollback": [ "git revert. The Kotlin change falls back to filtering every FLAG_SYSTEM package, and the extra asset field is then ignored.", "After reverting, regenerate the asset so blockable_system_packages disappears: python -m python_pkg.focus_policy --config phone_focus_mode/config.sh --redact-home --output focus_owner/assets/policy.json", "Anything already hidden stays hidden until a pass unhides it; on this device the same apps are also removed via distraction_purge.sh, which is independent." ] }