SessionLock
Session auto-lock (#14). The decrypted seed lives in the process-singleton MidnightSdk owned by MidnightSdkProvider; after one biometric every value-bearing call runs with zero re-auth (the SDK cache bypasses Keystore's short auth window). This locks the session by dropping that cache — MidnightSdkProvider.close — so the next value-bearing action has to re-authenticate. Because the wallet panel never auto-triggers biometric (actions are user-initiated), a lock holds until the user acts; it is never silently undone.
Triggers (all funnel to lock):
foreground idle: no onUserActivity for idleTimeoutMs
app backgrounded: onBackground → lock after backgroundGraceMs
device screen off: onScreenOff
manual "Lock now": lockNow
Wiring. Call attach once from the host Application.onCreate — it registers the background (activity-count) + screen-off hooks. For foreground idle, each Activity forwards onUserInteraction() → onUserActivity.
The lock is whole-app, Chase-style. The host wraps its Compose root in com.midnight.kuira.dapp.lock.SessionLockGate, which observes locked and, while locked, covers the ENTIRE app with a re-auth screen (no content visible or tappable behind it) and marks the window FLAG_SECURE so nothing leaks to the recents thumbnail. unlock is the gate's biometric. This class owns the policy + the unlock teeth; the gate owns the full-screen presentation.
Types
Properties
Grace period after backgrounding before locking (covers brief app-switches).
Ceiling on how long the decrypted seed may stay alive while the app is backgrounded. Backgrounding only soft-locks (UI gated, SDK kept alive for monitoring), so without this a seed could sit decrypted in memory indefinitely on a device whose screen never turns off. When the ceiling elapses it routes through the hold-aware lock — so an in-flight operation or a backgrounded Kicks match (which holds the session) DEFERS the wipe until it finishes, never gets torn out mid-flight; an idle, unheld session is wiped. Mutable so hosts can tune it.
True only when a HARD lock (doLock) has actually torn the SDK down (provider.close()), as opposed to a softLock that gates the UI but keeps the SDK alive. The foreground service keys its immediate-teardown on THIS, not locked: a soft-lock must not stop the FGS while a value-bearing operation is still in flight (that would defeat #261-264). Cleared when the SDK is rebuilt (re-auth / network switch).
Foreground idle timeout before locking. Mutable so hosts can tune it.
Whether the app is currently foregrounded (≥1 started Activity). Flipped by the activity-lifecycle callbacks. The dust-sync foreground service (#235) uses this to decide when a background notification is warranted — in-app the WalletSyncIndicator already shows progress, so the service only surfaces while backgrounded.
True from the moment a lock fires until the SDK is rebuilt (i.e. the user re-authenticated). The wallet panel observes this to hide the balance while locked. Distinct from provider.sdk == null, which is also briefly true during a network-switch rebuild — that must NOT read as "locked".
Functions
Hold off the AUTO-lock (idle / background / screen-off) for the duration of a value-bearing wallet operation, so a backgrounded transaction isn't torn down mid-flight (the SDK stays alive until the op finishes). If an auto-lock fired while the hold was active, it runs the moment the last hold releases. Manual lockNow is unaffected.
Register the app-level lock triggers (background + screen-off). Idempotent. Prefer the attach companion which resolves the singleton for you.
App left the foreground: SOFT-lock after the grace period unless we come back. App-switching isn't the same threat as the device being secured, so backgrounding gates the UI + requires re-auth on return but KEEPS the SDK alive — so background monitoring (received-funds, sync) keeps running instead of dying the moment you switch apps. The HARD triggers (screen-off / idle / manual) still wipe the SDK.
App returned to foreground: cancel the pending background lock + lifetime ceiling, re-arm idle.
Device screen turned off — lock immediately.
Reset the idle countdown — call on any user interaction.
Re-authenticate and lift the lock — the unlock half of the session lock, driven by the app-wide com.midnight.kuira.dapp.lock.SessionLockGate.