Android applications with seed
This guide describes how to use the current Android seed platform and framework. The implemented support combines a seed library (mobile), an Android host based on Gradle/Kotlin/CMake, and a JNI bridge that loads the native seed library.
Current state: Android is the active mobile platform. The framework provides a retained Canvas surface driven by frame commands, basic input, lifecycle, insets, screen metrics, and multi-ABI packaging. It is not yet a complete widget toolkit.
What is supported
android-arm64as executed mobile ABI;android-x86_64remains only as compatible build/link target;- Android API 37, command-line tools 22.0, build-tools 37.0.0, Gradle 9.5.0,
AGP 9.3.0 and NDK
27.2.12479018as baseline; - shared/static seed libraries loaded by an Android host;
seed_mobile_entrysingle entry and lifecycle hooks;- frames with a background color, up to 256 rectangles, 32 text items, and 128 semantic nodes;
- touch, keys, and
InputConnection.commitTextthrough a bounded queue; - window insets, focus, memory pressure, density and private directories;
- an optional context per
Activitygeneration and stereo PCM output via OpenSL ES with pause/resume; - execution in AVD and installation/launch via
adb; - development/release signing and execution validated on the declared Motorola Android 15/API 35 ARM64 physical-device slice.
The following are not claims of current support: a complete widget catalog, physical-device TalkBack, complete IME selection/composition, text shaping, hot reload, a broad device matrix, store publishing, or production readiness.
Architecture
seed app (.sd)
│ integrated build: .so + .a per ABI
▼
libseed_app.so ─► JNI bridge (preferred packaged dependency)
libseed_app.a ─► separate static artifact
│ C ABI
▼
JNI bridge + SeedSurfaceView
│
▼
Activity Android / Canvas / adb / Gradle
The seed application does not yet replace Activity. The Android host remains responsible for process, window, Canvas, JNI and integration with the SDK. The mobile library provides the portable imports of the implemented slice and should only be used with an Android target.
Requirements
You must have:
- macOS Apple Silicon for the currently validated ARM64 matrix;
- JDK 17;
- NDK's
clang,adb, Android CLI/sdkmanager,aapt2, Gradle and emulator; - a
arm64-v8aAVD oradb-authorized ARM64 Android device.
The project's official installer uses $HOME/Android/Sdk, does not require sudo, and installs the full baseline:
./seed mobile android setup
./seed mobile android doctor
Useful options:
./seed mobile android setup --sdk "$HOME/Android/Sdk" --start
./seed mobile android setup --skip-avd
./seed mobile android setup --dry-run
Use --api, --abi, --image-flavor, --ndk, --avd, and --device to customize the installation. --skip-gradle leaves the installation of Gradle up to the user. To use an NDK other than the baseline, explicitly declare SEED_ANDROID_NDK_REVISION and confirm compatibility with the project.
The diagnostics should show Android 37.0, build-tools 37.0.0, Gradle 9.5.0, platform-tools, NDK, and the API 21 sysroots of aarch64-linux-android and x86_64-linux-android:
./seed mobile android doctor
Tutorial 1: first application
Create app.sd:
fn main() -> i64 {
0
}
pub fn seed_mobile_entry() -> i64 {
0
}
Create a host by copying the versioned template:
cp -R seed/compiler/llvm/mobile/android my-android
Compile both ABIs, stage and generate the APK:
./seed mobile android build app.sd my-android \
--package-name seed_app \
--application-id com.example.seedapp \
--package
The command produces libseed_app.so and libseed_app.a for arm64-v8a and
x86_64, copies the artifacts to my-android/app/src/main/jniLibs/, binds
the seed shared object as a packaged JNI bridge dependency, and executes
assembleDebug; the archive remains available separately to native
consumers. The my-android/seed-android.properties file records the native
name and application ID used by subsequent commands.
Tutorial 2: draw a screen
Import the mobile Android library into your application code. A complete example is in compiler/llvm/tests/mobile/android_framework.sd. The frame callback receives width, height and time in nanoseconds:
use "mobile"
pub fn seed_mobile_entry() -> i64 { 0 }
pub fn seed_mobile_on_frame(width: i32, height: i32, time_nanos: i64) {
mobile.frame_clear()
mobile.frame_color(4278190335 as i64) // ARGB: opaque blue
mobile.frame_rect(1000, 1000, 2000, 2000, 4294902015 as i64)
mobile.frame_text("Hello, Android", 1500, 4500, 2400, 4294967295 as i64)
}
Coordinates and font size are expressed in millipixels. Colors are ARGB integers. The host limits each frame to 256 rectangles, 32 text items, 128 semantic nodes, and 256 UTF-8 bytes per label or item. Text is drawn by the Android Canvas; font selection and advanced shaping are not yet part of the contract.
Tutorial 3: Consume Touch, Keyboard and IME
The library maintains a bounded queue of 128 events. Read the fields before removing an event:
use "mobile"
fn read_input() {
while mobile.input_available() {
let kind = mobile.input_kind()
let a = mobile.input_a()
let b = mobile.input_b()
let c = mobile.input_c()
// kind 1: touch; a=action, b=x in millipixels, c=y in millipixels.
// kind 2: key; a=action, b=Android key code, c=Unicode code point.
// kind 3: text committed by the IME.
if kind == 3 {
let length = mobile.input_text_length()
// input_text_byte(index) exposes the committed text's UTF-8 bytes.
let _ = length
}
mobile.input_pop()
}
}
input_pop must be called once per event. If producers exceed capacity, the queue retains the most recent events. Selection and composition region are not yet exposed.
Tutorial 4: Lifecycle and resources
Define only the necessary hooks. They run on the Activity thread and must not block, retain JNI objects or create tasks without a structured group:
pub fn seed_mobile_on_create() {}
pub fn seed_mobile_on_start() {}
pub fn seed_mobile_on_resume() {}
pub fn seed_mobile_on_pause() {}
pub fn seed_mobile_on_stop() {}
pub fn seed_mobile_on_destroy() {}
seed_mobile_entry is protected against repeated execution in the same
process; recreating Activity does not restart the application. Destruction
cancels pending work on the bridge; process death is the final cleanup
boundary.
Applications with cross-frame persistent capabilities can export seed_mobile_context_create, seed_mobile_context_on_event, seed_mobile_context_on_frame, and seed_mobile_context_destroy. The host maintains at most one context per generation of Activity, stops forwarding callbacks when this generation is invalidated, and destroys the context exactly once. Legacy hooks remain the fallback.
Use mobile.window_focused() to pause visual work during loss of focus and
mobile.memory_pressure() to flush caches after onTrimMemory. Do not treat
these signals as a guarantee that the process will remain alive.
Tutorial 5: layout, safe area and storage
Insets are physical pixels: 0=left, 1=top, 2=right, 3=bottom.
use "mobile"
fn content_top() -> i64 {
mobile.window_inset(1)
}
fn text_size() -> i64 {
// scaled_density_milli is the font density multiplied by 1000.
16000 * mobile.scaled_density_milli() / 1000
}
mobile.density_dpi() and mobile.scaled_density_milli() let you adapt
layout and typography. mobile.app_files_* and mobile.app_cache_* return
bounded views of the private paths provided by Activity; use them for
internal storage and caching. There is no automatic access to external
storage. The network permission declared by the template covers the current
net slice, but additional permissions remain the host's responsibility.
Build Flows
Manual build
seed --emit-shared libseed_app.so \
--target android-arm64 --runtime-profile android \
--sysroot "$ANDROID_NDK_SYSROOT" --soname libseed_app.so app.sd
seed --emit-shared libseed_app.so \
--target android-x86_64 --runtime-profile android \
--sysroot "$ANDROID_NDK_SYSROOT" --soname libseed_app.so app.sd
./seed mobile android stage my-android arm64.so x86_64.so \
seed_app com.example.seedapp
./seed mobile android package my-android
Integrated build
./seed mobile android build app.sd my-android \
--ndk "$ANDROID_NDK_HOME" \
--package-name seed_app \
--application-id com.example.seedapp
build looks for the NDK in ANDROID_NDK_HOME when --ndk is not provided and generates shared/static pairs for the two ABIs; the host prefers the packaged shared object and keeps the archive as a separate artifact. stage continues to accept ELF shared libraries, validates artifacts, and rejects invalid application IDs. The native package name cannot contain / or ...
If the Seed package that contains app.sd has an assets/ folder, the
integrated build also copies its regular, flat files to the Android host.
Names are limited to ASCII letters and digits, ., _, and -; subfolders,
hidden files, and collisions with the host probe are rejected. At startup,
the host reads the generated seed-mobile-assets.list index and extracts
exactly those files — at most 256 files and 64 MiB — to the read-only private
root already exposed to Seed. Framework or overlay assets also listed by
AssetManager do not enter the Seed package. The same manifest and image,
font, WAV, and Ogg assets can therefore follow desktop and Android without
application-specific copy logic.
Direct dependencies on audio-vorbis and grove-game-assets cause the
integrated build to include the corresponding stb backends in both ABIs. The
JNI bridge provides the shared audio contract over OpenSL ES, with bounded
streams, generation-tagged handles, a 48 kHz stereo PCM queue, pending-byte
queries, pause/resume, cleanup, and exactly-once close.
Debug, release and execution
./seed mobile android package my-android
./seed mobile android package my-android --release
./seed mobile android run my-android
./seed mobile android run my-android --release
./seed mobile android run my-android --serial emulator-5554
./seed mobile android doctor --serial ZF524P7D2T
run packages, installs, force stops the previous instance, and runs .MainActivity via adb, requiring Status: ok. ANDROID_SERIAL can also select the device.
To start the AVD installed by setup:
emulator -avd seed-api37 -no-snapshot
adb wait-for-device
./seed mobile android run my-android
The actual AVD name can be queried with avdmanager list avd.
AVD matrix
The implemented matrix keeps the system-images installed and creates ephemeral AVD state per run. Each line uses temporary ANDROID_AVD_HOME, initialization without snapshot and clean data; After testing, close and delete only the AVD. SDK images, NDK, Gradle, and build caches remain installed. Native staging and Gradle run on a temporary copy of the host; the matrix does not rewrite seed-android.properties, jniLibs or outputs in the original project.
Fast smoke declares ARM64 phone AVDs in APIs 21, 29, and 37. The release suite uses stable API 37 with 16 KB tablet pages, foldable profile, limited memory, and eight lifecycles. There is no nightly tier and no requirement to run x86_64; android-x86_64 remains covered by compile/link contracts only.
The runner builds the APK once and reuses it. Each line validates boot, doctor --serial, install/cold launch, accessibility, basic touch/key/IME, insets/density/focus, lifecycle/rotation, Logcat and shutdown. The release suite repeats lifecycle as per line and injects RUNNING_CRITICAL in the limited memory scenario. Failures preserve configuration, Logcat, dumpsys, gfxinfo, meminfo, accessibility tree and screenshot in compiler/llvm/build/android-avd-matrix/.
make -C seed/compiler/llvm android-avd-matrix-check
make -C seed/compiler/llvm android-avd-smoke
make -C seed/compiler/llvm android-avd-release
make -C seed/compiler/llvm game-seed-garden-android-avd-check
SEED_ANDROID_AVD_API=37.0 \
SEED_ANDROID_AVD_RUNNER_POLICY=required \
make -C seed/compiler/llvm android-avd-smoke
Policy auto (default) records SKIP for missing tools or images; required fails; off disables execution. --install-images, in direct call to scripts/android_avd_matrix.sh, installs selected images. SEED_ANDROID_ADB_TIMEOUT and SEED_ANDROID_AVD_BOOT_TIMEOUT limit adb and boot calls; SEED_ANDROID_AVD_VERBOSE=true preserves verbose emulator output. The three smokes and four ARM64 release scenarios pass on the Apple M4.
The Garden contract uses the same ephemeral infrastructure. It validates the 64 semantic cells in portrait and landscape during eight rotations, motion with reduced animation, pause/resume, audio-session removal during teardown, startup, frames/jank, and PSS retention. These measurements are the AVD baseline; they do not replace Android LeakSanitizer or performance, thermal, and latency measurements on a physical device.
This matrix only expands the evidence in emulator. Motorola registered in ANDROID_TARGET_BASELINE.md remains all physical claim. The full policy and implementation order is in roadmap-mobile.md.
Testing and validation
Validate contracts without full SDK with:
make -C seed/compiler/llvm android-target-test
make -C seed/compiler/llvm android-host-check
make -C seed/compiler/llvm android-package-check
make -C seed/compiler/llvm android-build-check
make -C seed/compiler/llvm android-run-check
make -C seed/compiler/llvm android-doctor-check
For real validation, run doctor, do build, install to AVD or device, and run run. The evidence baseline is in ANDROID_TARGET_BASELINE.md. doctor --serial <adb-serial> also validates model, API, ABI, security patch, and authorization state of the selected device.
Problem diagnosis
-
missing Android SDK/NDK: runseed mobile android setupor setANDROID_HOME/ANDROID_NDK_HOME. -
missing android.jar,aapt2or sysroot API 21: rundoctorand install the indicated component; do not use a sysroot without ABI-qualified directories. -
missing seed-android.properties: Runstageorbuildbeforepackage/run. -
Android packaging requires Gradle: install Gradle or put agradlewexecutable on the host. -
device
offlineor missing: checkadb devices, accept the USB debugging key prompt, and use--serial. -
Missing
Status: ok: seeadb logcat, confirm the application ID and check whether the two libraries were installed injniLibs. -
ABI failure: do not mix
android-arm64withx86_64; staging validates the ELF type before copying the library. -
warning
This app isn’t 16 KB compatible: use the current compiler/host; both link Android ELF segments with 16,384 byte alignment.
Limits and next steps
The current bridge is an operational foundation, not a promise of compatibility with the entire Android SDK. A widget catalog, retained layout, navigation, a complete text editor, physical-device TalkBack, stable plugins, an inspector, a profiler, hot reload, a broad device matrix, store publishing, and production signing remain on the roadmap. iOS is active in the simulator through the compiler, runtime, Swift host, and XCFramework; physical-device execution, provisioning, signing, and store publication remain open.
References
- Roadmap mobile
- Android host versioned
- Library
mobile - Library installation
- Public JNI ABI