aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Wong <wongma@protonmail.ch>2015-08-09 16:04:09 -0400
committerMatthew Wong <wongma@protonmail.ch>2015-08-14 17:36:48 -0400
commitad173d9d05c29790d6f647df641e5de28e32fd58 (patch)
tree4cbf4579d39392c2d647459eec10ff7f95466a7e
parent51a05087e5a349afa3f4e0febdc3a339179e9bbd (diff)
Autofill per-app settings dialog opens on click on suggestion or on existing app
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java49
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java82
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java53
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java15
-rw-r--r--app/src/main/res/layout/autofill_recycler_view.xml6
-rw-r--r--app/src/main/res/layout/fragment_autofill.xml52
-rw-r--r--app/src/main/res/xml/preference.xml2
7 files changed, 245 insertions, 14 deletions
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 5c42ab8d..77a44c7a 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java
@@ -1,5 +1,6 @@
package com.zeapo.pwdstore.autofill;
+import android.app.DialogFragment;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -33,7 +34,7 @@ public class AutofillActivity extends AppCompatActivity {
private boolean bound = false;
private RecyclerView recyclerView;
- private AutofillRecyclerAdapter recyclerAdapter;
+ AutofillRecyclerAdapter recyclerAdapter; // let fragment have access
private RecyclerView.LayoutManager layoutManager;
@Override
@@ -41,6 +42,7 @@ public class AutofillActivity extends AppCompatActivity {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
Bundle extras = intent.getExtras();
+
// if called by service just for startIntentSenderForResult
if (extras != null) {
try {
@@ -64,21 +66,26 @@ public class AutofillActivity extends AppCompatActivity {
// apps for which the user has custom settings should be in the recycler
final PackageManager pm = getPackageManager();
- final List<ApplicationInfo> allApps = pm.getInstalledApplications(0);
SharedPreferences prefs
= getSharedPreferences("autofill", Context.MODE_PRIVATE);
Map<String, ?> prefApps = prefs.getAll();
ArrayList<ApplicationInfo> apps = new ArrayList<>();
- for (ApplicationInfo applicationInfo : allApps) {
- if (prefApps.containsKey(applicationInfo.packageName)) {
- apps.add(applicationInfo);
+ for (String packageName : prefApps.keySet()) {
+ try {
+ apps.add(pm.getApplicationInfo(packageName, 0));
+ } catch (PackageManager.NameNotFoundException e) {
+ // remove invalid entries (from uninstalled apps?)
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.remove(packageName).apply();
}
}
- recyclerAdapter = new AutofillRecyclerAdapter(apps, pm);
+ recyclerAdapter = new AutofillRecyclerAdapter(apps, pm, this);
recyclerView.setAdapter(recyclerAdapter);
+ // show the search bar by default but don't open the keyboard
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
final SearchView searchView = (SearchView) findViewById(R.id.app_search);
+ searchView.clearFocus();
// create search suggestions of apps with icons & names
final SimpleCursorAdapter.ViewBinder viewBinder = new SimpleCursorAdapter.ViewBinder() {
@@ -97,6 +104,8 @@ public class AutofillActivity extends AppCompatActivity {
return true;
}
};
+
+ final List<ApplicationInfo> allApps = pm.getInstalledApplications(0);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
@@ -108,7 +117,9 @@ public class AutofillActivity extends AppCompatActivity {
// should be a better/faster way to do this?
MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_id", "package", "label"});
for (ApplicationInfo applicationInfo : allApps) {
- if (applicationInfo.loadLabel(pm).toString().toLowerCase().contains(newText.toLowerCase())) {
+ // exclude apps that already have settings; the search is just for adding
+ if (applicationInfo.loadLabel(pm).toString().toLowerCase().contains(newText.toLowerCase())
+ && !recyclerAdapter.contains(applicationInfo.packageName)) {
matrixCursor.addRow(new Object[]{0, applicationInfo.packageName, applicationInfo.loadLabel(pm)});
}
}
@@ -121,6 +132,30 @@ public class AutofillActivity extends AppCompatActivity {
}
});
+ searchView.setOnSuggestionListener(new SearchView.OnSuggestionListener() {
+ @Override
+ public boolean onSuggestionSelect(int position) {
+ return false;
+ }
+
+ @Override
+ public boolean onSuggestionClick(int position) {
+ Cursor cursor = searchView.getSuggestionsAdapter().getCursor();
+ String packageName = cursor.getString(1);
+ String appName = cursor.getString(2);
+
+ // similar to what happens in ViewHolder.onClick but position -1
+ DialogFragment df = new AutofillFragment();
+ Bundle args = new Bundle();
+ args.putString("packageName", packageName);
+ args.putString("appName", appName);
+ args.putInt("position", -1);
+ df.setArguments(args);
+ df.show(getFragmentManager(), "autofill_dialog");
+ return false;
+ }
+ });
+
setTitle("Autofill Apps");
}
diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java
new file mode 100644
index 00000000..e9aa86f0
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java
@@ -0,0 +1,82 @@
+package com.zeapo.pwdstore.autofill;
+
+import android.content.DialogInterface;
+import android.support.v7.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.RadioButton;
+import android.widget.RadioGroup;
+
+import com.zeapo.pwdstore.R;
+
+public class AutofillFragment extends DialogFragment {
+
+ public AutofillFragment() {
+ }
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ // this fragment is only created from the settings page (AutofillActivity)
+ // need to interact with the recyclerAdapter which is a member of activity
+ final AutofillActivity callingActivity = (AutofillActivity) getActivity();
+ LayoutInflater inflater = callingActivity.getLayoutInflater();
+ final View view = inflater.inflate(R.layout.fragment_autofill, null);
+
+ builder.setView(view);
+
+ final String packageName = getArguments().getString("packageName");
+ String appName = getArguments().getString("appName");
+
+ builder.setTitle(appName);
+ SharedPreferences prefs
+ = getActivity().getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
+ String preference = prefs.getString(packageName, "first");
+ switch (preference) {
+ case "first":
+ ((RadioButton) view.findViewById(R.id.first)).toggle();
+ break;
+ case "never":
+ ((RadioButton) view.findViewById(R.id.never)).toggle();
+ break;
+ default:
+ ((RadioButton) view.findViewById(R.id.match)).toggle();
+ ((EditText) view.findViewById(R.id.matched)).setText(preference);
+ }
+
+ final SharedPreferences.Editor editor = prefs.edit();
+ builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.autofill_radiogroup);
+ switch (radioGroup.getCheckedRadioButtonId()) {
+ case R.id.first:
+ editor.putString(packageName, "first");
+ break;
+ case R.id.never:
+ editor.putString(packageName, "never");
+ break;
+ default:
+ EditText matched = (EditText) view.findViewById(R.id.matched);
+ String path = matched.getText().toString();
+ editor.putString(packageName, path);
+ }
+ editor.apply();
+ int position = getArguments().getInt("position");
+ if (position == -1) {
+ callingActivity.recyclerAdapter.add(packageName);
+ } else {
+ callingActivity.recyclerAdapter.notifyItemChanged(position);
+ }
+ }
+ });
+ builder.setNegativeButton("Cancel", null);
+ return builder.create();
+ }
+}
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 b59223bd..eff94c51 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java
@@ -1,7 +1,11 @@
package com.zeapo.pwdstore.autofill;
+import android.app.DialogFragment;
+import android.content.Context;
+import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@@ -16,29 +20,40 @@ import java.util.ArrayList;
public class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapter.ViewHolder> {
private ArrayList<ApplicationInfo> apps;
private PackageManager pm;
+ private AutofillActivity activity;
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public View view;
public TextView name;
+ public TextView secondary;
public ImageView icon;
+ public String packageName;
public ViewHolder(View view) {
super(view);
this.view = view;
name = (TextView) view.findViewById(R.id.app_name);
+ secondary = (TextView) view.findViewById(R.id.secondary_text);
icon = (ImageView) view.findViewById(R.id.app_icon);
view.setOnClickListener(this);
}
@Override
public void onClick(View v) {
-
+ DialogFragment df = new AutofillFragment();
+ Bundle args = new Bundle();
+ args.putString("packageName", packageName);
+ args.putString("appName", name.getText().toString());
+ args.putInt("position", getAdapterPosition());
+ df.setArguments(args);
+ df.show(activity.getFragmentManager(), "autofill_dialog");
}
}
- public AutofillRecyclerAdapter(ArrayList<ApplicationInfo> apps, PackageManager pm) {
+ public AutofillRecyclerAdapter(ArrayList<ApplicationInfo> apps, PackageManager pm, AutofillActivity activity) {
this.apps = apps;
this.pm = pm;
+ this.activity = activity;
}
@Override
@@ -52,11 +67,45 @@ public class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecycl
public void onBindViewHolder(AutofillRecyclerAdapter.ViewHolder holder, int position) {
ApplicationInfo app = apps.get(position);
holder.name.setText(pm.getApplicationLabel(app));
+ SharedPreferences prefs
+ = activity.getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
+ String preference = prefs.getString(app.packageName, "first");
+ switch (preference) {
+ case "first":
+ holder.secondary.setText("Automatically match with password");
+ break;
+ case "never":
+ holder.secondary.setText("Never autofill");
+ break;
+ default:
+ holder.secondary.setText("Match with " + preference);
+ break;
+ }
holder.icon.setImageDrawable(pm.getApplicationIcon(app));
+ holder.packageName = app.packageName;
}
@Override
public int getItemCount() {
return apps.size();
}
+
+ public boolean contains(String packageName) {
+ for (ApplicationInfo app : apps) {
+ if (app.packageName.equals(packageName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void add(String packageName) {
+ try {
+ ApplicationInfo app = pm.getApplicationInfo(packageName, 0);
+ this.apps.add(app);
+ notifyItemInserted(apps.size());
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
}
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 f3108733..dbd018b6 100644
--- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java
+++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java
@@ -68,9 +68,13 @@ public class AutofillService extends AccessibilityService {
&& event.getPackageName().equals(packageName) && unlockOK) {
decryptAndVerify();
}
+
+ // nothing to do if not password field focus, android version, or field is keychain app
if (!event.isPassword()
|| Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2
|| event.getPackageName().equals("org.sufficientlysecure.keychain")) {
+ // dismiss dialog if WINDOW_STATE_CHANGED unless the keyboard caused it
+ // there may be other exceptions...
if (!(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
&& event.getPackageName().toString().contains("com.android.inputmethod"))
&& dialog != null && dialog.isShowing()) {
@@ -78,14 +82,19 @@ public class AutofillService extends AccessibilityService {
}
return;
}
+
+ // if past this point, a new dialog will be created, so dismiss the existing
if (dialog != null && dialog.isShowing()) {
dialog.dismiss();
}
+
// ignore the ACTION_FOCUS from decryptAndVerify
if (ignoreActionFocus) {
ignoreActionFocus = false;
return;
}
+
+ // get the app name and find a corresponding password
info = event.getSource();
PackageManager packageManager = getPackageManager();
ApplicationInfo applicationInfo;
@@ -109,7 +118,7 @@ public class AutofillService extends AccessibilityService {
decryptAndVerify();
}
});
- builder.setNeutralButton("Match", new DialogInterface.OnClickListener() {
+ builder.setNeutralButton("Settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
@@ -121,7 +130,7 @@ public class AutofillService extends AccessibilityService {
dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
}
- dialog.setTitle(items.get(0).getName());
+ dialog.setTitle(items.get(0).toString());
dialog.show();
dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
}
@@ -202,6 +211,7 @@ public class AutofillService extends AccessibilityService {
case OpenPgpApi.RESULT_CODE_SUCCESS: {
try {
String[] passContent = os.toString("UTF-8").split("\n");
+
// if the user focused on something else, take focus back
// but this will open another dialog...hack to ignore this
ignoreActionFocus = info.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
@@ -215,6 +225,7 @@ public class AutofillService extends AccessibilityService {
ClipData clip = ClipData.newPlainText("autofill_pm", passContent[0]);
clipboard.setPrimaryClip(clip);
info.performAction(AccessibilityNodeInfo.ACTION_PASTE);
+
clip = ClipData.newPlainText("autofill_pm", "MyPasswordIsDaBest!");
clipboard.setPrimaryClip(clip);
if (settings.getBoolean("clear_clipboard_20x", false)) {
diff --git a/app/src/main/res/layout/autofill_recycler_view.xml b/app/src/main/res/layout/autofill_recycler_view.xml
index 98ff779d..6130fd1a 100644
--- a/app/src/main/res/layout/autofill_recycler_view.xml
+++ b/app/src/main/res/layout/autofill_recycler_view.xml
@@ -2,13 +2,15 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
- android:layout_height="match_parent">
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:focusableInTouchMode="true">
<SearchView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/app_search"
- android:queryHint="Add an app to configure"
+ android:queryHint="Add an app to change its autofill setting"
android:iconifiedByDefault="false"/>
<android.support.v7.widget.RecyclerView
diff --git a/app/src/main/res/layout/fragment_autofill.xml b/app/src/main/res/layout/fragment_autofill.xml
new file mode 100644
index 00000000..6ad44a0d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_autofill.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin">
+
+ <RadioGroup
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/autofill_radiogroup"
+ >
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Automatically match with password"
+ android:id="@+id/first"
+ android:layout_gravity="center_vertical"
+ android:checked="false"/>
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Match with..."
+ android:id="@+id/match"
+ android:layout_gravity="center_vertical"
+ android:checked="false"
+ android:layout_marginTop="8dp"/>
+
+ <EditText
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/matched"
+ android:layout_gravity="center_horizontal"
+ android:editable="false"/>
+
+ <RadioButton
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Never autofill"
+ android:id="@+id/never"
+ android:layout_gravity="center_vertical"
+ android:checked="false"
+ android:layout_marginTop="8dp"/>
+
+ </RadioGroup>
+
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/xml/preference.xml b/app/src/main/res/xml/preference.xml
index 3a8c4fdf..538f0387 100644
--- a/app/src/main/res/xml/preference.xml
+++ b/app/src/main/res/xml/preference.xml
@@ -74,7 +74,7 @@
<PreferenceCategory android:title="Autofill">
<Preference
android:key="autofill_apps"
- android:summary="Configure behaviour of autofill for specific apps."
+ android:summary="Customize autofill settings for specific apps."
android:title="Autofill Apps"/>
</PreferenceCategory>