PeachApplication.kt

5.1 KB · raw

1package com.peachpasswords23import android.app.Application4import androidx.hilt.work.HiltWorkerFactory5import androidx.lifecycle.DefaultLifecycleObserver6import androidx.lifecycle.LifecycleOwner7import androidx.lifecycle.ProcessLifecycleOwner8import androidx.work.Configuration9import com.peachpasswords.sync.BackgroundSyncPreference10import com.peachpasswords.sync.ForegroundSyncCoordinator11import com.peachpasswords.sync.LockedSyncSpoolHooks12import com.peachpasswords.sync.LockedSyncSpoolStore13import com.peachpasswords.sync.PeachSyncService14import com.peachpasswords.shared.PeachClipboard15import com.peachpasswords.ui.lock.BiometricKeyStore16import com.peachpasswords.updates.UpdateCheckWorker17import com.peachpasswords.vault.PinUnlockStore18import com.peachpasswords.vault.VaultKeyRotationHooks19import com.peachpasswords.vault.VaultLockTimer20import com.peachpasswords.vault.VaultMutationHooks21import com.peachpasswords.vault.VaultRepository22import dagger.hilt.android.HiltAndroidApp23import javax.inject.Inject2425@HiltAndroidApp26class PeachApplication : Application(), Configuration.Provider {27    @Inject lateinit var vault: VaultRepository28    @Inject lateinit var lockTimer: VaultLockTimer29    @Inject lateinit var foregroundSync: ForegroundSyncCoordinator30    @Inject lateinit var backgroundSync: BackgroundSyncPreference31    @Inject lateinit var lockedSyncSpool: LockedSyncSpoolStore32    @Inject lateinit var pinUnlock: PinUnlockStore33    @Inject lateinit var workerFactory: HiltWorkerFactory3435    /**36     * Set by MainActivity when its stop is a configuration change — a fold, an37     * unfold, or a rotation — rather than a real backgrounding. ProcessLifecycleOwner38     * is debounced, but switching a foldable between its displays can outlast that39     * debounce, so without this the process-level stop locks a vault the user is40     * still looking at and they meet a second lock screen drawn with the new41     * metrics.42     */43    var stopWasConfigurationChange = false4445    override val workManagerConfiguration: Configuration46        get() = Configuration.Builder().setWorkerFactory(workerFactory).build()4748    override fun onCreate() {49        super.onCreate()50        LockedSyncSpoolHooks.refresh = lockedSyncSpool::refresh51        // Both convenience escrows wrap a copy of the slot-A key, so every52        // rotation site has to invalidate them together — restoring a Codex or53        // resetting through recovery words rotated the key while leaving a54        // biometric wrapper the hardware would still accept (PB-26-126).55        VaultKeyRotationHooks.onRotated = {56            pinUnlock.clear()57            BiometricKeyStore.clear(this)58        }59        VaultMutationHooks.onCommitted = {60            if (backgroundSync.enabled.value && vault.syncVaultId() != null) {61                PeachSyncService.start(this)62            }63        }6465        // Update checks: once a day in the background, plus one now — process66        // start is the honest "app open". Neither ever downloads or installs;67        // the most they do is post a notification.68        UpdateCheckWorker.schedulePeriodic(this)69        UpdateCheckWorker.checkNow(this)70        // Lock the vault the moment the whole app goes to background (CF-1).71        // ProcessLifecycleOwner is process-wide and debounced, so a rotation or a72        // brief Activity restart does not trip it — only a genuine backgrounding.73        ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {74            override fun onStart(owner: LifecycleOwner) {75                foregroundSync.triggerForegroundSync()76            }7778            override fun onStop(owner: LifecycleOwner) {79                // A SAF file picker/save dialog or share sheet also stops the80                // process's activities while it's in the foreground — that's81                // an expected, momentary handoff as part of an in-app export/82                // import flow, not a real backgrounding, so it's exempted via83                // VaultRepository.expectTransientBackground().84                if (vault.consumeTransientBackgroundSuppression()) return85                if (stopWasConfigurationChange) {86                    stopWasConfigurationChange = false87                    return88                }89                if (backgroundSync.enabled.value && vault.isUnlocked.value) {90                    // Cache only opaque encrypted sync material, then perform a91                    // real lock. The service keeps transport authority, not the92                    // vault root or plaintext entries.93                    runCatching { lockedSyncSpool.refresh(vault) }94                    vault.lock()95                } else {96                    vault.lock()97                    lockTimer.cancel()98                }99                // The 30s timer can outlive the app being visible, and a process100                // without window focus cannot read the clipboard to confirm its101                // own value — so lock is the last point at which the wipe can be102                // guaranteed (PB-26-119).103                PeachClipboard.clearNow(this@PeachApplication)104            }105        })106    }107}