search for webview recursively (& search files instead of passworditems)

This commit is contained in:
Matthew Wong
2015-11-05 22:58:42 -05:00
committed by Matthew Wong
parent b78465b744
commit 28379439de

View File

@@ -1,5 +1,6 @@
package com.zeapo.pwdstore.autofill; package com.zeapo.pwdstore.autofill;
import android.Manifest;
import android.accessibilityservice.AccessibilityService; import android.accessibilityservice.AccessibilityService;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.ClipData; import android.content.ClipData;
@@ -10,11 +11,10 @@ import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.provider.Settings; import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.util.Log; import android.util.Log;
import android.view.WindowManager; import android.view.WindowManager;
@@ -38,6 +38,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
public class AutofillService extends AccessibilityService { public class AutofillService extends AccessibilityService {
@@ -69,17 +70,32 @@ public class AutofillService extends AccessibilityService {
// TODO change search/search results (just use first result) // TODO change search/search results (just use first result)
@Override @Override
public void onAccessibilityEvent(AccessibilityEvent event) { 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 returning to the source app from a successful AutofillActivity
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
&& event.getPackageName().equals(packageName) && resultData != null) { && event.getPackageName().equals(packageName) && resultData != null) {
bindDecryptAndVerify(); 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 // nothing to do if not password field focus, android version, or field is keychain app
if (!event.isPassword() if (!event.isPassword()
|| event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2 || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2
|| event.getPackageName().equals("org.sufficientlysecure.keychain")) { || event.getPackageName().equals("org.sufficientlysecure.keychain")) {
dismissDialog(event); dismissDialog(event);
source.recycle(); // is this necessary???
return; return;
} }
@@ -87,6 +103,7 @@ public class AutofillService extends AccessibilityService {
// the current dialog must belong to this window; ignore clicks on this password field // 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 // why handle clicks at all then? some cases e.g. Paypal there is no initial focus event
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) { if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
source.recycle();
return; return;
} }
// if it was not a click, the field was refocused or another field was focused; recreate // 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 // ignore the ACTION_FOCUS from decryptAndVerify otherwise dialog will appear after Fill
if (ignoreActionFocus) { if (ignoreActionFocus) {
ignoreActionFocus = false; ignoreActionFocus = false;
source.recycle();
return; return;
} }
// need to request permission before attempting to draw dialog // we are now going to attempt to fill, save AccessibilityNodeInfo for later in decryptAndVerify
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M // (there should be a proper way to do this, although this seems to work 90% of the time)
&& !Settings.canDrawOverlays(this)) { info = source;
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();
// save the dialog's corresponding window so we can use getWindows() in dismissDialog // save the dialog's corresponding window so we can use getWindows() in dismissDialog
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 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(); final String appName = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "").toString();
getMatchingPassword(appName, info.getPackageName().toString()); setMatchingPasswords(appName, info.getPackageName().toString());
if (items.isEmpty()) { if (items.isEmpty()) {
return; return;
} }
@@ -134,6 +144,24 @@ public class AutofillService extends AccessibilityService {
showDialog(appName); 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 // dismiss the dialog if the window has changed
private void dismissDialog(AccessibilityEvent event) { private void dismissDialog(AccessibilityEvent event) {
// the default keyboard showing/hiding is a window state changed 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" // 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"; String defValue = settings.getBoolean("autofill_default", true) ? "/first" : "/never";
SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE); SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE);
@@ -162,7 +190,10 @@ public class AutofillService extends AccessibilityService {
if (!PasswordRepository.isInitialized()) { if (!PasswordRepository.isInitialized()) {
PasswordRepository.initialize(this); 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; break;
case "/never": case "/never":
items.clear(); items.clear();
@@ -178,17 +209,24 @@ public class AutofillService extends AccessibilityService {
} }
} }
private ArrayList<PasswordItem> recursiveFilter(String filter, File dir) { private ArrayList<File> searchPasswords(File path, String appName) {
ArrayList<PasswordItem> items = new ArrayList<>(); ArrayList<File> passList
ArrayList<PasswordItem> passwordItems = dir == null ? = PasswordRepository.getFilesList(path);
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(this)) :
PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(this)); if (passList.size() == 0) return new ArrayList<>();
for (PasswordItem item : passwordItems) {
if (item.getType() == PasswordItem.TYPE_CATEGORY) { ArrayList<File> items = new ArrayList<>();
items.addAll(recursiveFilter(filter, item.getFile()));
} for (File file : passList) {
if (item.toString().toLowerCase().contains(filter.toLowerCase())) { if (file.isFile()) {
items.add(item); 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; return items;
@@ -281,6 +319,7 @@ public class AutofillService extends AccessibilityService {
// if the user focused on something else, take focus back // if the user focused on something else, take focus back
// but this will open another dialog...hack to ignore this // 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); ignoreActionFocus = info.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Bundle args = new Bundle(); Bundle args = new Bundle();
@@ -302,6 +341,7 @@ public class AutofillService extends AccessibilityService {
} }
} }
} }
info.recycle();
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
Log.e(Constants.TAG, "UnsupportedEncodingException", e); Log.e(Constants.TAG, "UnsupportedEncodingException", e);
} }