aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Wong <wongma@protonmail.ch>2015-07-28 03:11:02 -0400
committerMatthew Wong <wongma@protonmail.ch>2015-08-14 17:36:44 -0400
commit02bfcf6c3f5ddd806dd9a92823926eced139842f (patch)
treed68ea46b5ce6993910d8a015e6a286a1f9ae9c9f
parenteced1dd314ba651f862396c340492c6f2d9da926 (diff)
Create app autofill service: a dialog pops up for all password fields & has a button to paste/set a password found in the store with name matching app's
-rw-r--r--app/src/main/AndroidManifest.xml16
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/AutofillActivity.java33
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/AutofillService.java200
-rw-r--r--app/src/main/res/layout/autofill_layout.xml12
-rw-r--r--app/src/main/res/values/strings.xml2
-rw-r--r--app/src/main/res/xml/autofill_config.xml8
6 files changed, 271 insertions, 0 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 2849695f..d45583c9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -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,19 @@
android:value="com.zeapo.pwdstore.PasswordStore" />
</activity>
+ <service android:name=".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=".AutofillActivity">
+
+ </activity>
+
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
</application>
diff --git a/app/src/main/java/com/zeapo/pwdstore/AutofillActivity.java b/app/src/main/java/com/zeapo/pwdstore/AutofillActivity.java
new file mode 100644
index 00000000..d7561029
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/AutofillActivity.java
@@ -0,0 +1,33 @@
+package com.zeapo.pwdstore;
+
+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;
+
+
+public class AutofillActivity extends AppCompatActivity {
+ public static final int REQUEST_CODE_DECRYPT_AND_VERIFY = 9913;
+ private boolean bound = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ Intent intent = getIntent();
+ PendingIntent pi = intent.getExtras().getParcelable("pending_intent");
+ try {
+ 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();
+ AutofillService.getService().decryptAndVerify();
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/AutofillService.java b/app/src/main/java/com/zeapo/pwdstore/AutofillService.java
new file mode 100644
index 00000000..ca738c67
--- /dev/null
+++ b/app/src/main/java/com/zeapo/pwdstore/AutofillService.java
@@ -0,0 +1,200 @@
+package com.zeapo.pwdstore;
+
+import android.accessibilityservice.AccessibilityService;
+import android.app.PendingIntent;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+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.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Button;
+import android.widget.Toast;
+
+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;
+ private static AutofillService service;
+
+ public final class Constants {
+ public static final String TAG = "Keychain";
+ }
+
+ @Override
+ protected void onServiceConnected() {
+ super.onServiceConnected();
+ serviceConnection = new OpenPgpServiceConnection(AutofillService.this, "org.sufficientlysecure.keychain");
+ serviceConnection.bindToService();
+ settings = PreferenceManager.getDefaultSharedPreferences(this);
+ service = this;
+ }
+
+ public static AutofillService getService() {
+ return service;
+ }
+
+ @Override
+ public void onAccessibilityEvent(AccessibilityEvent event) {
+ if (!event.isPassword() || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ return;
+ }
+ info = event.getSource();
+ PackageManager packageManager = getPackageManager();
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = packageManager.getApplicationInfo(event.getPackageName().toString(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ applicationInfo = null;
+ }
+ String appName = (applicationInfo != null ? packageManager.getApplicationLabel(applicationInfo) : "").toString();
+ if (appName.equals("OpenKeychain")) {
+ return;
+ }
+ items = recursiveFilter(appName, null);
+ if (items.isEmpty()) {
+ return;
+ }
+ ArrayList<CharSequence> itemNames = new ArrayList<>();
+ for (PasswordItem item : items) {
+ itemNames.add(item.toString());
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this, R.style.Theme_AppCompat_Light_Dialog_Alert);
+ builder.setNegativeButton("Cancel", null);
+ builder.setView(R.layout.autofill_layout);
+ final AlertDialog dialog = builder.create();
+
+ dialog.setTitle("Fill with Password Store");
+ 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.show();
+ ((Button) dialog.findViewById(R.id.button)).setText(itemNames.get(0).toString());
+ dialog.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ decryptAndVerify();
+ dialog.dismiss();
+ }
+ });
+ }
+
+ private ArrayList<PasswordItem> recursiveFilter(String filter, File dir) {
+ ArrayList<PasswordItem> items = new ArrayList<>();
+ if (!PasswordRepository.isInitialized()) {
+ return items;
+ }
+ ArrayList<PasswordItem> passwordItems = dir == null ?
+ PasswordRepository.getPasswords() :
+ PasswordRepository.getPasswords(dir);
+ for (PasswordItem item : passwordItems) {
+ if (item.getType() == PasswordItem.TYPE_CATEGORY) {
+ recursiveFilter(filter, item.getFile());
+ }
+ if (item.toString().toLowerCase().contains(filter.toLowerCase())) {
+ items.add(item);
+ }
+ }
+ return items;
+ }
+
+ @Override
+ public void onInterrupt() {
+
+ }
+
+ public void decryptAndVerify() {
+ 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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ // if the user focused on something else, take focus back
+ // but this will open another dialog...
+ info.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ Bundle args = new Bundle();
+ args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
+ passContent[0]);
+ info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
+ } else {
+ info.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
+ 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.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ 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;
+ }
+ }
+ }
+}
diff --git a/app/src/main/res/layout/autofill_layout.xml b/app/src/main/res/layout/autofill_layout.xml
new file mode 100644
index 00000000..0108160d
--- /dev/null
+++ b/app/src/main/res/layout/autofill_layout.xml
@@ -0,0 +1,12 @@
+<?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">
+
+ <Button
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/button"
+ android:layout_gravity="center"/>
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3a3d4061..825643bf 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -154,4 +154,6 @@
<string name="pwd_generate_button">Generate</string>
<string name="category_string">"Category: "</string>
+ <!-- Autofill -->
+ <string name="autofill_description">Auto-fills login fields.</string>
</resources>
diff --git a/app/src/main/res/xml/autofill_config.xml b/app/src/main/res/xml/autofill_config.xml
new file mode 100644
index 00000000..618442ab
--- /dev/null
+++ b/app/src/main/res/xml/autofill_config.xml
@@ -0,0 +1,8 @@
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+ android:description="@string/autofill_description"
+ android:accessibilityEventTypes="typeViewFocused"
+ android:accessibilityFlags="flagDefault"
+ android:accessibilityFeedbackType="feedbackGeneric"
+ android:notificationTimeout="100"
+ android:canRetrieveWindowContent="true"
+ /> \ No newline at end of file