From 28379439de6542d747fb9f2fdfb0eb8d261bd57e Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Thu, 5 Nov 2015 22:58:42 -0500 Subject: search for webview recursively (& search files instead of passworditems) --- .../zeapo/pwdstore/autofill/AutofillService.java | 94 +++++++++++++++------- 1 file changed, 67 insertions(+), 27 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java index 2b32a809..894fb62b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -1,5 +1,6 @@ package com.zeapo.pwdstore.autofill; +import android.Manifest; import android.accessibilityservice.AccessibilityService; import android.app.PendingIntent; import android.content.ClipData; @@ -10,11 +11,10 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; -import android.provider.Settings; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.WindowManager; @@ -38,6 +38,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; +import java.util.ArrayDeque; import java.util.ArrayList; public class AutofillService extends AccessibilityService { @@ -69,17 +70,32 @@ public class AutofillService extends AccessibilityService { // TODO change search/search results (just use first result) @Override public void onAccessibilityEvent(AccessibilityEvent event) { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.SYSTEM_ALERT_WINDOW) + == PackageManager.PERMISSION_DENIED) { + // may need a way to request the permission but only activities can, so by notification? + return; + } + // if returning to the source app from a successful AutofillActivity if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && event.getPackageName().equals(packageName) && resultData != null) { bindDecryptAndVerify(); } + // need to see if window has a WebView every time, so future events are sent? + AccessibilityNodeInfo source = event.getSource(); + if (source == null) { + return; + } + searchWebView(source); + // nothing to do if not password field focus, android version, or field is keychain app if (!event.isPassword() + || event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2 || event.getPackageName().equals("org.sufficientlysecure.keychain")) { dismissDialog(event); + source.recycle(); // is this necessary??? return; } @@ -87,6 +103,7 @@ public class AutofillService extends AccessibilityService { // the current dialog must belong to this window; ignore clicks on this password field // why handle clicks at all then? some cases e.g. Paypal there is no initial focus event if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) { + source.recycle(); return; } // if it was not a click, the field was refocused or another field was focused; recreate @@ -96,20 +113,13 @@ public class AutofillService extends AccessibilityService { // ignore the ACTION_FOCUS from decryptAndVerify otherwise dialog will appear after Fill if (ignoreActionFocus) { ignoreActionFocus = false; + source.recycle(); return; } - // need to request permission before attempting to draw dialog - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M - && !Settings.canDrawOverlays(this)) { - Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, - Uri.parse("package:" + getApplicationContext().getPackageName())); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - return; - } - - info = event.getSource(); + // we are now going to attempt to fill, save AccessibilityNodeInfo for later in decryptAndVerify + // (there should be a proper way to do this, although this seems to work 90% of the time) + info = source; // save the dialog's corresponding window so we can use getWindows() in dismissDialog if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { @@ -126,7 +136,7 @@ public class AutofillService extends AccessibilityService { } final String appName = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "").toString(); - getMatchingPassword(appName, info.getPackageName().toString()); + setMatchingPasswords(appName, info.getPackageName().toString()); if (items.isEmpty()) { return; } @@ -134,6 +144,24 @@ public class AutofillService extends AccessibilityService { showDialog(appName); } + private boolean searchWebView(AccessibilityNodeInfo source) { + for (int i = 0; i < source.getChildCount(); i++) { + AccessibilityNodeInfo u = source.getChild(i); + if (u == null) { + continue; + } + // this is not likely to always work + if (u.getContentDescription() != null && u.getContentDescription().equals("Web View")) { + return true; + } + if (searchWebView(u)) { + return true; + } + u.recycle(); + } + return false; + } + // dismiss the dialog if the window has changed private void dismissDialog(AccessibilityEvent event) { // the default keyboard showing/hiding is a window state changed event @@ -152,7 +180,7 @@ public class AutofillService extends AccessibilityService { } } - private void getMatchingPassword(String appName, String packageName) { + private void setMatchingPasswords(String appName, String packageName) { // if autofill_default is checked and prefs.getString DNE, 'Automatically match with password'/"first" otherwise "never" String defValue = settings.getBoolean("autofill_default", true) ? "/first" : "/never"; SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE); @@ -162,7 +190,10 @@ public class AutofillService extends AccessibilityService { if (!PasswordRepository.isInitialized()) { PasswordRepository.initialize(this); } - items = recursiveFilter(appName, null); + items = new ArrayList<>(); + for (File file : searchPasswords(PasswordRepository.getRepositoryDirectory(this), appName)) { + items.add(PasswordItem.newPassword(file.getName(), file, PasswordRepository.getRepositoryDirectory(this))); + } break; case "/never": items.clear(); @@ -178,17 +209,24 @@ public class AutofillService extends AccessibilityService { } } - private ArrayList recursiveFilter(String filter, File dir) { - ArrayList items = new ArrayList<>(); - ArrayList passwordItems = dir == null ? - PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(this)) : - PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(this)); - for (PasswordItem item : passwordItems) { - if (item.getType() == PasswordItem.TYPE_CATEGORY) { - items.addAll(recursiveFilter(filter, item.getFile())); - } - if (item.toString().toLowerCase().contains(filter.toLowerCase())) { - items.add(item); + private ArrayList searchPasswords(File path, String appName) { + ArrayList passList + = PasswordRepository.getFilesList(path); + + if (passList.size() == 0) return new ArrayList<>(); + + ArrayList items = new ArrayList<>(); + + for (File file : passList) { + if (file.isFile()) { + if (file.toString().toLowerCase().contains(appName.toLowerCase())) { + items.add(file); + } + } else { + // ignore .git directory + if (file.getName().equals(".git")) + continue; + items.addAll(searchPasswords(file, appName)); } } return items; @@ -281,6 +319,7 @@ public class AutofillService extends AccessibilityService { // if the user focused on something else, take focus back // but this will open another dialog...hack to ignore this + // & need to ensure performAction correct (i.e. what is info now?) ignoreActionFocus = info.performAction(AccessibilityNodeInfo.ACTION_FOCUS); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Bundle args = new Bundle(); @@ -302,6 +341,7 @@ public class AutofillService extends AccessibilityService { } } } + info.recycle(); } catch (UnsupportedEncodingException e) { Log.e(Constants.TAG, "UnsupportedEncodingException", e); } -- cgit v1.2.3 From 4a6901b30928291ed0a09359a3506dce68633aeb Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Thu, 5 Nov 2015 22:59:17 -0500 Subject: Separate service & main process since service should be always running by itself --- app/src/main/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0789fd33..bc15eb08 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,7 +46,8 @@ + android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" + android:process=":background"> -- cgit v1.2.3 From f2a4b944d8250a06430a91f4e2268c6c8956646a Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Wed, 4 Nov 2015 21:46:43 -0500 Subject: Make chrome/webview send password field focus events --- .../zeapo/pwdstore/autofill/AutofillService.java | 66 ++++++++++------------ 1 file changed, 30 insertions(+), 36 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java index 894fb62b..beed28ec 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -136,7 +136,7 @@ public class AutofillService extends AccessibilityService { } final String appName = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "").toString(); - setMatchingPasswords(appName, info.getPackageName().toString()); + getMatchingPassword(appName, info.getPackageName().toString()); if (items.isEmpty()) { return; } @@ -144,22 +144,26 @@ public class AutofillService extends AccessibilityService { showDialog(appName); } - private boolean searchWebView(AccessibilityNodeInfo source) { - for (int i = 0; i < source.getChildCount(); i++) { - AccessibilityNodeInfo u = source.getChild(i); - if (u == null) { - continue; - } - // this is not likely to always work + private void searchWebView(AccessibilityNodeInfo source) { + ArrayDeque q = new ArrayDeque<>(); + q.add(source); + while (!q.isEmpty()) { + AccessibilityNodeInfo u = q.remove(); if (u.getContentDescription() != null && u.getContentDescription().equals("Web View")) { - return true; + if (!u.equals(source)) { + u.recycle(); + } + return; } - if (searchWebView(u)) { - return true; + for (int i = 0; i < u.getChildCount(); i++) { + if (u.getChild(i) != null) { + q.add(u.getChild(i)); + } + } + if (!u.equals(source)) { + u.recycle(); } - u.recycle(); } - return false; } // dismiss the dialog if the window has changed @@ -180,7 +184,7 @@ public class AutofillService extends AccessibilityService { } } - private void setMatchingPasswords(String appName, String packageName) { + private void getMatchingPassword(String appName, String packageName) { // if autofill_default is checked and prefs.getString DNE, 'Automatically match with password'/"first" otherwise "never" String defValue = settings.getBoolean("autofill_default", true) ? "/first" : "/never"; SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE); @@ -190,10 +194,7 @@ public class AutofillService extends AccessibilityService { if (!PasswordRepository.isInitialized()) { PasswordRepository.initialize(this); } - items = new ArrayList<>(); - for (File file : searchPasswords(PasswordRepository.getRepositoryDirectory(this), appName)) { - items.add(PasswordItem.newPassword(file.getName(), file, PasswordRepository.getRepositoryDirectory(this))); - } + items = recursiveFilter(appName, null); break; case "/never": items.clear(); @@ -209,24 +210,17 @@ public class AutofillService extends AccessibilityService { } } - private ArrayList searchPasswords(File path, String appName) { - ArrayList passList - = PasswordRepository.getFilesList(path); - - if (passList.size() == 0) return new ArrayList<>(); - - ArrayList items = new ArrayList<>(); - - for (File file : passList) { - if (file.isFile()) { - if (file.toString().toLowerCase().contains(appName.toLowerCase())) { - items.add(file); - } - } else { - // ignore .git directory - if (file.getName().equals(".git")) - continue; - items.addAll(searchPasswords(file, appName)); + private ArrayList recursiveFilter(String filter, File dir) { + ArrayList items = new ArrayList<>(); + ArrayList passwordItems = dir == null ? + PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(this)) : + PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(this)); + for (PasswordItem item : passwordItems) { + if (item.getType() == PasswordItem.TYPE_CATEGORY) { + items.addAll(recursiveFilter(filter, item.getFile())); + } + if (item.toString().toLowerCase().contains(filter.toLowerCase())) { + items.add(item); } } return items; -- cgit v1.2.3 From 28eae3356739984f2c5057fa15b10e64917deb62 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Thu, 5 Nov 2015 23:44:46 -0500 Subject: Use File instead of PasswordItem for using file to fill --- .../zeapo/pwdstore/autofill/AutofillService.java | 126 ++++++++++----------- app/src/main/res/xml/autofill_config.xml | 1 - 2 files changed, 62 insertions(+), 65 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java index beed28ec..05e10b90 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -24,7 +24,6 @@ import android.view.accessibility.AccessibilityWindowInfo; import android.widget.Toast; import com.zeapo.pwdstore.R; -import com.zeapo.pwdstore.utils.PasswordItem; import com.zeapo.pwdstore.utils.PasswordRepository; import org.apache.commons.io.FileUtils; @@ -38,14 +37,13 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; -import java.util.ArrayDeque; import java.util.ArrayList; public class AutofillService extends AccessibilityService { private OpenPgpServiceConnection serviceConnection; private SharedPreferences settings; private AccessibilityNodeInfo info; // the original source of the event (the edittext field) - private ArrayList items; // password choices + private ArrayList items; // password choices private AlertDialog dialog; private AccessibilityWindowInfo window; private static Intent resultData = null; // need the intent which contains results from user interaction @@ -136,7 +134,7 @@ public class AutofillService extends AccessibilityService { } final String appName = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "").toString(); - getMatchingPassword(appName, info.getPackageName().toString()); + setMatchingPasswords(appName, info.getPackageName().toString()); if (items.isEmpty()) { return; } @@ -144,26 +142,22 @@ public class AutofillService extends AccessibilityService { showDialog(appName); } - private void searchWebView(AccessibilityNodeInfo source) { - ArrayDeque q = new ArrayDeque<>(); - q.add(source); - while (!q.isEmpty()) { - AccessibilityNodeInfo u = q.remove(); - if (u.getContentDescription() != null && u.getContentDescription().equals("Web View")) { - if (!u.equals(source)) { - u.recycle(); - } - return; + private boolean searchWebView(AccessibilityNodeInfo source) { + for (int i = 0; i < source.getChildCount(); i++) { + AccessibilityNodeInfo u = source.getChild(i); + if (u == null) { + continue; } - for (int i = 0; i < u.getChildCount(); i++) { - if (u.getChild(i) != null) { - q.add(u.getChild(i)); - } + // this is not likely to always work + if (u.getContentDescription() != null && u.getContentDescription().equals("Web View")) { + return true; } - if (!u.equals(source)) { - u.recycle(); + if (searchWebView(u)) { + return true; } + u.recycle(); } + return false; } // dismiss the dialog if the window has changed @@ -184,7 +178,7 @@ public class AutofillService extends AccessibilityService { } } - private void getMatchingPassword(String appName, String packageName) { + private void setMatchingPasswords(String appName, String packageName) { // if autofill_default is checked and prefs.getString DNE, 'Automatically match with password'/"first" otherwise "never" String defValue = settings.getBoolean("autofill_default", true) ? "/first" : "/never"; SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE); @@ -194,7 +188,7 @@ public class AutofillService extends AccessibilityService { if (!PasswordRepository.isInitialized()) { PasswordRepository.initialize(this); } - items = recursiveFilter(appName, null); + items = searchPasswords(PasswordRepository.getRepositoryDirectory(this), appName); break; case "/never": items.clear(); @@ -204,57 +198,61 @@ public class AutofillService extends AccessibilityService { PasswordRepository.initialize(this); } String path = PasswordRepository.getWorkTree() + "/" + preference + ".gpg"; - File file = new File(path); items = new ArrayList<>(); - items.add(PasswordItem.newPassword(file.getName(), file, PasswordRepository.getRepositoryDirectory(this))); + items.add(new File(path)); } } - private ArrayList recursiveFilter(String filter, File dir) { - ArrayList items = new ArrayList<>(); - ArrayList passwordItems = dir == null ? - PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(this)) : - PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(this)); - for (PasswordItem item : passwordItems) { - if (item.getType() == PasswordItem.TYPE_CATEGORY) { - items.addAll(recursiveFilter(filter, item.getFile())); - } - if (item.toString().toLowerCase().contains(filter.toLowerCase())) { - items.add(item); + private ArrayList searchPasswords(File path, String appName) { + ArrayList passList + = PasswordRepository.getFilesList(path); + + if (passList.size() == 0) return new ArrayList<>(); + + ArrayList items = new ArrayList<>(); + + for (File file : passList) { + if (file.isFile()) { + if (file.toString().toLowerCase().contains(appName.toLowerCase())) { + items.add(file); + } + } else { + // ignore .git directory + if (file.getName().equals(".git")) + continue; + items.addAll(searchPasswords(file, appName)); } } return items; } private void showDialog(final String appName) { - if (dialog == null) { - AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog); - builder.setNegativeButton(R.string.dialog_cancel, null); - builder.setPositiveButton(R.string.autofill_fill, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - bindDecryptAndVerify(); - } - }); - builder.setNeutralButton("Settings", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { //TODO make icon? gear? - // the user will have to return to the app themselves. - Intent intent = new Intent(AutofillService.this, AutofillPreferenceActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); - intent.putExtra("packageName", info.getPackageName()); - intent.putExtra("appName", appName); - startActivity(intent); - } - }); - dialog = builder.create(); - dialog.setIcon(R.drawable.ic_launcher); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); - dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); - } - dialog.setTitle(items.get(0).toString()); + AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog); + builder.setNegativeButton(R.string.dialog_cancel, null); + builder.setPositiveButton(R.string.autofill_fill, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + bindDecryptAndVerify(); + } + }); + builder.setNeutralButton("Settings", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { //TODO make icon? gear? + // the user will have to return to the app themselves. + Intent intent = new Intent(AutofillService.this, AutofillPreferenceActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra("packageName", info.getPackageName()); + intent.putExtra("appName", appName); + startActivity(intent); + } + }); + dialog = builder.create(); + dialog.setIcon(R.drawable.ic_launcher); + dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); + dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); + dialog.setTitle(items.get(0).getName().replace(".gpg", "")); dialog.show(); } @@ -298,7 +296,7 @@ public class AutofillService extends AccessibilityService { } InputStream is = null; try { - is = FileUtils.openInputStream(items.get(0).getFile()); + is = FileUtils.openInputStream(items.get(0)); } catch (IOException e) { e.printStackTrace(); } diff --git a/app/src/main/res/xml/autofill_config.xml b/app/src/main/res/xml/autofill_config.xml index 62f82a5e..7dce1d83 100644 --- a/app/src/main/res/xml/autofill_config.xml +++ b/app/src/main/res/xml/autofill_config.xml @@ -5,5 +5,4 @@ android:accessibilityFeedbackType="feedbackGeneric" android:notificationTimeout="100" android:canRetrieveWindowContent="true" - android:canRequestEnhancedWebAccessibility="true" /> \ No newline at end of file -- cgit v1.2.3 From d68c06a4a6a06a2b27889d9907b2188607d98572 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Fri, 6 Nov 2015 00:01:03 -0500 Subject: Let dialog be null --- app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/src') diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java index 05e10b90..e694dfa4 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -106,6 +106,7 @@ public class AutofillService extends AccessibilityService { } // if it was not a click, the field was refocused or another field was focused; recreate dialog.dismiss(); + dialog = null; } // ignore the ACTION_FOCUS from decryptAndVerify otherwise dialog will appear after Fill @@ -175,6 +176,7 @@ public class AutofillService extends AccessibilityService { } if (dismiss && dialog != null && dialog.isShowing()) { dialog.dismiss(); + dialog = null; } } -- cgit v1.2.3 From 72e32c95f6fffca62ebb4312e2730042e552ed92 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Sat, 7 Nov 2015 10:50:42 -0500 Subject: undo process change. (not a simple change, sharedpreferences don't work) --- app/src/main/AndroidManifest.xml | 3 +-- .../main/java/com/zeapo/pwdstore/autofill/AutofillService.java | 8 +++++++- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'app/src') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bc15eb08..67502faa 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,8 +46,7 @@ + android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java index e694dfa4..365c48df 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -230,7 +230,13 @@ public class AutofillService extends AccessibilityService { private void showDialog(final String appName) { AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog); - builder.setNegativeButton(R.string.dialog_cancel, null); + builder.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface d, int which) { + dialog.dismiss(); + dialog = null; + } + }); builder.setPositiveButton(R.string.autofill_fill, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { -- cgit v1.2.3 From 8fa173e7d206c2ddff19890d96dbdb2edf6c9139 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Sat, 7 Nov 2015 16:25:22 -0500 Subject: set and pick from multiple passwords per app --- .../zeapo/pwdstore/autofill/AutofillFragment.java | 51 ++++++++-- .../pwdstore/autofill/AutofillRecyclerAdapter.java | 2 +- .../zeapo/pwdstore/autofill/AutofillService.java | 51 +++++++--- app/src/main/res/layout/fragment_autofill.xml | 113 +++++++++++---------- 4 files changed, 142 insertions(+), 75 deletions(-) (limited to 'app/src') diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java index 5d342f4a..e4986354 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java @@ -4,6 +4,7 @@ import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; +import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; @@ -12,15 +13,20 @@ import android.content.SharedPreferences; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; -import android.widget.EditText; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ListView; import android.widget.RadioButton; import android.widget.RadioGroup; +import android.widget.TextView; import com.zeapo.pwdstore.PasswordStore; import com.zeapo.pwdstore.R; public class AutofillFragment extends DialogFragment { private static final int MATCH_WITH = 777; + ArrayAdapter adapter; public AutofillFragment() { } @@ -47,6 +53,27 @@ public class AutofillFragment extends DialogFragment { e.printStackTrace(); } + // set up the listview now for items added by button/from preferences + adapter = new ArrayAdapter(getActivity().getApplicationContext() + , android.R.layout.simple_list_item_1, android.R.id.text1) { + // set text color to black because default is white... + @Override + public View getView(int position, View convertView, ViewGroup parent) { + TextView textView = (TextView) super.getView(position, convertView, parent); + textView.setTextColor(ContextCompat.getColor(getContext(), R.color.grey_black_1000)); + return textView; + } + }; + ((ListView) view.findViewById(R.id.matched)).setAdapter(adapter); + // delete items by clicking them + ((ListView) view.findViewById(R.id.matched)).setOnItemClickListener( + new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + adapter.remove(adapter.getItem(position)); + } + }); + SharedPreferences prefs = getActivity().getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE); String preference = prefs.getString(packageName, ""); @@ -62,9 +89,11 @@ public class AutofillFragment extends DialogFragment { break; default: ((RadioButton) view.findViewById(R.id.match)).toggle(); - ((EditText) view.findViewById(R.id.matched)).setText(preference); + // trim to remove the last blank element + adapter.addAll(preference.trim().split("\n")); } + // add items with the + button View.OnClickListener matchPassword = new View.OnClickListener() { @Override public void onClick(View v) { @@ -74,8 +103,7 @@ public class AutofillFragment extends DialogFragment { startActivityForResult(intent, MATCH_WITH); } }; - view.findViewById(R.id.match).setOnClickListener(matchPassword); - view.findViewById(R.id.matched).setOnClickListener(matchPassword); + view.findViewById(R.id.matchButton).setOnClickListener(matchPassword); final SharedPreferences.Editor editor = prefs.edit(); builder.setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() { @@ -93,9 +121,14 @@ public class AutofillFragment extends DialogFragment { editor.putString(packageName, "/never"); break; default: - EditText matched = (EditText) view.findViewById(R.id.matched); - String path = matched.getText().toString(); - editor.putString(packageName, path); + StringBuilder paths = new StringBuilder(); + for (int i = 0; i < adapter.getCount(); i++) { + paths.append(adapter.getItem(i)); + if (i != adapter.getCount()) { + paths.append("\n"); + } + } + editor.putString(packageName, paths.toString()); } editor.apply(); @@ -113,9 +146,7 @@ public class AutofillFragment extends DialogFragment { @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == Activity.RESULT_OK) { - ((EditText) getDialog().findViewById(R.id.matched)).setText(data.getStringExtra("path")); - } else { - ((RadioButton) getDialog().findViewById(R.id.use_default)).toggle(); + adapter.add(data.getStringExtra("path")); } } } diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java index 15f114c6..54be40dc 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java @@ -110,7 +110,7 @@ public class AutofillRecyclerAdapter extends RecyclerView.Adapter items; // password choices + private int lastWhichItem; private AlertDialog dialog; private AccessibilityWindowInfo window; private static Intent resultData = null; // need the intent which contains results from user interaction @@ -199,9 +200,14 @@ public class AutofillService extends AccessibilityService { if (!PasswordRepository.isInitialized()) { PasswordRepository.initialize(this); } - String path = PasswordRepository.getWorkTree() + "/" + preference + ".gpg"; + String preferred[] = preference.split("\n"); items = new ArrayList<>(); - items.add(new File(path)); + for (String prefer : preferred) { + String path = PasswordRepository.getWorkTree() + "/" + prefer + ".gpg"; + if (new File(path).exists()) { + items.add(new File(path)); + } + } } } @@ -237,12 +243,12 @@ public class AutofillService extends AccessibilityService { dialog = null; } }); - builder.setPositiveButton(R.string.autofill_fill, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - bindDecryptAndVerify(); - } - }); +// builder.setPositiveButton(R.string.autofill_fill, new DialogInterface.OnClickListener() { +// @Override +// public void onClick(DialogInterface dialog, int which) { +// bindDecryptAndVerify(); +// } +// }); builder.setNeutralButton("Settings", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //TODO make icon? gear? @@ -254,13 +260,34 @@ public class AutofillService extends AccessibilityService { startActivity(intent); } }); + CharSequence itemNames[] = new CharSequence[items.size()]; + for (int i = 0; i < items.size(); i++) { + itemNames[i] = items.get(i).getName().replace(".gpg", ""); + } + + builder.setItems(itemNames, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == items.size()) { + // the user will have to return to the app themselves. + Intent intent = new Intent(AutofillService.this, AutofillPreferenceActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + intent.putExtra("packageName", info.getPackageName()); + intent.putExtra("appName", appName); + startActivity(intent); + } else { + lastWhichItem = which; + bindDecryptAndVerify(); + } + } + }); dialog = builder.create(); - dialog.setIcon(R.drawable.ic_launcher); dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); - dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); - dialog.setTitle(items.get(0).getName().replace(".gpg", "")); + // TODO size dialog + dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT + , WindowManager.LayoutParams.WRAP_CONTENT); dialog.show(); } @@ -304,7 +331,7 @@ public class AutofillService extends AccessibilityService { } InputStream is = null; try { - is = FileUtils.openInputStream(items.get(0)); + is = FileUtils.openInputStream(items.get(lastWhichItem)); } catch (IOException e) { e.printStackTrace(); } diff --git a/app/src/main/res/layout/fragment_autofill.xml b/app/src/main/res/layout/fragment_autofill.xml index 7bdf3d84..007147da 100644 --- a/app/src/main/res/layout/fragment_autofill.xml +++ b/app/src/main/res/layout/fragment_autofill.xml @@ -1,57 +1,66 @@ - - - - - - - - - - - + + + android:layout_height="wrap_content"> + + + + + + + + + +