190 lines
12 KiB
Markdown
190 lines
12 KiB
Markdown
# Android hardening
|
|
|
|
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.
|
|
|
|
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 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.
|
|
|
|
| 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 |
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
## Adaptive and environment checks
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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 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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
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.
|
|
|
|
## 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.
|