e4607bb6696b
fix: Make vault destruction reset Sync state (v2.6.2)
- time
- 2026-07-27 21:11 UTC · 2 months ago
- machine
- koh2
- parent
- fb46840009b2
↓ peach-android-e4607bb6.tar.gzatlas at this save
files changed10 files
app/build.gradle+2 −2app/src/main/java/com/peachpasswords/sync/BackgroundSyncPreference.kt+5 −0app/src/main/java/com/peachpasswords/sync/DeviceIdentity.kt+7 −0app/src/main/java/com/peachpasswords/sync/PairedDeviceStore.kt+4 −1app/src/main/java/com/peachpasswords/sync/PendingVouchedDeviceStore.kt+4 −0app/src/main/java/com/peachpasswords/sync/RelaySettingsStore.kt+4 −0app/src/main/java/com/peachpasswords/sync/SyncFreshStartResetter.kt+54 −0app/src/main/java/com/peachpasswords/ui/lock/LockViewModel.kt+3 −0app/src/main/java/com/peachpasswords/ui/settings/PanicScreen.kt+3 −0app/src/test/java/com/peachpasswords/sync/SyncFreshStartResetterTest.kt+35 −0app/build.gradle+2 −2
@@ -16,8 +16,8 @@1616 applicationId "com.peachpasswords"1717 minSdk 261818 targetSdk 3519- versionCode 24012520- versionName "2.6.1"19+ versionCode 24012620+ versionName "2.6.2"2121 2222 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"2323 vectorDrawables {app/src/main/java/com/peachpasswords/sync/BackgroundSyncPreference.kt+5 −0
@@ -23,6 +23,11 @@2323 _enabled.value = enabled2424 }2525 26+ fun reset() {27+ prefs.edit().clear().apply()28+ _enabled.value = true29+ }30+2631 companion object {2732 private const val PREFS_FILE = "peach_background_sync"2833 private const val KEY_ENABLED = "enabled"app/src/main/java/com/peachpasswords/sync/DeviceIdentity.kt+7 −0
@@ -51,6 +51,13 @@5151 .apply()5252 }5353 54+fun clearDeviceIdentity(context: Context) {55+ context.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)56+ .edit()57+ .clear()58+ .apply()59+}60+5461 fun buildLocalIdentity(context: Context, masterKey: ByteArray): LocalDeviceIdentity {5562 val deviceId = getOrCreateDeviceId(context)5663 val deviceName = getDeviceName(context)app/src/main/java/com/peachpasswords/sync/PairedDeviceStore.kt+4 −1
@@ -137,7 +137,10 @@137137 }138138 139139 fun clear() {140- prefs.edit().remove(KEY_DEVICES).apply()140+ prefs.edit()141+ .remove(KEY_DEVICES)142+ .remove(KEY_REVOCATIONS)143+ .apply()141144 }142145 143146 /**app/src/main/java/com/peachpasswords/sync/PendingVouchedDeviceStore.kt+4 −0
@@ -66,6 +66,10 @@6666 persist(getAll().filterNot { it.deviceId == id })6767 }6868 69+ fun clear() {70+ persist(emptyList())71+ }72+6973 fun observeAll(): Flow<List<PendingVouchedDevice>> = callbackFlow {7074 trySend(getAll())7175 val listener = android.content.SharedPreferences.OnSharedPreferenceChangeListener { _, key ->app/src/main/java/com/peachpasswords/sync/RelaySettingsStore.kt+4 −0
@@ -143,6 +143,10 @@143143 return normalized144144 }145145 146+ fun reset() {147+ prefs.edit().clear().apply()148+ }149+146150 private fun storedRelayUrl(): String = prefs.getString(KEY_URL, null)147151 ?.let { runCatching { Rendezvous.normalizeRelayUrl(it) }.getOrNull() }148152 ?: Rendezvous.DEFAULT_RELAY_URLapp/src/main/java/com/peachpasswords/sync/SyncFreshStartResetter.kt+54 −0
@@ -0,0 +1,54 @@1+package com.peachpasswords.sync2+3+import android.content.Context4+import dagger.hilt.android.qualifiers.ApplicationContext5+import javax.inject.Inject6+import javax.inject.Singleton7+8+/**9+ * Clears every piece of state that can make a destroyed vault still look10+ * paired. The Pro license deliberately lives outside this boundary.11+ */12+@Singleton13+class SyncFreshStartResetter @Inject constructor(14+ @ApplicationContext private val context: Context,15+ private val devices: PairedDeviceStore,16+ private val pendingDevices: PendingVouchedDeviceStore,17+ private val alerts: SyncAlertStore,18+ private val outcomes: SyncOutcomeStore,19+ private val relaySettings: RelaySettingsStore,20+ private val backgroundSync: BackgroundSyncPreference,21+) {22+ fun reset() {23+ resetSyncStateForFreshStart(24+ stopService = { PeachSyncService.stop(context) },25+ clearDevices = devices::clear,26+ clearPendingDevices = pendingDevices::clear,27+ clearAlerts = alerts::clear,28+ clearOutcomes = outcomes::reset,29+ clearRelaySettings = relaySettings::reset,30+ clearBackgroundPreference = backgroundSync::reset,31+ clearLocalIdentity = { clearDeviceIdentity(context) },32+ )33+ }34+}35+36+internal fun resetSyncStateForFreshStart(37+ stopService: () -> Unit,38+ clearDevices: () -> Unit,39+ clearPendingDevices: () -> Unit,40+ clearAlerts: () -> Unit,41+ clearOutcomes: () -> Unit,42+ clearRelaySettings: () -> Unit,43+ clearBackgroundPreference: () -> Unit,44+ clearLocalIdentity: () -> Unit,45+) {46+ stopService()47+ clearDevices()48+ clearPendingDevices()49+ clearAlerts()50+ clearOutcomes()51+ clearRelaySettings()52+ clearBackgroundPreference()53+ clearLocalIdentity()54+}app/src/main/java/com/peachpasswords/ui/lock/LockViewModel.kt+3 −0
@@ -6,6 +6,7 @@66 import androidx.lifecycle.ViewModel77 import androidx.lifecycle.viewModelScope88 import com.peachpasswords.sync.ForegroundSyncCoordinator9+import com.peachpasswords.sync.SyncFreshStartResetter910 import com.peachpasswords.ui.PeachNav1011 import com.peachpasswords.ui.vault.entryTapDestination1112 import com.peachpasswords.vault.CreateVaultResult@@ -87,6 +88,7 @@8788 private val lockTimer: VaultLockTimer,8889 private val lastViewed: LastViewedEntryStore,8990 private val foregroundSync: ForegroundSyncCoordinator,91+ private val syncFreshStart: SyncFreshStartResetter,9092 @ApplicationContext private val appContext: Context,9193 ) : ViewModel() {9294 private val _state = MutableStateFlow(LockUiState(hasVault = vault.hasVault()))@@ -146,6 +148,7 @@146148 DuressAction.DESTROY -> {147149 vault.discardDuressUnlock(duressUnlock)148150 vault.destroyVault()151+ syncFreshStart.reset()149152 duress.clear()150153 _state.update { it.copy(loading = false, hasVault = false, duressDestroyed = true) }151154 }app/src/main/java/com/peachpasswords/ui/settings/PanicScreen.kt+3 −0
@@ -48,6 +48,7 @@4848 import dagger.hilt.android.qualifiers.ApplicationContext4949 import com.peachpasswords.ui.onboarding.ONBOARD_KEY5050 import com.peachpasswords.ui.onboarding.ONBOARD_PREFS51+import com.peachpasswords.sync.SyncFreshStartResetter5152 import com.peachpasswords.vault.VaultRepository5253 import dagger.hilt.android.lifecycle.HiltViewModel5354 import kotlinx.coroutines.launch@@ -56,12 +57,14 @@5657 @HiltViewModel5758 class PanicViewModel @Inject constructor(5859 private val vault: VaultRepository,60+ private val syncFreshStart: SyncFreshStartResetter,5961 @ApplicationContext private val appContext: Context,6062 ) : ViewModel() {6163 val entryCount: Int get() = vault.getEntries().size6264 6365 fun destroy(onDone: () -> Unit) = viewModelScope.launch {6466 vault.destroyVault()67+ syncFreshStart.reset()6568 // Destroying the vault puts the app back to factory state, so clear the6669 // "already onboarded" flag too — otherwise the next launch skipped the6770 // intro and dropped straight onto create-vault, which is not what aapp/src/test/java/com/peachpasswords/sync/SyncFreshStartResetterTest.kt+35 −0
@@ -0,0 +1,35 @@1+package com.peachpasswords.sync2+3+import org.junit.Assert.assertEquals4+import org.junit.Test5+6+class SyncFreshStartResetterTest {7+ @Test fun fresh_start_stops_the_service_then_clears_every_sync_state_store() {8+ val calls = mutableListOf<String>()9+10+ resetSyncStateForFreshStart(11+ stopService = { calls += "service" },12+ clearDevices = { calls += "devices" },13+ clearPendingDevices = { calls += "pending" },14+ clearAlerts = { calls += "alerts" },15+ clearOutcomes = { calls += "outcomes" },16+ clearRelaySettings = { calls += "relay" },17+ clearBackgroundPreference = { calls += "background" },18+ clearLocalIdentity = { calls += "identity" },19+ )20+21+ assertEquals(22+ listOf(23+ "service",24+ "devices",25+ "pending",26+ "alerts",27+ "outcomes",28+ "relay",29+ "background",30+ "identity",31+ ),32+ calls,33+ )34+ }35+}