Complete Android adaptive and accessibility hardening (#105)
Android ARM verification / rust (push) Failing after 50m4s
Android ARM verification / android (push) Failing after 9s
Dependency security audit / rustsec (push) Failing after 22s

This commit is contained in:
hugo committed 2026-10-15 11:44:31 +02:00
1 parent f4a6edbc68
commit 45d78aa5b3
33 files changed
+4596 -1376

No files matched your search

+170 -89
View File
@@ -1,108 +1,189 @@
# Android hardening
The Android presentation layer uses resource strings for Kotlin-owned notices,
action labels, counts, and durations. Rust-owned presentation text remains opaque:
frontends do not parse or translate it. Repeated editor actions include the field
label, and hidden editor values are checked against the actual Android accessibility
node tree as well as Compose semantics. Compose accessibility checks run before
interactions in every app screen test.
Kotlin-owned notices, action labels, counts and durations use Android resources.
Rust-owned presentation text stays opaque; frontends do not parse or translate it.
Screen tests run Compose accessibility checks, including the finished mutation,
camera, provider and TOTP screens. Hidden editor/viewer values are also checked
against Android's actual accessibility node tree. Explicit buttons and semantic
actions provide alternatives to swipe, reorder and pull-to-refresh gestures.
Consent and selection rows have at least 48 dp touch targets.
Blank name searches do not traverse the password store. QR luminance inputs reject
zero or greater-than-4096 dimensions before allocating the decoder image; owned
decoded luminance bytes are zeroized. License loading runs on the IO dispatcher.
Editor keystrokes coalesce encrypted recovery checkpoints for 150 ms; save,
reorder, and recovery wait for the latest checkpoint. This changes persistence
scheduling, not authorization or the interactive unlock boundary.
The shell, provider selection and all modal dialogs avoid separating/occluding
hinges. Book posture uses the largest unobstructed pane; tabletop uses the upper
pane. Bounds are in window coordinates, offsets remain correct in RTL, and insets
already removed by the pane are consumed before applying system-bar/IME padding.
Dialog titles, forms and actions can scroll at large font sizes; form state stays
composed while scrolling. Regressions exercise live flat/book/tabletop changes,
preserve an entered commit message, check action bounds against the hinge, and
reach every generator control at 200% text before submitting its entered length.
Blank name searches do not traverse the store. QR luminance inputs reject zero or
greater-than-4096 dimensions before allocation and owned decoded bytes are
zeroized. License loading runs on IO. Editor input coalesces encrypted recovery
checkpoints for 150 ms; save/reorder/recovery wait for the latest checkpoint.
Interactive authorization remains in Rust and is never bypassed to hide latency.
Foreground cleanup synchronously masks protected surfaces and clears registered
inputs. The parent composition observes the cleanup generation before rebuilding
content; its reveal effect checks the latest generation again before exposing the
clean foreground. This fixes a permanent black surface after failed authentication
and tab navigation. The regression was observed failing before the fix, and
computer use verified failed unlock followed by usable Search in the final build.
## Reproducible gates
From the repository root, run the four required Cargo checks and:
```sh
CARGO=/opt/homebrew/opt/rustup/bin/cargo python3 .gitea/scripts/android_ci.py test
CARGO=/opt/homebrew/opt/rustup/bin/cargo python3 .gitea/scripts/android_ci.py build
CARGO=/opt/homebrew/opt/rustup/bin/cargo python3 .gitea/scripts/android_ci.py emulator <serial>
CARGO=/opt/homebrew/opt/rustup/bin/cargo python3 .gitea/scripts/android_ci.py performance <serial>
```
The emulator gates check ARM ABI, emulator identity, unlocked credential storage,
actual keyguard state and wakefulness before installing or invoking tests. Run
one emulator suite at a time. It reports completion only after the runner returns
success. The completed harness and target processes are stopped even on failure
or timeout, retaining application data. ADB calls have a 15-minute ceiling.
Platform coverage includes real CameraX frames, background/resume and
Keystore round-trip/tamper behavior, including rejection of an unsupported
unlocked-device key flag on API 26. Temporary test camera permission is restored.
Preserve each AVD and install updates over the same package. After a crash, stop
the affected instrumentation, cold boot the same AVD without wiping data, unlock
it and verify the visible app before repeating validation. A runner process alone
is not evidence of progress. The 2026-10-15 results below use fresh post-restart
runs; preliminary results from before the emulator restart are excluded.
## Native journey budgets
The `android_ci.py emulator <serial>` gate requires exactly one numeric measurement
for each journey below and rejects missing, duplicate, malformed, or over-budget
values. Raw fixture output remains local; published reports contain numeric values
and test status only. The large-tree fixture contains 1,000 valid encrypted entries
in 50 folders. These timings measure native/repository work, not rendered frame
latency or a physical device.
The gate rejects missing, duplicate, malformed or over-budget measurements. Raw
fixture output stays local; published reports retain reviewed numbers and status.
The tree fixture contains 10,000 encrypted files in 50 folders, replicated from
Rust-produced public fixture ciphertext. Cached
TOTP/provider preparation uses 100 catalogued encrypted entries while locked,
with 10 warmups and 30 samples per p95 measurement. Authenticated fill separately
uses real Rust/GPG decryption and explicit consent.
| Journey | Required budget | API 26 ARM64 / 4 KB | API 37 ARM64 / strict 16 KB |
| --- | ---: | ---: | ---: |
| 100 editor input updates | < 500 ms | 4 ms | 6 ms |
| Editor update p95 | < 32,000 μs | 61 μs | 102 μs |
| TOTP discovery cache rebuild | < 15,000 ms | 1,924 ms | 1,861 ms |
| QR image analysis | < 5,000 ms | 99 ms | 91 ms |
| 1,000-entry tree navigation | < 3,000 ms | 11 ms | 11 ms |
| 1,000-entry name search | < 5,000 ms | 27 ms | 26 ms |
| Native journey | Budget | API 26 / 4 KB | API 37 / 4 KB | API 37 / strict 16 KB |
| --- | ---: | ---: | ---: | ---: |
| 100 editor input updates | < 500 ms | 4 ms | 6 ms | 12 ms |
| Editor update p95 | < 32,000 us | 44 us | 70 us | 231 us |
| TOTP discovery cache rebuild | < 15,000 ms | 1,831 ms | 1,892 ms | 2,516 ms |
| QR image analysis | < 5,000 ms | 86 ms | 93 ms | 122 ms |
| 10,000-entry tree navigation | < 3,000 ms | 75 ms | 95 ms | 121 ms |
| 10,000-entry name search | < 5,000 ms | 228 ms | 281 ms | 355 ms |
| Cached TOTP open p95 | < 1,000,000 us | 5,059 us | 8,574 us | 5,322 us |
| Warm TOTP refresh p95 | < 3,000,000 us | 7,353 us | 10,509 us | 7,890 us |
| Locked provider preparation p95 | < 1,000,000 us | 3,775 us | 6,607 us | 4,155 us |
| Authenticated provider fill | < 5,000 ms | 1,212 ms | 1,278 ms | 1,247 ms |
Both configurations reported `OK (31 tests)` for app instrumentation and passed
the native core journey and bridge suite. Opt-in inspection and biometric tests
remain assumption-skipped unless explicitly requested; these runs do not establish
their manual coverage. Native load samples ranged from 32.7–35.2 ms on API 26 and
63.3–78.7 ms on API 37 with strict 16 KB pages. Five debug cold starts ranged from
202–279 ms and 708–1,086 ms respectively. These debug activity timings are separate
from release Macrobenchmark startup measurements.
All three configurations passed app/core/bridge/platform instrumentation and five
real debug cold starts after the emulator restart. Strict 16 KB validation disabled
the bionic/PackageManager 4 KB application compatibility path. Its five native-load
samples were 66.4-89.7 ms and debug activity starts were 836-932 ms.
## Adaptive checks
These are native/repository timings on emulators, not rendered-frame latency or
physical-device performance. The API 37 / 4 KB authenticated-fill figure comes
from a separate targeted native test after its full suite. Opt-in inspection and
biometric tests remain assumption-skipped during ordinary instrumentation; their
presence in the runner's reported test count does not establish manual coverage.
After the foreground-cleanup fix, the final app suite was repeated on all three
configurations: each reported 53 tests with no failures.
`Tier2WindowTest` exercises searchable Preferences and keyboard activation using
actual window dimensions. The API 26 emulator passed compact (411 dp), medium
(640 dp), expanded (1,067 dp), 200% font with RTL, landscape with 200% font and RTL,
and a smaller window with 200% font. Size, density, font scale, and rotation settings
were restored after the run. This covers window geometry; it does not establish
hinge occlusion or folded/tabletop posture behavior.
## Adaptive and environment checks
The shell consumes IME insets, and the activity explicitly requests `adjustResize`.
The optional `hardeningIme=true` instrumentation argument opens a real keyboard
and checks shell content bounds against the platform IME inset. It reproduced an
overlap before the shared-shell fix and passes afterward on API 26 and API 37 with
strict 16 KB pages. Temporarily enable `show_ime_with_hard_keyboard` for this check
and restore its original value afterward. Full app instrumentation also reported
`OK (31 tests)` with `hardeningRtl=true` on the strict 16 KB image.
On 2026-10-15 API 26 passed actual compact (411 dp), medium (640 dp), expanded
(1,067 dp), 200% text with RTL, landscape with 200% text and RTL, and smaller-window
checks. The real keyboard opened and shell bounds ended above its IME inset.
Live resize from 640 to 450 dp preserved a query entered into the actual Rust
search repository across activity recreation, followed by layout/keyboard checks.
All size, density, font, rotation and IME settings were restored.
## Release benchmarks
The Pixel Fold AVD passed unfolded, half-open book, half-open tabletop, folded
cover and tabletop with a real IME. The controller waits for the active inner/cover
display before setting rotation, then verifies the committed posture and rotation;
issuing rotation during the cover-to-inner switch previously left the keyboard
case on the wrong layout. Computer use also inspected the actual setup/key-selection
screens in tabletop posture. Original posture, physical rotation and IME settings
were restored.
`android_ci.py performance <serial>` installs the benchmark app over the retained
installation and records five cold and five warm startup iterations. The benchmark
variant uses release code and shrinking, debug signing, and shell profiling; the
distributed release variant does not enable profiling. `ReportDrawnWhen` waits for
the shell to leave its native loading state. Compilation is preserved rather than
resetting application data.
The available ARM images do not advertise freeform/desktop support. An explicit
freeform launch on the foldable was accepted but fell back to fullscreen; it is
not counted as desktop validation. Actual split-screen, live resize and expanded
window geometry are covered. Desktop-specific system decorations remain an image
availability limit; no x86_64 or physical-device coverage is implied.
The startup gate requires five complete numeric initial/full-display samples for
each mode, with cold startup below 5,000 ms and warm startup below 3,000 ms.
Before installing the generated profile, release-like median full-display times
were 268.6 ms cold / 131.2 ms warm on API 26 and 1,203.5 ms cold / 427.0 ms warm on
API 37. These emulator observations preserve existing compilation state and are
not a claim of profile speedup. Raw traces and device metadata stay local; CI
artifacts retain only API/ABI and the reviewed numeric samples.
The old API 26 Pixel Launcher crashed during a tablet-size transition. The blocked
attempt was stopped and its display overrides restored. Remaining cases passed
with Settings in front during transitions, avoiding that launcher defect. An
actual split-screen smoke check also showed IronStorage alongside Settings;
setup controls remained reachable by scrolling and fullscreen was restored.
`android_ci.py profiles <serial>` generates a profile from startup and the existing
Preferences journey. Generation requires API 34 or newer so compilation reset
does not fall back to reinstalling the app on older non-rooted images. Inspect the
generated local artifacts before promoting them into the app source set. Generation
uses a non-obfuscated `profile` variant, while startup measurements use the shrunk
`benchmark` variant. Both use release source code and debug signing.
The current phone passed the full app suite with 200% system text, RTL, high text
contrast, grayscale and all three animation scales at zero. A focused follow-up
also covered the final scrolling dialogs and tabletop generator. The native
cached-catalog/provider fixture passed under forced deep doze (still IDLE after
the run), battery saver, disabled Wi-Fi/mobile data, and Android's forced-low
storage notification. A separate native cached-catalog/provider run passed on connected metered Wi-Fi;
Settings showed Metered and the active Wi-Fi network lacked NOT_METERED. Automatic
network usage was restored afterward. These network checks exercise local cached
work, not an unconfigured remote synchronization service.
Every modified setting was restored. This is storage-low
notification coverage, not a destructive attempt to fill the emulator filesystem.
Rust transaction/checkpoint tests separately cover failing writes and rollback.
App tests cover memory-pressure masking, saved-state redaction and recreation;
five real process cold starts additionally exercise retained app storage.
The committed `app/src/main/baseline-prof.txt` was generated on the API 37 ARM64
emulator from the startup/Preferences journey on 2026-10-11, with 22,331 rules and
SHA-256 `687afdc199dacf0736dc1c577767dea9b81ddc987b5b61b02b5afed5e501b503`.
Regenerate it with the `profiles` command after changing those journeys; use the
untimestamped `BaselineProfile_startupAndPreferences-baseline-prof.txt` output,
check that application class names are unobfuscated, and rebuild the release APK
to verify its compiled `assets/dexopt/baseline.prof` remains within ART's size limit.
Computer use authenticated and decrypted the retained public provider fixture on
strict-16-KB API 37 using its source-defined passphrase. Its six Git commits and
clean worktree survived install-over updates. The password stayed Hidden with
explicit Reveal/Copy/Edit/Lock controls. TalkBack was enabled through Android
Studio and verified as a bound service with touch exploration active. Computer
use focused the native Hidden value and activated/cancelled the native editor
through TalkBack. This is separate from Compose checks, whose UI automation
normally suppresses accessibility services. Spoken audio was not verified.
## Completion boundary
Switch Access was enabled through its launcher and Android Settings. Its USB
setup reported no connected device; manual Next assignment did not capture the
emulator's volume keys or forwarded host keyboard. No scanning result is claimed.
This is a limit of the current emulator/input setup; keyboard/D-pad and semantic-action
coverage remain in the presentation suite. TalkBack and Switch Access were
restored to off, and no switch assignments were created.
Issue #105 explicitly depends on every Android feature UI issue. CameraX (#106),
TOTP list/detail (#107), mutation workflows (#108), Credential Manager/Autofill
(#109), and TOTP discovery UI (#110) are still open. Their final screen accessibility,
camera interruption, cached-open/refresh presentation, and provider response
latency cannot be certified by tests of the existing screens. Keep #105 open until
those implementations and the remaining environment/posture checks are complete.
ARMv7 is package-validated; the installed runnable emulator images are ARM64.
The other retained phone vault rejected its historical public test passphrase;
that journey is not a successful manual unlock. Rejection and subsequent Search
navigation did verify the foreground-cleanup fix. No credential or vault reset
was performed. The separately source-defined provider fixture above supplies the
successful native decryption journey.
On 2026-10-11 the user explicitly requested committing the independent implementation
now and delaying final coverage. Issues #106–#110 received `critical`; resume those
eligible Android enhancements in numeric order, then revisit #105. Remaining
checks include TalkBack/Switch Access and contrast/motion modes across the finished
screens, real hinge/posture and live-resize coverage, and low-memory, doze, battery,
network, camera-interruption, and storage-pressure scenarios. Do not close #105
based solely on the current instrumentation and window-geometry results.
## Release startup and profiles
`performance` installs the shrunk benchmark variant over the retained app. It uses
release source, debug signing and shell profiling; distributed release builds do
not enable profiling. `ReportDrawnWhen` waits for native shell loading to finish.
CompilationMode.Ignore preserves compilation and application data. Five complete
initial/full-display samples are required for each startup mode; every cold sample
must be below 5,000 ms and every warm sample below 3,000 ms.
The fresh API 26 full-display samples were 191.0-216.4 ms cold and 56.5-84.2 ms
warm. The current strict-16-KB API 37 samples were 246.4-475.9 ms cold and
78.5-139.3 ms warm. These are observations with retained compilation, not a profile-speedup
comparison.
`profiles <serial>` requires API 34 or newer, avoiding the older non-rooted
benchmark library fallback that can reinstall the app. The unshrunk profile
variant records startup, searchable Preferences, Passwords, Search and cached
TOTP navigation. Inspect generated application names before promoting the
untimestamped `BaselineProfile_startupAndPreferences-baseline-prof.txt`; rebuild
the release APK and verify compiled `assets/dexopt/baseline.prof` size afterward.
The committed profile was regenerated on API 37 ARM64 / strict 16 KB on
2026-10-15. It has 24,647 rules, including 2,826 application rules, and SHA-256
`947599f29c326a02a55bab8f12ac5cff3743560319993424667ec864fc862929`.
The release APK contains an 10,148-byte `baseline.prof` and 1,376-byte
`baseline.profm`, both checked after the final package build.
ARMv7 is compiled/package-audited. Available runnable images are ARM64; x86_64
and physical-device validation are outside the user-directed milestone scope.