Preference activity overhaul

This commit is contained in:
Matthew Wong 2015-08-13 15:18:47 -04:00
parent 29b92f4a6d
commit ec07e1eea6
9 changed files with 119 additions and 236 deletions

View file

@ -3,7 +3,7 @@ package com.zeapo.pwdstore.autofill;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.preference.PreferenceManager;
import android.content.pm.PackageManager;
import android.support.v7.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
@ -40,15 +40,20 @@ public class AutofillFragment extends DialogFragment {
String appName = getArguments().getString("appName");
builder.setTitle(appName);
try {
// since we can't (easily?) pass the drawable as an argument
builder.setIcon(callingActivity.getPackageManager().getApplicationIcon(packageName));
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
// when an app is added for the first time, the radio button selection should reflect
// the autofill_default setting: hence, defValue
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(callingActivity);
String defValue = settings.getBoolean("autofill_default", true) ? "first" : "never";
SharedPreferences prefs
= getActivity().getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
String preference = prefs.getString(packageName, defValue);
String preference = prefs.getString(packageName, "");
switch (preference) {
case "":
((RadioButton) view.findViewById(R.id.use_default)).toggle();
break;
case "first":
((RadioButton) view.findViewById(R.id.first)).toggle();
break;
@ -77,6 +82,9 @@ public class AutofillFragment extends DialogFragment {
public void onClick(DialogInterface dialog, int which) {
RadioGroup radioGroup = (RadioGroup) view.findViewById(R.id.autofill_radiogroup);
switch (radioGroup.getCheckedRadioButtonId()) {
case R.id.use_default:
editor.remove(packageName);
break;
case R.id.first:
editor.putString(packageName, "first");
break;
@ -90,10 +98,10 @@ public class AutofillFragment extends DialogFragment {
}
editor.apply();
int position = getArguments().getInt("position");
if (position == -1) {
callingActivity.recyclerAdapter.add(packageName);
} else {
callingActivity.recyclerAdapter.notifyItemChanged(position);
callingActivity.recyclerAdapter.notifyItemChanged(position);
if (getArguments().getBoolean("finish")) {
callingActivity.finish();
}
}
});
@ -105,7 +113,8 @@ public class AutofillFragment extends DialogFragment {
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK) {
((EditText) getDialog().findViewById(R.id.matched)).setText(data.getStringExtra("path"));
} else {
((RadioButton) getDialog().findViewById(R.id.use_default)).toggle();
}
}
}

View file

@ -1,22 +1,17 @@
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.Intent;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
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 android.support.v7.widget.SearchView;
import android.view.Menu;
import android.view.MenuItem;
import com.zeapo.pwdstore.R;
@ -24,7 +19,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
public class AutofillPreferenceActivity extends AppCompatActivity {
@ -43,104 +37,64 @@ public class AutofillPreferenceActivity extends AppCompatActivity {
layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
// apps for which the user has custom settings should be in the recycler
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
List<ResolveInfo> allApps = getPackageManager().queryIntentActivities(intent, 0);
final PackageManager pm = getPackageManager();
SharedPreferences prefs
= getSharedPreferences("autofill", Context.MODE_PRIVATE);
Map<String, ?> prefApps = prefs.getAll();
ArrayList<ApplicationInfo> 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();
}
}
Collections.sort(apps, new Comparator<ApplicationInfo>() {
Collections.sort(allApps, new Comparator<ResolveInfo>() {
@Override
public int compare(ApplicationInfo lhs, ApplicationInfo rhs) {
public int compare(ResolveInfo lhs, ResolveInfo rhs) {
return lhs.loadLabel(pm).toString().compareTo(rhs.loadLabel(pm).toString());
}
});
recyclerAdapter = new AutofillRecyclerAdapter(apps, pm, this);
recyclerAdapter = new AutofillRecyclerAdapter(new ArrayList<>(allApps), 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<ApplicationInfo> 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?
// TODO do this async probably. it lags.
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(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);
showDialog(packageName, appName);
return true;
}
});
setTitle("Autofill Apps");
Bundle extras = getIntent().getExtras();
if (extras != null) {
recyclerView.scrollToPosition(recyclerAdapter.getPosition(extras.getString("packageName")));
showDialog(extras.getString("packageName"), extras.getString("appName"));
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.autofill_preference, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String s) {
return true;
}
@Override
public boolean onQueryTextChange(String s) {
return true;
}
});
// When using the support library, the setOnActionExpandListener() method is
// static and accepts the MenuItem object as an argument
MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
@Override
public boolean onMenuItemActionCollapse(MenuItem item) {
return true;
}
@Override
public boolean onMenuItemActionExpand(MenuItem item) {
return true;
}
});
return super.onCreateOptionsMenu(menu);
}
public void showDialog(String packageName, String appName) {
DialogFragment df = new AutofillFragment();
Bundle args = new Bundle();

View file

@ -2,14 +2,11 @@ package com.zeapo.pwdstore.autofill;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.preference.PreferenceManager;
import android.content.pm.ResolveInfo;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@ -18,19 +15,14 @@ import android.widget.TextView;
import com.zeapo.pwdstore.R;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapter.ViewHolder> {
private ArrayList<ApplicationInfo> apps;
private ArrayList<ResolveInfo> apps;
private PackageManager pm;
private AutofillPreferenceActivity activity;
private final Set<Integer> selectedItems;
private ActionMode actionMode;
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public View view;
public TextView name;
public TextView secondary;
@ -44,38 +36,19 @@ public class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecycl
secondary = (TextView) view.findViewById(R.id.secondary_text);
icon = (ImageView) view.findViewById(R.id.app_icon);
view.setOnClickListener(this);
view.setOnLongClickListener(this);
}
@Override
public void onClick(View v) {
if (actionMode != null) {
toggleSelection(getAdapterPosition(), view);
if (selectedItems.isEmpty()) {
actionMode.finish();
}
} else {
activity.showDialog(packageName, name.getText().toString());
}
activity.showDialog(packageName, name.getText().toString());
}
@Override
public boolean onLongClick(View v) {
if (actionMode != null) {
return false;
}
toggleSelection(getAdapterPosition(), view);
// Start the CAB using the ActionMode.Callback
actionMode = activity.startSupportActionMode(actionModeCallback);
return true;
}
}
public AutofillRecyclerAdapter(ArrayList<ApplicationInfo> apps, PackageManager pm, AutofillPreferenceActivity activity) {
public AutofillRecyclerAdapter(ArrayList<ResolveInfo> apps, PackageManager pm, AutofillPreferenceActivity activity) {
this.apps = apps;
this.pm = pm;
this.activity = activity;
this.selectedItems = new TreeSet<>(Collections.reverseOrder());
}
@Override
@ -87,30 +60,33 @@ public class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecycl
@Override
public void onBindViewHolder(AutofillRecyclerAdapter.ViewHolder holder, int position) {
ApplicationInfo app = apps.get(position);
holder.name.setText(pm.getApplicationLabel(app));
ResolveInfo app = apps.get(position);
holder.name.setText(app.loadLabel(pm));
holder.icon.setImageDrawable(app.loadIcon(pm));
holder.packageName = app.activityInfo.packageName;
holder.secondary.setVisibility(View.VISIBLE);
holder.view.setBackgroundResource(R.color.grey_white_1000);
// it shouldn't be possible for prefs.getString to not find the app...use defValue anyway
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(activity);
String defValue = settings.getBoolean("autofill_default", true) ? "first" : "never";
SharedPreferences prefs
= activity.getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
String preference = prefs.getString(app.packageName, defValue);
String preference = prefs.getString(holder.packageName, "");
switch (preference) {
case "":
holder.secondary.setVisibility(View.GONE);
// "android:windowBackground"
holder.view.setBackgroundResource(R.color.indigo_50);
break;
case "first":
holder.secondary.setText("Automatically match with password");
holder.secondary.setText("Automatically match");
break;
case "never":
holder.secondary.setText("Never autofill");
holder.secondary.setText("Never match");
break;
default:
holder.secondary.setText("Match with " + preference);
break;
}
holder.icon.setImageDrawable(pm.getApplicationIcon(app));
holder.packageName = app.packageName;
holder.view.setSelected(selectedItems.contains(position));
}
@Override
@ -118,79 +94,13 @@ public class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecycl
return apps.size();
}
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();
}
}
public int getPosition(String packageName) {
for (int i = 0; i < apps.size(); i++) {
if (apps.get(i).packageName.equals(packageName)) {
if (apps.get(i).activityInfo.packageName.equals(packageName)) {
return i;
}
}
return -1;
}
private ActionMode.Callback actionModeCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.context_pass, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_delete_password:
// don't ask for confirmation
for (int position : selectedItems) {
remove(position);
}
mode.finish(); // Action picked, so close the CAB
return true;
default:
return false;
}
}
@Override
public void onDestroyActionMode(ActionMode mode) {
for (Iterator it = selectedItems.iterator(); it.hasNext();) {
// need the setSelected line in onBind
notifyItemChanged((Integer) it.next());
it.remove();
}
actionMode = null;
}
};
public void remove(int position) {
ApplicationInfo applicationInfo = this.apps.get(position);
SharedPreferences prefs
= activity.getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
prefs.edit().remove(applicationInfo.packageName).apply();
this.apps.remove(position);
this.notifyItemRemoved(position);
}
public void toggleSelection(int position, View view) {
if (!selectedItems.remove(position)) {
selectedItems.add(position);
view.setSelected(true);
} else {
view.setSelected(false);
}
}
}

