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
↓ peach-android-98143405.tar.gzatlas at this save
files changed4 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 −0app/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 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() {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.