99985d508ba1
trigger Sync after local vault commits and keep Android listener alive (v2.17.43)
- time
- 2026-09-21 12:53 UTC · 1 month ago
- machine
- koh2
- agent
- gpt-5
- session
- restore automatic sync freshness
- parent
- dcc055003dbc
↓ peach-android-99985d50.tar.gzatlas at this save
files changed6 files
VERSION+1 −1app/src/main/AndroidManifest.xml+8 −4app/src/main/java/com/peachpasswords/PeachApplication.kt+7 −0app/src/main/java/com/peachpasswords/sync/PeachSyncService.kt+2 −2app/src/main/java/com/peachpasswords/vault/VaultRepository.kt+5 −0app/src/test/java/com/peachpasswords/vault/VaultRepositoryTest.kt+25 −0app/src/main/AndroidManifest.xml+8 −4
@@ -20,7 +20,7 @@2020 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />2121 <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />2222 <!-- Android 14+ requires the specific type alongside the generic permission. -->23- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />23+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />2424 <!-- Doze defers network work even for a foreground service, which would make2525 an already-paired browser's reconnect time out with no visible cause.2626 Play-policy-restricted: only ever requested from an explicit user tap, and@@ -109,12 +109,16 @@109109 android:resource="@xml/file_paths" />110110 </provider>111111 112- <!-- Keeps the phone dialable for sync; a browser extension can never113- listen, so this side has to stay reachable. -->112+ <!-- Keeps the phone dialable for sync. This is continuous encrypted113+ device reachability, not Android's time-limited dataSync work. -->114114 <service115115 android:name=".sync.PeachSyncService"116116 android:exported="false"117- android:foregroundServiceType="dataSync" />117+ android:foregroundServiceType="specialUse">118+ <property119+ android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"120+ android:value="continuous encrypted sync reachability for paired Peach devices" />121+ </service>118122 119123 <receiver120124 android:name=".sync.SyncNotificationDismissedReceiver"app/src/main/java/com/peachpasswords/PeachApplication.kt+7 −0
@@ -10,12 +10,14 @@1010 import com.peachpasswords.sync.ForegroundSyncCoordinator1111 import com.peachpasswords.sync.LockedSyncSpoolHooks1212 import com.peachpasswords.sync.LockedSyncSpoolStore13+import com.peachpasswords.sync.PeachSyncService1314 import com.peachpasswords.shared.PeachClipboard1415 import com.peachpasswords.ui.lock.BiometricKeyStore1516 import com.peachpasswords.updates.UpdateCheckWorker1617 import com.peachpasswords.vault.PinUnlockStore1718 import com.peachpasswords.vault.VaultKeyRotationHooks1819 import com.peachpasswords.vault.VaultLockTimer20+import com.peachpasswords.vault.VaultMutationHooks1921 import com.peachpasswords.vault.VaultRepository2022 import dagger.hilt.android.HiltAndroidApp2123 import javax.inject.Inject@@ -53,6 +55,11 @@5355 VaultKeyRotationHooks.onRotated = {5456 pinUnlock.clear()5557 BiometricKeyStore.clear(this)58+ }59+ VaultMutationHooks.onCommitted = {60+ if (backgroundSync.enabled.value && vault.syncVaultId() != null) {61+ PeachSyncService.start(this)62+ }5663 }5764 5865 // Update checks: once a day in the background, plus one now — processapp/src/main/java/com/peachpasswords/sync/PeachSyncService.kt+2 −2
@@ -227,8 +227,8 @@227227 PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT,228228 )229229 return NotificationCompat.Builder(context, SYNC_NOTIFICATION_CHANNEL_ID)230- .setContentTitle("Peach sync is active")231- .setContentText("Your other devices can reach this phone for encrypted sync.")230+ .setContentTitle("Peach sync is on")231+ .setContentText("Checking for encrypted changes from your paired devices.")232232 .setSmallIcon(R.drawable.ic_launcher_monochrome)233233 .setOngoing(true)234234 .setPriority(NotificationCompat.PRIORITY_LOW)app/src/main/java/com/peachpasswords/vault/VaultRepository.kt+5 −0
@@ -71,6 +71,10 @@7171 @Volatile var onRotated: () -> Unit = {}7272 }7373 74+internal object VaultMutationHooks {75+ @Volatile var onCommitted: () -> Unit = {}76+}77+7478 /**7579 * In-memory vault session backed by [VaultStore]. Faithful port of the browser7680 * `vault.ts` semantics that matter for correctness:@@ -3134,5 +3138,6 @@31343138 // being republished into a newly locked (or newly unlocked) session.31353139 mutateIfCurrentEpoch(expectedEpoch) { publish() }31363140 runCatching { LockedSyncSpoolHooks.refresh(this) }3141+ runCatching { VaultMutationHooks.onCommitted() }31373142 }31383143 }app/src/test/java/com/peachpasswords/vault/VaultRepositoryTest.kt+25 −0
@@ -43,6 +43,31 @@4343 private fun note(title: String) =4444 VaultEntry(id = "", title = title, type = "note", createdAt = 0, modifiedAt = 0)4545 46+ @Test fun local_entry_commit_requests_sync_but_remote_merge_does_not() = runBlocking {47+ val repo = VaultRepository(FakeStorage())48+ repo.createVault("pw")49+ var requests = 050+ VaultMutationHooks.onCommitted = { requests += 1 }51+ try {52+ repo.addEntry(login("Local"))53+ assertEquals(1, requests)54+55+ repo.applyMergedRemoteEntries(56+ listOf(57+ login("Remote").copy(58+ id = "remote-1",59+ createdAt = 1,60+ modifiedAt = System.currentTimeMillis() + 10_000,61+ ),62+ ),63+ emptyList(),64+ )65+ assertEquals(1, requests)66+ } finally {67+ VaultMutationHooks.onCommitted = {}68+ }69+ }70+4671 @Test fun full_lock_offers_committed_state_to_opaque_sync_spool_before_zeroing() = runBlocking {4772 val repo = VaultRepository(FakeStorage())4873 repo.createVault("pw")