Browse Source

Merge branch 'issue-27' of isundil/pass into master

isundil 7 years ago
parent
commit
b09d24feab
26 changed files with 583 additions and 100 deletions
  1. 15 1
      app/src/main/AndroidManifest.xml
  2. 12 16
      app/src/main/java/info/knacki/pass/generator/ui/PasswordGeneratorWizard.java
  3. 0 3
      app/src/main/java/info/knacki/pass/git/BaseGitProtocol.java
  4. 193 0
      app/src/main/java/info/knacki/pass/services/AccessibilityService.java
  5. 70 0
      app/src/main/java/info/knacki/pass/services/AccessibilityView.java
  6. 16 17
      app/src/main/java/info/knacki/pass/services/InputService.java
  7. 41 8
      app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java
  8. 2 3
      app/src/main/java/info/knacki/pass/ui/MainActivity.java
  9. 5 5
      app/src/main/java/info/knacki/pass/ui/alertPrompt/ServiceAlertPrompt.java
  10. 5 5
      app/src/main/java/info/knacki/pass/ui/alertPrompt/ServiceAlertPromptGenerator.java
  11. 29 9
      app/src/main/java/info/knacki/pass/ui/passwordList/EditablePasswordListView.java
  12. 21 5
      app/src/main/java/info/knacki/pass/ui/passwordList/PasswordListView.java
  13. 3 3
      app/src/main/java/info/knacki/pass/ui/passwordPicker/PasswordPickerFactory.java
  14. 36 13
      app/src/main/java/info/knacki/pass/ui/widget/Checkbox.java
  15. 26 0
      app/src/main/res/layout/activity_accessibility.xml
  16. 6 1
      app/src/main/res/layout/activity_pass_list.xml
  17. 14 0
      app/src/main/res/layout/checkbox.xml
  18. 5 8
      app/src/main/res/layout/input.xml
  19. 43 0
      app/src/main/res/layout/password_generator_wizard.xml
  20. 10 1
      app/src/main/res/values-fr/lang.xml
  21. 6 0
      app/src/main/res/values/checkbox_attrs.xml
  22. 2 2
      app/src/main/res/values/dimens.xml
  23. 9 0
      app/src/main/res/values/lang.xml
  24. 2 0
      app/src/main/res/values/strings.xml
  25. 10 0
      app/src/main/res/xml/accessibility_service.xml
  26. 2 0
      app/src/main/res/xml/pref_general.xml

+ 15 - 1
app/src/main/AndroidManifest.xml

@@ -12,6 +12,9 @@
     <!-- For GPG keys -->
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
+    
+    <!-- Work with Accessibility service -->
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
 
     <application
         android:allowBackup="true"
@@ -22,7 +25,7 @@
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
         <service
-            android:name=".input.InputService"
+            android:name=".services.InputService"
             android:permission="android.permission.BIND_INPUT_METHOD">
             <intent-filter>
                 <action android:name="android.view.InputMethod" />
@@ -32,6 +35,17 @@
                 android:name="android.view.im"
                 android:resource="@xml/method" />
         </service>
+        <service
+            android:name=".services.AccessibilityService"
+            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/accessibility_service" />
+        </service>
 
         <activity
             android:name=".settings.ui.SettingsActivity"

+ 12 - 16
app/src/main/java/info/knacki/pass/generator/ui/PasswordGeneratorWizard.java

@@ -2,12 +2,12 @@ package info.knacki.pass.generator.ui;
 
 import android.content.Context;
 import android.support.v7.widget.AppCompatSpinner;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.LinearLayout;
 import android.widget.NumberPicker;
-import android.widget.TextView;
 
 import info.knacki.pass.R;
 import info.knacki.pass.generator.PasswordGenerator;
@@ -75,10 +75,11 @@ public class PasswordGeneratorWizard extends LinearLayout implements PasswordGen
     public PasswordGeneratorWizard(Context c) {
         super(c);
 
-        LinearLayout difficultyLayout = new LinearLayout(c);
+        LayoutInflater.from(c).inflate(R.layout.password_generator_wizard, this, true);
+
         ArrayAdapter fDifficultyAdapter = new ArrayAdapter<>(c, android.R.layout.simple_spinner_item, c.getResources().getStringArray(R.array.fDifficulty));
         fDifficultyAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        fDifficulty = new AppCompatSpinner(c);
+        fDifficulty = findViewById(R.id.difficulty);
         fDifficulty.setAdapter(fDifficultyAdapter);
         fDifficulty.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
             @Override
@@ -89,22 +90,17 @@ public class PasswordGeneratorWizard extends LinearLayout implements PasswordGen
             @Override
             public void onNothingSelected(AdapterView<?> adapterView) {}
         });
-        TextView tv = new TextView(c);
-        tv.setText(R.string.generate_difficulty);
-        difficultyLayout.setOrientation(HORIZONTAL);
-        difficultyLayout.addView(tv);
-        difficultyLayout.addView(fDifficulty);
-        addView(difficultyLayout);
-        fNum = new Checkbox(c, R.string.generate_number, fChangeListener, this);
-        fAlpha = new Checkbox(c, R.string.generate_alpha, fChangeListener, this);
-        fCapitalize = new Checkbox(c, R.string.generate_capitalize, fChangeListener, this);
-        fSpecial = new Checkbox(c, R.string.generate_special, fChangeListener, this);
-        fLength = new NumberPicker(c);
+
+        fNum = ((Checkbox) findViewById(R.id.generate_numbers)).SetOnCheckedChangeListener(fChangeListener);
+        fAlpha = ((Checkbox) findViewById(R.id.generate_alpha)).SetOnCheckedChangeListener(fChangeListener);
+        fCapitalize = ((Checkbox) findViewById(R.id.generate_capitalize)).SetOnCheckedChangeListener(fChangeListener);
+        fSpecial = ((Checkbox) findViewById(R.id.generate_special)).SetOnCheckedChangeListener(fChangeListener);
+
+        fLength = findViewById(R.id.length);
         fLength.setMinValue(4);
         fLength.setMaxValue(21);
         fLength.setOnValueChangedListener(fChangeListener);
