From 2d7c37d379ae48fa3ff464032ad4cb1759b1dc31 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Fri, 31 Jul 2015 09:00:06 -0400 Subject: Add per-app settings page --- .../zeapo/pwdstore/autofill/AutofillActivity.java | 84 ++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java (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 new file mode 100644 index 00000000..5d8fac5e --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java @@ -0,0 +1,84 @@ +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.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.Log; + +import com.zeapo.pwdstore.R; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +// blank activity started by service for calling startIntentSenderForResult +public class AutofillActivity extends AppCompatActivity { + public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913; + private boolean bound = false; + + private RecyclerView recyclerView; + private AutofillRecyclerAdapter recyclerAdapter; + private RecyclerView.LayoutManager layoutManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Intent intent = getIntent(); + Bundle extras = intent.getExtras(); + // if called by service just for startIntentSenderForResult + if (extras != null) { + try { + PendingIntent pi = intent.getExtras().getParcelable("pending_intent"); + if (pi == null) { + return; + } + startIntentSenderForResult(pi.getIntentSender() + , REQUEST_CODE_DECRYPT_AND_VERIFY, null, 0, 0, 0); + } catch (IntentSender.SendIntentException e) { + Log.e(AutofillService.Constants.TAG, "SendIntentException", e); + } + return; + } + // otherwise if called from settings + final PackageManager pm = getPackageManager(); + List allApps = pm.getInstalledApplications(0); + + SharedPreferences prefs + = getSharedPreferences("autofill", Context.MODE_PRIVATE); + Map prefApps = prefs.getAll(); + ArrayList apps = new ArrayList<>(); + for (ApplicationInfo applicationInfo : allApps) { + if (prefApps.containsKey(applicationInfo.packageName)) { + apps.add(applicationInfo); + } + } + + setContentView(R.layout.autofill_recycler_view); + recyclerView = (RecyclerView) findViewById(R.id.autofill_recycler); + + layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + + recyclerAdapter = new AutofillRecyclerAdapter(apps, pm); + recyclerView.setAdapter(recyclerAdapter); + + setTitle("Autofill Apps"); + + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + finish(); // go back to the password field app + if (resultCode == RESULT_OK) { + AutofillService.setUnlockOK(); // report the result to service + } + } +} -- cgit v1.2.3 From 51a05087e5a349afa3f4e0febdc3a339179e9bbd Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Mon, 3 Aug 2015 08:31:23 -0400 Subject: Add searching for apps in autofill settings page --- app/src/main/AndroidManifest.xml | 3 + .../zeapo/pwdstore/autofill/AutofillActivity.java | 68 +++++++++++++++++++--- .../zeapo/pwdstore/autofill/AutofillService.java | 2 +- app/src/main/res/layout/app_list_item.xml | 23 ++++++++ app/src/main/res/layout/autofill_recycler_view.xml | 7 +++ 5 files changed, 93 insertions(+), 10 deletions(-) create mode 100644 app/src/main/res/layout/app_list_item.xml (limited to 'app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b3600c53..3ddc04e8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,7 +55,10 @@ + android:parentActivityName=".PasswordStore"> + 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 5d8fac5e..5c42ab8d 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java @@ -7,11 +7,19 @@ import android.content.IntentSender; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.database.Cursor; +import android.database.MatrixCursor; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.SearchView; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; import com.zeapo.pwdstore.R; @@ -48,9 +56,15 @@ public class AutofillActivity extends AppCompatActivity { return; } // otherwise if called from settings - final PackageManager pm = getPackageManager(); - List allApps = pm.getInstalledApplications(0); + setContentView(R.layout.autofill_recycler_view); + recyclerView = (RecyclerView) findViewById(R.id.autofill_recycler); + + layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + // apps for which the user has custom settings should be in the recycler + final PackageManager pm = getPackageManager(); + final List allApps = pm.getInstalledApplications(0); SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE); Map prefApps = prefs.getAll(); @@ -60,18 +74,54 @@ public class AutofillActivity extends AppCompatActivity { apps.add(applicationInfo); } } + recyclerAdapter = new AutofillRecyclerAdapter(apps, pm); + recyclerView.setAdapter(recyclerAdapter); - setContentView(R.layout.autofill_recycler_view); - recyclerView = (RecyclerView) findViewById(R.id.autofill_recycler); + getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + final SearchView searchView = (SearchView) findViewById(R.id.app_search); - layoutManager = new LinearLayoutManager(this); - recyclerView.setLayoutManager(layoutManager); + // create search suggestions of apps with icons & names + final SimpleCursorAdapter.ViewBinder viewBinder = new SimpleCursorAdapter.ViewBinder() { + @Override + public boolean setViewValue(View view, Cursor cursor, int columnIndex) { + if (view instanceof TextView) { + ((TextView) view).setText(cursor.getString(columnIndex)); + } else if (view instanceof ImageView) { + try { + ((ImageView) view).setImageDrawable(pm.getApplicationIcon(cursor.getString(columnIndex))); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return false; + } + } + return true; + } + }; + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } - recyclerAdapter = new AutofillRecyclerAdapter(apps, pm); - recyclerView.setAdapter(recyclerAdapter); + @Override + public boolean onQueryTextChange(String newText) { + // 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())) { + matrixCursor.addRow(new Object[]{0, applicationInfo.packageName, applicationInfo.loadLabel(pm)}); + } + } + SimpleCursorAdapter simpleCursorAdapter = new SimpleCursorAdapter(AutofillActivity.this + , R.layout.app_list_item, matrixCursor, new String[]{"package", "label"} + , new int[]{android.R.id.icon1, android.R.id.text1}, 0); + simpleCursorAdapter.setViewBinder(viewBinder); + searchView.setSuggestionsAdapter(simpleCursorAdapter); + return false; + } + }); setTitle("Autofill Apps"); - } @Override 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 6188ef6d..f3108733 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -60,7 +60,7 @@ public class AutofillService extends AccessibilityService { serviceConnection.bindToService(); settings = PreferenceManager.getDefaultSharedPreferences(this); } - + // TODO handle CLICKS and change search/search results (just use first result) @Override public void onAccessibilityEvent(AccessibilityEvent event) { // if returning to the source app from a successful AutofillActivity diff --git a/app/src/main/res/layout/app_list_item.xml b/app/src/main/res/layout/app_list_item.xml new file mode 100644 index 00000000..7df28cc6 --- /dev/null +++ b/app/src/main/res/layout/app_list_item.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/autofill_recycler_view.xml b/app/src/main/res/layout/autofill_recycler_view.xml index 27afa77d..98ff779d 100644 --- a/app/src/main/res/layout/autofill_recycler_view.xml +++ b/app/src/main/res/layout/autofill_recycler_view.xml @@ -4,6 +4,13 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + + Date: Sun, 9 Aug 2015 16:04:09 -0400 Subject: Autofill per-app settings dialog opens on click on suggestion or on existing app --- .../zeapo/pwdstore/autofill/AutofillActivity.java | 49 +++++++++++-- .../zeapo/pwdstore/autofill/AutofillFragment.java | 82 ++++++++++++++++++++++ .../pwdstore/autofill/AutofillRecyclerAdapter.java | 53 +++++++++++++- .../zeapo/pwdstore/autofill/AutofillService.java | 15 +++- app/src/main/res/layout/autofill_recycler_view.xml | 6 +- app/src/main/res/layout/fragment_autofill.xml | 52 ++++++++++++++ app/src/main/res/xml/preference.xml | 2 +- 7 files changed, 245 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java create mode 100644 app/src/main/res/layout/fragment_autofill.xml (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 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 allApps = pm.getInstalledApplications(0); SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE); Map prefApps = prefs.getAll(); ArrayList 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 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 { private ArrayList 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 apps, PackageManager pm) { + public AutofillRecyclerAdapter(ArrayList 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 + android:layout_height="match_parent" + android:focusable="true" + android:focusableInTouchMode="true"> + + + + + + + + + + + + + + + \ 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 @@ -- cgit v1.2.3 From 2889454d324c9d210cafc51b47b7645cdd8abb26 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Mon, 10 Aug 2015 13:11:42 -0400 Subject: More tweaks of dialog dismissal behaviour --- .../zeapo/pwdstore/autofill/AutofillActivity.java | 1 - .../autofill/AutofillPreferenceActivity.java | 4 ++++ .../zeapo/pwdstore/autofill/AutofillService.java | 27 +++++++++++++++++----- app/src/main/res/layout/autofill_recycler_view.xml | 2 +- app/src/main/res/xml/autofill_config.xml | 2 +- 5 files changed, 27 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java (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 77a44c7a..2d538504 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java @@ -31,7 +31,6 @@ import java.util.Map; // blank activity started by service for calling startIntentSenderForResult public class AutofillActivity extends AppCompatActivity { public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913; - private boolean bound = false; private RecyclerView recyclerView; AutofillRecyclerAdapter recyclerAdapter; // let fragment have access diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java new file mode 100644 index 00000000..6f212149 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java @@ -0,0 +1,4 @@ +package com.zeapo.pwdstore.autofill; + +public class AutofillPreferenceActivity { +} 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 ebe12c9c..b30277ba 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillService.java @@ -18,6 +18,7 @@ import android.util.Log; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityWindowInfo; import android.widget.Toast; import com.zeapo.pwdstore.R; @@ -43,6 +44,7 @@ public class AutofillService extends AccessibilityService { private AccessibilityNodeInfo info; // the original source of the event (the edittext field) private ArrayList items; // password choices private AlertDialog dialog; + private AccessibilityWindowInfo window; private static boolean unlockOK = false; // if openkeychain user interaction was successful private CharSequence packageName; private boolean ignoreActionFocus = false; @@ -73,11 +75,18 @@ public class AutofillService extends AccessibilityService { 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 + // the default keyboard showing/hiding is a window state changed event + // on Android 5+ we can use getWindows() to determine when the original window is not visible + // on Android 4.3 we have to use window state changed events and filter out the keyboard ones // there may be other exceptions... - if (!(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED - && event.getPackageName().toString().contains("com.android.inputmethod")) - && dialog != null && dialog.isShowing()) { + boolean dismiss; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + dismiss = !getWindows().contains(window); + } else { + dismiss = !(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED + && event.getPackageName().toString().contains("inputmethod")); + } + if (dismiss && dialog != null && dialog.isShowing()) { dialog.dismiss(); } return; @@ -99,8 +108,14 @@ public class AutofillService extends AccessibilityService { return; } - // get the app name and find a corresponding password info = event.getSource(); + + // save the dialog's corresponding window so we can use getWindows() above to check whether dismiss + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window = info.getWindow(); + } + + // get the app name and find a corresponding password PackageManager packageManager = getPackageManager(); ApplicationInfo applicationInfo; try { @@ -134,10 +149,10 @@ public class AutofillService extends AccessibilityService { dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE); dialog.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); + dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); } dialog.setTitle(items.get(0).toString()); dialog.show(); - dialog.getWindow().setLayout(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT); } private ArrayList recursiveFilter(String filter, File dir) { diff --git a/app/src/main/res/layout/autofill_recycler_view.xml b/app/src/main/res/layout/autofill_recycler_view.xml index 6130fd1a..836cbcf5 100644 --- a/app/src/main/res/layout/autofill_recycler_view.xml +++ b/app/src/main/res/layout/autofill_recycler_view.xml @@ -10,7 +10,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/app_search" - android:queryHint="Add an app to change its autofill setting" + android:queryHint="Add an app to change its setting" android:iconifiedByDefault="false"/> Date: Mon, 10 Aug 2015 13:28:15 -0400 Subject: Move preferences to its own activity, leaving a "blank" openkeychain api one --- app/src/main/AndroidManifest.xml | 7 ++ .../java/com/zeapo/pwdstore/UserPreference.java | 6 +- .../zeapo/pwdstore/autofill/AutofillActivity.java | 127 ------------------- .../zeapo/pwdstore/autofill/AutofillFragment.java | 4 +- .../autofill/AutofillPreferenceActivity.java | 137 ++++++++++++++++++++- .../pwdstore/autofill/AutofillRecyclerAdapter.java | 4 +- 6 files changed, 150 insertions(+), 135 deletions(-) (limited to 'app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java') diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3ddc04e8..a6ffa791 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -61,6 +61,13 @@ android:value="com.zeapo.pwdstore.PasswordStore" /> + + android:parentActivityName=".PasswordStore"> + + + + diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.java b/app/src/main/java/com/zeapo/pwdstore/UserPreference.java index e2a9e5a7..cb2d2881 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.java +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.java @@ -17,7 +17,7 @@ import android.widget.Toast; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; -import com.zeapo.pwdstore.autofill.AutofillActivity; +import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity; import com.zeapo.pwdstore.crypto.PgpHandler; import com.zeapo.pwdstore.git.GitActivity; import com.zeapo.pwdstore.utils.PasswordRepository; @@ -187,9 +187,9 @@ public class UserPreference extends AppCompatActivity { findPreference("autofill_apps").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { @Override public boolean onPreferenceClick(Preference preference) { - Intent intent = new Intent(callingActivity, AutofillActivity.class); + Intent intent = new Intent(callingActivity, AutofillPreferenceActivity.class); startActivity(intent); - return false; + return true; } }); } 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 2d538504..5a097d07 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java @@ -1,48 +1,22 @@ package com.zeapo.pwdstore.autofill; -import android.app.DialogFragment; import android.app.PendingIntent; -import android.content.Context; import android.content.Intent; import android.content.IntentSender; -import android.content.SharedPreferences; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.database.MatrixCursor; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.util.Log; -import android.view.View; -import android.view.WindowManager; -import android.widget.ImageView; -import android.widget.SearchView; -import android.widget.SimpleCursorAdapter; -import android.widget.TextView; - -import com.zeapo.pwdstore.R; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; // blank activity started by service for calling startIntentSenderForResult public class AutofillActivity extends AppCompatActivity { public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913; - private RecyclerView recyclerView; - AutofillRecyclerAdapter recyclerAdapter; // let fragment have access - private RecyclerView.LayoutManager layoutManager; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); Bundle extras = intent.getExtras(); - // if called by service just for startIntentSenderForResult if (extras != null) { try { PendingIntent pi = intent.getExtras().getParcelable("pending_intent"); @@ -54,108 +28,7 @@ public class AutofillActivity extends AppCompatActivity { } catch (IntentSender.SendIntentException e) { Log.e(AutofillService.Constants.TAG, "SendIntentException", e); } - return; - } - // otherwise if called from settings - setContentView(R.layout.autofill_recycler_view); - recyclerView = (RecyclerView) findViewById(R.id.autofill_recycler); - - layoutManager = new LinearLayoutManager(this); - recyclerView.setLayoutManager(layoutManager); - - // apps for which the user has custom settings should be in the recycler - final PackageManager pm = getPackageManager(); - SharedPreferences prefs - = getSharedPreferences("autofill", Context.MODE_PRIVATE); - Map prefApps = prefs.getAll(); - ArrayList apps = new ArrayList<>(); - 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, 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() { - @Override - public boolean setViewValue(View view, Cursor cursor, int columnIndex) { - if (view instanceof TextView) { - ((TextView) view).setText(cursor.getString(columnIndex)); - } else if (view instanceof ImageView) { - try { - ((ImageView) view).setImageDrawable(pm.getApplicationIcon(cursor.getString(columnIndex))); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - return false; - } - } - return true; - } - }; - - final List allApps = pm.getInstalledApplications(0); - searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { - @Override - public boolean onQueryTextSubmit(String query) { - return false; - } - - @Override - public boolean onQueryTextChange(String newText) { - // should be a better/faster way to do this? - MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_id", "package", "label"}); - for (ApplicationInfo applicationInfo : allApps) { - // 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)}); - } - } - SimpleCursorAdapter simpleCursorAdapter = new SimpleCursorAdapter(AutofillActivity.this - , R.layout.app_list_item, matrixCursor, new String[]{"package", "label"} - , new int[]{android.R.id.icon1, android.R.id.text1}, 0); - simpleCursorAdapter.setViewBinder(viewBinder); - searchView.setSuggestionsAdapter(simpleCursorAdapter); - return false; - } - }); - - 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"); } @Override 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 c1d75acd..b85dec6a 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java @@ -25,9 +25,9 @@ public class AutofillFragment extends DialogFragment { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); - // this fragment is only created from the settings page (AutofillActivity) + // this fragment is only created from the settings page (AutofillPreferenceActivity) // need to interact with the recyclerAdapter which is a member of activity - final AutofillActivity callingActivity = (AutofillActivity) getActivity(); + final AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) getActivity(); LayoutInflater inflater = callingActivity.getLayoutInflater(); final View view = inflater.inflate(R.layout.fragment_autofill, null); 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 6f212149..9dac9a07 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java @@ -1,4 +1,139 @@ package com.zeapo.pwdstore.autofill; -public class AutofillPreferenceActivity { +import android.app.DialogFragment; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.database.MatrixCursor; +import android.os.Bundle; +import android.os.PersistableBundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.SearchView; +import android.widget.SimpleCursorAdapter; +import android.widget.TextView; + +import com.zeapo.pwdstore.R; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class AutofillPreferenceActivity extends AppCompatActivity { + + private RecyclerView recyclerView; + AutofillRecyclerAdapter recyclerAdapter; // let fragment have access + private RecyclerView.LayoutManager layoutManager; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // otherwise if called from settings + setContentView(R.layout.autofill_recycler_view); + recyclerView = (RecyclerView) findViewById(R.id.autofill_recycler); + + layoutManager = new LinearLayoutManager(this); + recyclerView.setLayoutManager(layoutManager); + + // apps for which the user has custom settings should be in the recycler + final PackageManager pm = getPackageManager(); + SharedPreferences prefs + = getSharedPreferences("autofill", Context.MODE_PRIVATE); + Map prefApps = prefs.getAll(); + ArrayList apps = new ArrayList<>(); + 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, 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() { + @Override + public boolean setViewValue(View view, Cursor cursor, int columnIndex) { + if (view instanceof TextView) { + ((TextView) view).setText(cursor.getString(columnIndex)); + } else if (view instanceof ImageView) { + try { + ((ImageView) view).setImageDrawable(pm.getApplicationIcon(cursor.getString(columnIndex))); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + return false; + } + } + return true; + } + }; + + final List allApps = pm.getInstalledApplications(0); + searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { + @Override + public boolean onQueryTextSubmit(String query) { + return false; + } + + @Override + public boolean onQueryTextChange(String newText) { + // should be a better/faster way to do this? + MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_id", "package", "label"}); + for (ApplicationInfo applicationInfo : allApps) { + // 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)}); + } + } + SimpleCursorAdapter simpleCursorAdapter = new SimpleCursorAdapter(AutofillPreferenceActivity.this + , R.layout.app_list_item, matrixCursor, new String[]{"package", "label"} + , new int[]{android.R.id.icon1, android.R.id.text1}, 0); + simpleCursorAdapter.setViewBinder(viewBinder); + searchView.setSuggestionsAdapter(simpleCursorAdapter); + return false; + } + }); + + 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/AutofillRecyclerAdapter.java b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java index eff94c51..b43f5d6e 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java @@ -20,7 +20,7 @@ import java.util.ArrayList; public class AutofillRecyclerAdapter extends RecyclerView.Adapter { private ArrayList apps; private PackageManager pm; - private AutofillActivity activity; + private AutofillPreferenceActivity activity; public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { public View view; @@ -50,7 +50,7 @@ public class AutofillRecyclerAdapter extends RecyclerView.Adapter apps, PackageManager pm, AutofillActivity activity) { + public AutofillRecyclerAdapter(ArrayList apps, PackageManager pm, AutofillPreferenceActivity activity) { this.apps = apps; this.pm = pm; this.activity = activity; -- cgit v1.2.3 From 7c41795ded9aff01c46cc798687e62736d2233e7 Mon Sep 17 00:00:00 2001 From: Matthew Wong Date: Mon, 10 Aug 2015 16:58:28 -0400 Subject: Open settings from password fill dialog --- .../zeapo/pwdstore/autofill/AutofillActivity.java | 5 ++- .../autofill/AutofillPreferenceActivity.java | 40 ++++++++++++++-------- .../pwdstore/autofill/AutofillRecyclerAdapter.java | 26 ++++++-------- .../zeapo/pwdstore/autofill/AutofillService.java | 8 +++-- app/src/main/res/layout/app_list_item.xml | 9 ++--- 5 files changed, 49 insertions(+), 39 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 5a097d07..d8e50ebd 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillActivity.java @@ -14,12 +14,11 @@ public class AutofillActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Intent intent = getIntent(); - Bundle extras = intent.getExtras(); + Bundle extras = getIntent().getExtras(); if (extras != null) { try { - PendingIntent pi = intent.getExtras().getParcelable("pending_intent"); + PendingIntent pi = extras.getParcelable("pending_intent"); if (pi == null) { return; } 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 9dac9a07..89d3474e 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillPreferenceActivity.java @@ -8,7 +8,6 @@ import android.content.pm.PackageManager; import android.database.Cursor; import android.database.MatrixCursor; import android.os.Bundle; -import android.os.PersistableBundle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; @@ -22,6 +21,8 @@ import android.widget.TextView; import com.zeapo.pwdstore.R; import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; @@ -57,6 +58,12 @@ public class AutofillPreferenceActivity extends AppCompatActivity { editor.remove(packageName).apply(); } } + Collections.sort(apps, new Comparator() { + @Override + public int compare(ApplicationInfo lhs, ApplicationInfo rhs) { + return lhs.loadLabel(pm).toString().compareTo(rhs.loadLabel(pm).toString()); + } + }); recyclerAdapter = new AutofillRecyclerAdapter(apps, pm, this); recyclerView.setAdapter(recyclerAdapter); @@ -95,9 +102,7 @@ public class AutofillPreferenceActivity extends AppCompatActivity { // should be a better/faster way to do this? MatrixCursor matrixCursor = new MatrixCursor(new String[]{"_id", "package", "label"}); for (ApplicationInfo applicationInfo : allApps) { - // 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)) { + if (applicationInfo.loadLabel(pm).toString().toLowerCase().contains(newText.toLowerCase())) { matrixCursor.addRow(new Object[]{0, applicationInfo.packageName, applicationInfo.loadLabel(pm)}); } } @@ -121,19 +126,26 @@ public class AutofillPreferenceActivity extends AppCompatActivity { 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; + showDialog(packageName, appName); + return true; } }); setTitle("Autofill Apps"); + + Bundle extras = getIntent().getExtras(); + if (extras != null) { + showDialog(extras.getString("packageName"), extras.getString("appName")); + } + } + + public void showDialog(String packageName, String appName) { + DialogFragment df = new AutofillFragment(); + Bundle args = new Bundle(); + args.putString("packageName", packageName); + args.putString("appName", appName); + args.putInt("position", recyclerAdapter.getPosition(packageName)); + df.setArguments(args); + df.show(getFragmentManager(), "autofill_dialog"); } } 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 b43f5d6e..26680152 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/AutofillRecyclerAdapter.java @@ -40,13 +40,7 @@ public class AutofillRecyclerAdapter extends RecyclerView.Adapter + android:paddingRight="8dp" + android:gravity="center_vertical"> + android:layout_width="24dp" + android:layout_height="24dp"/>