Add an opt-in "Require screen lock" setting that gates the whole app behind
BiometricPrompt (strong biometric with device-credential fallback) and binds
the encrypted cache's SQLCipher passphrase to user authentication.
- App-lock gate: AppLockGateHost wraps the app; a pure AppLockGate state machine
locks on cold start / resume-after-timeout and unlocks on auth.
- Auth-bound decrypt: DatabaseKeyCipher seals the DB passphrase with a Keystore
key requiring user auth (setUserAuthenticationRequired, time-bound validity,
setInvalidatedByBiometricEnrollment). PassphraseSession holds the unwrapped
passphrase in memory; provideDatabase reads it only after auth.
- The non-auth master key (KeystoreCrypto) is unchanged, so background credential
access (IDLE push) still works.
- Invalidation / lock removal: KeyInvalidationPolicy decides clear-vs-disable;
the cache is wiped only at cold start in provideDatabase (never while Room holds
it open) via a persisted flag + process restart, then re-synced. No corruption.
- Enabling requires a secure device lock; disabling reseals the passphrase back
under the master key first (guarded to avoid a passphrase mismatch).
Adds androidx.biometric; MainActivity becomes a FragmentActivity (required by
BiometricPrompt). JVM tests cover the gate state machine, invalidation policy,
and passphrase session; the Keystore/BiometricPrompt/restart paths are
device-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>