View file

@ -156,8 +156,9 @@ public class AutofillService extends AccessibilityService {
builder.setNeutralButton("Settings", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// the user will have to return to the app themselves.
Intent intent = new Intent(AutofillService.this, AutofillPreferenceActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("packageName", info.getPackageName());
intent.putExtra("appName", appName);
startActivity(intent);

View file

@ -2,16 +2,7 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="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 change its setting"
android:iconifiedByDefault="false"/>
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/autofill_recycler"

View file

@ -31,15 +31,14 @@
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="App"
android:id="@+id/app_name"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceSmall"
android:text="The matched file.gpg/Don't ask"
android:id="@+id/secondary_text"/>
android:id="@+id/secondary_text"
android:textColor="@color/grey_600"/>
</LinearLayout>

View file

@ -13,11 +13,18 @@
android:layout_height="wrap_content"
android:id="@+id/autofill_radiogroup"
>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Use default setting"
android:id="@+id/use_default"
android:layout_gravity="center_vertical"
android:checked="false"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Automatically match with password"
android:text="Automatically match"
android:id="@+id/first"
android:layout_gravity="center_vertical"
android:checked="false"/>
@ -29,7 +36,7 @@
android:id="@+id/match"
android:layout_gravity="center_vertical"
android:checked="false"
android:layout_marginTop="8dp"/>
/>
<EditText
android:layout_width="match_parent"
@ -41,11 +48,11 @@
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Never autofill"
android:text="Never match"
android:id="@+id/never"
android:layout_gravity="center_vertical"
android:checked="false"
android:layout_marginTop="8dp"/>
/>
</RadioGroup>

View file

@ -0,0 +1,12 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:pwstore="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".pwdstore.autofill.AutofillPreferenceActivity">
<item
android:id="@+id/action_search"
android:icon="@drawable/ic_action_search"
android:title="@string/action_search"
pwstore:actionViewClass="android.support.v7.widget.SearchView"
pwstore:showAsAction="ifRoom|collapseActionView"/>
</menu>

View file

@ -79,7 +79,7 @@
<CheckBoxPreference
android:defaultValue="true"
android:key="autofill_default"
android:summary="Default to 'Automatically match with password' for apps without custom settings."
android:summary="Default to 'Automatically match' for apps without custom settings. Otherwise, 'Never match.'"
android:title="Automatically match by default"/>
</PreferenceCategory>