# Tally for Android Two screens, reached from a hamburger drawer: the standard calculator (design 9.2), an NCalc-style pad with a live answer as you type, and the converter (design 9.6), which reopens where it was left and shows a value in every unit at once. Settings has the theme: follow the device, light or dark. The engine behind it is the finished one, so `2^100 + 1`, `x = 5` then `x * 2` and `cagr(10000, 25000, 5) * 100` all work. The programmer and financial screens (design 9) are not built. ## Building Nothing is installed system-wide; the toolchain comes from mise. **Setup, emulator and deployment, including wireless adb, are in [SETUP.md](SETUP.md)** - this file is about what the app is. ``` mise install && mise run android-sdk # toolchain, then the SDK packages zig build android # the native library, one .so per ABI cd android && gradle assembleDebug ``` `zig build android` writes `zig-out/android/{arm64-v8a,x86_64,armeabi-v7a}/libtally.so`, which is the `jniLibs` layout Gradle expects, so Gradle only packages it. Gradle never invokes Zig, and there is no CMake or `ndk-build` in this project: the library is built without the NDK entirely (design 6.2), and the JNI glue is hand-written Zig (`engine/src/jni.zig`) rather than generated from `jni.h`. Because Gradle never invokes Zig, `zig build android` has to be re-run by hand after any engine or JNI change, or the APK gets a stale library. The Gradle wrapper is not committed; `gradle wrapper` writes one, or Android Studio offers to on first open. ## The first thing to run, and it needs no device ``` zig build jvm-test ``` `engine/src/jni.zig` reaches the JNI function table by index, because there is no `jni.h` here to name the entries. Four of those indices have to be right. JNI's table is fixed by the specification rather than by the platform, so a desktop JVM checks them just as well as a phone: this step builds the library for the host, loads it from Java (which runs `JNI_OnLoad`, which round-trips a string through two of the four), and drives every entry point. Moving one index by a slot makes it fail. Then, with a device or emulator attached: ``` cd android && gradle connectedAndroidTest ``` `TallyEngineTest` covers what only the real app can: that a session remembers `x` and `Ans`, that `2^100 + 1` arrives with its last digit intact, that `12 in to ft` is exactly `1`, that an error carries the engine's own wording rather than a second copy written in Kotlin, and that a closed session refuses to evaluate instead of crashing. It passes on an Android 15 x86_64 image with 16KB pages. Note that it uninstalls the app when it finishes. Nothing has been verified on a physical device yet, and arm64-v8a is the interesting one: it is the ABI phones actually use, and the only one where the page-size bug in design 6.3 showed up. ## What is deliberately not here - **No arithmetic.** Kotlin parses no expressions, formats no numbers and words no errors. All of it is the shared engine, so a wrong answer is wrong in the CLI and TUI too, which is the point of having one engine. - **No error table.** Messages come from `engine.phrase` through the JSON. - **No unit list.** `TallySession.unitCatalog()` returns the engine's own tables, aliases included, for whenever the convert screen is built.