SessionLock

@Singleton
class SessionLock @Inject constructor(provider: MidnightSdkProvider, walletSeedSource: WalletSeedSource)

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):

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.

Constructors

Link copied to clipboard
@Inject
constructor(provider: MidnightSdkProvider, walletSeedSource: WalletSeedSource)

Types

Link copied to clipboard
object Companion
Link copied to clipboard

Properties

Link copied to clipboard

Grace period after backgrounding before locking (covers brief app-switches).

Link copied to clipboard

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.

Link copied to clipboard
val hardLocked: StateFlow<Boolean>

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).

Link copied to clipboard

Foreground idle timeout before locking. Mutable so hosts can tune it.

Link copied to clipboard
val inForeground: StateFlow<Boolean>

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.

Link copied to clipboard
val locked: StateFlow<Boolean>

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

Link copied to clipboard

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.

Link copied to clipboard

Register the app-level lock triggers (background + screen-off). Idempotent. Prefer the attach companion which resolves the singleton for you.

Link copied to clipboard
fun lockNow()

Manual "Lock now" — explicit user intent; ignores operation holds.

Link copied to clipboard

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.

Link copied to clipboard

App returned to foreground: cancel the pending background lock + lifetime ceiling, re-arm idle.

Link copied to clipboard

Device screen turned off — lock immediately.

Link copied to clipboard

Reset the idle countdown — call on any user interaction.

Link copied to clipboard
suspend fun unlock(activity: FragmentActivity): Boolean

Re-authenticate and lift the lock — the unlock half of the session lock, driven by the app-wide com.midnight.kuira.dapp.lock.SessionLockGate.

Link copied to clipboard
suspend fun <T> withHold(block: suspend () -> T): T

Suspend-friendly acquireHold: holds for the duration of block.