# Who Need Help for Android

This module is a native Android WebView shell for the Phoenix application. It
keeps authentication cookies, LiveView WebSockets, MapLibre, and chat in the
same trusted origin. Live location is sent by a user-started native foreground
service with a persistent notification and Stop action, so it can continue
while the Activity is minimized without requesting Android's
background-location permission.

The native tracking bridge uses `WebViewCompat.addWebMessageListener` with the
exact configured origin and rejects messages outside the main frame. It does
not expose a legacy `addJavascriptInterface` object to every frame.

## Verified build configuration

- Android Gradle Plugin 9.3.0
- Gradle 9.6.1
- Android SDK Command-line Tools 22.0
- Android CLI 1.0.15857036 (embedded in the locked Command-line Tools archive)
- AndroidX WebKit 1.16.0
- compileSdk / targetSdk 37
- Build Tools 37.0.0
- Java source and bytecode level 17
- minSdk 24 (project baseline, not an Android SDK requirement)

The debug origin is not stored in the Dockerfile or Gradle project. Set
`WNH_DEBUG_BASE_URL` in the repository's ignored `.env` file. The supplied
local configuration uses loopback together with `adb reverse`; debug builds
allow cleartext traffic, while the WebView still restricts in-app navigation to
that one configured origin.

The same ignored file supplies `WNH_TRACKING_MIN_TIME_MS` and
`WNH_TRACKING_HTTP_TIMEOUT_MS`. The checked-in example preserves the original
local client freshness and timeout behavior; these values are not claimed as
measured production capacity settings.

Release builds do not have a default server and cannot be produced unsigned.
The repository reads non-secret version/origin settings from the selected
ignored environment file, while the upload key and its randomized password
stay outside the repository. Create that key once:

```sh
./scripts/init-android-release-signing.sh
```

Back up both reported files before uploading the first bundle. Losing this
dedicated upload key is recoverable through Play App Signing, but keeping an
offline backup avoids a reset. Do not copy either file into the repository.

Configure these non-secret values in the deployment's ignored environment
file, then produce the signed APK and Play bundle:

```dotenv
WNH_BASE_URL=https://your-final-origin.example
WNH_ANDROID_VERSION_CODE=1
WNH_ANDROID_VERSION_NAME=0.1.0
WNH_ANDROID_SIGNING_KEY_ALIAS=who-need-help-upload
```

```sh
WNH_ENV_FILE=.env.production ./scripts/android-release-build.sh
```

The build passes the private files with Docker BuildKit secret mounts, runs
release unit tests and lint, enables code/resource shrinking, signs both
artifacts, verifies the APK and AAB signatures, and validates the AAB with the
same `bundletool` used underneath Android Gradle Plugin and Google Play before
exporting anything.
It rejects a missing, HTTP, credentialed, query-bearing, or fragment-bearing
release URL and rejects missing or partial signing configuration.

Google Play requires an Android App Bundle for a new app. With Play App
Signing, the locally held key signs the uploaded bundle and Google holds the
separate app-signing key used for distributed APKs:

- <https://developer.android.com/studio/publish/app-signing>
- <https://support.google.com/googleplay/android-developer/answer/9859152>
- <https://docs.docker.com/build/building/secrets/>

## Public staging build

The installable `staging` build type uses the explicit public HTTPS
`WNH_BASE_URL`, disables cleartext traffic, and has its own
`org.whoneedhelp.mobile.staging` application ID. Configure a missing local value
from the existing `PHX_HOST`, `PHX_SCHEME`, and `PHX_URL_PORT`, then build:

```sh
./scripts/ensure-local-public-origin.sh
./scripts/android-staging-build.sh
sha256sum android/dist-staging/who-need-help-staging.apk
```

This variant uses Android's generic debug signing key so it can be installed
for staging verification. It is not a production-signed artifact and must not
be published as a release. The manifest accepts same-origin HTTPS deep links,
but verified Android App Links additionally require the final signing
certificate fingerprint in the deployment's `/.well-known/assetlinks.json`.

With the temporary public origin reachable, run the API 37 emulator smoke test:

```sh
./scripts/android-staging-smoke.sh
```

The script installs the exported staging APK into a fresh project-scoped
emulator container, loads the configured HTTPS home page, follows a
same-origin `/safety` deep link, verifies that the package does not claim an
external HTTPS origin, and retains UI dumps, screenshots, package metadata,
and logcat diagnostics under ignored `output/android-staging-smoke/`. The
one-run container and image are removed on success or failure.

## Reproducible Docker build

From the repository root:

```sh
./scripts/android-build.sh
sha256sum android/dist/who-need-help-debug.apk
```

The Docker build runs JVM unit tests, Android lint, `assembleDebug`, and
`assembleDebugAndroidTest` before it exports the application APK and lint
report.

## Automated device tests

Run the default API 37 suite or the complete API 24/30/34/37 matrix from the
repository root:

```sh
./scripts/android-instrumentation-test.sh
./scripts/android-matrix-test.sh
```

The command requires `/dev/kvm`. It generates an ignored
`.env.android-test` once with a randomized `http://127.0.0.1:PORT` origin and
mode `0600`; the application and its in-process fixture server both derive the
origin from that file. It then builds both APKs, boots a fresh selected emulator
container without external networking, injects emulator coordinates, and runs
`AndroidJUnitRunner`. `WNH_ANDROID_TEST_API` selects one supported API, while
`WNH_ANDROID_TEST_API_MATRIX` controls the matrix command.

The tests cover the missing-location-permission boundary, the WebView-triggered
Android permission dialog, same-origin deep-link routing across Activity
recreation, foreground location upload after Home and Activity destruction, the
persistent notification Stop action, a disconnected Stop request with visible
retry state, and externally forced process death for the non-sticky tracking
service. Results are stored by API under ignored
`output/android-instrumentation/`. On failure, logcat, service state, and
emulator logs are retained; the exact container and one-run image are removed
in either outcome.

## Emulator verification

The optional `emulator` target contains the API 37.0 Google APIs x86_64 system
image. Manual verification against the local Compose application requires KVM
and host networking. Pass the same `.env` value as a build argument, then
expose the Compose proxy to Android with `adb reverse`:

```sh
set -a
. ./.env
set +a
docker build \
  --build-arg "WNH_DEBUG_BASE_URL=$WNH_DEBUG_BASE_URL" \
  --build-arg "WNH_TRACKING_MIN_TIME_MS=$WNH_TRACKING_MIN_TIME_MS" \
  --build-arg "WNH_TRACKING_HTTP_TIMEOUT_MS=$WNH_TRACKING_HTTP_TIMEOUT_MS" \
  --target emulator \
  -t who-need-help-android:emulator \
  android
docker run --rm --name who-need-help-android-emulator \
  --device /dev/kvm \
  --network host \
  who-need-help-android:emulator
docker exec who-need-help-android-emulator adb reverse tcp:4010 tcp:4010
docker exec who-need-help-android-emulator adb install \
  /opt/who-need-help/who-need-help-debug.apk
```

The application ID `org.whoneedhelp.mobile` is provisional until the publishing
identity and store listing are chosen. Google documents Play package names as
unique and permanent, so do not create the Play Console app or publish this
identifier until that choice is explicit. Changing it after publication creates
a different Android application.
