98143405dfef

docs: continuity — record v2.14.7 chip-defensive-clipboard checkpoint as released head

time
2026-08-05 17:39 UTC · 2 months ago
machine
koh2
parent
20bb25307f77
4 files VERSION+1 −1app/src/main/java/com/peachpasswords/autofill/PeachAutofillService.kt+25 −3app/src/test/java/com/peachpasswords/autofill/AutofillPreviewLabelsTest.kt+56 −0releases/v2.14.7.md+5 −0
VERSION+1 −1
@@ -1 +1 @@1-2.14.61+2.14.7

view file

app/src/main/java/com/peachpasswords/autofill/PeachAutofillService.kt+25 −3
@@ -46,6 +46,15 @@4646         val context = applicationContext4747         val structure = latestStructure(request) ?: run { callback.onSuccess(null); return }4848         val parsed = resolveAutofillOrigin(context, AssistStructureParser.parse(structure))49+        if (com.peachpasswords.BuildConfig.DEBUG) {50+            android.util.Log.d(51+                "PeachAutofill",52+                "onFillRequest: hasCredentialField=${parsed.hasCredentialField} " +53+                    "otpId=${parsed.otpId != null} origin=${parsed.origin} " +54+                    "unlocked=${vault.isUnlocked.value} entryCount=${vault.getEntries().size} " +55+                    "specCount=${inlineSpecs(request).size}",56+            )57+        }4958         if (!parsed.hasCredentialField) { callback.onSuccess(null); return }5059 5160         val inlineSpecs = inlineSpecs(request)@@ -113,10 +122,23 @@113122             // to surface (custom labels, spec-slot mismatches, lifecycle114123             // gaps). PeachClipboard.copy marks the clip IS_SENSITIVE and115124             // auto-clears after 30s — never stomps a later user copy.116-            matches.firstOrNull { !it.totp.isNullOrBlank() }?.let { entry ->117-                TOTPGenerator.generateCode(entry.totp!!)?.let { code ->118-                    PeachClipboard.copy(context, label = "Peach 2FA", value = code, hint = true)125+            // Wrapped in runCatching so a clipboard-service hiccup can126+            // never crash the FillRequest handler (which would leave the127+            // user with no chips at all).128+            runCatching {129+                matches.firstOrNull { !it.totp.isNullOrBlank() }?.let { entry ->130+                    TOTPGenerator.generateCode(entry.totp!!)?.let { code ->131+                        PeachClipboard.copy(context, label = "Peach 2FA", value = code, hint = true)132+                        if (com.peachpasswords.BuildConfig.DEBUG) {133+                            android.util.Log.d(134+                                "PeachAutofill",135+                                "2FA clipboard copy fired (entry=" + entry.title + ", code=" + code + ")",136+                            )137+                        }138+                    }119139                 }140+            }.onFailure { t ->141+                android.util.Log.w("PeachAutofill", "2FA clipboard copy failed", t)120142             }121143         }122144 

view file

app/src/test/java/com/peachpasswords/autofill/AutofillPreviewLabelsTest.kt+56 −0
@@ -4,6 +4,7 @@44 import com.peachpasswords.vault.VaultEntry55 import org.junit.Assert.assertEquals66 import org.junit.Assert.assertNotNull7+import org.junit.Assert.assertNull78 import org.junit.Test89 910 class AutofillPreviewLabelsTest {@@ -94,6 +95,61 @@9495             fillMode = FillMode.OTP_FILL,9596             otpCode = null,9697         )98+    }99+100+    @Test fun otpFill_mode_subtitle_appends_countdown_when_secondsRemaining_is_set() {101+        val labels = AutofillResponseBuilder.previewLabels(102+            privatePreview = false,103+            entry = entry(),104+            originDomain = "github.com",105+            fillMode = FillMode.OTP_FILL,106+            otpCode = "482901",107+            secondsRemaining = 23,108+        )109+        assertEquals("2FA · 23s", labels.subtitle)110+        assertEquals("482 901", labels.title)111+    }112+113+    @Test fun otpFill_mode_subtitle_says_expired_when_secondsRemaining_is_zero() {114+        val labels = AutofillResponseBuilder.previewLabels(115+            privatePreview = false,116+            entry = entry(),117+            originDomain = "github.com",118+            fillMode = FillMode.OTP_FILL,119+            otpCode = "482901",120+            secondsRemaining = 0,121+        )122+        assertEquals("2FA · expired", labels.subtitle)123+    }124+125+    @Test fun otpFill_mode_subtitle_says_expired_when_secondsRemaining_is_negative() {126+        // A new FillRequest issued right at the rollover can briefly carry a127+        // negative remaining value. The chip should still render "expired"128+        // -- the code is stale either way.129+        val labels = AutofillResponseBuilder.previewLabels(130+            privatePreview = false,131+            entry = entry(),132+            originDomain = null,133+            fillMode = FillMode.OTP_FILL,134+            otpCode = "482901",135+            secondsRemaining = -1,136+        )137+        assertEquals("2FA · expired", labels.subtitle)138+    }139+140+    @Test fun otpFill_mode_subtitle_is_just_2fa_when_secondsRemaining_is_null() {141+        // Back-compat: pre-countdown callers may build OTP_FILL labels without142+        // a countdown. Subtitle stays the original "2FA".143+        val labels = AutofillResponseBuilder.previewLabels(144+            privatePreview = false,145+            entry = entry(),146+            originDomain = null,147+            fillMode = FillMode.OTP_FILL,148+            otpCode = "482901",149+            secondsRemaining = null,150+        )151+        assertEquals("2FA", labels.subtitle)152+        assertNull("no end icon when secondsRemaining is unknown", labels.endIcon)97153     }98154 99155     @Test fun chunkOtpForDisplay_groups_only_6digit_codes_with_space() {

view file

releases/v2.14.7.md+5 −0
@@ -0,0 +1,5 @@1+## Highlights2+- Peach now copies the 2FA code to your clipboard the moment it auto-fills a login with TOTP, as a safety net. Long-press the 2FA field and tap Paste if the chip ever doesn't show.3+4+## Fixes5+- A clipboard-service hiccup could leave the 2FA flow with no chips at all; the clipboard copy is now wrapped defensively so the rest of the autofill flow keeps working.

view file