Fix settings and add "pick and match" option
This commit is contained in:
parent
a3e10d3ca8
commit
963859b347
3 changed files with 126 additions and 70 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
|
|
Loading…
Reference in a new issue