From 3a8c3ab5c71fced20600de4f814cef97311c9eaa Mon Sep 17 00:00:00 2001 From: Konrad Kollnig <5175206+kasnder@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:00:16 +0200 Subject: [PATCH 1/2] Document how to drive the app on a device without popups MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent sessions kept burning turns tapping through onboarding, the VPN consent dialog and permission prompts — and re-entering onboarding after overwriting the preferences file, which drops onboarding_version. All of it can be pre-satisfied from adb, with no code changes and nothing debug-only to maintain: `install -g` for runtime permissions, `appops set ACTIVATE_VPN allow` so VpnService.prepare() returns null (which skips the system dialog and our own explainer before it), and a seeded onboarding_version, which run-as can write before the first launch. Also records the traps behind the churn: force-stop before editing preferences or the running process overwrites them, merge rather than replace so onboarding_version survives, and uninstall/pm clear wipe the seeded state along with the appop and the grants. Two notes for someone else's device: VpnService.prepare() revokes whatever VPN app currently holds consent, and the github debug package is also what a maintainer's own dev build installs as, so seeding preferences into it overwrites their real configuration. Verified end to end on a Pixel 8 (Android 17). Co-Authored-By: Claude Opus 5 --- AGENTS.md | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index dd86e61f..a94689d8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -116,6 +116,73 @@ the app or run `pm clear` unless the user explicitly requests a clean install. adb install -r app/build/outputs/apk/github/debug/TrackerControl-githubDebug-latest.apk ``` +### Driving the app without permission popups + +Onboarding, the VPN consent dialog and the runtime permission prompts can all be +pre-satisfied from adb, so an agent never has to tap through them. + +Note the package first: github debug is `net.kollnig.missioncontrol.test`, which +is also what a maintainer's own day-to-day dev build installs as. Seeding +preferences into it overwrites their real configuration. If the device is +someone's daily driver, either confirm before touching that package or use a +different flavour's debug build (`net.kollnig.missioncontrol.fdroid.test`), which +installs alongside it. + +```bash +PKG=net.kollnig.missioncontrol.test + +# 1. Runtime permissions. -g grants everything the manifest declares, so revoke +# whichever one you are actually testing (checkSelfPermission would lie). +adb install -r -g app/build/outputs/apk/github/debug/TrackerControl-githubDebug-latest.apk +adb shell pm revoke $PKG android.permission.ACCESS_LOCAL_NETWORK + +# 2. VPN consent. Makes VpnService.prepare() return null, which skips BOTH the +# system ConfirmDialog and TrackerControl's own explainer before it. +adb shell appops set $PKG ACTIVATE_VPN allow + +# 3. Onboarding. ActivityMain checks onboarding_version against +# ActivityOnboarding.ONBOARDING_VERSION; seeding it lands you on the main +# screen. This works before the first launch — run-as can create the file. +cat > /tmp/seed.xml <<'XML' + + + + +XML +adb push /tmp/seed.xml /data/local/tmp/seed.xml +adb shell "run-as $PKG sh -c 'mkdir -p shared_prefs && cat /data/local/tmp/seed.xml > shared_prefs/${PKG}_preferences.xml'" + +adb shell monkey -p $PKG -c android.intent.category.LAUNCHER 1 +``` + +(Push through `/data/local/tmp` rather than piping a heredoc into `adb shell` — +quoting survives the round trip intact.) + +The VPN switch is at roughly `input tap 108 216` on a 1080×2400 screen; from +there the tunnel comes up with no dialogs at all. Verify with +`adb shell dumpsys connectivity | grep -o "VPN:net.kollnig[a-z.]*"`. + +Preferences, once seeded: + +- **Force-stop before editing.** A running process holds prefs in memory and + will overwrite your file on exit: `adb shell am force-stop $PKG` first. +- **Merge, never replace.** Rewriting the whole file drops `onboarding_version` + and drops you back into onboarding. Append inside `` with `sed`, or read + the file, edit, and write it back whole. +- **`adb uninstall` and `pm clear` wipe all of the above** — the seeded prefs, + the appop, and the granted permissions. Prefer leaving the app installed and + resetting only the state your test cares about. + +Two things to keep in mind on someone's personal device: + +- `VpnService.prepare()` **revokes whatever VPN app currently holds consent**, + including the user's own build or their real VPN. Say so before you enable a + tunnel, and remind them afterwards that it needs re-enabling. +- To check a notification, read it with + `adb shell dumpsys notification --noredact | grep -A6 ''` rather + than screenshotting the shade, which captures the user's private + notifications. + ```bash # From the repo root. Use ./gradlew (the wrapper). From 9aa42cbb5d6742138f80897dc50385f76e276577 Mon Sep 17 00:00:00 2001 From: Konrad Kollnig <5175206+kasnder@users.noreply.github.com> Date: Wed, 5 Aug 2026 11:06:28 +0200 Subject: [PATCH 2/2] Default device testing to fdroid debug, and require asking before destroying state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit github debug installs as net.kollnig.missioncontrol.test, which is also what a maintainer's own day-to-day dev build installs as. Following the previous advice meant testing on top of their real configuration, and enabling its VPN takes the consent slot from whatever they were running. The flavours differ only in the update-check API, so fdroid debug costs nothing and installs alongside. Also states the rule the previous wording only implied: uninstall, pm clear, overwriting preferences and revoking permissions are irreversible, so ask first and name what will be lost — including when the target looks like a throwaway you installed yourself, since that is one mistaken package name away from the real install. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 61 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a94689d8..19d5af0b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -106,34 +106,47 @@ Three product flavours — **github**, **fdroid**, **play** — differ only in t update-check API; **github** is the normal local dev flavour. Debug builds install side-by-side (`applicationIdSuffix ".test"`). -**Connected-device testing:** Always build and install the **github debug** -variant. Update the existing installation in place with `adb install -r` so its -app data, preferences, VPN consent, and test state are preserved. Do not uninstall -the app or run `pm clear` unless the user explicitly requests a clean install. +**Connected-device testing:** build and install the **fdroid debug** variant, +which installs as `net.kollnig.missioncontrol.fdroid.test`. ```bash -./gradlew assembleGithubDebug -adb install -r app/build/outputs/apk/github/debug/TrackerControl-githubDebug-latest.apk +./gradlew assembleFdroidDebug +adb install -r app/build/outputs/apk/fdroid/debug/TrackerControl-fdroidDebug-latest.apk ``` +Not github debug: that installs as `net.kollnig.missioncontrol.test`, which is +also what a maintainer's own day-to-day dev build installs as. Testing against it +overwrites their real preferences, and enabling its VPN takes the consent slot +from whatever they were running. The flavours differ only in the update-check +API, so nothing is lost by using fdroid for device work. (Working on the +update-check itself is the exception — that needs github, and needs asking +first.) + +**Never destroy state without asking.** `adb uninstall`, `pm clear`, overwriting +a preferences file, revoking a permission — every one of these is irreversible +and the device usually belongs to someone who has real configuration on it. Ask +first, naming the package and what will be lost, and wait for a yes. This applies +even when the target looks like a throwaway you installed yourself: it is one +mistaken package name away from wiping the real install. Update in place with +`adb install -r`, which preserves app data, preferences, VPN consent, and test +state, and for cleanup reset only the specific state your test touched. + +One more side effect worth announcing before you trigger it: enabling the VPN +calls `VpnService.prepare()`, which **revokes whatever VPN app currently holds +consent** — the maintainer's own build, or their real VPN. It will need +re-enabling afterwards. + ### Driving the app without permission popups Onboarding, the VPN consent dialog and the runtime permission prompts can all be pre-satisfied from adb, so an agent never has to tap through them. -Note the package first: github debug is `net.kollnig.missioncontrol.test`, which -is also what a maintainer's own day-to-day dev build installs as. Seeding -preferences into it overwrites their real configuration. If the device is -someone's daily driver, either confirm before touching that package or use a -different flavour's debug build (`net.kollnig.missioncontrol.fdroid.test`), which -installs alongside it. - ```bash -PKG=net.kollnig.missioncontrol.test +PKG=net.kollnig.missioncontrol.fdroid.test # 1. Runtime permissions. -g grants everything the manifest declares, so revoke # whichever one you are actually testing (checkSelfPermission would lie). -adb install -r -g app/build/outputs/apk/github/debug/TrackerControl-githubDebug-latest.apk +adb install -r -g app/build/outputs/apk/fdroid/debug/TrackerControl-fdroidDebug-latest.apk adb shell pm revoke $PKG android.permission.ACCESS_LOCAL_NETWORK # 2. VPN consent. Makes VpnService.prepare() return null, which skips BOTH the @@ -170,18 +183,12 @@ Preferences, once seeded: and drops you back into onboarding. Append inside `` with `sed`, or read the file, edit, and write it back whole. - **`adb uninstall` and `pm clear` wipe all of the above** — the seeded prefs, - the appop, and the granted permissions. Prefer leaving the app installed and - resetting only the state your test cares about. - -Two things to keep in mind on someone's personal device: - -- `VpnService.prepare()` **revokes whatever VPN app currently holds consent**, - including the user's own build or their real VPN. Say so before you enable a - tunnel, and remind them afterwards that it needs re-enabling. -- To check a notification, read it with - `adb shell dumpsys notification --noredact | grep -A6 ''` rather - than screenshotting the shade, which captures the user's private - notifications. + the appop, and the granted permissions — which is a second reason not to reach + for them, on top of needing to ask first. + +To check a notification, read it with +`adb shell dumpsys notification --noredact | grep -A6 ''` rather than +screenshotting the shade, which captures the user's private notifications. ```bash # From the repo root. Use ./gradlew (the wrapper).