Improve android autofill matching logic for common usecases (#1332)

This commit is contained in:
Leendert de Borst
2025-11-05 22:19:08 +01:00
parent 005d56f7e4
commit 7b45d7d652
3 changed files with 27 additions and 14 deletions

View File

@@ -80,7 +80,6 @@ class AutofillService : AutofillService() {
safeCallback()
return
}
launchActivityForAutofill(fieldFinder) { response -> safeCallback(response) }
} catch (e: Exception) {
Log.e(TAG, "Unexpected error in onFillRequest", e)
@@ -279,7 +278,7 @@ class AutofillService : AutofillService() {
}
}
FieldType.EMAIL -> {
if (credential.alias?.email != null) {
if (credential.alias?.email != null && credential.alias.email.isNotEmpty()) {
dataSetBuilder.setValue(
field.first,
AutofillValue.forText(credential.alias.email),
@@ -290,7 +289,7 @@ class AutofillService : AutofillService() {
} else if (!credential.username.isNullOrEmpty()) {
presentationDisplayValue += " (${credential.username})"
}
} else if (credential.username != null) {
} else if (!credential.username.isNullOrEmpty()) {
dataSetBuilder.setValue(
field.first,
AutofillValue.forText(credential.username),
@@ -304,7 +303,7 @@ class AutofillService : AutofillService() {
}
}
FieldType.USERNAME -> {
if (credential.username != null) {
if (!credential.username.isNullOrEmpty()) {
dataSetBuilder.setValue(
field.first,
AutofillValue.forText(credential.username),
@@ -315,7 +314,7 @@ class AutofillService : AutofillService() {
} else if ((credential.alias?.email ?: "").isNotEmpty()) {
presentationDisplayValue += " (${credential.alias?.email})"
}
} else if (credential.alias?.email != null) {
} else if (credential.alias?.email != null && credential.alias.email.isNotEmpty()) {
dataSetBuilder.setValue(
field.first,
AutofillValue.forText(credential.alias.email),
@@ -328,7 +327,7 @@ class AutofillService : AutofillService() {
}
else -> {
// For unknown field types, try both email and username
if (credential.alias?.email != null) {
if (credential.alias?.email != null && credential.alias.email.isNotEmpty()) {
dataSetBuilder.setValue(
field.first,
AutofillValue.forText(credential.alias.email),
@@ -337,7 +336,7 @@ class AutofillService : AutofillService() {
if (credential.alias.email.isNotEmpty()) {
presentationDisplayValue += " (${credential.alias.email})"
}
} else if (credential.username != null) {
} else if (!credential.username.isNullOrEmpty()) {
dataSetBuilder.setValue(
field.first,
AutofillValue.forText(credential.username),

View File

@@ -35,9 +35,9 @@ class FieldFinder(var structure: AssistStructure) {
var foundPasswordField = false
/**
* Whether a username field has been found.
* List of undetected editable fields (not identified as password/username/email).
*/
var lastField: AutofillId? = null
private val unknownFields = mutableListOf<AutofillId>()
/**
* Parse the structure.
@@ -49,6 +49,15 @@ class FieldFinder(var structure: AssistStructure) {
val rootNode = windowNode.rootViewNode
parseNode(rootNode)
}
// If only a password field was found, but there is exactly one other undetected field,
// assume it's the username/email field
if (foundPasswordField && !foundUsernameField && unknownFields.size == 1) {
val unknownFieldId = unknownFields.first()
Log.d(TAG, "Found password field without username - promoting unknown field to USERNAME type")
autofillableFields.add(Pair(unknownFieldId, FieldType.USERNAME))
foundUsernameField = true
}
}
/**
@@ -81,7 +90,7 @@ class FieldFinder(var structure: AssistStructure) {
/**
* Determines if a field is most likely an email field, username field, password field, or unknown.
*/
fun determineFieldType(fieldId: AutofillId): FieldType {
private fun determineFieldType(fieldId: AutofillId): FieldType {
// Find the node in the structure
val node = findNodeById(fieldId) ?: return FieldType.UNKNOWN
@@ -221,12 +230,15 @@ class FieldFinder(var structure: AssistStructure) {
if (fieldType == FieldType.PASSWORD) {
foundPasswordField = true
autofillableFields.add(Pair(viewId, fieldType))
Log.d(TAG, "Found PASSWORD field: ${node.idEntry}")
} else if (fieldType == FieldType.USERNAME || fieldType == FieldType.EMAIL) {
foundUsernameField = true
autofillableFields.add(Pair(viewId, fieldType))
Log.d(TAG, "Found ${fieldType.name} field: ${node.idEntry}")
} else {
// Store the last field we saw in case we need it for username detection
lastField = viewId
// Store undetected editable fields for potential username promotion
unknownFields.add(viewId)
Log.d(TAG, "Found UNKNOWN editable field: ${node.idEntry}, hint=${node.hint}, inputType=${node.inputType}")
}
}
@@ -263,7 +275,9 @@ class FieldFinder(var structure: AssistStructure) {
val hintContains = hint?.contains(pattern, ignoreCase = false) == true
val contentContains = contentDescription?.contains(pattern, ignoreCase = false) == true
return idEntryContains || hintContains || contentContains
if (idEntryContains || hintContains || contentContains) {
return true
}
}
return false

View File

@@ -20,7 +20,7 @@ complexity:
thresholdInObjects: 60
CyclomaticComplexMethod:
active: true
threshold: 35
threshold: 40
NestedBlockDepth:
active: true
threshold: 5