[PM-41291] feat: Add model and data layer for Identity Autofill - #7232
Conversation
🤖 Bitwarden Claude Code ReviewOverall Assessment: APPROVE Reviewed the current head of the identity autofill model/data layer: the new The one deferred branch that yields a value rather than an inert result is Code Review DetailsNo findings at or above the reporting threshold. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #7232 +/- ##
==========================================
+ Coverage 85.22% 85.66% +0.43%
==========================================
Files 1033 922 -111
Lines 68135 66484 -1651
Branches 9940 9970 +30
==========================================
- Hits 58070 56952 -1118
+ Misses 6575 5998 -577
- Partials 3490 3534 +44
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| internal val IdentityView.identityAddress: String? | ||
| get() = listOfNotNull( | ||
| address1, | ||
| address2, | ||
| address3, | ||
| listOf(city ?: "-", state ?: "-", postalCode ?: "-") | ||
| .nullIfAllEqual("-") | ||
| ?.joinToString(", "), | ||
| country, | ||
| ) | ||
| .joinToString("\n") | ||
| .orNullIfBlank() |
There was a problem hiding this comment.
identityAddress is a display formatter; reusing it as AutofillCipher.Identity.fullAddress will fill - placeholders and newlines into form fields.
Details and fix
identityAddress was written for the vault item detail screen, where - placeholders and \n separators render acceptably. AutofillCipher.Identity.fullAddress is documented as "address parts joined for filling a combined full-address field", so the later fill phase will push this exact string into a PostalAddressFull input.
The new test in CipherViewExtensionsTest.kt locks in the problematic output:
assertEquals("address3\n-, state, -", identityView.identityAddress)For a cipher with only state populated, the user's address field would be filled with a literal -, state, -. Newlines in a single-line EditText are also dropped or rendered as spaces depending on the app.
Suggested fix: keep identityAddress for display and add a separate autofill-oriented formatter that omits missing parts entirely (no - placeholders) and joins with ", " — e.g. listOfNotNull(address1, address2, address3, city, state, postalCode, country).joinToString(", ").orNullIfBlank().
Same root cause applies to identityName: it prefixes the title, so a PersonNameFull field would be filled with Mr John Doe even though PersonNamePrefix exists as its own view for the title.
Since fill is a no-op in this phase, this is not user-visible yet — but the shape is being locked in here and asserted by tests, so it's cheaper to correct now than in the fill phase.
There was a problem hiding this comment.
@claude I agree with you. Reverted this method to the CipherViewExtensions it originally belonged and added a new one for the autofill
982905e to
6f0c34c
Compare
| saveCallback.onSuccess(intentSender) | ||
| } else { | ||
| saveCallback.onSuccess() | ||
| } |
There was a problem hiding this comment.
Can we simplify this:
autofillRequest
.toAutofillSaveItem()
?.let { autofillSaveItem ->
createAutofillSavedItemIntentSender(
autofillAppInfo = autofillAppInfo,
autofillSaveItem = autofillSaveItem,
)
}
?.let { saveCallback.onSuccess(it) }
?: saveCallback.onSuccess()| } | ||
| ?.let { nonNullCipherListView -> | ||
| nonNullCipherListView.id?.let { cipherId -> | ||
| decryptCipherOrNull(cipherId = cipherId)?.let { cipherView -> |
There was a problem hiding this comment.
We shouldn't do this in this PR but we might want to consider optimizing this flow in the future with new DB functions.
We can make a make specific DB queries to fetch the relevant ciphers only, filtering by type, active, and reprompt. That should make for a fairly meaningful performance boost.
What you have here conforms to the existing pattern and seems perfectly fine for now though.
| is AutofillPartition.Identity -> { | ||
| // Capturing identity data from a filled form is out of scope. This is never actually | ||
| // reached because AutofillPartition.Identity.canPerformSaveRequest is always false, so | ||
| // SaveInfo (and therefore a save callback) is never built for it. |
| val licenseNumber: String, | ||
| ) : AutofillCipher() { | ||
| override val iconRes: Int | ||
| @DrawableRes get() = BitwardenDrawable.ic_id_card |
There was a problem hiding this comment.
Is this the correct icon?
There was a problem hiding this comment.
yes 😅 The one for cards is ic_payment_card this is the used on each Identity scenario
| postalCode = identityView?.postalCode.orEmpty(), | ||
| country = identityView?.country.orEmpty(), | ||
| company = identityView?.company.orEmpty(), | ||
| email = identityView?.email.orEmpty(), |
There was a problem hiding this comment.
I see we have the orEmpty usages in the other spots too, does this cause autofill to clear the value if the user has already typed into the field?
There was a problem hiding this comment.
No, later on the flow we are ignoring empty fields
0cc466b to
532e403
Compare
| is AutofillView.Identity.AddressCountry -> { | ||
| this.copy(data = this.data.copy(website = site)) | ||
| } | ||
| is AutofillView.Identity.AddressLocality -> { |
There was a problem hiding this comment.
Wanna run the formatter on this file, you should have some newlines in here
47cfacc to
4de9092
Compare
4de9092 to
f33ce8d
Compare
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-41291
📔 Objective
Phase A/B of Identity Autofill (PM-38138): adds the model layer and data-layer plumbing that later phases build on.
AutofillView.Identity,AutofillPartition.Identity,AutofillCipher.Identity, and the correspondingAutofillHintentries.AutofillCipherProvider.getIdentityAutofillCiphers()(+ implementation) to fetch identity ciphers, mirroring the existinggetCardAutofillCiphers().whenforced by the new sealed-class members is completed now, either with permanent trivial logic or an explicit inert stub (e.g.AutofillRequest.Unfillable) commented to say which later phase replaces it — no behavior change yet.AutofillCipherProviderimplementation (CipherViewExtensions.kt'stoAutofillCipherProvider(), used by the manual-selection/accessibility completion flow) is updated in parallel so the two don't drift.This PR is intentionally behavior-neutral — nothing yet classifies a field as Identity, so none of this is reachable in production. Detection and fill land in later stacked phases (heuristic detection, fill-assist mapping, and the identity partition build-out).
📸 Screenshots
N/A — model/data layer only, no UI changes.