android-usb-verify
Use when adb, device auth, the Gradle wrapper or AGP 9's built-in Kotlin misbehaves here, or when installing, launching, driving or screenshotting an app via adb, e.g. Ironvellum (formerly Monarch) on
SKILL.mdskills/managed/android-usb-verify/SKILL.md
GitHubAndroid device verification (this Windows workstation)
Install, launch, drive and prove an Android app on a USB phone or emulator over adb.
Ironvellum (formerly Monarch, D:\Monarch) specifics are at the end.
Toolchain
- SDK:
%LOCALAPPDATA%\Android\Sdk(platform-tools\adb.exe,emulator\,build-tools\36.0.0,platforms\android-37.0); from git-bash,"$LOCALAPPDATA/Android/Sdk/platform-tools/adb.exe". RegistryHKLM\SOFTWARE\Android Studiomay have an empty SdkPath — don't trust it. - JDK: the system Oracle 21 on PATH works; Android Studio's
jbr\bin\java.exeis flaky from bash. - Gradle: no gradle CLI is installed. Use the project wrapper from git-bash,
./gradlew.bat <task>or./gradlew <task>(both verified 2026-09-24). Nevercmd //c gradlew.bat: it exits 1 and prints nothing. A project without wrapper scripts runsjava -cp gradle/wrapper/gradle-wrapper.jar org.gradle.wrapper.GradleWrapperMain <task> --console=plainfrom the project dir (the SDK comes from itslocal.properties); fetch missing wrapper files fromraw.githubusercontent.com/gradle/gradle/v<ver>/gradlew{,.bat}+gradle/wrapper/gradle-wrapper.jar. - AGP 9.x has built-in Kotlin: applying
org.jetbrains.kotlin.androidFAILS ("no longer required"). Remove it (root + app + catalog); keeporg.jetbrains.kotlin.plugin.compose+ KSP (≥2.3.1 for AGP 9). Kotlin DSL: dropkotlin {}compilerOptions unless needed. - Version pins: read
maven-metadata.xmlfrom dl.google.com / repo1.maven.org for AGP (com/android/tools/build/gradle), Kotlin, Compose BOM, Room, Navigation, activity-compose, lifecycle. Pick the latest STABLE (skip alphas). - Health Connect (
androidx.health.connect:connect-client1.1.0): the record class isBodyFatRecord(NOT BodyFatMassRecord); permissions in the manifest + rationale intent-filter; wrap reads inrunCatching; gate the connect button onHealthConnectClient.getSdkStatus.
Device choreography (every session)
adb devices— want<serial> device.unauthorizedmeans the RSA prompt is on the phone: the user must tap Allow (tick "Always allow"), then poll until it readsdevice. Missing entirely: replug. Afteradb kill-server/start-serverthe list can read empty for a moment — poll a few times before concluding the device is gone.adb shell svc power stayon usbkeeps the screen awake while plugged in; without it, screenshots come back as the lock screen or all black.- Before ANY screencap:
adb shell input keyevent KEYCODE_WAKEUP+adb shell wm dismiss-keyguard. A black screenshot = screen off; a lock screen = re-run this step. - Install:
adb install -r app.apk. A fresh state meansadb uninstallfirst, which wipes the app's data (Room then reseeds cleanly) — never on a device that holds real user data. - Launch and check for crashes:
Samsung system noise (minksocket, keystore2, BatteryDump) is normal; onlyadb logcat -c adb shell am start -W -n <package>/.MainActivity # expect Status: ok adb logcat -d *:E AndroidRuntime:E | tail -20 # crash history: adb logcat -b crash -dFATAL EXCEPTIONfrom your package matters.am start -Wafterforce-stopis a COLD launch; a resumed task reopens on its last screen (the user may have navigated), so force-stop for a clean start.
Driving Compose UIs headlessly
- Dump:
adb shell uiautomator dump /sdcard/ui.xml >/dev/null 2>&1; adb shell cat /sdcard/ui.xml > .tmp/ui.xml(one-line XML). - Extract tap targets by
text=(or class) with sed — grep can't slice one-line XML, and some harnesses block shell grep:sed 's/></>\n</g' .tmp/ui.xml | sed -n 's/.*text="NAME".*bounds="\[\([0-9]*\),\([0-9]*\)\]\[\([0-9]*\),\([0-9]*\)\]".*/\1 \2 \3 \4/p'→ tap the centre((l+r)/2, (t+b)/2)withadb shell input tap X Y. - Checkboxes are
checkable="true"nodes (unchecked =checked="false"); buttons are theirtext=bounds. - Do NOT pipe sed output into
while read— git-bash throws I/O error 87 mid-loop and silently skips taps. Write the bounds to a file first (while read ... done < file) or loop over whitespace-split tokens with a for-loop and a counter. - Text entry:
input tap <field>,sleep 2, THENinput text "..."(typing races focus);input keyevent 111(ESC) dismisses the IME between fields. - Dumps often omit Compose field values and can be stale (check the byte size changes) — confirm
field values with a screenshot, not the dump. Read
enabled/selected/checkedoff the clickable ancestor, not the label node:compose-control-tap-verification.
Screenshot proof
adb exec-out screencap -p > .tmp/shots/<name>.png, then read the PNG with a vision query (?q=)
asking for the specific labels and colours you expect. Trust only what the vision answer confirms.
Editing and build pitfalls
- Editing source between a background Gradle run's start and finish gives stale or mixed builds — land ALL file writes first, then build.
- Edit-tool anchors on a file changed since the last read corrupt it. After any failed or mis-anchored edit, and on files edited more than once per session, prefer a full-file rewrite over another anchored patch.
Ironvellum (formerly Monarch) specifics
- Repo
D:\Monarch; package and applicationIdcom.ironvellum.appin both thefossandplayflavours. Standing repo facts (flavour task names, the gate, backups, rename vocabulary) live inmonarch-session-context. - Devices: the owner's Samsung S25 Ultra
R5GL14GXV3J(SM-S938B) holds real training history; the emulator isemulator-5554(AVDIronvellumEmu, boot it withpython tools/device.py up). With both attached, pin every command withadb -s <serial>/ANDROID_SERIAL, and do anything destructive (uninstall,pm clear, instrumented suites) on the emulator. A deliberate reset of the phone goes throughmonarch-fresh-account-reset, after the backup inmonarch-session-context. - Build and install:
./gradlew.bat :app:installFossDebug, oradb install -r app/build/outputs/apk/foss/debug/app-foss-debug.apk. - Launch:
adb shell am start -W -n com.ironvellum.app/.MainActivity; onlyAndroidRuntimeFATAL lines fromcom.ironvellum.appmatter. - Build quirks: AGP 9.4 with built-in Kotlin, KSP 2.3.12.
SectionHeaderlives incom.ironvellum.app.ui.components(Common.kt) — a wrong-package import has bitten repeatedly.