Items 1-3 of Iris's 22:16 phone report, plus the two defects that were hiding behind item 1 and only became visible once the first one was fixed. Emulator evidence and the numbers are in docs/RUST.md. **Keyboard reopen.** `attr.rs`'s already-focused branch calls `focus_gained` on a tap that stays inside `DRAG_SLOP` -- what Android's own `EditText` does, `showSoftInput` being idempotent. Dismissing the IME leaves the field focused, so the only branch that requested it never ran again. Negative control run: without this one call the second tap leaves `mInputShown=false`. Swipes across and out of the focused field still summon nothing. **IME height.** `MainActivity` sends `getInsets(ime()).bottom` and `isVisible(ime())` as two values; the height used to be sent *as* the boolean, so nothing had a number to pad by. `Insets`/`WindowInsets` carry both, `bench_client` reads the boolean for its state machine and the height for `Composer::set_bottom_inset`, and the list follows because it is `rest(1)` in the same `Span`. **Fling.** Three defects, in the order they were found: 1. `on_touch_event` read only each `MotionEvent`'s final position, so a batched 120Hz flick fed the tracker one sample and `velocity()` answered 0.0. Historical samples are replayed through the sensor pass now, `CursorState::time` carries each sample's own time (so a replay loop's speed cannot become the measured velocity -- the winit backend sets it too), the press is a sample as AOSP's own tracker does, and `iris drag release:` logs the decision for the phone's logcat. 2. Nothing advanced a fling between input events: `tick_fling`'s only caller was the benchmark's own loop, so the bench flung and a finger never did. iris has one animation mechanism now -- `Widget::tick`, `UiData::animate`/`tick_animations`, called by both backends before the draw and re-requesting a frame while it answers true. 3. With flings finally animating, one lasted 45 seconds: `List::fling` hardcoded density 1.0 against physical-pixel velocities, and `FlingCalculator`'s coefficient used the scroll friction where AOSP uses its 0.84 tuning constant -- 56x, inside an exponential. Emulator: 1.62s for v=11064, against AOSP's own 1.586s. **Two pre-existing faults found on the way.** `MOVE_CHAIN_LIMIT` was 16 and the composer's chain is 17, so every debug build aborted on a tap of the composer and every release build silently drew and hit-tested that subtree short; it is 64 in both the CPU walk and shader.wgsl, and the assert prints the chain so a cycle and a deep tree can be told apart. And `minSdk` is 29, since `getEventTimeNanos` is API 29 and a missing JNI method is a crash rather than a degraded fling. Every new invariant carries its guard: sample times non-decreasing in `on_touch_event`, and tests confirmed to fail without their fix for the press-seeded velocity, the animation registration and the AOSP magnitudes. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
96 lines
4.1 KiB
Bash
Executable File
96 lines
4.1 KiB
Bash
Executable File
#!/bin/sh
|
|
# Builds iris-android-app end to end: the cdylib (cargo ndk, straight into
|
|
# app/src/main/jniLibs/) then the APK (Gradle). Written to stop re-typing
|
|
# the same incantation by hand every time (ANDROID_HOME/NDK exports, the
|
|
# cargo ndk invocation, the keystore env for a release build, apksigner/
|
|
# aapt2 verification) -- see docs/RUST.md's P0 box. Same shape as `app/
|
|
# build-apk.sh` (the Compose app's own build script) and `app/
|
|
# iris-scroll.sh` (no coordinates, set -eu, exit 0 on success).
|
|
#
|
|
# Usage: ./build-apk.sh [debug|release] [--abi arm64-v8a|x86_64] [--features "a b c"]
|
|
# debug/release default to debug (matches this-machine-android's "the
|
|
# emulator stays on debug" rule -- pass `release` explicitly for a phone
|
|
# build). --abi defaults to arm64-v8a (a phone/real device); pass
|
|
# x86_64 for this checkout's own AVD. --features defaults to
|
|
# "transcript-screen bench" -- deliberately *without* `force-gles`, unlike
|
|
# an earlier version of this default. `force-gles` (`iris/Cargo.toml`'s
|
|
# own doc) exists only to force the emulator off its default software
|
|
# Vulkan and onto GLES for one specific measurement (RUST.md's I5, "Where
|
|
# iris's frame time goes") -- it was never meant to reach a real device,
|
|
# but this script's old default put it in every arm64 build regardless,
|
|
# so the P0 bench APK delivered to Iris's phone forced GLES there too.
|
|
# That is the named hypothesis in RUST.md's P0 box ("iris bench crash on
|
|
# the phone, 2026-09-06"): a real Vulkan driver is what a phone should
|
|
# run, and GLES is the backend the same box's own SwiftShader finding
|
|
# already flagged as the fragile one for this shader's storage buffers.
|
|
# Pass `--features "transcript-screen force-gles bench"` explicitly for
|
|
# an emulator backend-isolation run; never for a build meant for a phone.
|
|
set -eu
|
|
cd "$(dirname "$0")"
|
|
|
|
BUILD_TYPE="debug"
|
|
ABI="arm64-v8a"
|
|
FEATURES="transcript-screen bench"
|
|
case "${1:-}" in
|
|
debug|release) BUILD_TYPE="$1"; shift ;;
|
|
esac
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
--abi) ABI="$2"; shift 2 ;;
|
|
--features) FEATURES="$2"; shift 2 ;;
|
|
*) echo "build-apk.sh: unknown argument: $1" >&2; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
SDK_ROOT="$HOME/Android/Sdk"
|
|
export ANDROID_HOME="$SDK_ROOT"
|
|
export ANDROID_SDK_ROOT="$SDK_ROOT"
|
|
NDK_DIR=$(ls -d "$SDK_ROOT"/ndk/*/ 2>/dev/null | sort -V | tail -1)
|
|
if [ -z "$NDK_DIR" ]; then
|
|
echo "build-apk.sh: no NDK found under $SDK_ROOT/ndk" >&2
|
|
exit 1
|
|
fi
|
|
export ANDROID_NDK_HOME="$NDK_DIR"
|
|
|
|
# Only the ABI asked for goes into the APK. cargo ndk adds its output beside
|
|
# whatever earlier builds left here, and Gradle packages every directory it
|
|
# finds -- a debug x86_64 emulator build left behind made an arm64 "release"
|
|
# 339 MB on 2026-09-06.
|
|
rm -rf app/src/main/jniLibs
|
|
echo "build-apk.sh: cargo ndk -t $ABI build ${BUILD_TYPE:+(${BUILD_TYPE})} --features \"$FEATURES\""
|
|
if [ "$BUILD_TYPE" = "release" ]; then
|
|
cargo ndk -t "$ABI" -P 29 -o app/src/main/jniLibs/ build --release --features "$FEATURES"
|
|
else
|
|
cargo ndk -t "$ABI" -P 29 -o app/src/main/jniLibs/ build --features "$FEATURES"
|
|
fi
|
|
|
|
GRADLE_TASK="assembleDebug"
|
|
APK_DIR="app/build/outputs/apk/debug"
|
|
APK_NAME="app-debug.apk"
|
|
if [ "$BUILD_TYPE" = "release" ]; then
|
|
GRADLE_TASK="assembleRelease"
|
|
APK_DIR="app/build/outputs/apk/release"
|
|
APK_NAME="app-release.apk"
|
|
# Same key `app/build-apk.sh` (the Compose app) generates once under
|
|
# ~/.config/ai-app/release.jks -- see AGENTS.md's "Checking your work".
|
|
export AI_APP_KEYSTORE="$HOME/.config/ai-app/release.jks"
|
|
if [ ! -f "$AI_APP_KEYSTORE" ]; then
|
|
echo "build-apk.sh: no release key at $AI_APP_KEYSTORE -- run app/build-apk.sh once first" >&2
|
|
exit 1
|
|
fi
|
|
export AI_APP_KEYSTORE_PASSWORD
|
|
AI_APP_KEYSTORE_PASSWORD=$(cat "$AI_APP_KEYSTORE.password")
|
|
fi
|
|
|
|
gradle ":app:$GRADLE_TASK" --console=plain
|
|
|
|
APK_PATH="$(pwd)/$APK_DIR/$APK_NAME"
|
|
BUILD_TOOLS=$(ls -d "$SDK_ROOT"/build-tools/*/ | sort -V | tail -1)
|
|
echo "--- aapt2 dump badging ---"
|
|
"${BUILD_TOOLS}aapt2" dump badging "$APK_PATH" | head -5
|
|
if [ "$BUILD_TYPE" = "release" ]; then
|
|
echo "--- apksigner verify ---"
|
|
"${BUILD_TOOLS}apksigner" verify --print-certs "$APK_PATH"
|
|
fi
|
|
echo "$APK_PATH"
|