-        addView(fLength);
-        setOrientation(VERTICAL);
+
         fDifficulty.setSelection(2, false);
         fChangeListener.setLocked(false);
     }

+ 0 - 3
app/src/main/java/info/knacki/pass/git/BaseGitProtocol.java

@@ -1,7 +1,5 @@
 package info.knacki.pass.git;
 
-import java.util.logging.Logger;
-
 import info.knacki.pass.git.entities.GitCommit;
 import info.knacki.pass.git.entities.GitRef;
 import info.knacki.pass.io.CharsetHelper;
@@ -11,7 +9,6 @@ import info.knacki.pass.settings.SettingsManager;
 
 public abstract class BaseGitProtocol implements GitInterface {
     protected final SettingsManager.Git fConfig;
-    private final static Logger log = Logger.getLogger(BaseGitProtocol.class.getName());
 
     BaseGitProtocol(SettingsManager.Git config) {
         fConfig = config;

+ 193 - 0
app/src/main/java/info/knacki/pass/services/AccessibilityService.java

@@ -0,0 +1,193 @@
+package info.knacki.pass.services;
+
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.support.annotation.RequiresApi;
+import android.view.Gravity;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.Toast;
+
+import java.io.File;
+import java.util.Date;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import info.knacki.pass.R;
+import info.knacki.pass.io.FileInterfaceFactory;
+import info.knacki.pass.io.OnResponseListener;
+import info.knacki.pass.ui.passwordPicker.PasswordPickerFactory;
+
+@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class AccessibilityService extends android.accessibilityservice.AccessibilityService {
+    private static final Logger log = Logger.getLogger(AccessibilityService.class.getName());
+    private final static long CANCEL_DELAY_SEC = 10;
+    private static AccessibilityService fInstance;
+    private boolean fManagingEvent;
+
+    private class LastCancellation {
+        public long lastCancelledTime;
+        public String lastCancelledPackage;
+
+        private LastCancellation() {
+            Clear();
+        }
+
+        public void Cancel(AccessibilityNodeInfo source) {
+            fLastCancellation.lastCancelledPackage = source.getPackageName().toString();
+            fLastCancellation.lastCancelledTime = new Date().getTime() / 1000;
+        }
+
+        public boolean IsCancelling(AccessibilityNodeInfo source, boolean clearCancelling) {
+            boolean cancelling = source.getPackageName().toString().equals(lastCancelledPackage) && (new Date().getTime() / 1000) -lastCancelledTime <= CANCEL_DELAY_SEC;
+            if (cancelling && clearCancelling)
+                this.Clear();
+            return cancelling;
+        }
+
+        public void Clear() {
+            this.lastCancelledPackage = null;
+            this.lastCancelledTime = 0;
+        }
+    }
+    protected LastCancellation fLastCancellation = new LastCancellation();
+
+    @Override
+    protected void onServiceConnected() {
+        super.onServiceConnected();
+        fInstance = this;
+        fManagingEvent = false;
+    }
+
+    @Override
+    public void onAccessibilityEvent(AccessibilityEvent event) {
+        synchronized (AccessibilityService.class) {
+            if (fManagingEvent || fLastCancellation.IsCancelling(event.getSource(), true))
+                return;
+            if (event.isPassword())
+                fManagingEvent = DisplayPasswordList(event.getSource());
+        }
+    }
+
+    private boolean DisplayPasswordList(final AccessibilityNodeInfo source) {
+        WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+                WindowManager.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.WRAP_CONTENT,
+                Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE,
+                0,
+                PixelFormat.TRANSLUCENT);
+        params.gravity = Gravity.START | Gravity.BOTTOM;
+
+        final AccessibilityView openWindow = new AccessibilityView(this);
+        openWindow.Init(this, new AccessibilityView.AccessibilityViewListener() {
+                    @Override
+                    public void OnPasswordClicked(File f) {
+                        new Handler(getMainLooper()).post(() -> {
+                            LoadFile(f, openWindow.getWindowToken(), source, () ->
+                                CloseOpenWindow(openWindow)
+                            );
+                        });
+                    }
+
+                    @Override
+                    public void cancel() {
+                        new Handler(getMainLooper()).post(() -> {
+                            fLastCancellation.Cancel(source);
+                            CloseOpenWindow(openWindow);
+                            fManagingEvent = false;
+                            SetFocus(source);
+                        });
+                    }
+                })
+                .AddCancelButton();
+
+        try {
+            WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
+            wm.addView(openWindow, params);
+            return true;
+        }
+        catch (WindowManager.BadTokenException e) {
+            Toast.makeText(this, getResources().getString(R.string.app_name) +": " +getResources().getString(R.string.unauthorized_draw_over), Toast.LENGTH_LONG).show();
+        }
+        return false;
+    }
+
+    private void CloseOpenWindow(AccessibilityView openWindow) {
+        if (openWindow != null) {
+            WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
+            wm.removeViewImmediate(openWindow);
+        }
+    }
+
+    private void LoadFile(File f, IBinder windowToken, AccessibilityNodeInfo source, Runnable done) {
+        fManagingEvent = false;
+        log.info("Loading " +f.getName());
+        FileInterfaceFactory.GetFileInterface(this, PasswordPickerFactory.GetPasswordPicker(this, windowToken), f).ReadFile(new OnResponseListener<String>() {
+            @Override
+            public void OnResponse(String result) {
+                done.run();
+                SendPassword(source, result);
+            }
+
+            @Override
+            public void OnError(String msg, Throwable e) {
+                done.run();
+                Toast.makeText(AccessibilityService.this, msg, Toast.LENGTH_LONG).show();
+                log.log(Level.SEVERE, msg, e);
+            }
+        });
+    }
+
+    private void SendPassword(AccessibilityNodeInfo accessibilityNode, String password) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            Bundle passContent = new Bundle();
+            passContent.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, password);
+            try {
+                accessibilityNode.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, passContent);
+            } catch (Throwable e) {
+                LegacySendPassword(accessibilityNode, password);
+            }
+        } else {
+            LegacySendPassword(accessibilityNode, password);
+        }
+        fManagingEvent = false;
+    }
+
+    private void SetFocus(AccessibilityNodeInfo source) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+            source.performAction(AccessibilityNodeInfo.ACTION_CLICK);
+        }
+    }
+
+    private void LegacySendPassword(AccessibilityNodeInfo accessibilityNode, String password) {
+        final ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
+        final ClipData passClip = ClipData.newPlainText("password", password);
+        final ClipData prev = clipboard.getPrimaryClip();
+
+        clipboard.setPrimaryClip(passClip);
+        accessibilityNode.performAction(AccessibilityNodeInfo.ACTION_PASTE);
+        clipboard.setPrimaryClip(prev == null ? ClipData.newPlainText("", "") : prev);
+    }
+
+    @Override
+    public void onInterrupt() {
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        fInstance = null;
+        return super.onUnbind(intent);
+    }
+
+    public static boolean IsRunning() {
+        return fInstance != null;
+    }
+}

