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.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_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"
|
<application android:allowBackup="true" android:icon="@drawable/ic_launcher"
|
||||||
android:label="@string/app_name" android:theme="@style/AppTheme">
|
android:label="@string/app_name" android:theme="@style/AppTheme">
|
||||||
<activity android:name=".PasswordStore" android:label="@string/app_name"
|
<activity android:name=".PasswordStore" android:label="@string/app_name"
|
||||||
|
@ -42,6 +45,31 @@
|
||||||
android:value="com.zeapo.pwdstore.PasswordStore" />
|
android:value="com.zeapo.pwdstore.PasswordStore" />
|
||||||
</activity>
|
</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" />
|
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,11 @@ public class PasswordFragment extends Fragment{
|
||||||
|
|
||||||
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
((AppCompatActivity) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||||
} else {
|
} 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();
|
Bundle args = new Bundle();
|
||||||
args.putString("Path", PasswordRepository.getWorkTree().getAbsolutePath());
|
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);
|
plist.setArguments(args);
|
||||||
|
|
||||||
fragmentTransaction.addToBackStack("passlist");
|
fragmentTransaction.addToBackStack("passlist");
|
||||||
|
@ -531,4 +537,13 @@ public class PasswordStore extends AppCompatActivity {
|
||||||
})
|
})
|
||||||
.show();
|
.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;
|
package com.zeapo.pwdstore;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.accessibilityservice.AccessibilityServiceInfo;
|
||||||
import android.app.DialogFragment;
|
import android.app.DialogFragment;
|
||||||
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.preference.CheckBoxPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceFragment;
|
import android.preference.PreferenceFragment;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.app.AppCompatActivity;
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.view.accessibility.AccessibilityManager;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.zeapo.pwdstore.autofill.AutofillPreferenceActivity;
|
||||||
import com.zeapo.pwdstore.crypto.PgpHandler;
|
import com.zeapo.pwdstore.crypto.PgpHandler;
|
||||||
import com.zeapo.pwdstore.git.GitActivity;
|
import com.zeapo.pwdstore.git.GitActivity;
|
||||||
import com.zeapo.pwdstore.utils.PasswordRepository;
|
import com.zeapo.pwdstore.utils.PasswordRepository;
|
||||||
|
@ -32,6 +39,7 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class UserPreference extends AppCompatActivity {
|
public class UserPreference extends AppCompatActivity {
|
||||||
|
@ -182,6 +190,40 @@ public class UserPreference extends AppCompatActivity {
|
||||||
|
|
||||||
findPreference("pref_select_external").setOnPreferenceChangeListener(resetRepo);
|
findPreference("pref_select_external").setOnPreferenceChangeListener(resetRepo);
|
||||||
findPreference("git_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
|
@Override
|
||||||
|
@ -189,6 +231,10 @@ public class UserPreference extends AppCompatActivity {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
final SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
|
final SharedPreferences sharedPreferences = getPreferenceManager().getSharedPreferences();
|
||||||
findPreference("ssh_see_key").setEnabled(sharedPreferences.getBoolean("use_generated_key", false));
|
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();
|
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,
|
protected void onActivityResult(int requestCode, int resultCode,
|
||||||
Intent data) {
|
Intent data) {
|
||||||
if (resultCode == RESULT_OK) {
|
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="ssh_key_error_dialog_text">Zpráva : \n</string>
|
||||||
<string name="pref_recursive_filter">Rekurzivní filtrová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_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_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 -->
|
<!-- pwgen fragment -->
|
||||||
<string name="pwgen_generate">Generovat</string>
|
<string name="pwgen_generate">Generovat</string>
|
||||||
|
|
|
@ -117,8 +117,16 @@
|
||||||
<string name="ssh_key_error_dialog_text">Message : \n</string>
|
<string name="ssh_key_error_dialog_text">Message : \n</string>
|
||||||
<string name="pref_recursive_filter">Recursive filtering</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_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_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 -->
|
<!-- pwgen fragment -->
|
||||||
<string name="pwgen_generate">Generate</string>
|
<string name="pwgen_generate">Generate</string>
|
||||||
|
@ -154,4 +162,11 @@
|
||||||
<string name="pwd_generate_button">Generate</string>
|
<string name="pwd_generate_button">Generate</string>
|
||||||
<string name="category_string">"Category: "</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>
|
</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:summary="@string/pref_recursive_filter_hint"
|
||||||
android:title="@string/pref_recursive_filter" />
|
android:title="@string/pref_recursive_filter" />
|
||||||
</PreferenceCategory>
|
</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">
|
<PreferenceCategory android:title="Misc">
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
android:defaultValue="false"
|
android:defaultValue="false"
|
||||||
android:key="clear_clipboard_20x"
|
android:key="clear_clipboard_20x"
|
||||||
android:summary="@string/pref_clear_clipboard_hint"
|
android:summary="@string/pref_clear_clipboard_hint"
|
||||||
android:title="@string/pref_clear_clipboard" />
|
android:title="@string/pref_clear_clipboard_title" />
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
Loading…
Reference in a new issue