From ad173d9d05c29790d6f647df641e5de28e32fd58 Mon Sep 17 00:00:00 2001 From: Matthew Wong 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 +++- 4 files changed, 188 insertions(+), 11 deletions(-) create mode 100644 app/src/main/java/com/zeapo/pwdstore/autofill/AutofillFragment.java (limited to 'app/src/main/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