Fix settings and add "pick and match" option

This commit is contained in:
Matthew Wong 2015-12-31 18:26:43 -05:00
parent a3e10d3ca8
commit 963859b347
3 changed files with 126 additions and 70 deletions

View file

@ -1,18 +1,27 @@
package com.zeapo.pwdstore.autofill; package com.zeapo.pwdstore.autofill;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentSender; import android.content.IntentSender;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.util.Log; import android.util.Log;
import com.zeapo.pwdstore.PasswordStore; import com.zeapo.pwdstore.PasswordStore;
import org.eclipse.jgit.util.StringUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
// blank activity started by service for calling startIntentSenderForResult // blank activity started by service for calling startIntentSenderForResult
public class AutofillActivity extends AppCompatActivity { public class AutofillActivity extends AppCompatActivity {
public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913; public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913;
public static final int REQUEST_CODE_MATCH_WITH = 777; public static final int REQUEST_CODE_PICK = 777;
public static final int REQUEST_CODE_PICK_MATCH_WITH = 778;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -30,10 +39,14 @@ public class AutofillActivity extends AppCompatActivity {
} catch (IntentSender.SendIntentException e) { } catch (IntentSender.SendIntentException e) {
Log.e(AutofillService.Constants.TAG, "SendIntentException", e); Log.e(AutofillService.Constants.TAG, "SendIntentException", e);
} }
} else if (extras != null && extras.containsKey("matchWith")) { } else if (extras != null && extras.containsKey("pick")) {
Intent intent = new Intent(getApplicationContext(), PasswordStore.class); Intent intent = new Intent(getApplicationContext(), PasswordStore.class);
intent.putExtra("matchWith", true); intent.putExtra("matchWith", true);
startActivityForResult(intent, REQUEST_CODE_MATCH_WITH); startActivityForResult(intent, REQUEST_CODE_PICK);
} else if (extras != null && extras.containsKey("pickMatchWith")) {
Intent intent = new Intent(getApplicationContext(), PasswordStore.class);
intent.putExtra("matchWith", true);
startActivityForResult(intent, REQUEST_CODE_PICK_MATCH_WITH);
} }
} }
@ -46,10 +59,43 @@ public class AutofillActivity extends AppCompatActivity {
AutofillService.getInstance().setResultData(data); // report the result to service AutofillService.getInstance().setResultData(data); // report the result to service
} }
break; break;
case REQUEST_CODE_MATCH_WITH: case REQUEST_CODE_PICK:
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
AutofillService.getInstance().setPickedPassword(data.getStringExtra("path")); AutofillService.getInstance().setPickedPassword(data.getStringExtra("path"));
} }
break;
case REQUEST_CODE_PICK_MATCH_WITH:
if (resultCode == RESULT_OK) {
Bundle extras = getIntent().getExtras();
String packageName = extras.getString("packageName");
boolean isWeb = extras.getBoolean("isWeb");
String path = data.getStringExtra("path");
AutofillService.getInstance().setPickedPassword(data.getStringExtra("path"));
SharedPreferences prefs;
if (!isWeb) {
prefs = getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
} else {
prefs = getApplicationContext().getSharedPreferences("autofill_web", Context.MODE_PRIVATE);
}
SharedPreferences.Editor editor = prefs.edit();
String preference = prefs.getString(packageName, "");
switch (preference) {
case "":
case "/first":
case "/never":
editor.putString(packageName, path);
break;
default:
List<String> matches = new ArrayList<>(Arrays.asList(preference.trim().split("\n")));
matches.add(path);
String paths = StringUtils.join(matches, "\n");
editor.putString(packageName, paths);
}
editor.apply();
}
break;
} }
} }
} }

View file

