PeachApplication.kt

3.8 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.updates.UpdateCheckWorker14import com.peachpasswords.vault.VaultLockTimer15import com.peachpasswords.vault.VaultRepository16import dagger.hilt.android.HiltAndroidApp17import javax.inject.Inject1819@HiltAndroidApp20class PeachApplication : Application(), Configuration.Provider {21    @Inject lateinit var vault: VaultRepository22    @Inject lateinit var lockTimer: VaultLockTimer23    @Inject lateinit var foregroundSync: ForegroundSyncCoordinator24    @Inject lateinit var backgroundSync: BackgroundSyncPreference25    @Inject lateinit var lockedSyncSpool: LockedSyncSpoolStore26    @Inject lateinit var workerFactory: HiltWorkerFactory2728    /**29     * Set by MainActivity when its stop is a configuration change — a fold, an30     * unfold, or a rotation — rather than a real backgrounding. ProcessLifecycleOwner31     * is debounced, but switching a foldable between its displays can outlast that32     * debounce, so without this the process-level stop locks a vault the user is33     * still looking at and they meet a second lock screen drawn with the new34     * metrics.35     */36    var stopWasConfigurationChange = false3738    override val workManagerConfiguration: Configuration39        get() = Configuration.Builder().setWorkerFactory(workerFactory).build()4041    override fun onCreate() {42        super.onCreate()43        LockedSyncSpoolHooks.refresh = lockedSyncSpool::refresh4445        // Update checks: once a day in the background, plus one now — process46        // start is the honest "app open". Neither ever downloads or installs;47        // the most they do is post a notification.48        UpdateCheckWorker.schedulePeriodic(this)49        UpdateCheckWorker.checkNow(this)50        // Lock the vault the moment the whole app goes to background (CF-1).51        // ProcessLifecycleOwner is process-wide and debounced, so a rotation or a52        // brief Activity restart does not trip it — only a genuine backgrounding.53        ProcessLifecycleOwner.get().lifecycle.addObserver(object : DefaultLifecycleObserver {54            override fun onStart(owner: LifecycleOwner) {55                foregroundSync.triggerForegroundSync()56            }5758            override fun onStop(owner: LifecycleOwner) {59                // A SAF file picker/save dialog or share sheet also stops the60                // process's activities while it's in the foreground — that's61                // an expected, momentary handoff as part of an in-app export/62                // import flow, not a real backgrounding, so it's exempted via63                // VaultRepository.expectTransientBackground().64                if (vault.consumeTransientBackgroundSuppression()) return65                if (stopWasConfigurationChange) {66                    stopWasConfigurationChange = false67                    return68                }69                if (backgroundSync.enabled.value && vault.isUnlocked.value) {70                    // Cache only opaque encrypted sync material, then perform a71                    // real lock. The service keeps transport authority, not the72                    // vault root or plaintext entries.73                    runCatching { lockedSyncSpool.refresh(vault) }74                    vault.lock()75                } else {76                    vault.lock()77                    lockTimer.cancel()78                }79            }80        })81    }82}