From cc7214b88006e0a09111c17c0fe3839480a5e07e Mon Sep 17 00:00:00 2001 From: Joe Julian Date: Wed, 1 Apr 2026 01:24:28 -0700 Subject: [PATCH] Add Android accessibility-based Chrome form fill --- android/application_snippets.xml | 12 ++ android/keepassgo-android.jar | Bin 7924 -> 12395 bytes .../xml/keepassgo_accessibility_service.xml | 8 + .../keepassgo/AutofillCacheStore.java | 4 + .../KeePassGOAccessibilityService.java | 195 ++++++++++++++++++ .../keepassgo/KeePassGOAutofillService.java | 20 ++ 6 files changed, 239 insertions(+) create mode 100644 android/res/xml/keepassgo_accessibility_service.xml create mode 100644 androidsrc/org/julianfamily/keepassgo/KeePassGOAccessibilityService.java diff --git a/android/application_snippets.xml b/android/application_snippets.xml index 322d1b8..0990299 100644 --- a/android/application_snippets.xml +++ b/android/application_snippets.xml @@ -10,3 +10,15 @@ android:name="android.autofill" android:resource="@xml/keepassgo_autofill_service" /> + + + + + + diff --git a/android/keepassgo-android.jar b/android/keepassgo-android.jar index d69db1ebf602b34b6340b7d88910b8100aa65e4c..b2345ae044ab01ba7c36dd60f47769b4899e80d0 100644 GIT binary patch delta 11154 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 delta 6803 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

