@@ -42,13 +42,94 @@ class AutofillResponseBuilder(private val context: Context) {
4242 dataset?.let { fillResponseBuilder.addDataset(it) }
4343 }
4444
45- // Add save info to allow saving new credentials
45+ // Add "Save Password" manual trigger dataset
46+ android.util.Log .d(" AutofillResponseBuilder" , " === Attempting to build manual save dataset ===" )
47+ android.util.Log .d(" AutofillResponseBuilder" , " Fields count: ${fields.size} , Package: $packageName " )
48+ val saveDataset = buildManualSaveDataset(fields, packageName)
49+ if (saveDataset != null ) {
50+ android.util.Log .d(" AutofillResponseBuilder" , " ✅ Manual save dataset created successfully, adding to response" )
51+ fillResponseBuilder.addDataset(saveDataset)
52+ } else {
53+ android.util.Log .w(" AutofillResponseBuilder" , " ❌ Manual save dataset is NULL - not added to response" )
54+ }
55+
56+ // Add save info to allow saving new credentials (still needed for native apps)
4657 val saveInfo = buildSaveInfo(fields)
47- saveInfo?.let { fillResponseBuilder.setSaveInfo(it) }
58+ if (saveInfo != null ) {
59+ fillResponseBuilder.setSaveInfo(saveInfo)
60+ android.util.Log .d(" AutofillResponseBuilder" , " SaveInfo added to FillResponse" )
61+ } else {
62+ android.util.Log .w(" AutofillResponseBuilder" , " SaveInfo is NULL - won't trigger save dialog!" )
63+ }
4864
4965 return try {
5066 fillResponseBuilder.build()
5167 } catch (e: Exception ) {
68+ android.util.Log .e(" AutofillResponseBuilder" , " Failed to build FillResponse" , e)
69+ null
70+ }
71+ }
72+
73+ /* *
74+ * Build a special "Save Password" dataset that launches manual save flow.
75+ */
76+ private fun buildManualSaveDataset (
77+ fields : List <AutofillField >,
78+ packageName : String
79+ ): Dataset ? {
80+ android.util.Log .d(" AutofillResponseBuilder" , " buildManualSaveDataset() called for package: $packageName " )
81+
82+ // Create intent to launch manual save capture
83+ val saveIntent = Intent (context, com.nexpass.passwordmanager.autofill.ui.ManualSaveCaptureActivity ::class .java).apply {
84+ putExtra(" packageName" , packageName)
85+ // NOTE: Dataset authentication does NOT provide EXTRA_ASSIST_STRUCTURE automatically!
86+ // This is a known Android limitation - only FillResponse.setAuthentication() provides it
87+ }
88+
89+ val savePendingIntent = PendingIntent .getActivity(
90+ context,
91+ 1 , // Different request code from auth prompt
92+ saveIntent,
93+ PendingIntent .FLAG_IMMUTABLE or PendingIntent .FLAG_CANCEL_CURRENT
94+ )
95+
96+ // Create presentation for "Save Password" option
97+ val presentation = RemoteViews (context.packageName, R .layout.autofill_item)
98+ presentation.setTextViewText(R .id.autofill_title, " \uD83D\uDCBE Save This Password" )
99+ presentation.setTextViewText(R .id.autofill_username, " Tap to save current credentials" )
100+ presentation.setImageViewResource(R .id.autofill_icon, android.R .drawable.ic_menu_save)
101+
102+ val datasetBuilder = Dataset .Builder (presentation)
103+
104+ // Set authentication - when user selects this, Android launches our activity
105+ // with EXTRA_ASSIST_STRUCTURE containing current field values
106+ val usernameField = fields.firstOrNull {
107+ it.fieldType == FieldType .USERNAME || it.fieldType == FieldType .EMAIL
108+ }
109+ val passwordField = fields.firstOrNull { it.fieldType == FieldType .PASSWORD }
110+
111+ // We need at least one field to attach the authentication to
112+ passwordField?.let { field ->
113+ android.util.Log .d(" AutofillResponseBuilder" , " Found password field for manual save dataset" )
114+ datasetBuilder.setValue(
115+ field.autofillId,
116+ null , // Don't actually fill any value
117+ presentation
118+ )
119+ } ? : run {
120+ android.util.Log .w(" AutofillResponseBuilder" , " ⚠️ No password field found - manual save dataset may not work!" )
121+ }
122+
123+ // Set authentication on the dataset
124+ datasetBuilder.setAuthentication(savePendingIntent.intentSender)
125+ android.util.Log .d(" AutofillResponseBuilder" , " Set authentication on manual save dataset" )
126+
127+ return try {
128+ val dataset = datasetBuilder.build()
129+ android.util.Log .d(" AutofillResponseBuilder" , " ✅ Manual save dataset built successfully" )
130+ dataset
131+ } catch (e: Exception ) {
132+ android.util.Log .e(" AutofillResponseBuilder" , " ❌ Failed to build manual save dataset" , e)
52133 null
53134 }
54135 }
@@ -143,18 +224,37 @@ class AutofillResponseBuilder(private val context: Context) {
143224 }
144225 val passwordField = fields.firstOrNull { it.fieldType == FieldType .PASSWORD }
145226
227+ android.util.Log .d(" AutofillResponseBuilder" , " buildSaveInfo - usernameField: ${usernameField != null } , passwordField: ${passwordField != null } " )
228+
146229 // We need at least a password field to save
147230 if (passwordField == null ) {
231+ android.util.Log .w(" AutofillResponseBuilder" , " No password field found - cannot build SaveInfo" )
148232 return null
149233 }
150234
151- val requiredIds = mutableListOf< AutofillId >(passwordField.autofillId)
152- usernameField?. let { requiredIds.add(it .autofillId) }
235+ // Only password is required, username is optional
236+ val requiredIds = arrayOf(passwordField .autofillId)
153237
154- return SaveInfo .Builder (
238+ val builder = SaveInfo .Builder (
155239 SaveInfo .SAVE_DATA_TYPE_USERNAME or SaveInfo .SAVE_DATA_TYPE_PASSWORD ,
156- requiredIds.toTypedArray()
157- ).build()
240+ requiredIds
241+ )
242+
243+ // Add optional username field if present
244+ usernameField?.let {
245+ builder.setOptionalIds(arrayOf(it.autofillId))
246+ android.util.Log .d(" AutofillResponseBuilder" , " Added optional username field to SaveInfo" )
247+ }
248+
249+ // Set flags to trigger save more aggressively
250+ // FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE: Trigger save when all views become invisible (form submission/navigation)
251+ // FLAG_DELAY_SAVE: Delay the save UI to better detect form submissions
252+ val flags = SaveInfo .FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE or SaveInfo .FLAG_DELAY_SAVE
253+ builder.setFlags(flags)
254+ android.util.Log .d(" AutofillResponseBuilder" , " Set SaveInfo flags: FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE | FLAG_DELAY_SAVE" )
255+
256+ android.util.Log .d(" AutofillResponseBuilder" , " Successfully built SaveInfo" )
257+ return builder.build()
158258 }
159259
160260 /* *
@@ -194,6 +294,15 @@ class AutofillResponseBuilder(private val context: Context) {
194294 presentation
195295 )
196296
297+ // IMPORTANT: Add SaveInfo so Android triggers save dialog after form submission
298+ val saveInfo = buildSaveInfo(fields)
299+ if (saveInfo != null ) {
300+ responseBuilder.setSaveInfo(saveInfo)
301+ android.util.Log .d(" AutofillResponseBuilder" , " SaveInfo added to authentication response" )
302+ } else {
303+ android.util.Log .w(" AutofillResponseBuilder" , " SaveInfo is NULL in authentication response" )
304+ }
305+
197306 return responseBuilder.build()
198307 }
199308}
0 commit comments