mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 18:07:06 +01:00
The enforcer failed in both directions: nothing blocked at home, and everything blocked at the office. The comparison was never inverted -- `metres <= threshold` was always right. Three separate causes: Only ACCESS_COARSE_LOCATION was declared. Android 12+ fuzzes coarse to a ~1-2 km grid against a 150 m fence, so a fix taken at home routinely landed outside it, read as AWAY, and restored every app the geofence exists to hide. FINE is now declared and self-granted by the device owner, along with coarse and background, re-asserted every pass so a reinstall cannot silently drop it. There were three states but two behaviours. LOCATION_UNKNOWN applies the identical sweep to AT_HOME -- correct fail-closed design, indistinguishable from outside. And nothing ever requested a fix: the pass read whatever another app happened to cache, so at the office with no navigation running the cache was empty. Passes now acquire a fix inside a location-typed foreground service, bounded at 20 s, and treat anything older than 30 min as no fix. That window is deliberately longer than the 15-minute cadence; a shorter one marks the newest cache stale on every pass of an idle phone and blocks everything. None of it was diagnosable. `adb logcat -d -s FocusOwner:V` came back empty while dumpsys showed 82 alarms had fired, and `run-as` is refused on the release build device owner requires. So the pass now writes a ring-buffered JSONL record read back in-app: reason, distance vs the fence, fix age/provider/accuracy, curfew state, and every hidden app with the rule that hid it. Coordinates are never recorded -- a distance is enough to debug a geofence, and this is a home address. Also fixed along the way: - Hysteresis keyed off `isEnforcing`, which is true under AWAY too because YouTube stays hidden, so the threshold was permanently radius+hysteresis. It now keys off whether the previous pass was inside the fence. - The scheduler's curfew-boundary alarms were documented but never scheduled; both callers invoked `scheduleNext()` bare, so 05:00 could land 15 min late. - Installing over the app cancelled its pending alarms, so `adb install -r` silently stopped enforcement until someone opened the app. MY_PACKAGE_REPLACED now re-arms it, verified on device without launching the app. - A wrong-typed log record threw instead of being skipped, and the exception escaped far enough to leave the page stuck busy -- which disables the release-device-owner button, the one control that must work when everything else has gone wrong. - `lib/enforcement.dart` was a second decision layer with no always-blocked concept, cited by KDoc as pinned by a `PolicyParityTest` that never existed. It would have made the new screen report YouTube visible while it is hidden. Deleted; the UI renders the Kotlin record rather than re-deciding. - Anchoring home accepted a stale or unknown-precision fix. A wrong home offsets the fence everywhere and forever, which is what produced a 766 m reading while standing at home. Policy: Play Store is allowed by day (not during curfew) so infakt can be installed and updated; the sweep is default-deny for third-party packages, so anything installed from Play is hidden on the next at-home pass. Tachiyomi's per-source extension apks are covered by a new prefix allowlist rather than an exact list that goes stale whenever an extension is installed. `export.py` stopped subtracting the allowlist from `blockable_system_packages`. Sweepable is not hideable: that list decides which system packages are eligible for a decision, and `isAllowed` is what protects them. Removing an allowed system package from the sweep freezes it in its last state -- it would have stranded Play hidden permanently the moment it was allowlisted. Verified on the Pixel 6a: FINE granted POLICY_FIXED, `applied AT_HOME` with a 0 m distance from a +/-11 m GPS fix, YouTube/Chrome/Firefox absent from `pm list packages -e`, Play Store, infakt, RunnerUp and all five Tachiyomi packages present. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HjxL9kdAzZmGqQA5tMNrZ8
70 lines
7.9 KiB
JSON
70 lines
7.9 KiB
JSON
{
|
|
"intent": "Make focus_owner's geofence trustworthy and self-explaining. It was failing in both directions: nothing blocked at home (a fuzzed coarse fix read as AWAY, restoring Firefox and so YouTube on the web) and everything blocked at the office (an empty last-known cache produced LOCATION_UNKNOWN, which applies the identical sweep to AT_HOME). Also unblock Play Store so inFakt can update, and allow Tachiyomi's per-source extension apks.",
|
|
"scope": [
|
|
"focus_owner/android/.../{EnforcementRunner,EnforcementDecision,EnforcementLog,LocationFix,EnforcementService,EnforcementScheduler,DevicePolicyBridge,MainActivity,FocusPolicy}.kt",
|
|
"focus_owner/android/app/src/main/AndroidManifest.xml",
|
|
"focus_owner/lib/{main,policy,device_policy,enforcement_record,enforcement_status_card,debug_log_page,theme}.dart",
|
|
"phone_focus_mode/config.sh (source of truth), python_pkg/focus_policy/{model,loader,export}.py",
|
|
"NON-GOAL: making the DNS/VPN layer actually filter youtube.com (private_dns_host is empty; port 853 is not forwarded and VPN lockdown bypasses system Private DNS). A browser still reaches YouTube when AWAY."
|
|
],
|
|
"changes": [
|
|
"Declared and self-granted ACCESS_FINE_LOCATION via setPermissionGrantState (device owner needs no prompt); coarse is fuzzed to a ~1-2 km grid against a 150 m fence. Also self-grants coarse/background and calls setLocationEnabled(true), re-asserted every pass so a reinstall cannot silently drop them.",
|
|
"Replaced passive getLastKnownLocation with bounded active acquisition (20 s timeout) inside a location-typed foreground service, plus a LocationFix carrying age/accuracy/provider/outcome. Freshness window is 30 min - deliberately longer than the 15 min cadence, since a shorter window would mark the newest cache stale every pass and block everything.",
|
|
"A stale fix is now treated as no fix (fail-closed), which is only safe because active acquisition landed first.",
|
|
"Fixed hysteresis keyed off the wrong boolean: isEnforcing is true under AWAY too (YouTube stays hidden), so the threshold was permanently radius+hysteresis. Now keyed off a separate persisted was-inside-the-fence flag.",
|
|
"EnforcementDecision now reports distanceM, thresholdM, insideFence (nullable when unanswerable), curfewActive (from the clock, not inferred from the reason enum) and a per-package HideReason.",
|
|
"Added EnforcementLog: ring-buffered JSONL in filesDir read back over the platform channel, because run-as is refused on the release build and logcat rotates (measured empty while dumpsys showed 82 alarms fired). Records never contain coordinates.",
|
|
"New status card + debug log page: reason with a plain-English explanation, distance vs fence, fix age/provider/accuracy, curfew state, and every hidden app with the rule that hid it. LOCATION_UNKNOWN and no-home-set are rendered distinctly instead of reading as AT_HOME.",
|
|
"config.sh: com.android.vending added to WHITELIST (day only); new ALLOWED_PREFIXES/NIGHT_ALLOWED_PREFIXES seeded with eu.kanade.tachiyomi (day and night, per user decision), with prefix support added to both parsers and the exporter.",
|
|
"export.py: stopped subtracting the allowlist from blockable_system_packages. Sweepable is not hideable - removing an allowed FLAG_SYSTEM package from the sweep freezes it in its last state, which would have stranded Play Store hidden forever the moment it was allowlisted.",
|
|
"EnforcementScheduler now schedules the next curfew boundary; both callers invoked scheduleNext() bare, so the documented 23:00/05:00 exact alarms were never set and could land 15 min late.",
|
|
"Deleted lib/enforcement.dart and its test: a test-only second evaluator with no always-blocked concept, cited by KDoc as pinned by a PolicyParityTest that never existed. The UI now renders the Kotlin record instead of re-deciding.",
|
|
"Fixed a crash introduced mid-change: foregroundServiceType=location requires FOREGROUND_SERVICE_LOCATION; without it startForeground threw SecurityException on every alarm. Now declared, and the type degrades to specialUse if refused.",
|
|
"cachedFix now prefers the most ACCURATE cached fix, tie-broken by recency, instead of simply the newest. Newest-wins let a network fix accurate to a couple of km outrank a slightly older GPS fix, which cannot answer a 150 m question - observed on device as the reported distance swinging 763 m to 2.0 km while the phone sat still."
|
|
],
|
|
"verification": [
|
|
{
|
|
"command": "python3 -m pytest python_pkg/focus_policy/tests/ -q",
|
|
"result": "pass",
|
|
"evidence": "135 passed; focus_policy package at 100% branch coverage (model/loader/export/blocklist/__main__ all 100%)."
|
|
},
|
|
{
|
|
"command": "cd focus_owner && flutter analyze && flutter test",
|
|
"result": "pass",
|
|
"evidence": "No issues found; 90 tests passed. enforcement_record.dart 98%, enforcement_status_card.dart 98%, debug_log_page.dart 98%."
|
|
},
|
|
{
|
|
"command": "cd focus_owner/android && JAVA_HOME=/usr/lib/jvm/java-21-openjdk ./gradlew :app:testDebugUnitTest",
|
|
"result": "pass",
|
|
"evidence": "BUILD SUCCESSFUL; EnforcementDecisionTest 25 tests, EnforcementLogTest 9 tests, 0 failures. Includes a test asserting no coordinate reaches the log record."
|
|
},
|
|
{
|
|
"command": "adb -s 23181JEGR08034 install -r app-release.apk; tap 'Run enforcement now'",
|
|
"result": "pass",
|
|
"evidence": "ACCESS_FINE_LOCATION: granted=true, flags=[POLICY_FIXED] - the device-owner self-grant works. logcat: 'applied AWAY: hid 0, restored 0' across repeated passes (steady state, no flapping)."
|
|
},
|
|
{
|
|
"command": "On-device status screen (uiautomator dump + screenshot)",
|
|
"result": "pass",
|
|
"evidence": "Renders: AWAY / 'Away from home. Everything is available except the always-blocked apps...' / Distance 763 m (fence 150 m) / Fix '3 min old · passive · ±100 m · cached_fresh' / Curfew inactive (23:00-05:00) / 3 hidden, 58 available; expanding shows youtube, youtube.music and chrome each as 'always blocked, everywhere'. Debug log page lists prior passes with timestamp, reason and distance."
|
|
},
|
|
{
|
|
"command": "adb shell pm list packages -e | grep -iE 'youtube|chrome|vending|infakt|tachiyomi'",
|
|
"result": "pass",
|
|
"evidence": "YouTube and Chrome absent (blocked even while AWAY); com.android.vending present; pl.infakt.infakt present; 5 eu.kanade.tachiyomi* packages present (reader + 4 extensions)."
|
|
}
|
|
],
|
|
"risks": [
|
|
"Active location acquisition costs battery each pass; bounded to 20 s and skipped entirely when a fix under 30 min old is already cached.",
|
|
"The 30 min freshness window means a genuinely stale fix can classify for up to that long. Chosen deliberately: a window under the 15 min cadence sends every pass of an idle phone to LOCATION_UNKNOWN, i.e. blocks everything.",
|
|
"Tachiyomi is now allowed during curfew (user decision 2026-08-14), which contradicts the previous night policy; the now-false prose in config.sh was updated in the same change so it cannot be 'fixed' back silently.",
|
|
"Prefix allowlisting is weaker than an exact list by construction - it permits packages that do not exist yet, so entries must stay vendor-specific.",
|
|
"Observed on device: the fix came from the 'passive' provider at ±100 m and distance jumped 763 m -> 2.0 km at rest. FINE is granted, but GPS indoors at the office is not resolving; worth re-checking fix.accuracy_m at home before trusting the 150 m fence."
|
|
],
|
|
"rollback": [
|
|
"git revert the commit; then regenerate the asset: python3 -m python_pkg.focus_policy --config phone_focus_mode/config.sh --output focus_owner/assets/policy.json --redact-home",
|
|
"Rebuild and 'adb install -r' the release APK. NEVER adb uninstall or pm clear - that strands device ownership with no holder and a factory reset becomes the only exit.",
|
|
"After rollback confirm: YouTube still absent from 'pm list packages -e', and the app still opens so the release-device-owner escape hatch stays reachable."
|
|
]
|
|
}
|