diff --git a/.pi/skills/yellowjacket-dev/references/android-tier.md b/.pi/skills/yellowjacket-dev/references/android-tier.md index 1e417f5..62ff776 100644 --- a/.pi/skills/yellowjacket-dev/references/android-tier.md +++ b/.pi/skills/yellowjacket-dev/references/android-tier.md @@ -258,20 +258,25 @@ one. `build/android/Taskfile.yml` ships more than the Makefile wraps, and they are the right thing to reach for when you want something one-off: -> **Do not run `android:run:device` or `android:deploy-device` -> against a device that has the released app on it (#159).** Both begin -> with `adb uninstall {{.APP_ID}}`, and `APP_ID` defaults to -> `app.yellowjacket` — the **release** id — while `run:device` builds -> the **debug** variant, whose id is `app.yellowjacket.dev`. So it -> uninstalls the user's app, taking the library with it, installs a -> different package, and then fails to launch the one it removed. This -> is "the identity is declared twice" (below) cashing out. The safe -> sequence is at the end of this section. +> **These four were unsafe until #159 and are now the way in.** All of +> them began with `adb uninstall {{.APP_ID}}`, where `APP_ID` defaulted +> to `app.yellowjacket` — the **release** id — while `run` and +> `run:device` build the **debug** variant, whose id is +> `app.yellowjacket.dev`. So they uninstalled the user's app, taking +> the library with it, installed a different package, and then failed +> to launch the one they had removed. +> +> They share `scripts/android-deploy.sh` now, which **never** +> uninstalls (`install -r`, and a changed signing certificate is +> reported with the command rather than acted on), reads the package id +> back out of the built APK, and refuses a target that is not the kind +> the task names. There is nothing left to avoid; the manual sequence +> below is kept because it is still the smallest thing that works. ``` wails3 task android:run # debug build + emulator install + launch -wails3 task android:run:device # UNSAFE, see #159 -wails3 task android:deploy-device # UNSAFE, see #159 +wails3 task android:run:device # debug build + install + launch on a phone +wails3 task android:deploy-device # release build, same wails3 task android:bundle:fat # AAB, for a Play Store upload wails3 task android:studio # open build/android/ in Android Studio wails3 task android:device:list @@ -279,6 +284,16 @@ wails3 task android:logs:all wails3 task android:clean ``` +**`run` and `deploy-emulator` mean the emulator, and now say so to +adb.** They used a bare `adb install`, which with exactly one device +attached picks that device whatever it is — so with a phone plugged in +and no emulator running, the task whose summary reads "in the Android +Emulator" installed on the phone. They pass `--target emulator` and +refuse with `make android-emulator` as the remedy. + +**`DEVICE_ID=` still names a device, and several attached +devices is now an error rather than a silent pick of the first.** + Two are deliberately **not** wrapped. `android:logs` greps logcat for `(Wails|yellowjacket)`, which catches the `WailsBridge` tag but misses the app's own process tag (`app.yellowjacket` — lowercase, so `Wails` @@ -288,16 +303,51 @@ instead. And `ensure-emulator` boots whatever `-list-avds | tail -1` returns, with no pidfile and no boot wait, so it cannot be stopped or sequenced. -## The identity is declared twice +## The identity is read back from the APK +It used to be **declared twice**, and that is what #159 was. `applicationId` in `build/android/app/build.gradle` is what Gradle -installs. `APP_ID` in `build/android/Taskfile.yml` is what every -adb-driven task uninstalls, launches and filters. **Nothing enforces -that they agree**, and `ANDROID.md`'s advice to set `APP_ID` in -`build/config.yml` does not work in beta.8 — `wails3 task` never reads -that file (verified with `--dry`), and even when set it feeds only the -adb commands, never Gradle. Change both or the official `run`/`deploy` -tasks address a package that is not installed. +installs; `APP_ID` in `build/android/Taskfile.yml` was what every +adb-driven task uninstalled, launched and filtered, and nothing +enforced that they agree. They did not: the debug buildType carries +`applicationIdSuffix ".dev"`, so every task that assembles a debug APK +addressed the release id. This file flagged the hazard for five phases +and it cashed out twice — once as a wrong `am start`, once as an +uninstall of the user's library. + +**`scripts/android-pkgid.sh` is the one answer now.** It prints the +package id an APK declares (`aapt2 dump packagename`, falling back to +`aapt dump badging`), and the deploy path installs and launches *that*. +The APK is the authority because the task that installs it has just +built it: whatever Gradle resolved the applicationId to, suffixes and +flavours included, is in the file, and no default can disagree with it. +An APK it cannot read is a hard failure, never a fallback to a written +down default — guessing is the bug. + +**`APP_ID` survives as an assertion, not a setting**, and has no +default. `wails3 task android:run APP_ID=app.yellowjacket` says "this +build had better declare that id" and is refused, naming both, *before* +anything is installed or a device is even chosen. It could never have +been a setting: `ANDROID.md`'s advice to put it in `build/config.yml` +does not work in beta.8 — `wails3 task` never reads that file (verified +with `--dry`) — and even when set it fed only the adb commands, never +Gradle. + +`scripts/android-emulator.sh` derives `PKG` the same way, from +`bin/yellowjacket.apk` when one is built, so `make android-install`, +`android-launch`, `android-logs` and `android-smoke` follow whichever +variant is actually in `bin/`. `YJ_ANDROID_PKG` still overrides, and +the old literal survives only for a tree with no APK built yet. + +**The uninstall is gone and is not coming back.** It existed to make +the bare `install` on the next line work at all — without `-r` Android +refuses an install over an existing package — so `install -r` removes +the *reason* for it rather than merely removing it. What is left is the +one case an uninstall really is the remedy, a changed signing +certificate, and that is exactly the case where performing it silently +costs the user their library. So it is named and not done, which is the +answer `scripts/android-emulator.sh` had already reached for +`make android-install`. Related, and it will bite once: the launcher activity is `com.wails.app.MainActivity` and the applicationId is @@ -306,8 +356,10 @@ resolves the leading dot against the *applicationId* and fails with a class-not-found that reads like a broken build. Always the fully-qualified form. -**The safe way to put a debug build on a real device**, which is what -#52 used and what #159 exists to make unnecessary: +**`wails3 task android:run:device` is the way to put a debug build on a +real device**, since #159. What #52 used, before it was safe, was the +longer form, and it is still the smallest thing that works if you want +no script between you and adb: ```bash wails3 task android:build ARCH=arm64 && wails3 task android:assemble:apk @@ -315,9 +367,15 @@ adb install -r bin/yellowjacket.apk # -r, never uninstall adb shell am start -n app.yellowjacket.dev/com.wails.app.MainActivity ``` -`YJ_ANDROID_PKG=app.yellowjacket.dev` points `scripts/android-emulator.sh` -— and therefore `make android-smoke`, `android-logs`, `android-launch` -— at the debug id, which is otherwise `app.yellowjacket`. +The id in that last line is the one thing to keep an eye on by hand — +`./scripts/android-pkgid.sh bin/yellowjacket.apk` is what the tasks ask, +and it is a good habit before any `am start` written out in full. + +`YJ_ANDROID_PKG=app.yellowjacket.dev` still overrides what +`scripts/android-emulator.sh` — and therefore `make android-smoke`, +`android-logs`, `android-launch` — addresses, but it is rarely needed +now: that default is read from `bin/yellowjacket.apk`, so it already +follows whichever variant was built last. ## What only a device can answer diff --git a/.planning/NOTES.md b/.planning/NOTES.md index 630793f..7c507e1 100644 --- a/.planning/NOTES.md +++ b/.planning/NOTES.md @@ -4155,3 +4155,90 @@ This is the hazard that file already names — "The identity is declared twice ... **Nothing enforces that they agree**" — reached by a second route: the two ids differ not because someone edited one, but because the debug buildType suffixes it. + +## The uninstall was there to make a bare `install` work (measured 2026-08-20) + +Fixing #159 turned up *why* the `adb uninstall` was in all four tasks, +which the issue does not say and which decides whether it can simply be +deleted. The line under it was `adb install`, with **no `-r`** — and +Android refuses an install over an existing package without it. So the +uninstall was not a deliberate clean-slate step; it was the price of +the missing flag, paid on every run, and `install -r` removes the +reason for it rather than merely removing it. + +That matters because "should the uninstall go at all" looked like a +trade — drop it and a signing-certificate change fails with +`INSTALL_FAILED_UPDATE_INCOMPATIBLE` instead of being handled. It is +not a trade: nothing else was relying on it. The certificate case is +reported with the command to run, which is what +`scripts/android-emulator.sh` already did for `make android-install`, +so this is one existing judgement applied consistently rather than a +new one. + +## `wails3 task android:run` installs on a phone (measured 2026-08-20) + +The emulator tasks (`run`, `deploy-emulator`) used a bare `adb install` +with no `-s`. adb with exactly one device attached uses that device +whatever kind it is, so with a phone plugged in and no emulator +running, the task whose summary reads "in the Android Emulator" +installed on the phone — and, before #159 was fixed, ran +`adb uninstall app.yellowjacket` against it first. The reported data +loss was reachable from the *emulator* task, which is not what the +issue describes and is worse, because nothing in the name warns you. + +Measured after the fix, phone attached and emulator stopped: + +``` +$ ./scripts/android-deploy.sh --apk bin/yellowjacket.apk --target emulator +android-deploy: no emulator target is online. + LP3LHMA531900746 device + Start one with: make android-emulator +``` + +The general form: **a task that names a target has to say so to adb.** +The device tasks always filtered on `$1 !~ /^emulator-/`; the emulator +tasks filtered on nothing at all. + +## The package id can be read back, and costs nothing (2026-08-20) + +`aapt2 dump packagename ` answers in one word and ~40 ms, from +`$ANDROID_HOME/build-tools/*/aapt2` (versioned, so resolved not +pinned); `aapt dump badging` is the fallback for older build-tools and +is what #159's own measurement used. That is cheap enough to do on +every deploy, which is what makes "the two ids agree by construction" +affordable rather than aspirational — the alternative considered was +giving the debug-flavoured tasks `APP_ID` + `.dev`, which is one line +and leaves the class of bug alive for the next flavour or suffix. + +The guard runs **before** a target is chosen, deliberately: it is a +question about the artifact, so it can be exercised with nothing +plugged in, and a build whose id is wrong should be refused whether or +not there is anything to install it onto. That is what let the negative +test run safely with the user's phone attached: + +``` +$ ./scripts/android-deploy.sh --apk bin/yellowjacket.apk \ + --target device --expect app.yellowjacket +android-pkgid: refusing to act on a package this APK does not declare. + the APK declares: app.yellowjacket.dev + the task expects: app.yellowjacket +rc=2 +``` + +That is exactly #159's configuration — debug APK, release id, real +device — refused with no adb call made. + +## `make android-emulator`'s boot wait can be satisfied by a phone (2026-08-20) + +Noticed while booting the emulator for #159's verification, with a +phone also attached. `scripts/android-emulator.sh start` reported +`waiting for boot ok / android 14` about **eight seconds** after +launching the emulator, which had not appeared in `adb devices` yet — +`pick_device`'s last resort is "exactly one device online", and at that +moment the one online device was the phone. So it waited for the +phone's boot, found it long since booted, and returned. The emulator +took another ~10 s to come up. + +Harmless here (the emulator was up before anything used it) and a +straightforward race otherwise: `start` should wait for a device that +is an emulator, not for whatever `pick_device` returns. Filed as #162.