9Yf_WvuLOR;9J?uPc4!`% zxM4vJK&ZE(7?K(_Iorwb;6Cs#@v@EJcAb?mRX+O@;boR+ zJnG)1Z*)(_JOnhAjb}m&y{c&7kz|`#O+f4xxYE4U9wLpT6Sc{--*ez<-*=DUb2j zZTRa$Kz2AyasLMYFzX*>LDadlF#ityqX|eHHvmZ$7ovtp{r><07qZGt`||IC{~kj4 hKh^~T!cV;aY#=64S4KtqJCFINuKaO5P^kV!{{sc;?kNBO diff --git a/android/res/xml/keepassgo_accessibility_service.xml b/android/res/xml/keepassgo_accessibility_service.xml new file mode 100644 index 0000000..79990a1 --- /dev/null +++ b/android/res/xml/keepassgo_accessibility_service.xml @@ -0,0 +1,8 @@ + + diff --git a/androidsrc/org/julianfamily/keepassgo/AutofillCacheStore.java b/androidsrc/org/julianfamily/keepassgo/AutofillCacheStore.java index 3d491b9..41b0fac 100644 --- a/androidsrc/org/julianfamily/keepassgo/AutofillCacheStore.java +++ b/androidsrc/org/julianfamily/keepassgo/AutofillCacheStore.java @@ -57,17 +57,21 @@ final class AutofillCacheStore { if (filesDir != null) { candidates.add(new File(filesDir, "keepassgo/autofill-cache.json")); candidates.add(new File(filesDir, ".config/keepassgo/autofill-cache.json")); + candidates.add(new File(filesDir, "config/keepassgo/autofill-cache.json")); } File baseDir = context.getDataDir(); if (baseDir != null) { candidates.add(new File(baseDir, "files/keepassgo/autofill-cache.json")); candidates.add(new File(baseDir, "files/.config/keepassgo/autofill-cache.json")); + candidates.add(new File(baseDir, "files/config/keepassgo/autofill-cache.json")); } for (File candidate : candidates) { if (candidate.isFile()) { + Log.i(TAG, "using autofill cache " + candidate.getAbsolutePath()); return candidate; } } + Log.i(TAG, "no autofill cache file in " + candidates); return null; } diff --git a/androidsrc/org/julianfamily/keepassgo/KeePassGOAccessibilityService.java b/androidsrc/org/julianfamily/keepassgo/KeePassGOAccessibilityService.java new file mode 100644 index 0000000..b347812 --- /dev/null +++ b/androidsrc/org/julianfamily/keepassgo/KeePassGOAccessibilityService.java @@ -0,0 +1,195 @@ +package org.julianfamily.keepassgo; + +import android.accessibilityservice.AccessibilityService; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; + +import java.util.ArrayList; +import java.util.List; + +public final class KeePassGOAccessibilityService extends AccessibilityService { + private static final String TAG = "KeePassGOA11y"; + + private String lastFilledSignature = ""; + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + try { + AccessibilityNodeInfo root = getRootInActiveWindow(); + if (root == null) { + return; + } + CharSequence packageName = root.getPackageName(); + if (packageName == null || !"com.android.chrome".contentEquals(packageName)) { + return; + } + + ChromeForm form = inspectChrome(root); + if (form == null || form.passwordField == null) { + return; + } + AutofillCacheStore.Entry entry = AutofillCacheStore.findBestMatch(this, form.url); + if (entry == null) { + Log.i(TAG, "no accessibility-fill match for " + form.url); + return; + } + + String signature = form.url + "|" + entry.username + "|" + nodeKey(form.passwordField); + if (signature.equals(lastFilledSignature)) { + return; + } + + boolean filled = false; + if (form.usernameField != null) { + filled |= setNodeText(form.usernameField, entry.username); + } + filled |= setNodeText(form.passwordField, entry.password); + if (filled) { + lastFilledSignature = signature; + Log.i(TAG, "filled login form for " + form.url + " with " + entry.username); + } + } catch (Exception err) { + Log.e(TAG, "accessibility fill failed", err); + } + } + + @Override + public void onInterrupt() { + Log.i(TAG, "accessibility service interrupted"); + } + + private static ChromeForm inspectChrome(AccessibilityNodeInfo root) { + List editables = new ArrayList<>(); + ChromeForm form = new ChromeForm(); + walk(root, editables, form); + if (form.url.isEmpty()) { + return null; + } + if (form.passwordField == null) { + for (AccessibilityNodeInfo node : editables) { + if (isPasswordNode(node)) { + form.passwordField = node; + break; + } + } + } + if (form.usernameField == null && form.passwordField != null) { + for (AccessibilityNodeInfo node : editables) { + if (node.equals(form.passwordField)) { + continue; + } + if (isUsernameNode(node)) { + form.usernameField = node; + break; + } + } + if (form.usernameField == null) { + for (AccessibilityNodeInfo node : editables) { + if (node.equals(form.passwordField)) { + continue; + } + form.usernameField = node; + break; + } + } + } + return form; + } + + private static void walk( + AccessibilityNodeInfo node, + List editables, + ChromeForm form + ) { + if (node == null) { + return; + } + CharSequence viewID = node.getViewIdResourceName(); + if (viewID != null && viewID.toString().endsWith(":id/url_bar")) { + CharSequence text = node.getText(); + if (text != null) { + form.url = text.toString().trim(); + } + } + if (node.isEditable()) { + editables.add(node); + if (form.passwordField == null && isPasswordNode(node)) { + form.passwordField = node; + } else if (form.usernameField == null && isUsernameNode(node)) { + form.usernameField = node; + } + } + for (int i = 0; i < node.getChildCount(); i++) { + AccessibilityNodeInfo child = node.getChild(i); + if (child != null) { + walk(child, editables, form); + } + } + } + + private static boolean isPasswordNode(AccessibilityNodeInfo node) { + if (node.isPassword()) { + return true; + } + return nodeMatches(node, "password"); + } + + private static boolean isUsernameNode(AccessibilityNodeInfo node) { + if (isPasswordNode(node)) { + return false; + } + return nodeMatches(node, "username") + || nodeMatches(node, "email") + || nodeMatches(node, "login") + || nodeMatches(node, "user"); + } + + private static boolean nodeMatches(AccessibilityNodeInfo node, String term) { + String lowerTerm = term.toLowerCase(); + return containsLower(node.getHintText(), lowerTerm) + || containsLower(node.getText(), lowerTerm) + || containsLower(node.getContentDescription(), lowerTerm) + || containsLower(node.getViewIdResourceName(), lowerTerm) + || containsLower(node.getPaneTitle(), lowerTerm); + } + + private static boolean containsLower(CharSequence value, String term) { + return value != null && value.toString().toLowerCase().contains(term); + } + + private static boolean setNodeText(AccessibilityNodeInfo node, String value) { + if (node == null || !node.isEditable() || value == null) { + return false; + } + Bundle args = new Bundle(); + args.putCharSequence( + AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, + value + ); + return node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args); + } + + private static String nodeKey(AccessibilityNodeInfo node) { + CharSequence viewID = node.getViewIdResourceName(); + if (viewID != null) { + return viewID.toString(); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + return String.valueOf(node.getUniqueId()); + } + CharSequence hint = node.getHintText(); + if (hint != null) { + return hint.toString(); + } + return String.valueOf(node.hashCode()); + } + + private static final class ChromeForm { + String url = ""; + AccessibilityNodeInfo usernameField; + AccessibilityNodeInfo passwordField; + } +} diff --git a/androidsrc/org/julianfamily/keepassgo/KeePassGOAutofillService.java b/androidsrc/org/julianfamily/keepassgo/KeePassGOAutofillService.java index 9133ad2..a2a48de 100644 --- a/androidsrc/org/julianfamily/keepassgo/KeePassGOAutofillService.java +++ b/androidsrc/org/julianfamily/keepassgo/KeePassGOAutofillService.java @@ -22,6 +22,18 @@ import java.util.List; public final class KeePassGOAutofillService extends AutofillService { private static final String TAG = "KeePassGOAutofill"; + @Override + public void onConnected() { + super.onConnected(); + Log.i(TAG, "service connected"); + } + + @Override + public void onDisconnected() { + Log.i(TAG, "service disconnected"); + super.onDisconnected(); + } + @Override public void onFillRequest( FillRequest request, @@ -29,8 +41,10 @@ public final class KeePassGOAutofillService extends AutofillService { FillCallback callback ) { try { + Log.i(TAG, "fill request flags=" + request.getFlags()); List contexts = request.getFillContexts(); if (contexts.isEmpty()) { + Log.i(TAG, "fill request had no contexts"); callback.onSuccess(null); return; } @@ -38,16 +52,20 @@ public final class KeePassGOAutofillService extends AutofillService { AssistStructure structure = contexts.get(contexts.size() - 1).getStructure(); ParsedFields fields = new ParsedFields(); String webDomain = parseWindow(structure, fields); + Log.i(TAG, "parsed domain=" + webDomain + " usernameId=" + fields.usernameId + " passwordId=" + fields.passwordId); if (fields.passwordId == null) { + Log.i(TAG, "no password field found"); callback.onSuccess(null); return; } AutofillCacheStore.Entry entry = AutofillCacheStore.findBestMatch(this, webDomain); if (entry == null) { + Log.i(TAG, "no autofill cache match"); callback.onSuccess(null); return; } + Log.i(TAG, "matched entry title=" + entry.title + " user=" + entry.username + " host=" + entry.host); RemoteViews presentation = new RemoteViews(getPackageName(), android.R.layout.simple_list_item_1); presentation.setTextViewText( @@ -64,6 +82,7 @@ public final class KeePassGOAutofillService extends AutofillService { FillResponse response = new FillResponse.Builder() .addDataset(dataset.build()) .build(); + Log.i(TAG, "returning dataset"); callback.onSuccess(response); } catch (Exception err) { Log.e(TAG, "fill request failed", err); @@ -73,6 +92,7 @@ public final class KeePassGOAutofillService extends AutofillService { @Override public void onSaveRequest(SaveRequest request, SaveCallback callback) { + Log.i(TAG, "save request"); callback.onSuccess(); }