@ -39,7 +39,7 @@ public class AutofillFragment extends DialogFragment {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
// this fragment is only created from the settings page (AutofillPreferenceActivity) // this fragment is only created from the settings page (AutofillPreferenceActivity)
// need to interact with the recyclerAdapter which is a member of activity // need to interact with the recyclerAdapter which is a member of activity
AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) getActivity(); final AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) getActivity();
LayoutInflater inflater = callingActivity.getLayoutInflater(); LayoutInflater inflater = callingActivity.getLayoutInflater();
final View view = inflater.inflate(R.layout.fragment_autofill, null); final View view = inflater.inflate(R.layout.fragment_autofill, null);
@ -137,10 +137,10 @@ public class AutofillFragment extends DialogFragment {
builder.setNeutralButton(R.string.autofill_apps_delete, new DialogInterface.OnClickListener() { builder.setNeutralButton(R.string.autofill_apps_delete, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
if (((AutofillPreferenceActivity) getActivity()).recyclerAdapter != null if (callingActivity.recyclerAdapter != null
&& packageName != null && !packageName.equals("")) { && packageName != null && !packageName.equals("")) {
editor.remove(packageName); editor.remove(packageName);
((AutofillPreferenceActivity) getActivity()).recyclerAdapter.removeWebsite(packageName); callingActivity.recyclerAdapter.removeWebsite(packageName);
editor.apply(); editor.apply();
} }
} }
@ -181,8 +181,7 @@ public class AutofillFragment extends DialogFragment {
return; return;
} }
String oldPackageName = getArguments().getString("packageName", ""); String oldPackageName = getArguments().getString("packageName", "");
int position = callingActivity.recyclerAdapter.getPosition(packageName); if (!oldPackageName.equals(packageName) && prefs.getAll().containsKey(packageName)) {
if (!oldPackageName.equals(packageName) && position != -1) {
webURL.setError("URL already exists"); webURL.setError("URL already exists");
return; return;
} }

View file

@ -110,11 +110,12 @@ public class AutofillService extends AccessibilityService {
webViewTitle = searchWebView(getRootInActiveWindow()); webViewTitle = searchWebView(getRootInActiveWindow());
webViewURL = null; webViewURL = null;
if (webViewTitle != null) { if (webViewTitle != null && getRootInActiveWindow() != null) {
List<AccessibilityNodeInfo> nodes = getRootInActiveWindow() List<AccessibilityNodeInfo> nodes = getRootInActiveWindow()
.findAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar"); .findAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar");
if (nodes.size() == 0) { if (nodes.isEmpty()) {
nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.android.browser:id/url"); nodes = getRootInActiveWindow()
.findAccessibilityNodeInfosByViewId("com.android.browser:id/url");
} }
for (AccessibilityNodeInfo node : nodes) for (AccessibilityNodeInfo node : nodes)
if (node.getText() != null) { if (node.getText() != null) {
@ -178,7 +179,11 @@ public class AutofillService extends AccessibilityService {
String packageName; String packageName;
String appName; String appName;
if (webViewTitle == null) { boolean isWeb;
// Match with the app if a webview was not found or one was found but
// there's no title or url to go by
if (webViewTitle == null || (webViewTitle.equals("") && webViewURL == null)) {
packageName = info.getPackageName().toString(); packageName = info.getPackageName().toString();
// get the app name and find a corresponding password // get the app name and find a corresponding password
@ -191,11 +196,13 @@ public class AutofillService extends AccessibilityService {
} }
appName = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "").toString(); appName = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "").toString();
setMatchingPasswords(appName, info.getPackageName().toString()); isWeb = false;
} else {
packageName = setMatchingPasswordsWeb(webViewTitle, webViewURL);
setMatchingPasswords(appName, packageName, false);
} else {
packageName = setMatchingPasswords(webViewTitle, webViewURL, true);
appName = packageName; appName = packageName;
isWeb = true;
} }
// if autofill_always checked, show dialog even if no matches (automatic // if autofill_always checked, show dialog even if no matches (automatic
@ -203,7 +210,7 @@ public class AutofillService extends AccessibilityService {
if (items.isEmpty() && !settings.getBoolean("autofill_always", false)) { if (items.isEmpty() && !settings.getBoolean("autofill_always", false)) {
return; return;
} }
showDialog(packageName, appName); showDialog(packageName, appName, isWeb);
} }
private String searchWebView(AccessibilityNodeInfo source) { private String searchWebView(AccessibilityNodeInfo source) {
@ -248,11 +255,35 @@ public class AutofillService extends AccessibilityService {
} }
} }
private void setMatchingPasswords(String appName, String packageName) { private String setMatchingPasswords(String appName, String packageName, boolean isWeb) {
// Return the URL needed to open the corresponding Settings.
String settingsURL = 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;
String preference = prefs.getString(packageName, defValue); String preference;
if (!isWeb) {
prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE);
preference = prefs.getString(packageName, defValue);
} else {
prefs = getSharedPreferences("autofill_web", Context.MODE_PRIVATE);
preference = defValue;
}
// for websites unlike apps there can be blank preference of "" which
// means use default, so ignore it.
if (isWeb) {
Map<String, ?> prefsMap = prefs.getAll();
for (String key : prefsMap.keySet()) {
if ((webViewURL.toLowerCase().contains(key.toLowerCase()) || key.toLowerCase().contains(webViewURL.toLowerCase()))
&& !prefs.getString(key, null).equals("")) {
preference = prefs.getString(key, null);
settingsURL = key;
}
}
}
switch (preference) { switch (preference) {
case "/first": case "/first":
if (!PasswordRepository.isInitialized()) { if (!PasswordRepository.isInitialized()) {
@ -262,34 +293,12 @@ public class AutofillService extends AccessibilityService {
break; break;
case "/never": case "/never":
items = new ArrayList<>(); items = new ArrayList<>();
return; break;
default: default:
getPreferredPasswords(preference); getPreferredPasswords(preference);
} }
}
// Return the the matched preference's key, which isn't necessarily equal to return settingsURL;
// the URL, if a preference is matched so it can be accessed with Settings.
private String setMatchingPasswordsWeb(String webViewTitle, String webViewURL) {
SharedPreferences prefs = getSharedPreferences("autofill_web", Context.MODE_PRIVATE);
Map<String, ?> prefsMap = prefs.getAll();
for (String key : prefsMap.keySet()) {
if (webViewURL.toLowerCase().contains(key.toLowerCase())) {
getPreferredPasswords(prefs.getString(key, ""));
return key;
}
}
// no user-defined match found, maybe auto match using title, not URL
if (settings.getBoolean("autofill_default", true)) {
if (!PasswordRepository.isInitialized()) {
PasswordRepository.initialize(this);
}
items = searchPasswords(PasswordRepository.getRepositoryDirectory(this), webViewTitle);
} else {
items = new ArrayList<>();
}
return webViewURL;
} }
// Put the newline separated list of passwords from the SharedPreferences // Put the newline separated list of passwords from the SharedPreferences
@ -331,7 +340,7 @@ public class AutofillService extends AccessibilityService {
return items; return items;
} }
private void showDialog(final String packageName, final String appName) { private void showDialog(final String packageName, final String appName, final boolean isWeb) {
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog); AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog);
builder.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() { builder.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
@Override @Override
@ -348,46 +357,48 @@ public class AutofillService extends AccessibilityService {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("packageName", packageName); intent.putExtra("packageName", packageName);
intent.putExtra("appName", appName); intent.putExtra("appName", appName);
if (webViewTitle != null) { intent.putExtra("isWeb", isWeb);
intent.putExtra("isWeb", true);
}
startActivity(intent); startActivity(intent);
} }
}); });
if (!items.isEmpty()) { CharSequence itemNames[] = new CharSequence[items.size() + 2];
CharSequence itemNames[] = new CharSequence[items.size()]; for (int i = 0; i < items.size(); i++) {
for (int i = 0; i < items.size(); i++) { itemNames[i] = items.get(i).getName().replace(".gpg", "");
itemNames[i] = items.get(i).getName().replace(".gpg", ""); }
} itemNames[items.size()] = "Pick...";
builder.setItems(itemNames, new DialogInterface.OnClickListener() { itemNames[items.size() + 1] = "Pick and match...";
@Override builder.setItems(itemNames, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { @Override
lastWhichItem = which; public void onClick(DialogInterface dialog, int which) {
lastWhichItem = which;
if (which < items.size()) {
bindDecryptAndVerify(); bindDecryptAndVerify();
} } else if (which == items.size()){
});
} else {
builder.setItems(new CharSequence[]{"Pick a password..."}, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
lastWhichItem = which; // always 0
// TODO option to remember a pick for the future when possible? or option to have this always visible?
Intent intent = new Intent(AutofillService.this, AutofillActivity.class); Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("matchWith", true); intent.putExtra("pick", true);
startActivity(intent);
} else {
lastWhichItem--;
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.putExtra("pickMatchWith", true);
intent.putExtra("packageName", packageName);
intent.putExtra("isWeb", isWeb);
startActivity(intent); startActivity(intent);
} }
}); }
} });
dialog = builder.create(); dialog = builder.create();
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
// arbitrary non-annoying size // arbitrary non-annoying size
int height = 154; int height = 154;
if (items.size() > 1) { if (itemNames.length > 1) {
height += 33; height += 46;
} }
dialog.getWindow().setLayout((int) (240 * getApplicationContext().getResources().getDisplayMetrics().density) dialog.getWindow().setLayout((int) (240 * getApplicationContext().getResources().getDisplayMetrics().density)
, (int) (height * getApplicationContext().getResources().getDisplayMetrics().density)); , (int) (height * getApplicationContext().getResources().getDisplayMetrics().density));