commit
327945f3b8
24 changed files with 1218 additions and 5 deletions
|
@ -4,6 +4,9 @@
|
|||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
|
||||
<application android:allowBackup="true" android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name" android:theme="@style/AppTheme">
|
||||
<activity android:name=".PasswordStore" android:label="@string/app_name"
|
||||
|
@ -42,6 +45,31 @@
|
|||
android:value="com.zeapo.pwdstore.PasswordStore" />
|
||||
</activity>
|
||||
|
||||
<service android:name=".autofill.AutofillService"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.accessibilityservice"
|
||||
android:resource="@xml/autofill_config" />
|
||||
</service>
|
||||
|
||||
<activity android:name=".autofill.AutofillActivity"
|
||||
android:parentActivityName=".PasswordStore"
|
||||
android:documentLaunchMode="intoExisting"
|
||||
android:excludeFromRecents="true">
|
||||
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.zeapo.pwdstore.PasswordStore" />
|
||||
</activity>
|
||||
|
||||
<activity android:name=".autofill.AutofillPreferenceActivity"
|
||||
android:parentActivityName=".PasswordStore">
|
||||
|
||||
<meta-data android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="com.zeapo.pwdstore.PasswordStore" />
|
||||
</activity>
|
||||
|
||||
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
||||
</application>
|
||||
|
||||
|
|
|
@ -112,7 +112,11 @@ public class PasswordFragment extends Fragment{
|
|||
|
||||
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
} else {
|
||||
((PasswordStore) getActivity()).decryptPassword(item);
|
||||
if (getArguments().getBoolean("matchWith", false)) {
|
||||
((PasswordStore) getActivity()).matchPasswordWithApp(item);
|
||||
} else {
|
||||
((PasswordStore) getActivity()).decryptPassword(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -289,6 +289,12 @@ public class PasswordStore extends AppCompatActivity {
|
|||
Bundle args = new Bundle();
|
||||
args.putString("Path", PasswordRepository.getWorkTree().getAbsolutePath());
|
||||
|
||||
// if the activity was started from the autofill settings, the
|
||||
// intent is to match a clicked pwd with app. pass this to fragment
|
||||
if (getIntent().getBooleanExtra("matchWith", false)) {
|
||||
args.putBoolean("matchWith", true);
|
||||
}
|
||||
|
||||
plist.setArguments(args);
|
||||
|
||||
fragmentTransaction.addToBackStack("passlist");
|
||||
|
@ -531,4 +537,13 @@ public class PasswordStore extends AppCompatActivity {
|
|||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
public void matchPasswordWithApp(PasswordItem item) {
|
||||
String path = item.getFile().getAbsolutePath();
|
||||
path = path.replace(PasswordRepository.getWorkTree() + "/", "").replace(".gpg", "");
|
||||
Intent data = new Intent();
|
||||
data.putExtra("path", path);
|
||||
setResult(RESULT_OK, data);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,29 @@
|
|||
package com.zeapo.pwdstore;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.CheckBoxPreference;
|
||||
import android.preference.Preference;
|
||||
import android.preference.PreferenceFragment;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.view.MenuItem;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
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.AutofillPreferenceActivity;
|
||||
import com.zeapo.pwdstore.crypto.PgpHandler;
|
||||
import com.zeapo.pwdstore.git.GitActivity;
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||
|
@ -32,6 +39,7 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class UserPreference extends AppCompatActivity {
|
||||
|
@ -182,6 +190,40 @@ public class UserPreference extends AppCompatActivity {
|
|||
|
||||
findPreference("pref_select_external").setOnPreferenceChangeListener(resetRepo);
|
||||
findPreference("git_external").setOnPreferenceChangeListener(resetRepo);
|
||||
|
||||
findPreference("autofill_apps").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent intent = new Intent(callingActivity, AutofillPreferenceActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
findPreference("autofill_enable").setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
new AlertDialog.Builder(callingActivity).
|
||||
setTitle(R.string.pref_autofill_enable_title).
|
||||
setView(R.layout.autofill_instructions).
|
||||
setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
|
||||
startActivity(intent);
|
||||
}
|
||||
}).
|
||||
setNegativeButton(R.string.dialog_cancel, null).
|
||||
setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
((CheckBoxPreference) findPreference("autofill_enable"))
|
||||
.setChecked(((UserPreference) getActivity()).isServiceEnabled());
|
||||
}
|
||||
}).show();
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -189,6 +231,10 @@ public class UserPreference extends AppCompatActivity {
|
|||
super.onStart();
|
||||
final SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
|
||||
findPreference("ssh_see_key").setEnabled(sharedPreferences.getBoolean("use_generated_key", false));
|
||||
|
||||
// see if the autofill service is enabled and check the preference accordingly
|
||||
((CheckBoxPreference) findPreference("autofill_enable"))
|
||||
.setChecked(((UserPreference) getActivity()).isServiceEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,6 +314,21 @@ public class UserPreference extends AppCompatActivity {
|
|||
sshKey.close();
|
||||
}
|
||||
|
||||
// Returns whether the autofill service is enabled
|
||||
private boolean isServiceEnabled() {
|
||||
AccessibilityManager am = (AccessibilityManager) this
|
||||
.getSystemService(Context.ACCESSIBILITY_SERVICE);
|
||||
List<AccessibilityServiceInfo> runningServices = am
|
||||
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
|
||||
for (AccessibilityServiceInfo service : runningServices) {
|
||||
if ("com.zeapo.pwdstore/.autofill.AutofillService".equals(service.getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected void onActivityResult(int requestCode, int resultCode,
|
||||
Intent data) {
|
||||
if (resultCode == RESULT_OK) {
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package com.zeapo.pwdstore.autofill;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.os.Bundle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
|
||||
// blank activity started by service for calling startIntentSenderForResult
|
||||
public class AutofillActivity extends AppCompatActivity {
|
||||
public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
Bundle extras = getIntent().getExtras();
|
||||
|
||||
if (extras != null) {
|
||||
try {
|
||||
PendingIntent pi = extras.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
package com.zeapo.pwdstore.autofill;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
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.PasswordStore;
|
||||
import com.zeapo.pwdstore.R;
|
||||
|
||||
public class AutofillFragment extends DialogFragment {
|
||||
private static final int MATCH_WITH = 777;
|
||||
|
||||
public AutofillFragment() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
// this fragment is only created from the settings page (AutofillPreferenceActivity)
|
||||
// need to interact with the recyclerAdapter which is a member of activity
|
||||
final AutofillPreferenceActivity callingActivity = (AutofillPreferenceActivity) 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);
|
||||
try {
|
||||
// since we can't (easily?) pass the drawable as an argument
|
||||
builder.setIcon(callingActivity.getPackageManager().getApplicationIcon(packageName));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
SharedPreferences prefs
|
||||
= getActivity().getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
View.OnClickListener matchPassword = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
((RadioButton) view.findViewById(R.id.match)).toggle();
|
||||
Intent intent = new Intent(getActivity(), PasswordStore.class);
|
||||
intent.putExtra("matchWith", true);
|
||||
startActivityForResult(intent, MATCH_WITH);
|
||||
}
|
||||
};
|
||||
view.findViewById(R.id.match).setOnClickListener(matchPassword);
|
||||
view.findViewById(R.id.matched).setOnClickListener(matchPassword);
|
||||
|
||||
final SharedPreferences.Editor editor = prefs.edit();
|
||||
builder.setPositiveButton(R.string.dialog_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.use_default:
|
||||
editor.remove(packageName);
|
||||
break;
|
||||
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");
|
||||
callingActivity.recyclerAdapter.notifyItemChanged(position);
|
||||
|
||||
if (getArguments().getBoolean("finish")) {
|
||||
callingActivity.finish();
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.dialog_cancel, null);
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
package com.zeapo.pwdstore.autofill;
|
||||
|
||||
import android.app.DialogFragment;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
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.support.v7.widget.SearchView;
|
||||
import android.util.Pair;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
import com.zeapo.pwdstore.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class AutofillPreferenceActivity extends AppCompatActivity {
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
AutofillRecyclerAdapter recyclerAdapter; // let fragment have access
|
||||
private RecyclerView.LayoutManager layoutManager;
|
||||
|
||||
private PackageManager pm;
|
||||
|
||||
private boolean recreate;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
setContentView(R.layout.autofill_recycler_view);
|
||||
recyclerView = (RecyclerView) findViewById(R.id.autofill_recycler);
|
||||
|
||||
layoutManager = new LinearLayoutManager(this);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST));
|
||||
|
||||
pm = getPackageManager();
|
||||
|
||||
new populateTask().execute();
|
||||
|
||||
setTitle("Autofill Apps");
|
||||
}
|
||||
|
||||
private class populateTask extends AsyncTask<Void, Void, Void> {
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
findViewById(R.id.progress_bar).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||
intent.addCategory(Intent.CATEGORY_LAUNCHER);
|
||||
List<ResolveInfo> allApps = pm.queryIntentActivities(intent, 0);
|
||||
|
||||
HashMap<String, Pair<Drawable, String>> iconMap = new HashMap<>(allApps.size());
|
||||
for (ResolveInfo app : allApps) {
|
||||
iconMap.put(app.activityInfo.packageName
|
||||
, Pair.create(app.loadIcon(pm), app.loadLabel(pm).toString()));
|
||||
}
|
||||
|
||||
recyclerAdapter = new AutofillRecyclerAdapter(allApps, iconMap, pm, AutofillPreferenceActivity.this);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
findViewById(R.id.progress_bar).setVisibility(View.GONE);
|
||||
|
||||
recyclerView.setAdapter(recyclerAdapter);
|
||||
|
||||
recreate = false;
|
||||
Bundle extras = getIntent().getExtras();
|
||||
if (extras != null) {
|
||||
recreate = true;
|
||||
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 false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String s) {
|
||||
if (recyclerAdapter != null) {
|
||||
recyclerAdapter.filter(s);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
// in service, we CLEAR_TASK. then we set the recreate flag.
|
||||
// something of a hack, but w/o CLEAR_TASK, behaviour was unpredictable
|
||||
case android.R.id.home:
|
||||
Intent upIntent = NavUtils.getParentActivityIntent(this);
|
||||
if (recreate) {
|
||||
TaskStackBuilder.create(this)
|
||||
.addNextIntentWithParentStack(upIntent)
|
||||
.startActivities();
|
||||
} else {
|
||||
NavUtils.navigateUpTo(this, upIntent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
package com.zeapo.pwdstore.autofill;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v7.util.SortedList;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.util.SortedListAdapterCallback;
|
||||
import android.util.Pair;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.zeapo.pwdstore.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class AutofillRecyclerAdapter extends RecyclerView.Adapter<AutofillRecyclerAdapter.ViewHolder> {
|
||||
private SortedList<ResolveInfo> apps;
|
||||
private ArrayList<ResolveInfo> allApps;
|
||||
private HashMap<String, Pair<Drawable, String>> iconMap;
|
||||
private PackageManager pm;
|
||||
private AutofillPreferenceActivity 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) {
|
||||
activity.showDialog(packageName, name.getText().toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public AutofillRecyclerAdapter(List<ResolveInfo> allApps, HashMap<String, Pair<Drawable, String>> iconMap
|
||||
, final PackageManager pm, AutofillPreferenceActivity activity) {
|
||||
SortedList.Callback<ResolveInfo> callback = new SortedListAdapterCallback<ResolveInfo>(this) {
|
||||
@Override
|
||||
public int compare(ResolveInfo o1, ResolveInfo o2) {
|
||||
return o1.loadLabel(pm).toString().toLowerCase().compareTo(o2.loadLabel(pm).toString().toLowerCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(ResolveInfo oldItem, ResolveInfo newItem) {
|
||||
return oldItem.loadLabel(pm).toString().equals(newItem.loadLabel(pm).toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(ResolveInfo item1, ResolveInfo item2) {
|
||||
return item1.loadLabel(pm).toString().equals(item2.loadLabel(pm).toString());
|
||||
}
|
||||
};
|
||||
this.apps = new SortedList<>(ResolveInfo.class, callback);
|
||||
this.apps.addAll(allApps);
|
||||
this.allApps = new ArrayList<>(allApps);
|
||||
this.iconMap = new HashMap<>(iconMap);
|
||||
this.pm = pm;
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AutofillRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
View v = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.autofill_row_layout, parent, false);
|
||||
return new ViewHolder(v);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(AutofillRecyclerAdapter.ViewHolder holder, int position) {
|
||||
ResolveInfo app = apps.get(position);
|
||||
holder.packageName = app.activityInfo.packageName;
|
||||
|
||||
holder.icon.setImageDrawable(iconMap.get(holder.packageName).first);
|
||||
holder.name.setText(iconMap.get(holder.packageName).second);
|
||||
|
||||
holder.secondary.setVisibility(View.VISIBLE);
|
||||
holder.view.setBackgroundResource(R.color.grey_white_1000);
|
||||
|
||||
SharedPreferences prefs
|
||||
= activity.getApplicationContext().getSharedPreferences("autofill", Context.MODE_PRIVATE);
|
||||
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(R.string.autofill_apps_first);
|
||||
break;
|
||||
case "/never":
|
||||
holder.secondary.setText(R.string.autofill_apps_never);
|
||||
break;
|
||||
default:
|
||||
holder.secondary.setText("Match with " + preference);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return apps.size();
|
||||
}
|
||||
|
||||
public int getPosition(String packageName) {
|
||||
for (int i = 0; i < apps.size(); i++) {
|
||||
if (apps.get(i).activityInfo.packageName.equals(packageName)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public void filter(String s) {
|
||||
if (s.isEmpty()) {
|
||||
apps.addAll(allApps);
|
||||
return;
|
||||
}
|
||||
apps.beginBatchedUpdates();
|
||||
for (ResolveInfo app : allApps) {
|
||||
if (app.loadLabel(pm).toString().toLowerCase().contains(s.toLowerCase())) {
|
||||
apps.add(app);
|
||||
} else {
|
||||
apps.remove(app);
|
||||
}
|
||||
}
|
||||
apps.endBatchedUpdates();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,274 @@
|
|||
package com.zeapo.pwdstore.autofill;
|
||||
|
||||
import android.accessibilityservice.AccessibilityService;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
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;
|
||||
import com.zeapo.pwdstore.utils.PasswordItem;
|
||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.openintents.openpgp.OpenPgpError;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AutofillService extends AccessibilityService {
|
||||
private OpenPgpServiceConnection serviceConnection;
|
||||
private SharedPreferences settings;
|
||||
private AccessibilityNodeInfo info; // the original source of the event (the edittext field)
|
||||
private ArrayList<PasswordItem> 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;
|
||||
|
||||
public final class Constants {
|
||||
public static final String TAG = "Keychain";
|
||||
}
|
||||
|
||||
public static void setUnlockOK() { unlockOK = true; }
|
||||
|
||||
@Override
|
||||
protected void onServiceConnected() {
|
||||
super.onServiceConnected();
|
||||
serviceConnection = new OpenPgpServiceConnection(AutofillService.this, "org.sufficientlysecure.keychain");
|
||||
serviceConnection.bindToService();
|
||||
settings = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
}
|
||||
// TODO change search/search results (just use first result)
|
||||
@Override
|
||||
public void onAccessibilityEvent(AccessibilityEvent event) {
|
||||
// if returning to the source app from a successful AutofillActivity
|
||||
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED
|
||||
&& 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")) {
|
||||
// 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...
|
||||
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;
|
||||
}
|
||||
|
||||
if (dialog != null && dialog.isShowing()) {
|
||||
// if the view was clicked, the click event follows the focus event
|
||||
// since the focus event was already handled, ignore click event
|
||||
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {
|
||||
return;
|
||||
}
|
||||
// if past this point, a new dialog will be created, so dismiss the existing
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
// ignore the ACTION_FOCUS from decryptAndVerify otherwise dialog will appear after Fill
|
||||
if (ignoreActionFocus) {
|
||||
ignoreActionFocus = false;
|
||||
return;
|
||||
}
|
||||
|
||||
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 {
|
||||
applicationInfo = packageManager.getApplicationInfo(event.getPackageName().toString(), 0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
applicationInfo = null;
|
||||
}
|
||||
final String appName = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "").toString();
|
||||
|
||||
// if autofill_default is checked and prefs.getString DNE, 'Automatically match with password'/"first" otherwise "never"
|
||||
String defValue = settings.getBoolean("autofill_default", true) ? "/first" : "/never";
|
||||
SharedPreferences prefs = getSharedPreferences("autofill", Context.MODE_PRIVATE);
|
||||
String preference = prefs.getString(event.getPackageName().toString(), defValue);
|
||||
switch (preference) {
|
||||
case "/first":
|
||||
if (!PasswordRepository.isInitialized()) {
|
||||
PasswordRepository.initialize(this);
|
||||
}
|
||||
items = recursiveFilter(appName, null);
|
||||
break;
|
||||
case "/never":
|
||||
return;
|
||||
default:
|
||||
if (!PasswordRepository.isInitialized()) {
|
||||
PasswordRepository.initialize(this);
|
||||
}
|
||||
String path = PasswordRepository.getWorkTree() + "/" + preference + ".gpg";
|
||||
File file = new File(path);
|
||||
items = new ArrayList<>();
|
||||
items.add(PasswordItem.newPassword(file.getName(), file, PasswordRepository.getRepositoryDirectory(this)));
|
||||
}
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dialog == null) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Dialog);
|
||||
builder.setNegativeButton(R.string.dialog_cancel, null);
|
||||
builder.setPositiveButton(R.string.autofill_fill, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
decryptAndVerify();
|
||||
}
|
||||
});
|
||||
builder.setNeutralButton("Settings", new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) { //TODO make icon? gear?
|
||||
// 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.putExtra("packageName", info.getPackageName());
|
||||
intent.putExtra("appName", appName);
|
||||
startActivity(intent);
|
||||
}
|
||||
});
|
||||
dialog = builder.create();
|
||||
dialog.setIcon(R.drawable.ic_launcher);
|
||||
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();
|
||||
}
|
||||
|
||||
private ArrayList<PasswordItem> recursiveFilter(String filter, File dir) {
|
||||
ArrayList<PasswordItem> items = new ArrayList<>();
|
||||
ArrayList<PasswordItem> passwordItems = dir == null ?
|
||||
PasswordRepository.getPasswords(PasswordRepository.getRepositoryDirectory(this)) :
|
||||
PasswordRepository.getPasswords(dir, PasswordRepository.getRepositoryDirectory(this));
|
||||
for (PasswordItem item : passwordItems) {
|
||||
if (item.getType() == PasswordItem.TYPE_CATEGORY) {
|
||||
items.addAll(recursiveFilter(filter, item.getFile()));
|
||||
}
|
||||
if (item.toString().toLowerCase().contains(filter.toLowerCase())) {
|
||||
items.add(item);
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupt() {
|
||||
|
||||
}
|
||||
|
||||
public void decryptAndVerify() {
|
||||
unlockOK = false;
|
||||
packageName = info.getPackageName();
|
||||
Intent data = new Intent();
|
||||
data.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
|
||||
InputStream is = null;
|
||||
try {
|
||||
is = FileUtils.openInputStream(items.get(0).getFile());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
|
||||
OpenPgpApi api = new OpenPgpApi(AutofillService.this, serviceConnection.getService());
|
||||
Intent result = api.executeApi(data, is, os);
|
||||
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
|
||||
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);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Bundle args = new Bundle();
|
||||
args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
|
||||
passContent[0]);
|
||||
info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
|
||||
} else {
|
||||
ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
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)) {
|
||||
for (int i = 0; i < 19; i++) {
|
||||
clip = ClipData.newPlainText(String.valueOf(i), String.valueOf(i));
|
||||
clipboard.setPrimaryClip(clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Log.e(Constants.TAG, "UnsupportedEncodingException", e);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: {
|
||||
Log.i("PgpHandler", "RESULT_CODE_USER_INTERACTION_REQUIRED");
|
||||
PendingIntent pi = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
|
||||
// need to start a blank activity to call startIntentSenderForResult
|
||||
Intent intent = new Intent(AutofillService.this, AutofillActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
intent.putExtra("pending_intent", pi);
|
||||
startActivity(intent);
|
||||
break;
|
||||
}
|
||||
case OpenPgpApi.RESULT_CODE_ERROR: {
|
||||
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
|
||||
Toast.makeText(AutofillService.this,
|
||||
"Error from OpenKeyChain : " + error.getMessage(),
|
||||
Toast.LENGTH_LONG).show();
|
||||
Log.e(Constants.TAG, "onError getErrorId:" + error.getErrorId());
|
||||
Log.e(Constants.TAG, "onError getMessage:" + error.getMessage());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package com.zeapo.pwdstore.autofill;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
|
||||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
public class DividerItemDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
private static final int[] ATTRS = new int[]{
|
||||
android.R.attr.listDivider
|
||||
};
|
||||
|
||||
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
|
||||
|
||||
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
|
||||
|
||||
private Drawable mDivider;
|
||||
|
||||
private int mOrientation;
|
||||
|
||||
public DividerItemDecoration(Context context, int orientation) {
|
||||
final TypedArray a = context.obtainStyledAttributes(ATTRS);
|
||||
mDivider = a.getDrawable(0);
|
||||
a.recycle();
|
||||
setOrientation(orientation);
|
||||
}
|
||||
|
||||
public void setOrientation(int orientation) {
|
||||
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
|
||||
throw new IllegalArgumentException("invalid orientation");
|
||||
}
|
||||
mOrientation = orientation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
|
||||
if (mOrientation == VERTICAL_LIST) {
|
||||
drawVertical(c, parent);
|
||||
} else {
|
||||
drawHorizontal(c, parent);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawVertical(Canvas c, RecyclerView parent) {
|
||||
final int left = parent.getPaddingLeft();
|
||||
final int right = parent.getWidth() - parent.getPaddingRight();
|
||||
|
||||
final int childCount = parent.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
|
||||
.getLayoutParams();
|
||||
final int top = child.getBottom() + params.bottomMargin;
|
||||
final int bottom = top + mDivider.getIntrinsicHeight();
|
||||
mDivider.setBounds(left, top, right, bottom);
|
||||
mDivider.draw(c);
|
||||
}
|
||||
}
|
||||
|
||||
public void drawHorizontal(Canvas c, RecyclerView parent) {
|
||||
final int top = parent.getPaddingTop();
|
||||
final int bottom = parent.getHeight() - parent.getPaddingBottom();
|
||||
|
||||
final int childCount = parent.getChildCount();
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
|
||||
.getLayoutParams();
|
||||
final int left = child.getRight() + params.rightMargin;
|
||||
final int right = left + mDivider.getIntrinsicHeight();
|
||||
mDivider.setBounds(left, top, right, bottom);
|
||||
mDivider.draw(c);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
|
||||
if (mOrientation == VERTICAL_LIST) {
|
||||
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
|
||||
} else {
|
||||
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
BIN
app/src/main/res/drawable/autofill_ins_1.png
Normal file
BIN
app/src/main/res/drawable/autofill_ins_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/drawable/autofill_ins_2.png
Normal file
BIN
app/src/main/res/drawable/autofill_ins_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.7 KiB |
BIN
app/src/main/res/drawable/autofill_ins_3.png
Normal file
BIN
app/src/main/res/drawable/autofill_ins_3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
5
app/src/main/res/drawable/autofill_row_background.xml
Normal file
5
app/src/main/res/drawable/autofill_row_background.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_selected="true" android:drawable="@color/blue_grey_200" />
|
||||
<item android:drawable="@color/grey_white_1000" />
|
||||
</selector>
|
24
app/src/main/res/layout/app_list_item.xml
Normal file
24
app/src/main/res/layout/app_list_item.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@android:id/icon1"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"/>
|
||||
|
||||
<TextView android:id="@android:id/text1"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
android:layout_marginLeft="8dp"/>
|
||||
|
||||
</LinearLayout>
|
63
app/src/main/res/layout/autofill_instructions.xml
Normal file
63
app/src/main/res/layout/autofill_instructions.xml
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent">
|
||||
|
||||
<LinearLayout android:orientation="vertical"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_width="fill_parent"
|
||||
android:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="20dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/abc_text_size_menu_material"
|
||||
android:text="@string/pref_autofill_enable_msg"
|
||||
android:id="@+id/textView"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/imageView"
|
||||
android:src="@drawable/autofill_ins_1"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/imageView2"
|
||||
android:src="@drawable/autofill_ins_2"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:adjustViewBounds="true"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/abc_text_size_menu_material"
|
||||
android:text="@string/pref_autofill_enable_msg2"
|
||||
android:id="@+id/textView3"/>
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="114dp"
|
||||
android:id="@+id/imageView3"
|
||||
android:src="@drawable/autofill_ins_3"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/abc_text_size_menu_material"
|
||||
android:text="@string/pref_autofill_enable_msg3"
|
||||
android:id="@+id/textView4"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
20
app/src/main/res/layout/autofill_recycler_view.xml
Normal file
20
app/src/main/res/layout/autofill_recycler_view.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/autofill_recycler"
|
||||
android:scrollbars="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<ProgressBar
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/progress_bar"
|
||||
android:indeterminate="true"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"/>
|
||||
</RelativeLayout>
|
46
app/src/main/res/layout/autofill_row_layout.xml
Normal file
46
app/src/main/res/layout/autofill_row_layout.xml
Normal file
|
@ -0,0 +1,46 @@
|
|||
<?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="64dp"
|
||||
android:background="@drawable/autofill_row_background">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:id="@+id/app_icon"
|
||||
/>
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginLeft="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/app_name"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:id="@+id/secondary_text"
|
||||
android:textColor="@color/grey_600"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
59
app/src/main/res/layout/fragment_autofill.xml
Normal file
59
app/src/main/res/layout/fragment_autofill.xml
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?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:paddingLeft="24dp"
|
||||
android:paddingRight="24dp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="20dp">
|
||||
|
||||
<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="@string/autofill_apps_default"
|
||||
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="@string/autofill_apps_first"
|
||||
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="@string/autofill_apps_match_ellipsis"
|
||||
android:id="@+id/match"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:checked="false"
|
||||
/>
|
||||
|
||||
<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="@string/autofill_apps_never"
|
||||
android:id="@+id/never"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:checked="false"
|
||||
/>
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
12
app/src/main/res/menu/autofill_preference.xml
Normal file
12
app/src/main/res/menu/autofill_preference.xml
Normal 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>
|
|
@ -116,8 +116,8 @@
|
|||
<string name="ssh_key_error_dialog_text">Zpráva : \n</string>
|
||||
<string name="pref_recursive_filter">Rekurzivní filtrování</string>
|
||||
<string name="pref_recursive_filter_hint">Rekurzivní hledání hesel v aktuálním adresáři.</string>
|
||||
<string name="pref_clear_clipboard_title">Zaplnit schránku 20krát</string>
|
||||
<string name="pref_clear_clipboard_hint">Uložit dvacet náhodných textů do schránky namísto pouze jednoho. Užitečné pro telefony Samsug, které nabízejí funkci historie schránky.</string>
|
||||
<string name="pref_clear_clipboard">Zaplnit schránku 20krát</string>
|
||||
|
||||
<!-- pwgen fragment -->
|
||||
<string name="pwgen_generate">Generovat</string>
|
||||
|
|
|
@ -117,8 +117,16 @@
|
|||
<string name="ssh_key_error_dialog_text">Message : \n</string>
|
||||
<string name="pref_recursive_filter">Recursive filtering</string>
|
||||
<string name="pref_recursive_filter_hint">Recursively find passwords of the current directory.</string>
|
||||
<string name="pref_autofill_enable_title">Enable autofill</string>
|
||||
<string name="pref_autofill_enable_msg">Tap OK to go to Accessibility settings. There, tap Password Store under Services then tap the switch in the top right to turn it on or off.</string>
|
||||
<string name="pref_autofill_enable_msg2">Once the service is on, a dialog will appear when you click on a password field in an app if a matching password for the app exists.</string>
|
||||
<string name="pref_autofill_enable_msg3">Password Store attempts to match apps with passwords automatically. You can change this default setting and also matching settings per-app.</string>
|
||||
<string name="pref_autofill_apps_title">Per-app settings</string>
|
||||
<string name="pref_autofill_apps_hint">Customize autofill settings for specific apps.</string>
|
||||
<string name="pref_autofill_default_title">Automatically match by default</string>
|
||||
<string name="pref_autofill_default_hint">Default to \'Automatically match\' for apps without custom settings. Otherwise, \'Never match.\'</string>
|
||||
<string name="pref_clear_clipboard_title">Clear clipboard 20 times</string>
|
||||
<string name="pref_clear_clipboard_hint">Store nonsense in the clipboard 20 times instead of just once. Useful on Samsung phones that feature clipboard history.</string>
|
||||
<string name="pref_clear_clipboard">Clear clipboard 20 times</string>
|
||||
|
||||
<!-- pwgen fragment -->
|
||||
<string name="pwgen_generate">Generate</string>
|
||||
|
@ -154,4 +162,11 @@
|
|||
<string name="pwd_generate_button">Generate</string>
|
||||
<string name="category_string">"Category: "</string>
|
||||
|
||||
<!-- Autofill -->
|
||||
<string name="autofill_description">Autofills password fields in apps. Only works for Android versions 4.3 and up. Does not rely on the clipboard for Android versions 5.0 and up.</string>
|
||||
<string name="autofill_fill">Fill</string>
|
||||
<string name="autofill_apps_default">Use default setting</string>
|
||||
<string name="autofill_apps_first">Automatically match</string>
|
||||
<string name="autofill_apps_match_ellipsis">Match with…</string>
|
||||
<string name="autofill_apps_never">Never match</string>
|
||||
</resources>
|
||||
|
|
8
app/src/main/res/xml/autofill_config.xml
Normal file
8
app/src/main/res/xml/autofill_config.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:description="@string/autofill_description"
|
||||
android:accessibilityEventTypes="typeViewFocused|typeViewClicked|typeWindowStateChanged"
|
||||
android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows"
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:notificationTimeout="100"
|
||||
android:canRetrieveWindowContent="true"
|
||||
/>
|
|
@ -70,11 +70,30 @@
|
|||
android:summary="@string/pref_recursive_filter_hint"
|
||||
android:title="@string/pref_recursive_filter" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="Autofill">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="true"
|
||||
android:key="autofill_enable"
|
||||
android:title="@string/pref_autofill_enable_title"/>
|
||||
<Preference
|
||||
android:dependency="autofill_enable"
|
||||
android:key="autofill_apps"
|
||||
android:summary="@string/pref_autofill_apps_hint"
|
||||
android:title="@string/pref_autofill_apps_title"/>
|
||||
<CheckBoxPreference
|
||||
android:dependency="autofill_enable"
|
||||
android:defaultValue="true"
|
||||
android:key="autofill_default"
|
||||
android:summary="@string/pref_autofill_default_hint"
|
||||
android:title="@string/pref_autofill_default_title"/>
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="Misc">
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="false"
|
||||
android:key="clear_clipboard_20x"
|
||||
android:summary="@string/pref_clear_clipboard_hint"
|
||||
android:title="@string/pref_clear_clipboard" />
|
||||
android:title="@string/pref_clear_clipboard_title" />
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
Loading…
Reference in a new issue