mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-11 18:34:04 +01:00
phone_focus_mode is built for a rooted phone: the hosts layer is a read-only
bind mount over /system/etc/hosts, chosen so that even root-in-a-terminal
cannot overwrite it. None of that ports to a stock locked device. This splits
out the parts that are device-independent so one policy can drive both.
python_pkg/focus_policy parses config.sh rather than copying it, so the shell
config stays the source of truth and the two cannot drift. It rejects the
shipped REDACTED_* coordinate placeholder instead of defaulting to (0,0),
which would move "home" to the Atlantic and quietly disable the location gate.
blocklist.py converts the hosts file to domains for a DoT resolver or a local
VpnService. Three properties of the real 194k-line file make the obvious parser
wrong, and all three are handled: generate_hosts_file.sh *comments out*
deliberate unblocks rather than deleting them, so stripping '#' re-blocks
facebook/messenger/linkedin/4chan; the StevenBlack header carries loopback and
IPv6 housekeeping that would blackhole localhost; and ~1060 upstream annotation
tokens ('#[adware.zeno]', '"spam') render as address=/"spam/, which makes
dnsmasq refuse to start -- taking DNS down rather than blocking anything.
Verified with dnsmasq --test over all 185681 generated domains.
strip_workout_hosts.py had the same class of bug on the live rooted phone:
exact set membership meant the allowlisted googlevideo.com never matched the
dynamic r*---sn-*.googlevideo.com hosts YouTube actually streams from, so the
workout unblock was only partly working. Now suffix-matched (17 lines stripped,
was 14). The three new tests were checked against the original code in a
throwaway worktree; two of them fail there, so they pin the fix rather than
passing vacuously.
focus_owner is a device-owner app that deliberately does nothing yet. It ships
clearDeviceOwnerApp() implemented and tested first, because dpm
remove-active-admin does not work on a device owner: an app that locks the
device down before proving it can let go leaves a soft-brick recoverable only
through fastboot on a locked bootloader. Built, installed and launched on the
Pixel 6a; dumpsys afterwards confirms Device Owner Type -1 and no user
restrictions, i.e. the phone is unchanged.
Measured while scoping this, and not yet reflected in any enforcement code:
on Android 16 both pm suspend and pm disable-user are cleared by a reboot, so
shell-level app blocking is per-session only. setApplicationHidden is assumed
to persist under Device Owner but is untested.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AwepyL5Lxs5QKbKkMpxxxL
71 lines
2.5 KiB
Python
Executable File
71 lines
2.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Produce the workout variant of the canonical hosts file.
|
|
|
|
Copies ``argv[1]`` (the full hosts file) to ``argv[2]``, dropping every entry
|
|
whose host name (or any alias) matches a workout-allowlisted domain. The domain
|
|
set is read from the ``WORKOUT_UNBLOCK_DOMAINS`` environment variable (whitespace
|
|
separated) so the generator and the on-device runtime share one source of truth.
|
|
|
|
Kept as a standalone module (not inline ``python <<PY`` in ``deploy.sh``) so the
|
|
repository's Python tooling applies; see CLAUDE.md "Shell Style".
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
# A hosts entry needs at least an IP and one name: "<ip> <name> [aliases...]".
|
|
_MIN_HOSTS_FIELDS = 2
|
|
|
|
|
|
def _matches_unblock(name: str, unblock: frozenset[str]) -> bool:
|
|
"""Return whether ``name`` is an unblocked domain or a subdomain of one.
|
|
|
|
Suffix matching, not equality. YouTube serves video from dynamically named
|
|
CDN hosts such as ``r1---sn-4g5e6nls.googlevideo.com``; testing those for
|
|
exact membership against ``googlevideo.com`` never matches, so playback
|
|
stayed blocked during workouts even though the domain was allowlisted.
|
|
"""
|
|
candidate = name.lower().rstrip(".")
|
|
return any(
|
|
candidate == domain or candidate.endswith(f".{domain}")
|
|
for domain in (entry.lower().rstrip(".") for entry in unblock)
|
|
)
|
|
|
|
|
|
def _strip(source: Path, dest: Path, unblock: frozenset[str]) -> None:
|
|
"""Write ``source`` to ``dest`` minus lines that map an unblocked domain."""
|
|
text = source.read_text(encoding="utf-8", errors="replace")
|
|
kept: list[str] = []
|
|
for line in text.splitlines(keepends=True):
|
|
stripped = line.strip()
|
|
if not stripped or stripped.startswith("#"):
|
|
kept.append(line)
|
|
continue
|
|
parts = stripped.split()
|
|
names = parts[1:]
|
|
if len(parts) >= _MIN_HOSTS_FIELDS and any(
|
|
_matches_unblock(name, unblock) for name in names
|
|
):
|
|
continue
|
|
kept.append(line)
|
|
dest.write_text("".join(kept), encoding="utf-8")
|
|
|
|
|
|
def main() -> int:
|
|
"""Read the source/dest paths from argv and the domains from the env."""
|
|
args = sys.argv[1:]
|
|
expected_args = 2
|
|
if len(args) != expected_args:
|
|
sys.stderr.write("usage: strip_workout_hosts.py <src-hosts> <dst-hosts>\n")
|
|
return 2
|
|
unblock = frozenset(os.environ.get("WORKOUT_UNBLOCK_DOMAINS", "").split())
|
|
_strip(Path(args[0]), Path(args[1]), unblock)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|