Android SDK installed on this machine (cmdline-tools via brew; platform-tools + platforms;android-35 + build-tools;35.0.0), local.properties points Gradle at it (gitignored). Wired AGP 9.2.1 into the version catalog + google() repos; proved the full chain by building a throwaway android-library module against SDK 35 (AAR produced), then removed the probe. Groundwork for AW2+: AGP plugins in the catalog, google() in settings, and the working recipe in README (incl. the AGP-9-has-built-in-Kotlin gotcha — Android modules apply ONLY the android plugin, never kotlin.android). Pure JVM modules still green (218 tests). Framework module stubs stay gated until implemented.
123 lines
6.8 KiB
Markdown
123 lines
6.8 KiB
Markdown
# WebTerm — Android client
|
|
|
|
A native Android client for the WebTerm browser-terminal server, targeting functional
|
|
parity with the shipped **iOS** client. See the full design in
|
|
[`docs/ANDROID_CLIENT_PLAN.md`](../docs/ANDROID_CLIENT_PLAN.md) (stack §2, module
|
|
architecture §3, server contract §4, task waves §5).
|
|
|
|
This directory is a **Gradle multi-module** project. The module set mirrors the iOS
|
|
SPM package set and inherits its rule: *dependencies only flow down; nothing points
|
|
upward* (ARCHITECTURE §1).
|
|
|
|
## ⚠️ No-SDK constraint (why only 5 modules build here)
|
|
|
|
The current build environment has **no Android SDK**. Everything that can be pure
|
|
**Kotlin/JVM** (`kotlin("jvm")`) is built and unit-tested now; anything that needs the
|
|
Android framework (`com.android.*` plugins) is **scaffolded but disabled**.
|
|
|
|
- **Enabled now (pure Kotlin/JVM, `./gradlew test`-able):**
|
|
`:wire-protocol`, `:session-core`, `:api-client`, `:client-tls`, `:test-support`.
|
|
- **Scaffolded but COMMENTED OUT** in [`settings.gradle.kts`](settings.gradle.kts)
|
|
(dirs + a `build.gradle.kts` stub exist, marked `// TODO(android-sdk)`):
|
|
`:app`, `:terminal-view`, `:host-registry`, `:client-tls-android`.
|
|
|
|
To bring the Android modules online later: install an SDK, add
|
|
`local.properties` → `sdk.dir`, add the Android Gradle Plugin + `google()` to
|
|
`pluginManagement`, then uncomment the `include(...)` lines and the plugin blocks in
|
|
each stub.
|
|
|
|
## Module map (mirror of the iOS SPM packages — plan §3)
|
|
|
|
| iOS SPM package | Android module | Kind | Status |
|
|
|------------------------|------------------------|-------------------------------|--------|
|
|
| WireProtocol | `:wire-protocol` | pure Kotlin/JVM | ✅ built |
|
|
| SessionCore (reducers) | `:session-core` | pure Kotlin/JVM | ✅ built |
|
|
| APIClient | `:api-client` | pure Kotlin/JVM | ✅ built |
|
|
| ClientTLS (pure half) | `:client-tls` | pure Kotlin/JVM | ✅ built |
|
|
| TestSupport | `:test-support` | pure Kotlin/JVM (fakes) | ✅ built |
|
|
| ClientTLS (fwk half) | `:client-tls-android` | Android (AndroidKeyStore/Tink)| ⏸ SDK-gated |
|
|
| HostRegistry | `:host-registry` | Android (DataStore) | ⏸ SDK-gated |
|
|
| SwiftTerm host view | `:terminal-view` | Android (Termux wrap) | ⏸ SDK-gated |
|
|
| App/WebTerm | `:app` | Android app (Compose/Hilt/FCM)| ⏸ SDK-gated |
|
|
|
|
> Not yet scaffolded: `:transport-okhttp` (OkHttp `TermTransport`/`HttpTransport`
|
|
> impls, JVM) is owned by task **A7** and will be added then. The iOS
|
|
> `URLSession*Transport`s consolidate into it (plan §3 framing note).
|
|
|
|
### Dependency graph (arrows = "depends on")
|
|
|
|
```
|
|
:app (SDK-gated)
|
|
┌───────────────┬───┴────┬──────────────┬───────────────┐
|
|
▼ ▼ ▼ ▼ ▼
|
|
:terminal-view :session-core :api-client :host-registry :client-tls-android
|
|
(SDK-gated) │ │ (SDK-gated) │
|
|
│ │ │ ▼
|
|
│ │ │ :client-tls (pure)
|
|
└──────┬───────┴──────────┴──────────────┬────────────────┘
|
|
▼ ▼
|
|
:wire-protocol ◀──────────── :transport-okhttp (A7, not yet)
|
|
▲
|
|
└──────── :test-support → test source sets only
|
|
```
|
|
|
|
`:wire-protocol` is the **frozen shared contract** (Android analogue of
|
|
`src/types.ts` + WireProtocol) — `ClientMessage`/`ServerMessage`, `MessageCodec`,
|
|
`Validation`, `WireConstants`, `HostEndpoint` (the single Origin/wsURL derivation),
|
|
and the `TermTransport` / `HttpTransport` / `PingableTermTransport` boundary
|
|
interfaces. New wire types are added **only** here (a coordination point).
|
|
|
|
## Toolchain
|
|
|
|
- **Gradle** 9.6.1 (via the committed wrapper — always use `./gradlew`).
|
|
- **Kotlin** 2.3.21 (matches the Kotlin embedded in Gradle 9.6.1).
|
|
- **JVM toolchain** 17 (`jvmToolchain(17)` in every module).
|
|
- Versions are pinned in the version catalog
|
|
[`gradle/libs.versions.toml`](gradle/libs.versions.toml): kotlinx-serialization-json,
|
|
kotlinx-coroutines-core/-test, JUnit5 (Jupiter), Turbine, MockK.
|
|
|
|
Pure modules apply `kotlin("jvm")` + `kotlin("plugin.serialization")`, wire the
|
|
`libs.bundles.unit-test` bundle into `testImplementation`, and run tests on the
|
|
JUnit Platform (`tasks.test { useJUnitPlatform() }`).
|
|
|
|
## Build & test
|
|
|
|
```bash
|
|
# Use the committed wrapper for everything.
|
|
./gradlew help # sanity: the build configures
|
|
./gradlew projects # lists the 5 pure modules
|
|
./gradlew build # compile all pure modules
|
|
./gradlew test # run JVM unit tests (JUnit5 + coroutines-test + Turbine + MockK)
|
|
```
|
|
|
|
> Testing target: **≥80% Kover coverage** on the pure modules (`:wire-protocol`,
|
|
> `:session-core`, `:api-client`, `:client-tls` pure half). TDD, immutable data,
|
|
> small focused files — same discipline as the rest of the repo.
|
|
|
|
## Android SDK setup (proven working)
|
|
|
|
The pure JVM modules need only a JDK + Gradle. The **Android-framework** modules
|
|
(`:app`, `:terminal-view`, `:host-registry`, `:client-tls-android` — plan AW2+)
|
|
need the Android SDK. This machine is set up and the toolchain is **proven** (an
|
|
AGP library module compiled against SDK 35 and produced an AAR):
|
|
|
|
- **SDK location:** `/usr/local/share/android-commandlinetools`
|
|
(installed via `brew install --cask android-commandlinetools`).
|
|
- **Installed packages:** `platform-tools`, `platforms;android-35`, `build-tools;35.0.0`.
|
|
- **`android/local.properties`** (gitignored) points Gradle at it:
|
|
`sdk.dir=/usr/local/share/android-commandlinetools`.
|
|
- **Shell env** (for `sdkmanager`/`adb`): `export ANDROID_HOME=/usr/local/share/android-commandlinetools`.
|
|
|
|
### Wiring an Android module (the working recipe)
|
|
|
|
- Repos: `google()` is in both `pluginManagement` and `dependencyResolutionManagement`
|
|
in `settings.gradle.kts` (needed to resolve AGP + androidx).
|
|
- Plugin: **AGP 9.2.1** (`libs.plugins.android.library` / `.android.application`),
|
|
compatible with Gradle 9.6.1.
|
|
- **Gotcha:** AGP 9 has **built-in Kotlin** — apply ONLY the android plugin. Adding
|
|
`org.jetbrains.kotlin.android` errors with "no longer required since AGP 9.0".
|
|
- Module block: `android { namespace = "…"; compileSdk = 35; defaultConfig { minSdk = 29 } }`.
|
|
|
|
To add more SDK pieces later (e.g. an emulator image for instrumented tests):
|
|
`sdkmanager "system-images;android-35;google_apis;arm64-v8a" "emulator"`.
|