# CST-agent on Android The engine runs on the phone as a plain bionic binary -- no Termux, no proot, no second process for the UI to talk to. It is the same C core the CLI, the TUI and the WebUI use, cross-compiled with the NDK; on the phone the Qt front end (app/) will link `libcstcore.a` in-process and register its own platform tools under the owner `app:android`. Two scripts cover the engine side: | script | what it does | | --- | --- | | `scripts/build_third_party_android.sh ABI [all\|mbedtls\|curl]` | static mbedTLS + libcurl into `third_party/prefix-android-ABI` | | `scripts/build_android.sh [ABI] [--push] [--run] [--run-tests]` | `libcstcore.a`, `cst-agent`, the test binaries; optional push and on-device run | ## Toolchain NixOS: nixpkgs carries the NDK (the Android SDK license makes it unfree, hence the flag; the packaged NDK has a patched interpreter, so its clang runs here): ```sh export ANDROID_NDK_HOME=$(NIXPKGS_ALLOW_UNFREE=1 nix build --impure --no-link \ --print-out-paths 'nixpkgs#androidenv.androidPkgs.ndk-bundle')/libexec/android-sdk/ndk-bundle ``` `adb` comes from `nix-shell -p android-tools`; the builds need cmake, ninja and curl. End to end: ```sh nix-shell -p cmake ninja curl android-tools --run ' JOBS=16 ./scripts/build_third_party_android.sh arm64-v8a all' nix-shell -p cmake ninja curl android-tools --run ' JOBS=16 ./scripts/build_android.sh arm64-v8a --push --run' ``` `--run` finishes with the two smoke checks on the device (`cst-agent --version` and `tests/test_embed`, the in-process embedding contract); `--run-tests` builds the whole `tests/` suite, pushes it and runs every binary on the phone, printing a pass/fail line per test. ## The APK (Qt front end) `scripts/build_apk_android.sh [--install] [--logs] [--clean]` configures with the Qt for Android kit, builds the `apk` target (Gradle, no port opened) and optionally installs it. Bits it needs, all overridable through the environment: | variable | what it points at | | --- | --- | | `QT_ANDROID` | Qt for Android 6.11.2 (`aqt install-qt all_os android 6.11.2 android_arm64_v8a -O ~/Qt --archives qtbase qtdeclarative qtquickcontrols2`) | | `QT_HOST` | the host Qt of the same version (`aqt install-qt linux desktop 6.11.2 linux_gcc_64 -O ~/Qt --archives qtbase qtdeclarative qtsvg`) | | `ANDROID_SDK_ROOT` | SDK with `platforms/android-` and `build-tools/` | | `ANDROID_NDK_HOME` | the NDK the engine was built with | | `JAVA_HOME` | JDK 17+ for Gradle | ### The SDK without sdkmanager `nixpkgs`'s full `androidsdk` pulls the emulator and system images (gigabytes); the two components actually needed are cheap. The platform comes from the nix store (unfree, so the license flag is required) and build-tools from Google's own zip, laid out as the SDK `sdkmanager` would: ```sh PLATFORM=$(NIXPKGS_ALLOW_UNFREE=1 NIXPKGS_ACCEPT_ANDROID_SDK_LICENSE=1 nix build \ --impure --no-link --print-out-paths --expr \ 'builtins.elemAt (import {}).androidenv.androidPkgs.platforms 2') # android-35 mkdir -p ~/android-sdk/{build-tools,platforms} ln -s $PLATFORM/libexec/android-sdk/platforms/android-35 ~/android-sdk/platforms/ ``` `androiddeployqt` then picks `build-tools` and `platform-tools` from there. For build-tools, `dl.google.com` is the only source: from here it hands out ~100 KB/s per connection, so the script downloads the zip in 16 ranged requests (≈25x faster) and fixes the exec bits Python's `zipfile` drops. ### Host Qt on NixOS The host Qt is built for a regular distribution: its tools (`qmlimportscanner` first of all) look for `libgssapi_krb5.so.2`, `libglib-2.0.so.0`, `libz.so.1`, `libfontconfig`, `libX11`, `libEGL`/`libGL`, `libzstd`, `libbrotlidec`, `libdbus-1`, `libfreetype`, `libstdc++`, none of which exist in NixOS's search paths. The tools carry `RUNPATH=$ORIGIN/../lib`, so symlinking those libraries from the nix store into `/lib/` is enough: ```sh nix build --no-link --print-out-paths nixpkgs#krb5.lib nixpkgs#dbus.lib \ nixpkgs#libglvnd nixpkgs#fontconfig.lib nixpkgs#freetype nixpkgs#glib.lib \ nixpkgs#libx11 nixpkgs#libxkbcommon nixpkgs#zlib nixpkgs#zstd \ nixpkgs#brotli.lib nixpkgs#stdenv.cc.cc.lib # then ln -s ~/Qt/6.11.2/gcc_64/lib/ ``` Two more things the script does on the way: * **GLESv2/EGL**: Qt's `FindGLESv2.cmake` only searches `CMAKE_FIND_ROOT_PATH` prefixes (`/include`), but the NDK keeps the headers in `/usr/include` -- without `-DGLESv2_INCLUDE_DIR`/`-DEGL_INCLUDE_DIR` `find_package(Qt6 Gui)` fails with "dependency GLESv2 could not be found". * **`-L$PREFIX/lib` on every link type**: the APK's payload is a *shared module*, so `CMAKE_SHARED_LINKER_FLAGS`/`CMAKE_MODULE_LINKER_FLAGS` need the prefix too, not just the EXE flags. ### Gradle The generated project resolves plugins and dependencies from `google()` and `mavenCentral()`, plus a Gradle distribution from `services.gradle.org`. From here all three are slow (dl.google.com ~100 KB/s), so two local tweaks: * `~/.gradle/init.d/*.gradle` (machine-local, not in the repo) prepends the Aliyun/Tencent mirrors for `pluginManagement`, `dependencyResolutionManagement` and every `buildscript`. * the wrapper in `/src/3rdparty/gradle/gradle/wrapper/` points at the Tencent Gradle mirror (51 MB/s vs 100 KB/s). `scripts/gradle-android-init.gradle` (installed into `~/.gradle/init.d/` by the build script) switches off release lint: `lintVitalAnalyzeRelease` resolves its own tool jars straight from `dl.google.com`, bypassing every repository the build configures, and fails the build after all the real work is done. ### Signing `androiddeployqt` copies the **unsigned** Gradle output and signs it itself (`QT_ANDROID_SIGN_APK` + the `QT_ANDROID_KEYSTORE_*` variables; a signed Gradle output makes it fail with "Could not copy unsigned APK"). The script exports those variables and creates the standard Android debug keystore (`~/.android/debug.keystore`, alias `androiddebugkey`) on first use; `CST_ANDROID_KEYSTORE` and `CST_ANDROID_KEY_ALIAS`/`_PASS` override it. `apksigner` is a bash script with a `#!/bin/bash` shebang, which NixOS does not have, so the script rewrites the shebang of the build-tools wrappers. ## What is Android-specific * **API level 24** (`API_LEVEL=24`): bionic provides threads, `exec` and `/system/etc/security/cacerts`, which is where libcurl is pointed (`CURL_CA_PATH`). Bump the level if a newer syscall or CA layout is needed. * **No TUI, no WebUI**: `-DCST_BUILD_TUI=OFF -DCST_BUILD_WEBUI=OFF` -- there is no ncurses on Android, and the phone front end talks to the engine in-process instead of over HTTP. `src/webui_stub.c` compiles in place of the server. * **Sandbox**: `src/sandbox_exec.c` only enables the seccomp/Landlock paths under `defined(__linux__) && !defined(__ANDROID__)`, so the phone build gets the plain exec path -- the same rule the Termux build follows. The seccomp supervisor used to compile in and the probe reported "seccomp", but on the device a write outside the workspace was not denied (`tests/test_sandbox.c:308`), so the phone build now reports no sandbox instead of one that does not hold. * **sashidown** is built per ABI (`vendor/sashidown/libsashidown-.a`) with the NDK's `aarch64-linux-android24-clang` wrapper: CMake's compiler is plain clang and keeps `--target`/`--sysroot` in its own variables, which the vendored makefile cannot see. The host archive stays host-built. * **Static libcurl** needs `-L$PREFIX/lib` on the link line, because curl's own CMake config lists its mbedTLS dependencies as `-l` flags rather than paths. ## Status * arm64-v8a, Android 16 (NX789J): `cst-agent` runs, `tests/test_embed` passes. * The whole suite runs on the device (`scripts/build_android.sh arm64-v8a --run-tests`): 36/36 after the diff test learned to tell toybox's `diff` from GNU diffutils. * The APK builds, installs and runs: the Qt front end links the engine in-process (no port), `main.cpp` puts the workspace, config and tmp under `/data/user/0/xyz.whcself.cstagent/files/`, and a turn does a real HTTPS request from inside the ART process (with no API key the provider answers 401 -- the engine, curl and TLS parts of the round trip are what that shows). Rotation keeps the session; the sidebar appears in landscape. * Still to come: the Android platform tools behind `app:android` (`notification_send`, clipboard, share sheet, SAF file picker), the back gesture, and the icon glyphs -- `ThemeIcon` finds no icon files on the phone and draws empty boxes where the desktop shows Breeze icons.