The native shell lives in apps/mobile/android, generated by Capacitor 8.5 from
apps/mobile/capacitor.config.ts. App id: com.droidmobile.client, display name:
Droid Mobile (unofficial client; no Factory trademarks in the icon or store name).
Toolchain: JDK 21 (JDK 17 cannot build Capacitor 8), Android SDK (default
%LOCALAPPDATA%\Android\Sdk, or ANDROID_HOME), AGP 8.13.0, Gradle wrapper 8.14.3,
compile/target SDK 36, minSdk 24. tools/dev/env-android.ps1 exports all of it for a shell.
It uses <DROIDMOBILE_TOOLS_DIR>\jdk-21 as JAVA_HOME when that folder exists (default tools
folder D:\droid-tools, which may also hold gradle-home and avd to keep large caches off the
system drive) and otherwise keeps the JAVA_HOME of your shell, which must be a JDK 21.
The debug and release builds are deliberately different runtimes:
| debug | release | |
|---|---|---|
| WebView origin | http://localhost (androidScheme: 'http') |
default https://localhost |
Cleartext (ws://, plain HTTP) |
allowed via android:usesCleartextTraffic="true" in android/app/src/debug/AndroidManifest.xml |
never |
| WebView DevTools (CDP) | on (webContentsDebuggingEnabled: true) |
off |
android:allowBackup |
off | off |
The variant is selected only by the CAPACITOR_VARIANT environment variable that
capacitor.config.ts reads during cap sync (unset/anything-but-debug fails secure to
release). Never hand-edit generated files under android/ to switch variants; the
automated check npm run android:check fails the build if a variant drifts.
Root npm scripts (they call the scripts in tools/dev/):
npm run android:debug # web build + cap sync (debug) + gradlew assembleDebug
npm run android:release # web build + cap sync (release) + gradlew bundleRelease assembleRelease
npm run android:sync # web build + cap sync only (debug variant), e.g. after web changes
npm run android:check # merges both variants' manifests and runs the automated variant tests
npm run android:icons # regenerate the placeholder launcher/adaptive/splash artAPK/AAB outputs land in apps/mobile/android/app/build/outputs/. google-services.json is copied from secrets/ by build-android.ps1 before each build. Release signing and the verified release pipeline are described in release.md.
Create an Android 16 (API 36) virtual device named droid-api36 first (Android Studio Device
Manager or avdmanager). The scripts expect that name.
npm run android:emulator # start detached + wait for boot (~79 s cold)
npm run android:install # install app-debug.apk and launch
npm run android:cdp # forward tcp:3150 -> WebView DevTools socket
agent-browser --session <name> connect 3150 # drive the WebView over CDP (debug builds only)The emulator reaches host services at 10.0.2.2 (e.g. the test daemon:
ws://10.0.2.2:3101); a physical phone uses adb reverse tcp:3101 tcp:3101 and
ws://127.0.0.1:3101.
Notes:
- CDP forwards must be re-created after every app restart (the DevTools socket name contains the app pid).
- When you finish, stop the emulator and delete
<AVD home>\droid-api36.avd\snapshots(the emulator writes a ~3 GB snapshot at exit even with-no-snapshot-save).
npm run android:check (script tools/dev/check-android-variants.ps1) syncs and merges
the manifest of both variants, then runs
apps/mobile/src/native/android-variants.test.ts, which asserts:
- merged debug manifest: cleartext on,
debuggable,allowBackup=false, app id intact; - merged release manifest: no cleartext, no
debuggable,allowBackup=false; - synced
capacitor.config.json: http scheme only together with debugging on (debug signature), otherwise the https origin with no debugging and noserver.url/cleartext.
Launcher icons (legacy + adaptive with background/foreground/monochrome layers) and the
splash screens are generated placeholders drawn by tools/dev/generate-android-icons.ps1
(pure System.Drawing). Re-run it after changing the palette there.