mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 19:34:03 +01:00
setApplicationHidden drops a package from the default PackageManager enumeration even though it stays installed=true, so a sweep querying with flags 0 could never see anything it had already hidden. That made packagesToShow permanently empty: measured on device, a pass following one that hid three apps reported "hid 0, restored 0" and left them hidden with no way back. Query with MATCH_UNINSTALLED_PACKAGES instead. Hiding the enforcer itself is the other unrecoverable case, since it takes the escape hatch and the trigger with it. The exporter now injects com.kuhy.focus_owner into both the day and night allowlists so the decision layer cannot propose it, and the runner keeps its point-of-use skip to cover a policy asset rendered before this fix or hand-edited. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQLwdquDCNmWtvgi3Lk72J
55 lines
4.3 KiB
JSON
55 lines
4.3 KiB
JSON
{
|
|
"intent": "Make it possible for focus_owner to un-hide an app it has hidden. Before this change a hidden app could never be restored, so leaving home (or the curfew ending) would not have given access back.",
|
|
"scope": [
|
|
"focus_owner/android/app/src/main/kotlin/com/kuhy/focus_owner/EnforcementRunner.kt",
|
|
"python_pkg/focus_policy/export.py",
|
|
"python_pkg/focus_policy/tests/test_export.py",
|
|
"python_pkg/focus_policy/tests/test_loader.py",
|
|
"focus_owner/docs/profile-owner-verification-2026-08-09.md",
|
|
"Non-goal: wiring the workout signal into the Kotlin path (pre-existing gap, documented not fixed)",
|
|
"Non-goal: Device Owner provisioning, which still needs a factory reset"
|
|
],
|
|
"changes": [
|
|
"EnforcementRunner.installedThirdPartyPackages() now queries with PackageManager.MATCH_UNINSTALLED_PACKAGES. A package hidden by setApplicationHidden is dropped from the default enumeration, so it left installedPackages, so it could never appear in packagesToShow, so it could never be unhidden.",
|
|
"export.py injects ENFORCER_PACKAGE (com.kuhy.focus_owner) into both allowlists. The enforcer was in neither, so the decision layer put it in the hide set on every enforcing pass; only an in-app check stopped the app hiding itself along with its own escape hatch.",
|
|
"Corrected the EnforcementRunner comment that described that in-app check as redundant defence-in-depth; until a policy asset is regenerated it is the sole protection.",
|
|
"Added test_export.py covering the injection guarantee, set-union idempotence, sorted output, curfew rendering and home redaction; updated the test_loader.py allowlist assertion the injection legitimately changes."
|
|
],
|
|
"verification": [
|
|
{
|
|
"command": "on-device: managed profile (user 12) with profile owner, tap 'Run enforcement now'",
|
|
"result": "fail-then-pass",
|
|
"evidence": "Before fix: 'applied LOCATION_UNKNOWN: hid 0, restored 0' with all three apps still hidden=true, and pm list packages --user 12 -3 returning only focus_owner. After fix: 'setApplicationHidden(...,false)=true' x3 and 'applied LOCATION_UNKNOWN: hid 0, restored 3'."
|
|
},
|
|
{
|
|
"command": "adb shell dumpsys package <pkg> (per-user hidden= flag)",
|
|
"result": "pass",
|
|
"evidence": "com.termux, org.breezyweather, de.duenndns.gmdice all hidden=false after the restore pass; back in the default enumeration; cmd package resolve-activity com.termux resolved to TermuxActivity."
|
|
},
|
|
{
|
|
"command": "adb reboot; re-check hidden state in user 12",
|
|
"result": "pass",
|
|
"evidence": "All three still hidden=true after reboot. This is the property pm suspend and pm disable-user both fail, and the one the Device Owner design depends on."
|
|
},
|
|
{
|
|
"command": "python -m pytest python_pkg/focus_policy/",
|
|
"result": "pass",
|
|
"evidence": "123 passed. Isolated coverage for python_pkg/focus_policy: 249 statements, 58 branches, 100%."
|
|
},
|
|
{
|
|
"command": "pre-commit run --files <changed-files>",
|
|
"result": "pass",
|
|
"evidence": "See commit; all hooks green."
|
|
}
|
|
],
|
|
"risks": [
|
|
"MATCH_UNINSTALLED_PACKAGES also returns packages uninstalled with data retained, which could put ghosts in packagesToHide. Measured on this device: pm list packages -3 and -u -3 both return 49, so there are none, and apply() wraps both bridge calls in runCatching so a ghost is a no-op.",
|
|
"Verification used a profile owner, not a device owner. The DPM calls are identical, but the harness could not exercise real home coordinates (every pass resolved to LOCATION_UNKNOWN), the full 373-package sweep, or persisted geofence hysteresis.",
|
|
"The committed policy asset still predates the exporter fix; it cannot be regenerated on this machine because config_secrets.sh holds the REDACTED_LAT placeholder rather than real coordinates. Until it is regenerated the in-app self-hide check remains the only protection, which is why that check was kept."
|
|
],
|
|
"rollback": [
|
|
"git revert the commit. The Kotlin change is one flag argument and the exporter change is one set union.",
|
|
"After rollback, re-check that no app is left hidden: adb shell pm list packages -u -3 --user 0 against pm list packages -3 --user 0. A difference means an app is hidden and, without the fix, cannot be restored by the app itself; clear it with adb shell pm unhide <pkg> or by removing the profile."
|
|
]
|
|
}
|