Broaden Android accessibility autofill fallback

This commit is contained in:
Joe Julian
2026-04-23 20:44:32 -07:00
parent d60a8d2fbf
commit 515eb730f0
7 changed files with 444 additions and 195 deletions
@@ -12,7 +12,6 @@ import java.util.List;
public final class KeePassGOAccessibilityService extends AccessibilityService {
private static final String TAG = "KeePassGOA11y";
private String lastFilledSignature = "";
@Override
@@ -22,22 +21,17 @@ public final class KeePassGOAccessibilityService extends AccessibilityService {
if (root == null) {
return;
}
CharSequence packageName = root.getPackageName();
if (packageName == null || !"com.android.chrome".contentEquals(packageName)) {
return;
}
ChromeForm form = inspectChrome(root);
ChromeForm form = inspectWindow(root);
if (form == null || form.passwordField == null) {
return;
}
AutofillCacheStore.Entry entry = AutofillCacheStore.findBestMatch(this, form.url);
AutofillCacheStore.Entry entry = AutofillCacheStore.findBestMatch(this, form.matchTarget);
if (entry == null) {
Log.i(TAG, "no accessibility-fill match for " + form.url);
Log.i(TAG, "no accessibility-fill match for " + form.matchTarget);
return;
}
String signature = form.url + "|" + entry.username + "|" + nodeKey(form.passwordField);
String signature = form.matchTarget + "|" + entry.username + "|" + nodeKey(form.passwordField);
if (signature.equals(lastFilledSignature)) {
return;
}
@@ -49,7 +43,7 @@ public final class KeePassGOAccessibilityService extends AccessibilityService {
filled |= setNodeText(form.passwordField, entry.password);
if (filled) {
lastFilledSignature = signature;
Log.i(TAG, "filled login form for " + form.url + " with " + entry.username);
Log.i(TAG, "filled login form for " + form.matchTarget + " with " + entry.username);
}
} catch (Exception err) {
Log.e(TAG, "accessibility fill failed", err);
@@ -61,11 +55,11 @@ public final class KeePassGOAccessibilityService extends AccessibilityService {
Log.i(TAG, "accessibility service interrupted");
}
private static ChromeForm inspectChrome(AccessibilityNodeInfo root) {
private static ChromeForm inspectWindow(AccessibilityNodeInfo root) {
List<AccessibilityNodeInfo> editables = new ArrayList<>();
ChromeForm form = new ChromeForm();
walk(root, editables, form);
if (form.url.isEmpty()) {
if (form.matchTarget.isEmpty()) {
return null;
}
if (form.passwordField == null) {
@@ -111,7 +105,13 @@ public final class KeePassGOAccessibilityService extends AccessibilityService {
if (viewID != null && viewID.toString().endsWith(":id/url_bar")) {
CharSequence text = node.getText();
if (text != null) {
form.url = text.toString().trim();
form.webDomain = text.toString().trim();
}
}
if (form.packageName.isEmpty()) {
CharSequence packageName = node.getPackageName();
if (packageName != null) {
form.packageName = packageName.toString().trim();
}
}
if (node.isEditable()) {
@@ -128,6 +128,7 @@ public final class KeePassGOAccessibilityService extends AccessibilityService {
walk(child, editables, form);
}
}
form.matchTarget = AutofillFallbackTarget.resolve(form.packageName, form.webDomain);
}
private static boolean isPasswordNode(AccessibilityNodeInfo node) {
@@ -188,7 +189,9 @@ public final class KeePassGOAccessibilityService extends AccessibilityService {
}
private static final class ChromeForm {
String url = "";
String packageName = "";
String webDomain = "";
String matchTarget = "";
AccessibilityNodeInfo usernameField;
AccessibilityNodeInfo passwordField;
}