mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-12 08:31:03 +01:00
The Device Owner sweep skipped every FLAG_SYSTEM package, which meant it could not touch the apps it exists to block: YouTube ships at /product/app/YouTube as a system app, and so does Chrome. Deleting the filter is not the fix. Measured on the Pixel 6a, that would expose 320 system packages 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. So system apps stay default-deny and blockable_system_packages is the opt-in. com.android.vending is on the list deliberately: leaving Play reachable makes every other removal a one-tap undo. The list lives in the Python exporter rather than config.sh because it is a safety boundary, and a shell typo should not be able to widen it. The exporter subtracts anything already allowed, so an asset can never state that a package is both allowed and blockable. Kotlin reads the field with a new optionalStringSet: an asset predating the field parses to the empty set instead of throwing, because a throw makes decide() skip the pass and silently disable enforcement. Verified on-device under a profile owner: hid 1 for YouTube, restored 1 when temporarily allowed, and cellbroadcastreceiver, credentialmanager, devicelockcontroller and settings all left hidden=false. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014DqEd1uUhyT52KjaNeLanC
101 lines
4.2 KiB
Python
101 lines
4.2 KiB
Python
"""Render a :class:`FocusPolicy` into a backend-neutral JSON document.
|
|
|
|
The Device Owner app is written in Kotlin and cannot read ``config.sh`` or
|
|
import this package. It reads this JSON instead, so both enforcement backends
|
|
stay driven by one policy definition.
|
|
|
|
Sorted keys and sorted lists keep the output stable, so committing a rendered
|
|
policy produces a meaningful diff when the policy actually changes rather than
|
|
noise from set iteration order.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
if TYPE_CHECKING:
|
|
from python_pkg.focus_policy.model import FocusPolicy
|
|
|
|
SCHEMA_VERSION = 1
|
|
|
|
# The enforcing app itself, which must never appear in the hide set. Hiding it
|
|
# removes both the escape hatch and the control that triggers the next pass,
|
|
# leaving no way back short of a factory reset. The Kotlin runner does check
|
|
# this at the point of use, but that check is the *only* protection unless the
|
|
# package is also allowed here -- so it is injected into the exported allowlists
|
|
# rather than left to a hand-edit of the rendered asset.
|
|
ENFORCER_PACKAGE = "com.kuhy.focus_owner"
|
|
|
|
# System apps the sweep is allowed to hide, named one by one.
|
|
#
|
|
# The Kotlin runner skips every package with FLAG_SYSTEM, which means the
|
|
# Device Owner path cannot touch the apps most worth blocking: YouTube ships
|
|
# at /product/app/YouTube as a system app, and so does Chrome. Simply dropping
|
|
# that filter is not the fix -- measured on the Pixel 6a, it would expose 320
|
|
# system packages 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.
|
|
# Hiding those under Device Owner risks an unrecoverable device.
|
|
#
|
|
# So the sweep stays default-deny for system apps and this is the opt-in.
|
|
# com.android.vending is included deliberately: leaving Play reachable makes
|
|
# every other removal a one-tap undo.
|
|
BLOCKABLE_SYSTEM_PACKAGES = frozenset(
|
|
{
|
|
"com.android.chrome",
|
|
"com.android.vending",
|
|
"com.google.android.apps.youtube.music",
|
|
"com.google.android.videos",
|
|
"com.google.android.youtube",
|
|
},
|
|
)
|
|
|
|
|
|
def policy_to_dict(policy: FocusPolicy) -> dict[str, Any]:
|
|
"""Return a JSON-serialisable representation of ``policy``."""
|
|
curfew: dict[str, str] | None = None
|
|
if policy.curfew is not None:
|
|
curfew = {
|
|
"start": policy.curfew.start.strftime("%H:%M"),
|
|
"end": policy.curfew.end.strftime("%H:%M"),
|
|
}
|
|
|
|
return {
|
|
"schema_version": SCHEMA_VERSION,
|
|
"home": {
|
|
"latitude": policy.home.latitude,
|
|
"longitude": policy.home.longitude,
|
|
"radius_m": policy.home.radius_m,
|
|
"hysteresis_m": policy.home.hysteresis_m,
|
|
},
|
|
"curfew": curfew,
|
|
"launcher_package": policy.launcher_package,
|
|
"allowed_packages": sorted({*policy.allowed_packages, ENFORCER_PACKAGE}),
|
|
"night_allowed_packages": sorted(
|
|
{*policy.night_allowed_packages, ENFORCER_PACKAGE},
|
|
),
|
|
"never_disable_prefixes": sorted(policy.never_disable_prefixes),
|
|
"workout_unblock_domains": sorted(policy.workout_unblock_domains),
|
|
"browser_packages": sorted(policy.browser_packages),
|
|
# Absent from an older asset, so the Kotlin loader must treat a
|
|
# missing key as the empty set -- i.e. keep today's behaviour of
|
|
# never touching a system app.
|
|
"blockable_system_packages": sorted(
|
|
BLOCKABLE_SYSTEM_PACKAGES - {*policy.allowed_packages, ENFORCER_PACKAGE},
|
|
),
|
|
}
|
|
|
|
|
|
def policy_to_json(policy: FocusPolicy, *, redact_home: bool = False) -> str:
|
|
"""Return ``policy`` as pretty-printed JSON.
|
|
|
|
``redact_home`` blanks the coordinates so a rendered policy can be attached
|
|
to a bug report or committed as a fixture without disclosing where the user
|
|
lives. The radius and hysteresis are kept, since they carry no location.
|
|
"""
|
|
payload = policy_to_dict(policy)
|
|
if redact_home:
|
|
payload["home"] = dict(payload["home"], latitude=None, longitude=None)
|
|
return json.dumps(payload, indent=2, sort_keys=True, ensure_ascii=False)
|