From a782c3f83e6f40ad24f6ffbb3fa5b0d5b4f409f5 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Thu, 31 Dec 2015 05:14:37 -0500 Subject: Add 'pick password' option that shows only when no other items are available (+travis) --- .../zeapo/pwdstore/autofill/AutofillActivity.java | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) (limited to 'app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java') diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java index 6d11936b..0f184bb7 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java @@ -7,16 +7,19 @@ import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; +import com.zeapo.pwdstore.PasswordStore; + // blank activity started by service for calling startIntentSenderForResult public class AutofillActivity extends AppCompatActivity { public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913; + public static final int REQUEST_CODE_MATCH_WITH = 777; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Bundle extras = getIntent().getExtras(); - if (extras != null) { + if (extras != null && extras.containsKey("pending_intent")) { try { PendingIntent pi = extras.getParcelable("pending_intent"); if (pi == null) { @@ -27,14 +30,26 @@ public class AutofillActivity extends AppCompatActivity { } catch (IntentSender.SendIntentException e) { Log.e(AutofillService.Constants.TAG, "SendIntentException", e); } + } else if (extras != null && extras.containsKey("matchWith")) { + Intent intent = new Intent(getApplicationContext(), PasswordStore.class); + intent.putExtra("matchWith", true); + startActivityForResult(intent, REQUEST_CODE_MATCH_WITH); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { finish(); // go back to the password field app - if (resultCode == RESULT_OK) { - AutofillService.setResultData(data); // report the result to service + switch (requestCode) { + case REQUEST_CODE_DECRYPT_AND_VERIFY: + if (resultCode == RESULT_OK) { + AutofillService.getInstance().setResultData(data); // report the result to service + } + break; + case REQUEST_CODE_MATCH_WITH: + if (resultCode == RESULT_OK) { + AutofillService.getInstance().setPickedPassword(data.getStringExtra("path")); + } } } } -- cgit v1.2.3 From 963859b3476b330445e8e0d1ded0553b0a2cc18a Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Thu, 31 Dec 2015 18:26:43 -0500 Subject: Fix settings and add "pick and match" option --- .../zeapo/pwdstore/autofill/AutofillActivity.java | 54 ++++++++- .../zeapo/pwdstore/autofill/AutofillFragment.java | 9 +- .../zeapo/pwdstore/autofill/AutofillService.java | 133 +++++++++++---------- 3 files changed, 126 insertions(+), 70 deletions(-) (limited to 'app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java') diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java index 0f184bb7..2ddcfa02 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java @@ -1,18 +1,27 @@ package com.zeapo.pwdstore.autofill; import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.content.IntentSender; +import android.content.SharedPreferences; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; 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 public class AutofillActivity extends AppCompatActivity { 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 protected void onCreate(Bundle savedInstanceState) { @@ -30,10 +39,14 @@ public class AutofillActivity extends AppCompatActivity { } catch (IntentSender.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.putExtra("matchWith", true); + 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_MATCH_WITH); + 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 } break; - case REQUEST_CODE_MATCH_WITH: + case REQUEST_CODE_PICK: if (resultCode == RESULT_OK) { 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 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; } } } 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 474c3e35..0ad28a31 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java @@ -39,7 +39,7 @@ public class AutofillFragment extends DialogFragment { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); // this fragment is only created from the settings page (AutofillPreferenceActivity) // 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(); 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() { @Override public void onClick(DialogInterface dialog, int which) { - if (((AutofillPreferenceActivity) getActivity()).recyclerAdapter != null + if (callingActivity.recyclerAdapter != null && packageName != null && !packageName.equals("")) { editor.remove(packageName); - ((AutofillPreferenceActivity) getActivity()).recyclerAdapter.removeWebsite(packageName); + callingActivity.recyclerAdapter.removeWebsite(packageName); editor.apply(); } } @@ -181,8 +181,7 @@ public class AutofillFragment extends DialogFragment { return; } String oldPackageName = getArguments().getString("packageName", ""); - int position = callingActivity.recyclerAdapter.getPosition(packageName); - if (!oldPackageName.equals(packageName) && position != -1) { + if (!oldPackageName.equals(packageName) && prefs.getAll().containsKey(packageName)) { webURL.setError("URL already exists"); return; } 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 305dfdfa..e6a49bb9 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -110,11 +110,12 @@ public class AutofillService extends AccessibilityService { webViewTitle = searchWebView(getRootInActiveWindow()); webViewURL = null; - if (webViewTitle != null) { + if (webViewTitle != null && getRootInActiveWindow() != null) { List nodes = getRootInActiveWindow() .findAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar"); - if (nodes.size() == 0) { - nodes = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("com.android.browser:id/url"); + if (nodes.isEmpty()) { + nodes = getRootInActiveWindow() + .findAccessibilityNodeInfosByViewId("com.android.browser:id/url"); } for (AccessibilityNodeInfo node : nodes) if (node.getText() != null) { @@ -178,7 +179,11 @@ public class AutofillService extends AccessibilityService { String packageName; 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(); // 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(); - setMatchingPasswords(appName, info.getPackageName().toString()); - } else { - packageName = setMatchingPasswordsWeb(webViewTitle, webViewURL); + isWeb = false; + setMatchingPasswords(appName, packageName, false); + } else { + packageName = setMatchingPasswords(webViewTitle, webViewURL, true); appName = packageName; + isWeb = true; } // 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)) { return; } - showDialog(packageName, appName); + showDialog(packageName, appName, isWeb); } 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" String defValue = settings.getBoolean("autofill_default", true) ? "/first" : "/never"; - SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE); - String preference = prefs.getString(packageName, defValue); + SharedPreferences prefs; + 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 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) { case "/first": if (!PasswordRepository.isInitialized()) { @@ -262,34 +293,12 @@ public class AutofillService extends AccessibilityService { break; case "/never": items = new ArrayList<>(); - return; + break; default: getPreferredPasswords(preference); } - } - - // Return the the matched preference's key, which isn't necessarily equal to - // 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 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; + return settingsURL; } // Put the newline separated list of passwords from the SharedPreferences @@ -331,7 +340,7 @@ public class AutofillService extends AccessibilityService { 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); builder.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() { @Override @@ -348,46 +357,48 @@ public class AutofillService extends AccessibilityService { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra("packageName", packageName); intent.putExtra("appName", appName); - if (webViewTitle != null) { - intent.putExtra("isWeb", true); - } + intent.putExtra("isWeb", isWeb); startActivity(intent); } }); - if (!items.isEmpty()) { - 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) { - lastWhichItem = which; + CharSequence itemNames[] = new CharSequence[items.size() + 2]; + for (int i = 0; i < items.size(); i++) { + itemNames[i] = items.get(i).getName().replace(".gpg", ""); + } + itemNames[items.size()] = "Pick..."; + itemNames[items.size() + 1] = "Pick and match..."; + builder.setItems(itemNames, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + lastWhichItem = which; + if (which < items.size()) { bindDecryptAndVerify(); - } - }); - } 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? + } else if (which == items.size()){ + Intent intent = new Intent(AutofillService.this, AutofillActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + 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("matchWith", true); + intent.putExtra("pickMatchWith", true); + intent.putExtra("packageName", packageName); + intent.putExtra("isWeb", isWeb); startActivity(intent); } - }); - } + } + }); + dialog = builder.create(); dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); // arbitrary non-annoying size int height = 154; - if (items.size() > 1) { - height += 33; + if (itemNames.length > 1) { + height += 46; } dialog.getWindow().setLayout((int) (240 * getApplicationContext().getResources().getDisplayMetrics().density) , (int) (height * getApplicationContext().getResources().getDisplayMetrics().density)); -- cgit v1.2.3 From 35e30a67c49055b89633b1330a0151a176a74ef2 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Thu, 31 Dec 2015 18:38:10 -0500 Subject: Comments --- .../java/com/zeapo/pwdstore/autofill/AutofillActivity.java | 2 ++ .../java/com/zeapo/pwdstore/autofill/AutofillFragment.java | 8 +++++--- .../pwdstore/autofill/AutofillPreferenceActivity.java | 3 ++- .../java/com/zeapo/pwdstore/autofill/AutofillService.java | 14 ++++++++------ 4 files changed, 17 insertions(+), 10 deletions(-) (limited to 'app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java') diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java index 2ddcfa02..d40c5123 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java @@ -66,6 +66,8 @@ public class AutofillActivity extends AppCompatActivity { break; case REQUEST_CODE_PICK_MATCH_WITH: if (resultCode == RESULT_OK) { + // need to not only decrypt the picked password, but also + // update the "match with" preference Bundle extras = getIntent().getExtras(); String packageName = extras.getString("packageName"); boolean isWeb = extras.getBoolean("isWeb"); 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 0ad28a31..583b5064 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java @@ -50,7 +50,7 @@ public class AutofillFragment extends DialogFragment { final String appName = getArguments().getString("appName"); isWeb = getArguments().getBoolean("isWeb"); - // set the dialog icon and title or webName editText + // set the dialog icon and title or webURL editText String iconPackageName; if (!isWeb) { iconPackageName = packageName; @@ -174,7 +174,7 @@ public class AutofillFragment extends DialogFragment { if (isWeb) { packageName = ((EditText) dialog.findViewById(R.id.webURL)).getText().toString(); - // handle some errors + // handle some errors and don't dismiss the dialog EditText webURL = (EditText) dialog.findViewById(R.id.webURL); if (packageName.equals("")) { webURL.setError("URL cannot be blank"); @@ -186,6 +186,8 @@ public class AutofillFragment extends DialogFragment { return; } } + + // write to preferences accordingly RadioGroup radioGroup = (RadioGroup) dialog.findViewById(R.id.autofill_radiogroup); switch (radioGroup.getCheckedRadioButtonId()) { case R.id.use_default: @@ -213,7 +215,7 @@ public class AutofillFragment extends DialogFragment { } editor.apply(); - // if recyclerAdapter has not loaded yet, there is no need to notify + // notify the recycler adapter if it is loaded if (callingActivity.recyclerAdapter != null) { int position; if (!isWeb) { diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java index 81d0ddde..79f6565b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java @@ -28,7 +28,7 @@ import java.util.Map; public class AutofillPreferenceActivity extends AppCompatActivity { - RecyclerView recyclerView; + private RecyclerView recyclerView; AutofillRecyclerAdapter recyclerAdapter; // let fragment have access private RecyclerView.LayoutManager layoutManager; @@ -51,6 +51,7 @@ public class AutofillPreferenceActivity extends AppCompatActivity { new populateTask().execute(); + // if the preference activity was started from the autofill dialog recreate = false; Bundle extras = getIntent().getExtras(); if (extras != null) { 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 e6a49bb9..ae99ba6d 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -109,6 +109,8 @@ public class AutofillService extends AccessibilityService { || event.getSource().getPackageName().equals("com.android.browser")))) { webViewTitle = searchWebView(getRootInActiveWindow()); + // non-null webViewTitle means there is a webView. But still somehow + // getRootInActiveWindow() can be null, when switching windows webViewURL = null; if (webViewTitle != null && getRootInActiveWindow() != null) { List nodes = getRootInActiveWindow() @@ -263,17 +265,15 @@ public class AutofillService extends AccessibilityService { String defValue = settings.getBoolean("autofill_default", true) ? "/first" : "/never"; SharedPreferences prefs; String preference; + + // for websites unlike apps there can be blank preference of "" which + // means use default, so ignore it. 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 prefsMap = prefs.getAll(); for (String key : prefsMap.keySet()) { if ((webViewURL.toLowerCase().contains(key.toLowerCase()) || key.toLowerCase().contains(webViewURL.toLowerCase())) @@ -362,6 +362,8 @@ public class AutofillService extends AccessibilityService { } }); + // populate the dialog items, always with pick + pick and match. Could + // make it optional (or make height a setting for the same effect) CharSequence itemNames[] = new CharSequence[items.size() + 2]; for (int i = 0; i < items.size(); i++) { itemNames[i] = items.get(i).getName().replace(".gpg", ""); @@ -380,7 +382,7 @@ public class AutofillService extends AccessibilityService { intent.putExtra("pick", true); startActivity(intent); } else { - lastWhichItem--; + lastWhichItem--; // will add one element to items, so lastWhichItem=items.size()+1 Intent intent = new Intent(AutofillService.this, AutofillActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra("pickMatchWith", true); -- cgit v1.2.3