Iris's 2026-09-07 phone report on ed04d4c: the resume glyph corruption is
fixed (item 4 closed with her evidence), flinging "seems to just be linear
velocity with an abrupt stop", and the keyboard still does not push
anything up. docs/RUST.md's new "The 2026-09-07 phone report" section has
the derivation and every number.
**The fling was arithmetically linear.** `android_fling_spline::
distance_fraction(t)` returned `t` for every `t`. Two halves of AOSP's
`SplineOverScroller` static initialiser had been transposed -- the
bisection solved the tension curve and the sample evaluated the P1/P2 one,
where AOSP does the opposite -- which made SPLINE_POSITION and SPLINE_TIME
identical; the lookup then bracketed `t` between SPLINE_TIME entries
instead of between even time steps, and the two cancelled to the identity.
Ported exactly now from OverScroller.java and androidx.compose.animation
1.12.0's SplineBasedDecay.kt, which agree line for line, as one table
indexed by even steps of time (AOSP's second table serves only
`adjustDuration`, which nothing here has, so it is deliberately not built
-- one array, one indexing rule). `FlingCalculator::velocity_at` is new
beside `position_at`, and `List::tick_fling` logs `iris fling tick:` with
the per-frame delta and speed.
Every existing test compared the calculator with itself -- monotonic,
signed, integrates to the closed form, deltas non-increasing -- and all of
them pass on a straight line. iris/benches/fling_spline_reference.py is an
independent hand transcription of both sources and supplies the numbers
now checked into `the_spline_matches_aosps_own_table` and
`a_flick_decelerates_the_way_aosp_says_it_does`;
`tick_fling_applies_shrinking_incremental_deltas` went from
"non-increasing" to "the last delta is under 80% of the first". Negative
control: with `sample` forced back to `t`, exactly those three fail.
Emulator (API 36, debug, force-gles): a released v=3750 decelerates
3746 -> 2624 -> 1834 -> 1144 -> 752 -> 449 -> 243 -> 83px/s over 32 frames
to t=0.664s; a flick into the end of the list stops there in one tick with
no overshoot; a tap 200ms into a fling ends it at 11 ticks.
**The keyboard: `targetSdk = 34`** in iris/android-app/app/build.gradle,
against compileSdk 37 and the Compose app's 37 -- and that app's keyboard
does push up on her phone. Below target 35 a window keeps the legacy
behaviour where adjustResize shrinks it for the IME, so
getInsets(ime()).bottom measures an already-shrunk window and is zero;
setDecorFitsSystemWindows(false) opts out of that and still takes on the
API 36 emulator here, which is why every test run passed. Now targetSdk 37.
That is a reading and not a measurement, so the other half is making the
phone able to answer it. MainActivity also registers a
WindowInsetsAnimation.Callback (onEnd re-reads getRootWindowInsets, so an
interrupted animation cannot freeze a value), which delivers the height
where only the animation path carries it and makes the push-up animate:
ime_bottom now arrives 509, 663, 833, 881, 883 instead of one jump.
`insets::Shared::updates` counts every dispatch and
`AndroidUiState::insets_report()` puts it in the Diagnostics pane --
screenshot-verified, `insets: dispatches=27 left=0 top=142 right=0
bottom=63 ime_bottom=0 ime_visible=false`. Iris has no logcat, and "the
listener never fired" and "it fired with a zero height" are otherwise the
same picture; dispatches=0 says so in words rather than showing defaults.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
91 lines
4.2 KiB
Groovy
91 lines
4.2 KiB
Groovy
plugins {
|
|
id("com.android.application")
|
|
}
|
|
|
|
// The Rust side (this directory's Cargo.toml) is built separately with
|
|
// `cargo ndk`, straight into src/main/jniLibs/ -- see the repo-root
|
|
// AGENTS.md-style comment at the top of Cargo.toml for why this crate
|
|
// stays outside the main Rust workspace, and RUST.md's I2 for the exact
|
|
// build command.
|
|
android {
|
|
namespace = "dev.iris.android.demo"
|
|
compileSdk = 37
|
|
|
|
defaultConfig {
|
|
applicationId = "dev.iris.android.demo"
|
|
// 29, not 26: `iris::android::view`'s touch handler dates each
|
|
// sample with `MotionEvent.getEventTimeNanos` and
|
|
// `getHistoricalEventTimeNanos`, both API 29, and a missing JNI
|
|
// method there is a hard crash on the first touch rather than a
|
|
// degraded fling. Raised deliberately rather than guarded at
|
|
// runtime: nothing this app is built for runs below 29, and an
|
|
// untested fallback path is its own defect. `build-apk.sh`'s
|
|
// `cargo ndk -P` is kept at the same number.
|
|
minSdk = 29
|
|
// 37, matching `compileSdk` and the Compose app in `app/` -- which
|
|
// is the one part of this that is measured rather than reasoned:
|
|
// that app targets 37 and its keyboard does push the transcript up
|
|
// on Iris's phone, and this one targeted 34 and does not
|
|
// (2026-09-07). The emulator here is API 36 and the push-up works
|
|
// there at either target, so the target is the only difference the
|
|
// two devices do not share.
|
|
//
|
|
// The mechanism, stated as the reading it is: below targetSdk 35
|
|
// a window keeps the legacy behaviour, where `adjustResize` shrinks
|
|
// the window for the IME and `getInsets(ime()).bottom` therefore
|
|
// measures the overlap with an already-shrunk window -- zero, with
|
|
// nothing left to push up. `MainActivity`'s
|
|
// `setDecorFitsSystemWindows(false)` opts out of that, and on API
|
|
// 36 it still takes; Android 16 deprecated it and Android 17 is
|
|
// where it appears not to. At 35+ edge-to-edge is not opt-in, so
|
|
// the app is handed the real overlap without relying on a
|
|
// deprecated call. If the phone still reports `ime_bottom=0` with
|
|
// a nonzero `dispatches` in the Diagnostics pane, this reading was
|
|
// wrong and the `WindowInsetsAnimation.Callback` in
|
|
// `MainActivity` is the other half to look at.
|
|
targetSdk = 37
|
|
versionCode = 1
|
|
versionName = "1.0"
|
|
}
|
|
|
|
// A release build must be signed, and the key is per machine rather than per repo -- same
|
|
// reasoning and the same key as `app/build-apk.sh` (the Compose app): it is what a phone
|
|
// recognises the app by, and a secret never lives in a checkout (the mount is shared with an
|
|
// untrusted VM). `build-apk.sh` generates this key once and points at it through the
|
|
// environment; without it a release build here is unsigned, which is fine for everything
|
|
// except installing.
|
|
def keystore = System.getenv("AI_APP_KEYSTORE")
|
|
signingConfigs {
|
|
if (keystore != null) {
|
|
release {
|
|
storeFile = file(keystore)
|
|
storePassword = System.getenv("AI_APP_KEYSTORE_PASSWORD")
|
|
keyAlias = "ai-app"
|
|
keyPassword = storePassword
|
|
}
|
|
}
|
|
}
|
|
|
|
buildTypes {
|
|
debug {
|
|
}
|
|
// P0's iris half (docs/RUST.md's P0 box): the build a phone actually runs. The `.so`
|
|
// itself is built separately with `cargo ndk --release --features "transcript-screen
|
|
// force-gles bench"` straight into src/main/jniLibs/ (this crate's own Cargo.toml) --
|
|
// Gradle here only packages and signs whatever is already there, the same division as the
|
|
// debug/tabs-screen build this project started with. `applicationIdSuffix` keeps it
|
|
// installable beside a debug build of the tabs demo rather than replacing it.
|
|
release {
|
|
applicationIdSuffix ".bench"
|
|
if (keystore != null) {
|
|
signingConfig = signingConfigs.release
|
|
}
|
|
}
|
|
}
|
|
|
|
compileOptions {
|
|
sourceCompatibility = JavaVersion.VERSION_17
|
|
targetCompatibility = JavaVersion.VERSION_17
|
|
}
|
|
}
|