+ 70 - 0
app/src/main/java/info/knacki/pass/services/AccessibilityView.java

@@ -0,0 +1,70 @@
+package info.knacki.pass.services;
+
+import android.content.Context;
+import android.support.v7.widget.LinearLayoutCompat;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import info.knacki.pass.R;
+import info.knacki.pass.io.PathUtils;
+import info.knacki.pass.ui.passwordList.PasswordClickListener;
+import info.knacki.pass.ui.passwordList.PasswordListView;
+
+public class AccessibilityView extends LinearLayoutCompat {
+    public interface AccessibilityViewListener extends PasswordClickListener {
+        void cancel();
+    }
+
+    private PasswordListView fPasswordListView;
+    private AccessibilityViewListener fAccessibilityViewListener;
+
+    public AccessibilityView Init(Context ctx, final AccessibilityViewListener listener) {
+        fPasswordListView = findViewById(R.id.password_list);
+        fPasswordListView.Init(listener, PathUtils.GetPassDir(ctx));
+        fAccessibilityViewListener = listener;
+        findViewById(R.id.cancel).setOnClickListener(v -> {
+            if (fAccessibilityViewListener != null)
+                fAccessibilityViewListener.cancel();
+        });
+        return this;
+    }
+
+    public AccessibilityView AddCancelButton() {
+        findViewById(R.id.cancel).setVisibility(VISIBLE);
+        return this;
+    }
+
+    public AccessibilityView(Context ctx) {
+        super(ctx);
+        setLayoutParams(new LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        LayoutInflater.from(ctx).inflate(R.layout.activity_accessibility, this);
+        findViewById(R.id.cancel).setVisibility(INVISIBLE);
+    }
+
+    public AccessibilityView(Context ctx, AttributeSet attrs) {
+        super(ctx, attrs);
+        setLayoutParams(new LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        LayoutInflater.from(ctx).inflate(R.layout.activity_accessibility, this);
+        findViewById(R.id.cancel).setVisibility(INVISIBLE);
+    }
+
+    public AccessibilityView(Context ctx, AttributeSet attrs, int defStyle) {
+        super(ctx, attrs, defStyle);
+        setLayoutParams(new LinearLayoutCompat.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+        LayoutInflater.from(ctx).inflate(R.layout.activity_accessibility, this);
+        findViewById(R.id.cancel).setVisibility(INVISIBLE);
+    }
+
+    public void reset() {
+        fPasswordListView.reset();
+    }
+
+    public void refresh() {
+        fPasswordListView.refresh();
+    }
+
+    public String GetCurrentDir() {
+        return fPasswordListView.fCurrentDir;
+    }
+}

+ 16 - 17
app/src/main/java/info/knacki/pass/input/InputService.java → app/src/main/java/info/knacki/pass/services/InputService.java

@@ -1,4 +1,4 @@
-package info.knacki.pass.input;
+package info.knacki.pass.services;
 
 import android.annotation.SuppressLint;
 import android.content.Context;
@@ -9,9 +9,9 @@ import android.os.Handler;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.Window;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
-import android.widget.ScrollView;
 import android.widget.Toast;
 
 import java.io.File;
@@ -26,17 +26,14 @@ import info.knacki.pass.io.FileInterfaceFactory;
 import info.knacki.pass.io.FileUtils;
 import info.knacki.pass.io.IFileInterface;
 import info.knacki.pass.io.OnResponseListener;
-import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.ui.MainActivity;
 import info.knacki.pass.ui.alertPrompt.ServiceAlertPromptGenerator;
 import info.knacki.pass.ui.alertPrompt.views.SimpleTextEditWithKeyboard;
-import info.knacki.pass.ui.passwordList.PasswordClickListener;
-import info.knacki.pass.ui.passwordList.PasswordListView;
 import info.knacki.pass.ui.passwordPicker.PasswordPickerFactory;
 
-public class InputService extends InputMethodService implements PasswordClickListener {
-    protected PasswordListView fPasswordListView;
+public class InputService extends InputMethodService implements AccessibilityView.AccessibilityViewListener {
+    protected AccessibilityView fPasswordListView;
     private static final Logger log = Logger.getLogger(InputService.class.getName());
     protected View fInputView;
 
@@ -73,7 +70,8 @@ public class InputService extends InputMethodService implements PasswordClickLis
                 log.log(Level.SEVERE, "Cannot get Input method service");
                 return;
             }
-            if (!service.switchToLastInputMethod(getWindow().getWindow().getAttributes().token))
+            Window win = getWindow().getWindow();
+            if (win != null && !service.switchToLastInputMethod(win.getAttributes().token))
                 PromptInputService();
         }
     }
@@ -82,11 +80,7 @@ public class InputService extends InputMethodService implements PasswordClickLis
     @Override
     public View onCreateInputView() {
         fInputView = LayoutInflater.from(this).inflate(R.layout.input, null, false);
-        fPasswordListView = new PasswordListView<>(this, PathUtils.GetPassDir(this));
-        ((ScrollView)fInputView.findViewById(R.id.passwordListContainer)).addView(fPasswordListView);
-        fInputView.findViewById(R.id.prevButton).setOnClickListener(view -> {
-            SwitchToPreviousInputMethod();
-        });
+        fInputView.findViewById(R.id.prevButton).setOnClickListener(view -> SwitchToPreviousInputMethod());
         fInputView.findViewById(R.id.backspaceButton).setOnTouchListener(new View.OnTouchListener() {
             private final static int INIT_DELAY = 650;
             private final static int DELAY = 250;
@@ -124,11 +118,12 @@ public class InputService extends InputMethodService implements PasswordClickLis
         });
         fInputView.findViewById(R.id.generateButton).setOnClickListener(view -> CreatePassword());
         fInputView.findViewById(R.id.openAppButton).setOnClickListener(view -> startActivity(new Intent(InputService.this, MainActivity.class)));
+        fPasswordListView = ((AccessibilityView) fInputView.findViewById(R.id.accessibility)).Init(this, this);
         return fInputView;
     }
 
     protected void CreatePassword() {
-        new ServiceAlertPromptGenerator(fInputView).Generate(this)
+        new ServiceAlertPromptGenerator(fInputView.getWindowToken()).Generate(this)
                 .setCancelable(true)
                 .setView(new SimpleTextEditWithKeyboard(this))
                 .setPositiveButton(R.string.add, (dialogInterface, view) -> {
@@ -137,7 +132,7 @@ public class InputService extends InputMethodService implements PasswordClickLis
                         Toast.makeText(InputService.this, "Error: Empty file name", Toast.LENGTH_LONG).show();
                         return;
                     }
-                    File f = new File(fPasswordListView.fCurrentDir +"/" +filename +FileInterfaceFactory.GetExtension(SettingsManager.GetDefaultEncryptionType(InputService.this)));
+                    File f = new File(fPasswordListView.GetCurrentDir() +"/" +filename +FileInterfaceFactory.GetExtension(SettingsManager.GetDefaultEncryptionType(InputService.this)));
                     try {
                         FileUtils.Touch(f);
                         fPasswordListView.refresh();
@@ -157,7 +152,7 @@ public class InputService extends InputMethodService implements PasswordClickLis
     protected void GeneratePassword(final File f) {
         final PasswordGeneratorWizard wiz = new PasswordGeneratorWizard(this);
 
-        new ServiceAlertPromptGenerator(fInputView).Generate(this)
+        new ServiceAlertPromptGenerator(fInputView.getWindowToken()).Generate(this)
                 .setCancelable(true)
                 .setView(wiz)
                 .setPositiveButton(R.string.add, (dialogInterface, view) -> {
@@ -196,7 +191,7 @@ public class InputService extends InputMethodService implements PasswordClickLis
 
     @Override
     public void OnPasswordClicked(File f) {
-        FileInterfaceFactory.GetFileInterface(this, PasswordPickerFactory.GetPasswordPicker(this, fInputView), f).ReadFile(new OnResponseListener<String>() {
+        FileInterfaceFactory.GetFileInterface(this, PasswordPickerFactory.GetPasswordPicker(this, fInputView.getWindowToken()), f).ReadFile(new OnResponseListener<String>() {
             @Override
             public void OnResponse(String passwordContent) {
                 new android.os.Handler(getMainLooper()).post(() -> sendPassword(passwordContent));
@@ -209,4 +204,8 @@ public class InputService extends InputMethodService implements PasswordClickLis
             }
         });
     }
+
+    @Override
+    public void cancel() {
+    }
 }

+ 41 - 8
app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java

@@ -16,6 +16,7 @@ import android.preference.Preference;
 import android.preference.PreferenceActivity;
 import android.preference.PreferenceFragment;
 import android.preference.SwitchPreference;
+import android.provider.Settings;
 import android.support.v4.app.ShareCompat;
 import android.support.v4.content.FileProvider;
 import android.support.v7.app.ActionBar;
@@ -49,6 +50,7 @@ import info.knacki.pass.io.OnResponseListener;
 import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.io.pgp.GPGStorageEngine;
 import info.knacki.pass.io.pgp.GPGUtil;
+import info.knacki.pass.services.AccessibilityService;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.ui.GitPullActivity;
 import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
@@ -138,8 +140,6 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
     }
 
     public static class GeneralPreferenceFragment extends PreferenceFragment {
-        public static final String INPUT_METHOD_SETTINGS = "android.settings.INPUT_METHOD_SETTINGS";
-
         @Override
         public void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
@@ -147,15 +147,38 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             setHasOptionsMenu(true);
 
             findPreference(getResources().getString(R.string.id_softSettings)).setOnPreferenceClickListener(preference -> {
-                startActivity(new Intent(INPUT_METHOD_SETTINGS));
+                startActivity(new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS));
+                return true;
+            });
+            findPreference(getResources().getString(R.string.id_accessibility_settings)).setOnPreferenceClickListener(preference -> {
+                startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
                 return true;
             });
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+                findPreference(getResources().getString(R.string.id_draw_over_settings)).setOnPreferenceClickListener(preference -> {
+                        startActivity(new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION));
+                    return true;
+                });
+            } else {
+                getPreferenceScreen().removePreference(findPreference(getResources().getString(R.string.id_draw_over_settings)));
+            }
         }
 
         @Override
         public void onResume() {
             super.onResume();
             findPreference(getResources().getString(R.string.id_softSettings)).setSummary(IsKeyboardEnabled() ? R.string.pref_summary_keyboard_enabled : R.string.pref_summary_enable_keyboard);
+            if (IsAccessibilityEnabled()) {
+                findPreference(getResources().getString(R.string.id_accessibility_settings)).setSummary(R.string.pref_summary_accessibility_enabled);
+                Preference p = findPreference(getResources().getString(R.string.id_draw_over_settings));
+                if (p != null)
+                    p.setSummary(IsDrawOverEnabled() ? R.string.pref_summary_draw_over_enabled : R.string.pref_summary_draw_over_enable);
+            } else {
+                findPreference(getResources().getString(R.string.id_accessibility_settings)).setSummary(R.string.pref_summary_accessibility_disabled);
+                Preference p = findPreference(getResources().getString(R.string.id_draw_over_settings));
+                if (p != null)
+                    p.setSummary(R.string.pref_summary_draw_over_unneeded);
+            }
         }
 
         @Override
@@ -168,7 +191,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             return super.onOptionsItemSelected(item);
         }
 
-        public boolean IsKeyboardEnabled() {
+        private boolean IsKeyboardEnabled() {
             InputMethodManager inputManager = (InputMethodManager) getActivity().getSystemService(INPUT_METHOD_SERVICE);
 
             for (InputMethodInfo i: inputManager.getEnabledInputMethodList())
@@ -176,6 +199,16 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                     return true;
             return false;
         }
+
+        private boolean IsAccessibilityEnabled() {
+            return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && AccessibilityService.IsRunning();
+        }
+
+        private boolean IsDrawOverEnabled() {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
+                return Settings.canDrawOverlays(getActivity());
+            return true;
+        }
     }
 
     public static class EncryptionPreferenceFragment extends PreferenceFragment {
@@ -361,7 +394,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             reload();
         }
 
-        private final void CreateBranch(String branchName) {
+        private void CreateBranch(String branchName) {
             if (branchName.equals(""))
                 return;
             GitPullActivity.StartCreateBranchActivity(getActivity(), branchName);
@@ -535,11 +568,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             SettingsManager.Git git = (SettingsManager.Git) SettingsManager.GetVCS(getActivity());
             if (in != null) {
                 try {
-                    File fout = new File(PathUtils.GetPrivateRSAKey(getActivity()));
-                    if (!fout.exists() && !fout.createNewFile()) {
+                    File fOut = new File(PathUtils.GetPrivateRSAKey(getActivity()));
+                    if (!fOut.exists() && !fOut.createNewFile()) {
                         throw new FileNotFoundException();
                     }
-                    FileUtils.pipe(in, new FileOutputStream(fout));
+                    FileUtils.pipe(in, new FileOutputStream(fOut));
                 } catch (IOException e) {
                     log.log(Level.SEVERE, "Cannot import private key: " + e.getMessage(), e);
                     return false;

+ 2 - 3
app/src/main/java/info/knacki/pass/ui/MainActivity.java

@@ -9,7 +9,6 @@ import android.os.Bundle;
 import android.support.v7.app.AppCompatActivity;
 import android.view.Menu;
 import android.view.MenuItem;
-import android.widget.ScrollView;
 import android.widget.Toast;
 
 import java.io.File;
@@ -120,8 +119,8 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
         setContentView(R.layout.activity_pass_list);
 
         setTitle(R.string.title);
-        fPasswordListView = new EditablePasswordListView<>(this, PathUtils.GetPassDir(this), false);
-        ((ScrollView)findViewById(R.id.passwordListContainer)).addView(fPasswordListView);
+        fPasswordListView = findViewById(R.id.password_list);
+        fPasswordListView.Init(this, PathUtils.GetPassDir(this), false);
 
         requestPermissions();
     }

+ 5 - 5
app/src/main/java/info/knacki/pass/ui/alertPrompt/ServiceAlertPrompt.java

@@ -1,18 +1,18 @@
 package info.knacki.pass.ui.alertPrompt;
 
 import android.content.Context;
-import android.view.View;
+import android.os.IBinder;
 import android.view.Window;
 import android.view.WindowManager;
 
 import info.knacki.pass.R;
 
 public class ServiceAlertPrompt extends AlertPrompt {
-    protected final View fInputView;
+    protected final IBinder fWindowToken;
 
-    ServiceAlertPrompt(Context c, View inputView) {
+    ServiceAlertPrompt(Context c, IBinder windowToken) {
         super(c);
-        fInputView = inputView;
+        fWindowToken = windowToken;
     }
 
     @Override
@@ -20,7 +20,7 @@ public class ServiceAlertPrompt extends AlertPrompt {
         final Window window = fDialog.getWindow();
         if (window != null) {
             WindowManager.LayoutParams lay = window.getAttributes();
-            lay.token = fInputView.getWindowToken();
+            lay.token = fWindowToken;
             lay.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
             window.setAttributes(lay);
             window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

+ 5 - 5
app/src/main/java/info/knacki/pass/ui/alertPrompt/ServiceAlertPromptGenerator.java

@@ -1,17 +1,17 @@
 package info.knacki.pass.ui.alertPrompt;
 
 import android.content.Context;
-import android.view.View;
+import android.os.IBinder;
 
 public class ServiceAlertPromptGenerator extends AlertPromptGenerator {
-    private final View fInputView;
+    private final IBinder fWindowToken;
 
-    public ServiceAlertPromptGenerator(View inputView) {
-        fInputView = inputView;
+    public ServiceAlertPromptGenerator(IBinder windowToken) {
+        fWindowToken = windowToken;
     }
 
     @Override
     public AlertPrompt Generate(Context ctx) {
-        return new ServiceAlertPrompt(ctx, fInputView);
+        return new ServiceAlertPrompt(ctx, fWindowToken);
     }
 }

+ 29 - 9
app/src/main/java/info/knacki/pass/ui/passwordList/EditablePasswordListView.java

@@ -1,6 +1,8 @@
 package info.knacki.pass.ui.passwordList;
 
 import android.app.Activity;
+import android.content.Context;
+import android.util.AttributeSet;
 import android.view.MenuItem;
 import android.widget.PopupMenu;
 
@@ -9,13 +11,31 @@ import java.io.File;
 import info.knacki.pass.R;
 import info.knacki.pass.io.PathUtils;
 
-public class EditablePasswordListView<T extends Activity & PasswordEditListener> extends PasswordListView<T> implements PopupMenu.OnMenuItemClickListener  {
+public class EditablePasswordListView extends PasswordListView implements PopupMenu.OnMenuItemClickListener  {
     private PasswordView fSelectedPassword;
     private boolean fDisplayHiddenFiles;
 
-    public EditablePasswordListView(T ctx, String rootPath, boolean displayHidden) {
-        super(ctx, rootPath);
+    public EditablePasswordListView(Context ctx) {
+        super(ctx);
+    }
+
+    public EditablePasswordListView(Context ctx, AttributeSet attrs) {
+        super(ctx, attrs);
+    }
+
+    public EditablePasswordListView(Context ctx, AttributeSet attrs, int style) {
+        super(ctx, attrs, style);
+    }
+
+    public EditablePasswordListView(Activity ctx, PasswordEditListener editListener, String rootPath, boolean displayHidden) {
+        super(ctx);
+        Init(editListener, rootPath, displayHidden);
+    }
+
+    public EditablePasswordListView Init(PasswordEditListener editListener, String rootPath, boolean displayHidden) {
+        super.Init(editListener, rootPath);
         fDisplayHiddenFiles = displayHidden;
+        return this;
     }
 
     public void SetDisplayHiddenFiles(boolean displayHidden) {
@@ -39,7 +59,7 @@ public class EditablePasswordListView<T extends Activity & PasswordEditListener>
             return;
         pv.setOnLongClickListener(v -> {
             fSelectedPassword = pv;
-            PopupMenu menu = new PopupMenu(fClickListener, pv);
+            PopupMenu menu = new PopupMenu(fContext, pv);
             menu.getMenuInflater().inflate(GetMenuForFile(file), menu.getMenu());
             menu.setOnMenuItemClickListener(EditablePasswordListView.this);
             menu.show();
@@ -61,22 +81,22 @@ public class EditablePasswordListView<T extends Activity & PasswordEditListener>
         switch (item.getItemId()) {
             case R.id.remove:
                 if (fSelectedPassword.IsDir()) {
-                    fClickListener.OnRemoveDirectory(new File(fCurrentDir + "/" + fSelectedPassword.fFullName));
+                    ((PasswordEditListener) fClickListener).OnRemoveDirectory(new File(fCurrentDir + "/" + fSelectedPassword.fFullName));
                 } else {
-                    fClickListener.OnRemovePassword(new File(fCurrentDir + "/" + fSelectedPassword.fFullName));
+                    ((PasswordEditListener) fClickListener).OnRemovePassword(new File(fCurrentDir + "/" + fSelectedPassword.fFullName));
                 }
                 break;
 
             case R.id.restore:
-                fClickListener.OnRestorePassword(new File(fCurrentDir + "/" + fSelectedPassword.fFullName));
+                ((PasswordEditListener) fClickListener).OnRestorePassword(new File(fCurrentDir + "/" + fSelectedPassword.fFullName));
                 break;
 
             case R.id.encryption_information:
-                fClickListener.OnEncryptionInformation(new File(fCurrentDir +"/" +fSelectedPassword.fFullName));
+                ((PasswordEditListener) fClickListener).OnEncryptionInformation(new File(fCurrentDir +"/" +fSelectedPassword.fFullName));
                 break;
 
             case R.id.clipboard:
-                fClickListener.OnCopyToClipboard(new File(fCurrentDir +"/" +fSelectedPassword.fFullName));
+                ((PasswordEditListener) fClickListener).OnCopyToClipboard(new File(fCurrentDir +"/" +fSelectedPassword.fFullName));
                 break;
 
             default:

+ 21 - 5
app/src/main/java/info/knacki/pass/ui/passwordList/PasswordListView.java

@@ -2,6 +2,7 @@ package info.knacki.pass.ui.passwordList;
 
 import android.content.Context;
 import android.support.annotation.NonNull;
+import android.util.AttributeSet;
 import android.widget.LinearLayout;
 
 import java.io.File;
@@ -11,10 +12,11 @@ import java.util.TreeSet;
 import info.knacki.pass.io.FileUtils;
 import info.knacki.pass.io.PathUtils;
 
-public class PasswordListView<T extends Context & PasswordClickListener> extends LinearLayout {
-    public final String fRootPath;
+public class PasswordListView extends LinearLayout {
+    public String fRootPath;
     public String fCurrentDir;
-    protected final T fClickListener;
+    protected PasswordClickListener fClickListener;
+    protected final Context fContext;
 
     class FileIdentity implements Comparable<FileIdentity> {
         final boolean fIsDir;
@@ -74,9 +76,23 @@ public class PasswordListView<T extends Context & PasswordClickListener> extends
         return pv;
     }
 
-    public PasswordListView(final T ctx, String rootPath) {
+    public PasswordListView(final Context ctx) {
         super(ctx);
-        fClickListener = ctx;
+        fContext = ctx;
+    }
+
+    public PasswordListView(Context ctx, AttributeSet attrs) {
+        super(ctx, attrs);
+        fContext = ctx;
+    }
+
+    public PasswordListView(Context ctx, AttributeSet attrs, int style) {
+        super(ctx, attrs, style);
+        fContext = ctx;
+    }
+
+    public void Init(PasswordClickListener clickListener, String rootPath) {
+        fClickListener = clickListener;
         File rootDir = new File(rootPath);
         if (!rootDir.exists())
             FileUtils.MkDir(rootDir, true);

+ 3 - 3
app/src/main/java/info/knacki/pass/ui/passwordPicker/PasswordPickerFactory.java

@@ -3,7 +3,7 @@ package info.knacki.pass.ui.passwordPicker;
 import android.app.Service;
 import android.content.Context;
 import android.os.Build;
-import android.view.View;
+import android.os.IBinder;
 
 import info.knacki.pass.io.FileInterfaceFactory;
 import info.knacki.pass.settings.SettingsManager;
@@ -19,8 +19,8 @@ public class PasswordPickerFactory {
         return new PasswordPicker(ctx, generator);
     }
 
-    public static FileInterfaceFactory.PasswordGetter GetPasswordPicker(Service ctx, View v) {
-        final AlertPromptGenerator generator = new ServiceAlertPromptGenerator(v);
+    public static FileInterfaceFactory.PasswordGetter GetPasswordPicker(Service ctx, IBinder windowToken) {
+        final AlertPromptGenerator generator = new ServiceAlertPromptGenerator(windowToken);
 
         if (SettingsManager.IsFingerprintEnabled(ctx) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
             return new FingerprintPicker(ctx, generator);

+ 36 - 13
app/src/main/java/info/knacki/pass/ui/widget/Checkbox.java

@@ -4,33 +4,56 @@ import android.annotation.SuppressLint;
 import android.content.Context;
 import android.support.v7.widget.AppCompatImageView;
 import android.support.v7.widget.AppCompatTextView;
-import android.view.ViewGroup;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
 import android.widget.Checkable;
 import android.widget.LinearLayout;
 
+import info.knacki.pass.R;
+
 @SuppressLint("ViewConstructor")
 public class Checkbox extends LinearLayout implements Checkable {
     protected final AppCompatImageView fCheckbox;
     protected final AppCompatTextView fTextView;
-    protected final OnCheckedChangeListener fCheckedChangeListener;
+    protected OnCheckedChangeListener fCheckedChangeListener;
     protected boolean fChecked;
 
     public interface OnCheckedChangeListener {
         void onCheckedChanged();
     }
 
-    public Checkbox(Context context, int text, OnCheckedChangeListener onCheckedChangeListener, ViewGroup parent) {
-        super(context);
-        setOrientation(HORIZONTAL);
-        fCheckbox = new AppCompatImageView(context);
-        fTextView = new AppCompatTextView(context);
-        addView(fCheckbox);
-        addView(fTextView);
-        fTextView.setText(text);
-        parent.addView(this);
-        setOnClickListener(view -> toggle());
-        setChecked(false);
+    public Checkbox(Context ctx) {
+        super(ctx);
+        LayoutInflater.from(ctx).inflate(R.layout.checkbox, this, true);
+        fCheckbox = findViewById(R.id.checkbox_check);
+        fTextView = findViewById(R.id.checkbox_label);
+    }
+
+    public Checkbox(Context ctx, AttributeSet attrs) {
+        super(ctx, attrs);
+        LayoutInflater.from(ctx).inflate(R.layout.checkbox, this, true);
+        fCheckbox = findViewById(R.id.checkbox_check);
+        fTextView = findViewById(R.id.checkbox_label);
+
+        final String textAttribute = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.Checkbox, 0, 0).getString(R.styleable.Checkbox_text);
+        if (textAttribute != null)
+            fTextView.setText(textAttribute);
+    }
+
+    public Checkbox(Context ctx, AttributeSet attrs, int style) {
+        super(ctx, attrs, style);
+        LayoutInflater.from(ctx).inflate(R.layout.checkbox, this, true);
+        fCheckbox = findViewById(R.id.checkbox_check);
+        fTextView = findViewById(R.id.checkbox_label);
+
+        final String textAttribute = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.Checkbox, 0, 0).getString(R.styleable.Checkbox_text);
+        if (textAttribute != null)
+            fTextView.setText(textAttribute);
+    }
+
+    public Checkbox SetOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) {
         fCheckedChangeListener = onCheckedChangeListener;
+        return this;
     }
 
     private void SetIcon() {

+ 26 - 0
app/src/main/res/layout/activity_accessibility.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.LinearLayoutCompat xmlns:tools="http://schemas.android.com/tools"
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:background="@color/inputBackground"
+    android:orientation="vertical"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    tools:context="info.knacki.pass.services.AccessibilityView">
+    <ScrollView
+        android:id="@+id/passwordListContainer"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:minHeight="@dimen/keyboard_min_height"
+        android:maxHeight="@dimen/keyboard_max_height">
+        <info.knacki.pass.ui.passwordList.PasswordListView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/password_list"/>
+    </ScrollView>
+    <android.support.v7.widget.AppCompatButton
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/cancel"
+        android:text="@string/cancel"
+        style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"/>
+</android.support.v7.widget.LinearLayoutCompat>

+ 6 - 1
app/src/main/res/layout/activity_pass_list.xml

@@ -12,7 +12,12 @@
     <ScrollView
         android:id="@+id/passwordListContainer"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="match_parent">
+        <info.knacki.pass.ui.passwordList.EditablePasswordListView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/password_list"/>
+    </ScrollView>
 </LinearLayout>
 
 </android.support.constraint.ConstraintLayout>

+ 14 - 0
app/src/main/res/layout/checkbox.xml

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.LinearLayoutCompat
+    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:orientation="horizontal">
+    <android.support.v7.widget.AppCompatImageView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/checkbox_check"/>
+    <android.support.v7.widget.AppCompatTextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:id="@+id/checkbox_label"/>
+</android.support.v7.widget.LinearLayoutCompat>

+ 5 - 8
app/src/main/res/layout/input.xml

@@ -1,17 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:minHeight="@dimen/keyboard_min_height"
-    android:maxHeight="@dimen/keyboard_max_height"
-    tools:context=".ui.MainActivity"
     android:background="@color/inputBackground">
 
 <LinearLayout
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:orientation="vertical">
+    android:orientation="vertical"
+    android:id="@+id/container">
     <LinearLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -73,10 +70,10 @@
         android:layout_width="match_parent"
         android:layout_height="1dp"
         android:background="@color/colorSeparator"/>
-    <ScrollView
-        android:id="@+id/passwordListContainer"
+    <info.knacki.pass.services.AccessibilityView
+        android:id="@+id/accessibility"
         android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+        android:layout_height="wrap_content"/>
 </LinearLayout>
 
 </android.support.constraint.ConstraintLayout>

+ 43 - 0
app/src/main/res/layout/password_generator_wizard.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.v7.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    xmlns:knacki="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical">
+    <android.support.v7.widget.LinearLayoutCompat
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <android.support.v7.widget.AppCompatTextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content" android:text="@string/generate_difficulty"/>
+        <android.support.v7.widget.AppCompatSpinner
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/difficulty"/>
+    </android.support.v7.widget.LinearLayoutCompat>
+    <info.knacki.pass.ui.widget.Checkbox
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/generate_numbers"
+        knacki:text="@string/generate_number"/>
+    <info.knacki.pass.ui.widget.Checkbox
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/generate_alpha"
+        knacki:text="@string/generate_alpha"/>
+    <info.knacki.pass.ui.widget.Checkbox
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/generate_capitalize"
+        knacki:text="@string/generate_capitalize"/>
+    <info.knacki.pass.ui.widget.Checkbox
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/generate_special"
+        knacki:text="@string/generate_special"/>
+    <NumberPicker
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:id="@+id/length"/>
+</android.support.v7.widget.LinearLayoutCompat>

+ 10 - 1
app/src/main/res/values-fr/lang.xml

@@ -56,8 +56,13 @@
         <item>Personalisé</item>
     </array>
     <string name="new_branch">Nouvelle branche</string>
-    <string name="pref_summary_keyboard_enabled"> </string>
+    <string name="pref_summary_keyboard_enabled">Clavier actif</string>
     <string name="pref_summary_enable_keyboard">Autoriser le clavier pour la saisie des mots de passes</string>
+    <string name="pref_summary_accessibility_enabled">Service d\'accessibilité actif</string>
+    <string name="pref_summary_accessibility_disabled">Service d\'accessibilité désactivé</string>
+    <string name="pref_summary_draw_over_enabled">Permission de se superposer aux autres applis accordée</string>
+    <string name="pref_summary_draw_over_unneeded">Uniquement utilisé par le service d\'accessibilité</string>
+    <string name="pref_summary_draw_over_enable">Accorder la permission de se superposer aux autres applis</string>
     <string name="pref_gpg_title_username_mail">Username</string>
     <string name="pref_gpg_username">Username</string>
     <string name="pref_gpg_user_mail">Mail Address</string>
@@ -110,4 +115,8 @@
     <string name="change">Changer</string>
     <string name="change_encryption_method">Changer la méthode de chiffrement</string>
     <string name="trash">Poubelle</string>
+    <string name="accessibility_service_description">Detection des formulaires contenant des champs de mot de passe.</string>
+    <string name="unauthorized_draw_over">Vous devez autoriser l\'affichage par dessus d\'autres applications</string>
+    <string name="pref_title_system_accessibility_settings">Paramètres d\'accessibilité</string>
+    <string name="pref_title_system_draw_over">Gerer l\'affichage par dessus les autres applications</string>
 </resources>

+ 6 - 0
app/src/main/res/values/checkbox_attrs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <declare-styleable name="Checkbox">
+        <attr name="text" format="string" />
+    </declare-styleable>
+</resources>

+ 2 - 2
app/src/main/res/values/dimens.xml

@@ -2,7 +2,7 @@
 <resources>
     <dimen name="keyboard_key_height">32dp</dimen>
     <dimen name="big_icon_height">64dp</dimen>
-    <dimen name="keyboard_min_height">600px</dimen>
-    <dimen name="keyboard_max_height">664px</dimen>
+    <dimen name="keyboard_min_height">550px</dimen>
+    <dimen name="keyboard_max_height">614px</dimen>
     <dimen name="fab_margin">16dp</dimen>
 </resources>

+ 9 - 0
app/src/main/res/values/lang.xml

@@ -58,6 +58,11 @@
     <string name="new_branch">New branch</string>
     <string name="pref_summary_keyboard_enabled">Keyboard is enabled</string>
     <string name="pref_summary_enable_keyboard">Allow pass keyboard</string>
+    <string name="pref_summary_accessibility_enabled">Accessibility service is enabled</string>
+    <string name="pref_summary_accessibility_disabled">Allow accessibility service</string>
+    <string name="pref_summary_draw_over_enabled">Draw over other app permission is granted</string>
+    <string name="pref_summary_draw_over_unneeded">Draw over other app permission only needed by Accessibility Service</string>
+    <string name="pref_summary_draw_over_enable">Grant Draw over other app permission</string>
     <string name="pref_gpg_title_username_mail">Username</string>
     <string name="pref_gpg_username">Username</string>
     <string name="pref_gpg_user_mail">Mail Address</string>
@@ -110,4 +115,8 @@
     <string name="change">Change</string>
     <string name="change_encryption_method">Change encryption method</string>
     <string name="trash">Trash</string>
+    <string name="accessibility_service_description">Detect password input automatically</string>
+    <string name="unauthorized_draw_over">You must authorized \'Draw over other apps\' permission</string>
+    <string name="pref_title_system_accessibility_settings">Accessibility settings</string>
+    <string name="pref_title_system_draw_over">Draw over other app permissions</string>
 </resources>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -25,4 +25,6 @@
     <integer name="id_keyboard_numbers" translatable="false">-2</integer>
     <integer name="id_keyboard_shift" translatable="false">-1</integer>
     <integer name="id_keyboard_delete" translatable="false">-5</integer>
+    <string name="id_accessibility_settings" translatable="false">id_accessibility_settings</string>
+    <string name="id_draw_over_settings" translatable="false">id_draw_over_settings</string>
 </resources>

+ 10 - 0
app/src/main/res/xml/accessibility_service.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- android:accessibilityEventTypes="typeAllMask" -->
+<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:description="@string/accessibility_service_description"
+    android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
+    android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagReportViewIds"
+    android:accessibilityFeedbackType="feedbackGeneric"
+    android:canRetrieveWindowContent="true"
+    android:settingsActivity=".services.AccessibilityService"
+    />

+ 2 - 0
app/src/main/res/xml/pref_general.xml

@@ -1,3 +1,5 @@
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
     <Preference android:title="@string/pref_title_system_keyboard_settings" android:key="@string/id_softSettings"/>
+    <Preference android:title="@string/pref_title_system_accessibility_settings" android:key="@string/id_accessibility_settings"/>
+    <Preference android:title="@string/pref_title_system_draw_over" android:key="@string/id_draw_over_settings"/>
 </PreferenceScreen>