isundil 7 лет назад
Родитель
Сommit
69ed11f95b
30 измененных файлов с 584 добавлено и 224 удалено
  1. 2 2
      app/src/main/java/info/knacki/pass/input/InputService.java
  2. 12 13
      app/src/main/java/info/knacki/pass/io/FileInterfaceFactory.java
  3. 17 0
      app/src/main/java/info/knacki/pass/io/FileUtils.java
  4. 0 83
      app/src/main/java/info/knacki/pass/io/FingerprintFileInterface.java
  5. 7 0
      app/src/main/java/info/knacki/pass/io/FingerprintUtil.java
  6. 20 11
      app/src/main/java/info/knacki/pass/io/GPGUtil.java
  7. 5 0
      app/src/main/java/info/knacki/pass/io/OnErrorListener.java
  8. 1 3
      app/src/main/java/info/knacki/pass/io/OnResponseListener.java
  9. 8 6
      app/src/main/java/info/knacki/pass/io/PasswordFileInterface.java
  10. 5 0
      app/src/main/java/info/knacki/pass/io/PathUtils.java
  11. 1 13
      app/src/main/java/info/knacki/pass/io/RawFileInterface.java
  12. 2 5
      app/src/main/java/info/knacki/pass/settings/SettingsManager.java
  13. 9 11
      app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java
  14. 4 4
      app/src/main/java/info/knacki/pass/ui/EditPasswordActivity.java
  15. 2 1
      app/src/main/java/info/knacki/pass/ui/GitPullActivity.java
  16. 8 7
      app/src/main/java/info/knacki/pass/ui/MainActivity.java
  17. 13 1
      app/src/main/java/info/knacki/pass/ui/alertPrompt/AlertPrompt.java
  18. 15 0
      app/src/main/java/info/knacki/pass/ui/alertPrompt/AlertPromptGenerator.java
  19. 1 1
      app/src/main/java/info/knacki/pass/ui/alertPrompt/ServiceAlertPrompt.java
  20. 17 0
      app/src/main/java/info/knacki/pass/ui/alertPrompt/ServiceAlertPromptGenerator.java
  21. 25 0
      app/src/main/java/info/knacki/pass/ui/alertPrompt/views/FingerprintView.java
  22. 335 0
      app/src/main/java/info/knacki/pass/ui/passwordPicker/FingerprintPicker.java
  23. 31 38
      app/src/main/java/info/knacki/pass/ui/passwordPicker/PasswordPicker.java
  24. 32 0
      app/src/main/java/info/knacki/pass/ui/passwordPicker/PasswordPickerFactory.java
  25. 0 21
      app/src/main/java/info/knacki/pass/ui/passwordPicker/ServicePasswordPicker.java
  26. 7 0
      app/src/main/res/layout/fingerprint_picker.xml
  27. 2 1
      app/src/main/res/values-fr/lang.xml
  28. 2 1
      app/src/main/res/values/lang.xml
  29. 0 1
      app/src/main/res/values/strings.xml
  30. 1 1
      build.gradle

+ 2 - 2
app/src/main/java/info/knacki/pass/input/InputService.java

@@ -22,7 +22,7 @@ import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.ui.MainActivity;
 import info.knacki.pass.ui.passwordList.PasswordClickListener;
 import info.knacki.pass.ui.passwordList.PasswordListView;
-import info.knacki.pass.ui.passwordPicker.ServicePasswordPicker;
+import info.knacki.pass.ui.passwordPicker.PasswordPickerFactory;
 
 public class InputService extends InputMethodService implements PasswordClickListener {
     protected PasswordListView fPasswordListView;
@@ -66,7 +66,7 @@ public class InputService extends InputMethodService implements PasswordClickLis
 
     @Override
     public void OnPasswordClicked(File f) {
-        FileInterfaceFactory.GetFileInterface(this, new ServicePasswordPicker(this, fInputView), f).ReadFile(new OnResponseListener<String>() {
+        FileInterfaceFactory.GetFileInterface(this, PasswordPickerFactory.GetPasswordPicker(this, fInputView), f).ReadFile(new OnResponseListener<String>() {
             @Override
             public void onResponse(String passwordContent) {
                 for (char i: passwordContent.toCharArray()) {

+ 12 - 13
app/src/main/java/info/knacki/pass/io/FileInterfaceFactory.java

@@ -1,15 +1,19 @@
 package info.knacki.pass.io;
 
 import android.content.Context;
+import android.os.Build;
 
 import java.io.File;
 
 import info.knacki.pass.settings.SettingsManager;
 
 public class FileInterfaceFactory {
+    public interface OnPasswordEnteredListener extends OnErrorListener {
+        boolean onResponse(String password);
+    }
+
     public interface PasswordGetter {
-        void GetPassword(OnResponseListener<String> onPassword);
-        void WrongPassword();
+        void GetPassword(OnPasswordEnteredListener onPassword);
     }
 
     public interface ChangePasswordGetter extends PasswordGetter {
@@ -22,24 +26,19 @@ public class FileInterfaceFactory {
         ChangePasswordGetter SetStep(eStep step);
     }
 
-    public final static String FINGERPRINT_SUFFIX = ".fck";
-    public final static String PASSWORD_SUFFIX = ".enc";
+    public final static String PASSWORD_SUFFIX = ".pwd";
     public final static String GPG_SUFFIX = ".gpg";
 
-    public static IFileInterface GetFileInterface(Context ctx, PasswordGetter passwordGetter, File f) {
-        if (f.getName().endsWith(FINGERPRINT_SUFFIX))
-            return new FingerprintFileInterface(ctx, f);
-        else if (f.getName().endsWith(PASSWORD_SUFFIX))
-            return new PasswordFileInterface(passwordGetter, f);
+    public static IFileInterface GetFileInterface(Context ctx, PasswordGetter passInput, File f) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && f.getName().endsWith(PASSWORD_SUFFIX))
+            return new PasswordFileInterface(passInput, f);
         else if (f.getName().endsWith(GPG_SUFFIX))
-            return new GPGFileInterface(ctx, passwordGetter, f);
+            return new GPGFileInterface(ctx, passInput, f);
         return new RawFileInterface(f);
     }
 
     public static String GetExtension(SettingsManager.EncryptionType type) {
-        if (SettingsManager.EncryptionType.TYPE_FINGERPRINT.equals(type))
-            return FINGERPRINT_SUFFIX;
-        else if (SettingsManager.EncryptionType.TYPE_PASSWORD.equals(type))
+        if (SettingsManager.EncryptionType.TYPE_PASSWORD.equals(type))
             return PASSWORD_SUFFIX;
         else if (SettingsManager.EncryptionType.TYPE_GPG.equals(type))
             return GPG_SUFFIX;

+ 17 - 0
app/src/main/java/info/knacki/pass/io/FileUtils.java

@@ -1,6 +1,9 @@
 package info.knacki.pass.io;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -17,6 +20,20 @@ public class FileUtils {
         }
     }
 
+    public static String ReadAllFile(File file) throws IOException {
+        BufferedReader buf = new BufferedReader(new FileReader(file));
+        StringBuilder result = new StringBuilder();
+        String tmp;
+
+        while ((tmp = buf.readLine()) != null) {
+            if (result.length() > 0)
+                result.append("\n");
+            result.append(tmp);
+        }
+        buf.close();
+        return result.toString();
+    }
+
     public static void pipe(InputStream in, OutputStream out) throws IOException {
         int len;
         byte[] buf = new byte[1024];

+ 0 - 83
app/src/main/java/info/knacki/pass/io/FingerprintFileInterface.java

@@ -1,83 +0,0 @@
-package info.knacki.pass.io;
-
-import android.content.Context;
-import android.support.annotation.NonNull;
-import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
-import android.support.v4.os.CancellationSignal;
-
-import java.io.File;
-import java.security.Security;
-import java.util.Arrays;
-import java.util.logging.Logger;
-
-import javax.crypto.Cipher;
-import javax.crypto.spec.SecretKeySpec;
-
-import info.knacki.pass.Sha1Builder;
-
-class FingerprintFileInterface implements IFileInterface {
-    private final File fFile;
-    private final Context fContext;
-
-    public class CryptoObject extends FingerprintManagerCompat.CryptoObject {
-        public CryptoObject(@NonNull Cipher cipher) {
-            super(cipher);
-        }
-    }
-
-    FingerprintFileInterface(Context c, File f) {
-        fFile = f;
-        fContext = c;
-    }
-
-    @Override
-    public void ReadFile(OnResponseListener<String> onResp) {
-        //FIXME
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void WriteFile(String content, OnResponseListener<Void> resp) {
-        CancellationSignal cancelSignal = new CancellationSignal();
-        final Cipher cipher;
-        Security.removeProvider("BC");
-        /*
-            AndroidNSSP
-2018-10-07 16:21:18.582 24744-24744/info.knacki.pass E/null: >> Provider >> AndroidOpenSSL
-2018-10-07 16:21:18.582 24744-24744/info.knacki.pass E/null: >> Provider >> CertPathProvider
-2018-10-07 16:21:18.582 24744-24744/info.knacki.pass E/null: >> Provider >> AndroidKeyStoreBCWorkaround
-2018-10-07 16:21:18.582 24744-24744/info.knacki.pass E/null: >> Provider >> BC
-2018-10-07 16:21:18.583 24744-24744/info.knacki.pass E/null: >> Provider >> HarmonyJSSE
-2018-10-07 16:21:18.583 24744-24744/info.knacki.pass E/null: >> Provider >> AndroidKeyStore
-         */
-        try {
-            cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
-            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(Arrays.copyOf(Sha1Builder.Hash("pass"), 16), "AES"));
-        } catch (Throwable e) {
-            resp.onError(e.getMessage(), e);
-            return;
-        }
-        Logger.getAnonymousLogger().severe("Coucou");
-        FingerprintManagerCompat.from(fContext).authenticate(this.new CryptoObject(cipher), 0, cancelSignal, new FingerprintManagerCompat.AuthenticationCallback() {
-            @Override
-            public void onAuthenticationError(int errMsgId, CharSequence errString) {
-                super.onAuthenticationError(errMsgId, errString);
-            }
-
-            @Override
-            public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
-                super.onAuthenticationHelp(helpMsgId, helpString);
-            }
-
-            @Override
-            public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
-                super.onAuthenticationSucceeded(result);
-            }
-
-            @Override
-            public void onAuthenticationFailed() {
-                super.onAuthenticationFailed();
-            }
-        }, null);
-    }
-}

+ 7 - 0
app/src/main/java/info/knacki/pass/io/FingerprintUtil.java

@@ -0,0 +1,7 @@
+package info.knacki.pass.io;
+
+public class FingerprintUtil {
+    public static void UnlockFinger() {
+
+    }
+}

+ 20 - 11
app/src/main/java/info/knacki/pass/io/GPGUtil.java

@@ -42,6 +42,7 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
 import java.security.SecureRandom;
 import java.util.ArrayList;
 import java.util.Date;
@@ -108,18 +109,20 @@ public class GPGUtil {
         if (priv != null) {
             resp.onResponse(priv);
         } else {
-            OnResponseListener<String> onResp = new OnResponseListener<String>() {
+            FileInterfaceFactory.OnPasswordEnteredListener onResp = new FileInterfaceFactory.OnPasswordEnteredListener() {
                 @Override
-                public void onResponse(String result) {
+                public boolean onResponse(String result) {
                     if (result == null) {
                         resp.onError("Invalid password", null);
+                        return false;
                     } else {
                         PGPPrivateKeyAndPass priv = TryPassword(pgpSecKey, result);
                         if (priv != null) {
                             resp.onResponse(priv);
+                            return true;
                         } else {
-                            passwordGetter.WrongPassword();
                             passwordGetter.GetPassword(this);
+                            return false;
                         }
                     }
                 }
@@ -217,6 +220,12 @@ public class GPGUtil {
         resp.onResponse(output);
     }
 
+    private static PGPSecretKeyRing DoChangePassword(PGPSecretKeyRing secKeyring, String oldPassword, String newPassword) throws PGPException {
+        final PBESecretKeyDecryptor keyDecryptor = CreateDecryptor(oldPassword.toCharArray());
+        final PBESecretKeyEncryptor newEncryptor = new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).setProvider("BC").build(newPassword.toCharArray());
+        return PGPSecretKeyRing.copyWithNewPassword(secKeyring, keyDecryptor, newEncryptor);
+    }
+
     public static void DecryptFile(final Context ctx, final FileInterfaceFactory.PasswordGetter passwordGetter, final File file, final OnResponseListener<byte[]> resp) throws IOException {
         PGPObjectFactory pgpF = new PGPObjectFactory(PGPUtil.getDecoderStream(new FileInputStream(file)), new JcaKeyFingerprintCalculator());
         Object o = pgpF.nextObject();
@@ -286,12 +295,6 @@ public class GPGUtil {
         }
     }
 
-    private static PGPSecretKeyRing DoChangePassword(PGPSecretKeyRing secKeyring, String oldPassword, String newPassword) throws PGPException {
-        final PBESecretKeyDecryptor keyDecryptor = CreateDecryptor(oldPassword.toCharArray());
-        final PBESecretKeyEncryptor newEncryptor = new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256).setProvider("BC").build(newPassword.toCharArray());
-        return PGPSecretKeyRing.copyWithNewPassword(secKeyring, keyDecryptor, newEncryptor);
-    }
-
     public static void ChangePassword(final Context ctx, final FileInterfaceFactory.ChangePasswordGetter passwordGetter, final OnResponseListener<Void> onDone) throws IOException {
         final PGPSecretKeyRing secretKeyring = findSecretKeyring(getKeyInputStream(ctx));
         final PGPSecretKey signingKey = secretKeyring.getSecretKey();
@@ -302,9 +305,9 @@ public class GPGUtil {
                 new OnResponseListener<PGPPrivateKeyAndPass>() {
                     @Override
                     public void onResponse(final PGPPrivateKeyAndPass sKey) {
-                        passwordGetter.SetStep(FileInterfaceFactory.ChangePasswordGetter.eStep.eNewPassword).GetPassword(new OnResponseListener<String>() {
+                        passwordGetter.SetStep(FileInterfaceFactory.ChangePasswordGetter.eStep.eNewPassword).GetPassword(new FileInterfaceFactory.OnPasswordEnteredListener() {
                             @Override
-                            public void onResponse(String result) {
+                            public boolean onResponse(String result) {
                                 if (result != null) {
                                     try {
                                         ArrayList<PGPSecretKeyRing> keyringCollection = new ArrayList<>();
@@ -313,10 +316,12 @@ public class GPGUtil {
                                         new PGPSecretKeyRingCollection(keyringCollection).encode(stream);
                                         SettingsManager.SetGPGKeyContent(ctx, SettingsManager.GPG_GENERATED, new ByteArrayInputStream(stream.toByteArray()));
                                         onDone.onResponse(null);
+                                        return true;
                                     } catch (PGPException | IOException e) {
                                         onError(e.getMessage(), e);
                                     }
                                 }
+                                return false;
                             }
 
                             @Override
@@ -332,6 +337,10 @@ public class GPGUtil {
                 });
     }
 
+    public static void Generate(final OutputStream out) {
+        // FIXME
+    }
+
     public static boolean CheckIsPasswordProtected(Context ctx) throws IOException {
         return TryPassword(findSecretKey(getKeyInputStream(ctx)), "") == null;
     }

+ 5 - 0
app/src/main/java/info/knacki/pass/io/OnErrorListener.java

@@ -0,0 +1,5 @@
+package info.knacki.pass.io;
+
+public interface OnErrorListener {
+    void onError(String msg, Throwable e);
+}

+ 1 - 3
app/src/main/java/info/knacki/pass/io/OnResponseListener.java

@@ -1,7 +1,5 @@
 package info.knacki.pass.io;
 
-public interface OnResponseListener<T> {
+public interface OnResponseListener<T> extends OnErrorListener {
     void onResponse(T result);
-
-    void onError(String msg, Throwable e);
 }

+ 8 - 6
app/src/main/java/info/knacki/pass/io/PasswordFileInterface.java

@@ -33,10 +33,10 @@ import java.security.Security;
 import java.util.Date;
 
 class PasswordFileInterface implements IFileInterface {
-    private final File fFile;
-    private final FileInterfaceFactory.PasswordGetter fPasswordGetter;
+    protected final File fFile;
+    protected final FileInterfaceFactory.PasswordGetter fPasswordGetter;
 
-    private final String passwordWrite = "mpass"; // FIXME
+    protected final String passwordWrite = "mpass"; // FIXME
 
     PasswordFileInterface(FileInterfaceFactory.PasswordGetter passwordGetter, File f) {
         fFile = f;
@@ -65,15 +65,17 @@ class PasswordFileInterface implements IFileInterface {
     }
 
     private void FindPassword(final OnResponseListener<InputStream> onResponse) {
-        OnResponseListener<String> onResp = new OnResponseListener<String>() {
+        FileInterfaceFactory.OnPasswordEnteredListener onResp = new FileInterfaceFactory.OnPasswordEnteredListener() {
             @Override
-            public void onResponse(String result) {
+            public boolean onResponse(String result) {
                 if (result == null) {
                     onResponse.onError("Invalid password", null);
+                    return false;
                 } else if (!TryPassword(result, onResponse)) {
-                    fPasswordGetter.WrongPassword();
                     fPasswordGetter.GetPassword(this);
+                    return false;
                 }
+                return true;
             }
 
             @Override

+ 5 - 0
app/src/main/java/info/knacki/pass/io/PathUtils.java

@@ -5,6 +5,7 @@ import android.content.Context;
 public class PathUtils {
     private static final String DATA_DIR_NAME = "pass";
     private static final String DATA_GIT_LOCAL = "gitfiles";
+    private static final String DATA_FINGER_LOCAL = "fingerprint";
 
     private static String GetAppRootDir(Context ctx) {
         return ctx.getFilesDir().getAbsolutePath();
@@ -17,4 +18,8 @@ public class PathUtils {
     public static String GetGitFile(Context ctx) {
         return GetAppRootDir(ctx) +"/" +DATA_GIT_LOCAL;
     }
+
+    public static String GetFingerprintFile(Context ctx) {
+        return GetAppRootDir(ctx) +"/" +DATA_FINGER_LOCAL;
+    }
 }

+ 1 - 13
app/src/main/java/info/knacki/pass/io/RawFileInterface.java

@@ -1,8 +1,6 @@
 package info.knacki.pass.io;
 
-import java.io.BufferedReader;
 import java.io.File;
-import java.io.FileReader;
 import java.io.FileWriter;
 import java.io.IOException;
 
@@ -16,17 +14,7 @@ class RawFileInterface implements IFileInterface {
     @Override
     public void ReadFile(OnResponseListener<String> resp) {
         try {
-            BufferedReader buf = new BufferedReader(new FileReader(fFile));
-            StringBuilder result = new StringBuilder();
-            String tmp;
-
-            while ((tmp = buf.readLine()) != null) {
-                if (result.length() > 0)
-                    result.append("\n");
-                result.append(tmp);
-            }
-            buf.close();
-            resp.onResponse(result.toString());
+            resp.onResponse(FileUtils.ReadAllFile(fFile));
         }
         catch (IOException e) {
             resp.onError(e.getMessage(), e);

+ 2 - 5
app/src/main/java/info/knacki/pass/settings/SettingsManager.java

@@ -20,9 +20,8 @@ public class SettingsManager {
 
     public static class EncryptionType {
         public static final EncryptionType TYPE_RAW = new EncryptionType(0);
-        public static final EncryptionType TYPE_FINGERPRINT = new EncryptionType(1);
-        public static final EncryptionType TYPE_GPG = new EncryptionType(2);
-        public static final EncryptionType TYPE_PASSWORD = new EncryptionType(3);
+        public static final EncryptionType TYPE_GPG = new EncryptionType(1);
+        public static final EncryptionType TYPE_PASSWORD = new EncryptionType(2);
 
         private final int fType;
 
@@ -42,8 +41,6 @@ public class SettingsManager {
         public static EncryptionType FromInt(int i) {
             if (i == TYPE_RAW.fType)
                 return TYPE_RAW;
-            else if (i == TYPE_FINGERPRINT.fType)
-                return TYPE_FINGERPRINT;
             else if (i == TYPE_PASSWORD.fType)
                 return TYPE_PASSWORD;
             else if (i == TYPE_GPG.fType)

+ 9 - 11
app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java

@@ -45,8 +45,9 @@ import info.knacki.pass.io.OnResponseListener;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.ui.GitPullActivity;
 import info.knacki.pass.ui.alertPrompt.AlertPrompt;
+import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
 import info.knacki.pass.ui.alertPrompt.views.TextView;
-import info.knacki.pass.ui.passwordPicker.PasswordPicker;
+import info.knacki.pass.ui.passwordPicker.PasswordPickerFactory;
 
 /**
  * A {@link PreferenceActivity} that presents a set of application settings. On
@@ -169,14 +170,11 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
             final int encTypeIndex = SettingsManager.GetDefaultEncryptionType(getActivity()).IntValue();
             encTypePref.setSummary(encTypePref.getEntries()[encTypeIndex]);
             encTypePref.setValueIndex(encTypeIndex);
-            encTypePref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
-                @Override
-                public boolean onPreferenceChange(Preference preference, Object o) {
-                    int value = Integer.parseInt(o.toString());
-                    preference.setSummary(((ListPreference) preference).getEntries()[value]);
-                    SettingsManager.SetDefaultEncryptionType(getActivity(), SettingsManager.EncryptionType.FromInt(value));
-                    return true;
-                }
+            encTypePref.setOnPreferenceChangeListener((preference, o) -> {
+                int value = Integer.parseInt(o.toString());
+                preference.setSummary(((ListPreference) preference).getEntries()[value]);
+                SettingsManager.SetDefaultEncryptionType(getActivity(), SettingsManager.EncryptionType.FromInt(value));
+                return true;
             });
         }
 
@@ -468,7 +466,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                 @Override
                 public boolean onPreferenceClick(Preference preference) {
                     try {
-                        GPGUtil.ChangePassword(getActivity(), new PasswordPicker.ChangePasswordPicker(getActivity()), new OnResponseListener<Void>() {
+                        GPGUtil.ChangePassword(getActivity(), PasswordPickerFactory.GetPasswordEditor(getActivity()), new OnResponseListener<Void>() {
                             @Override
                             public void onResponse(Void result) {
                                 updateGpgFileLabel();
@@ -490,7 +488,7 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
                 public boolean onPreferenceClick(Preference preference) {
                     try {
                         if (!GPGUtil.CheckIsPasswordProtected(getActivity())) {
-                            new AlertPrompt(getActivity())
+                            AlertPromptGenerator.StaticMake(getActivity())
                                     .setCancelable(true)
                                     .setPositiveButton(R.string.yes, new AlertPrompt.OnClickListener() {
                                         @Override

+ 4 - 4
app/src/main/java/info/knacki/pass/ui/EditPasswordActivity.java

@@ -17,7 +17,7 @@ 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.PasswordPicker;
+import info.knacki.pass.ui.passwordPicker.PasswordPickerFactory;
 
 public class EditPasswordActivity extends AppCompatActivity {
     private static final Logger log = Logger.getLogger(EditPasswordActivity.class.getName());
@@ -51,7 +51,7 @@ public class EditPasswordActivity extends AppCompatActivity {
                 final Editable text = fTextEdit.getText();
                 final String textStr = text == null ? "" : text.toString();
 
-                FileInterfaceFactory.GetFileInterface(EditPasswordActivity.this, new PasswordPicker(EditPasswordActivity.this), fOutputFile).WriteFile(textStr, new OnResponseListener<Void>() {
+                FileInterfaceFactory.GetFileInterface(EditPasswordActivity.this, PasswordPickerFactory.GetPasswordPicker(EditPasswordActivity.this), fOutputFile).WriteFile(textStr, new OnResponseListener<Void>() {
                     @Override
                     public void onResponse(Void result) {
                         EditPasswordActivity.this.finish();
@@ -62,7 +62,7 @@ public class EditPasswordActivity extends AppCompatActivity {
                         EditPasswordActivity.this.runOnUiThread(new Runnable() {
                             @Override
                             public void run() {
-                                Toast.makeText(EditPasswordActivity.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
+                                Toast.makeText(EditPasswordActivity.this, "Error: " +msg, Toast.LENGTH_LONG).show();
                                 log.log(Level.WARNING, msg, e);
                             }
                         });
@@ -79,7 +79,7 @@ public class EditPasswordActivity extends AppCompatActivity {
 
     protected void populateContent(final boolean requestFocus) {
         fTextEdit.setEnabled(false);
-        FileInterfaceFactory.GetFileInterface(this, new PasswordPicker(this), fOutputFile).ReadFile(new OnResponseListener<String>() {
+        FileInterfaceFactory.GetFileInterface(this, PasswordPickerFactory.GetPasswordPicker(this), fOutputFile).ReadFile(new OnResponseListener<String>() {
             @Override
             public void onResponse(final String pass) {
                 EditPasswordActivity.this.runOnUiThread(new Runnable() {

+ 2 - 1
app/src/main/java/info/knacki/pass/ui/GitPullActivity.java

@@ -31,6 +31,7 @@ import info.knacki.pass.io.OnStreamResponseListener;
 import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.ui.alertPrompt.AlertPrompt;
+import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
 import info.knacki.pass.ui.alertPrompt.views.ConflictView;
 
 public class GitPullActivity extends AppCompatActivity {
@@ -179,7 +180,7 @@ public class GitPullActivity extends AppCompatActivity {
         runOnUiThread(new Runnable() {
             @Override
             public void run() {
-                AlertPrompt pt = new AlertPrompt(GitPullActivity.this)
+                AlertPrompt pt = AlertPromptGenerator.StaticMake(GitPullActivity.this)
                         .setCancelable(true)
                         .setNegativeButton(R.string.cancel, new AlertPrompt.OnClickListener() {
                             @Override

+ 8 - 7
app/src/main/java/info/knacki/pass/ui/MainActivity.java

@@ -30,12 +30,13 @@ import info.knacki.pass.io.PathUtils;
 import info.knacki.pass.settings.SettingsManager;
 import info.knacki.pass.settings.ui.SettingsActivity;
 import info.knacki.pass.ui.alertPrompt.AlertPrompt;
+import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
 import info.knacki.pass.ui.alertPrompt.views.SimpleTextEdit;
 import info.knacki.pass.ui.alertPrompt.views.TextEditAndCheckbox;
 import info.knacki.pass.ui.alertPrompt.views.TextView;
 import info.knacki.pass.ui.passwordList.EditablePasswordListView;
 import info.knacki.pass.ui.passwordList.PasswordEditListener;
-import info.knacki.pass.ui.passwordPicker.PasswordPicker;
+import info.knacki.pass.ui.passwordPicker.PasswordPickerFactory;
 
 public class MainActivity extends AppCompatActivity implements PasswordEditListener {
     private final Logger log = Logger.getLogger(MainActivity.class.getName());
@@ -44,7 +45,7 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
     protected TextEditAndCheckbox lastFileName = null;
 
     protected void CreateNewFolder() {
-        new AlertPrompt(this)
+        AlertPromptGenerator.StaticMake(this)
                 .setCancelable(true)
                 .setView(new SimpleTextEdit(this))
                 .setPositiveButton(R.string.add, new AlertPrompt.OnClickListener() {
@@ -77,7 +78,7 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
         if (lastFileName == null) {
             lastFileName = new TextEditAndCheckbox(this);
         }
-        new AlertPrompt(this)
+        AlertPromptGenerator.StaticMake(this)
                 .setCancelable(true)
                 .setView(new TextEditAndCheckbox(this)
                     .setText(lastFileName.getStr())
@@ -181,13 +182,13 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
     protected void GeneratePassword(final File f) {
         final PasswordGeneratorWizard wiz = new PasswordGeneratorWizard(this);
 
-        new AlertPrompt(this)
+        AlertPromptGenerator.StaticMake(this)
             .setCancelable(true)
             .setView(wiz)
             .setPositiveButton(R.string.add, new AlertPrompt.OnClickListener() {
                 @Override
                 public void onClick(DialogInterface dialogInterface, View view) {
-                    IFileInterface writer = FileInterfaceFactory.GetFileInterface(MainActivity.this, new PasswordPicker(MainActivity.this), f);
+                    IFileInterface writer = FileInterfaceFactory.GetFileInterface(MainActivity.this, PasswordPickerFactory.GetPasswordPicker(MainActivity.this), f);
                     writer.WriteFile(PasswordGenerator.generate(wiz), new OnResponseListener<Void>() {
                         @Override
                         public void onResponse(Void result) {
@@ -246,7 +247,7 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
     @Override
     public void OnRemoveDirectory(final File f) {
         if (f.listFiles().length > 0) {
-            new AlertPrompt(this)
+            AlertPromptGenerator.StaticMake(this)
                     .setTitle(R.string.are_you_sure)
                     .setView(new TextView(this)
                             .SetText(R.string.about_to_rm_directory))
@@ -270,7 +271,7 @@ public class MainActivity extends AppCompatActivity implements PasswordEditListe
 
     @Override
     public void OnCopyToClipboard(File f) {
-        FileInterfaceFactory.GetFileInterface(this, new PasswordPicker(this), f).ReadFile(new OnResponseListener<String>() {
+        FileInterfaceFactory.GetFileInterface(this, PasswordPickerFactory.GetPasswordPicker(this), f).ReadFile(new OnResponseListener<String>() {
             @Override
             public void onResponse(String result) {
                 ClipboardManager clip = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);

+ 13 - 1
app/src/main/java/info/knacki/pass/ui/alertPrompt/AlertPrompt.java

@@ -35,7 +35,7 @@ public class AlertPrompt {
         }
     }
 
-    public AlertPrompt(Context c) {
+    AlertPrompt(Context c) {
         fAlertBuilder = new AlertDialog.Builder(c);
         fOnDismiss = new DismissListener();
     }
@@ -46,6 +46,10 @@ public class AlertPrompt {
         return this;
     }
 
+    public View getView() {
+        return fView;
+    }
+
     public AlertPrompt setCancelable(boolean value) {
         fAlertBuilder.setCancelable(value);
         return this;
@@ -82,6 +86,14 @@ public class AlertPrompt {
         return this;
     }
 
+    public AlertPrompt close() {
+        if (fDialog != null) {
+            fDialog.hide();
+            fDialog = null;
+        }
+        return this;
+    }
+
     public AlertPrompt setPositiveButtonEnabled(boolean enabled) {
         fDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(enabled);
         return this;

+ 15 - 0
app/src/main/java/info/knacki/pass/ui/alertPrompt/AlertPromptGenerator.java

@@ -0,0 +1,15 @@
+package info.knacki.pass.ui.alertPrompt;
+
+import android.content.Context;
+
+import info.knacki.pass.ui.alertPrompt.AlertPrompt;
+
+public class AlertPromptGenerator {
+    public AlertPrompt Generate(Context ctx) {
+        return new AlertPrompt(ctx);
+    }
+
+    public static AlertPrompt StaticMake(Context ctx) {
+        return new AlertPrompt(ctx);
+    }
+}

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

@@ -8,7 +8,7 @@ import android.view.WindowManager;
 public class ServiceAlertPrompt extends AlertPrompt {
     protected final View fInputView;
 
-    public ServiceAlertPrompt(Context c, View inputView) {
+    ServiceAlertPrompt(Context c, View inputView) {
         super(c);
         fInputView = inputView;
     }

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

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

+ 25 - 0
app/src/main/java/info/knacki/pass/ui/alertPrompt/views/FingerprintView.java

@@ -0,0 +1,25 @@
+package info.knacki.pass.ui.alertPrompt.views;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import info.knacki.pass.R;
+
+public class FingerprintView extends LinearLayout {
+    private FingerprintManagerCompat fManager;
+
+    public FingerprintView(Context context) {
+        super(context);
+        fManager = FingerprintManagerCompat.from(context);
+        @SuppressLint("InflateParams") View v = LayoutInflater.from(context).inflate(R.layout.fingerprint_picker, null);
+        addView(v);
+    }
+
+    public void onAuthFailed() {
+
+    }
+}

+ 335 - 0
app/src/main/java/info/knacki/pass/ui/passwordPicker/FingerprintPicker.java

@@ -0,0 +1,335 @@
+package info.knacki.pass.ui.passwordPicker;
+
+import android.content.Context;
+import android.os.Build;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyProperties;
+import android.support.annotation.RequiresApi;
+import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
+import android.support.v4.os.CancellationSignal;
+import android.util.Base64;
+import android.util.Base64DataException;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Key;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+
+import info.knacki.pass.R;
+import info.knacki.pass.io.FileInterfaceFactory;
+import info.knacki.pass.io.FileUtils;
+import info.knacki.pass.io.OnErrorListener;
+import info.knacki.pass.io.OnResponseListener;
+import info.knacki.pass.io.PathUtils;
+import info.knacki.pass.ui.alertPrompt.AlertPrompt;
+import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
+import info.knacki.pass.ui.alertPrompt.views.FingerprintView;
+
+@RequiresApi(api = Build.VERSION_CODES.M)
+class FingerprintPicker extends PasswordPicker {
+    private static final Logger log = Logger.getLogger(FingerprintPicker.class.getName());
+    private static final String KEY_STORE_PROVIDER = "AndroidKeyStore";
+    private static final String KEY_NAME = "AndroidPassFingerprintKey";
+    private static final String KEY_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES;
+    private static final String KEY_BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC;
+    private static final String KEY_PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7;
+
+    private final File fFile;
+    private IvAndPayload fEncrypted;
+    private Queue<String> fPassword;
+    private Queue<String> fPasswordIterator;
+
+    FingerprintPicker(Context ctx, AlertPromptGenerator alertFactory) {
+        super(ctx, alertFactory);
+        fFile = new File(PathUtils.GetFingerprintFile(ctx));
+        fPassword = fPasswordIterator = null;
+    }
+
+    private FingerprintManagerCompat GetFingerprintManager() {
+        return FingerprintManagerCompat.from(fContext);
+    }
+
+    private Key GetKey(KeyStore keyStore) {
+        try {
+            return keyStore.getKey(KEY_NAME, null);
+        }
+        catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException e) {
+            log.log(Level.SEVERE, e.getMessage(), e);
+            return null;
+        }
+    }
+
+    private Key TryGetKey(OnErrorListener resp) {
+        KeyStore keyStore;
+        try {
+            keyStore = KeyStore.getInstance(KEY_STORE_PROVIDER);
+            keyStore.load(null);
+        } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) {
+            final String msg = "Cannot get android keystore";
+            resp.onError(msg, e);
+            log.log(Level.SEVERE, msg, e);
+            return null;
+        }
+        try {
+            return keyStore.getKey(KEY_NAME, null);
+        }
+        catch (NoSuchAlgorithmException | KeyStoreException | UnrecoverableKeyException e) {
+            return null;
+        }
+    }
+
+    private boolean GenerateKey(OnErrorListener resp) {
+        log.info("Generating new key");
+        try {
+            KeyGenerator keyGenerator;
+            keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM, KEY_STORE_PROVIDER);
+            keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                    .setBlockModes(KEY_BLOCK_MODE)
+                    .setEncryptionPaddings(KEY_PADDING)
+                    .build());
+            keyGenerator.generateKey();
+        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
+            final String msg = "Cannot create Key generator";
+            resp.onError(msg, e);
+            log.log(Level.SEVERE, msg, e);
+            return false;
+        }
+        return true;
+    }
+
+    private FingerprintManagerCompat.CryptoObject InitCryptoObject(IvAndPayload ivForDecoding, OnErrorListener resp) {
+        Key key = TryGetKey(resp);
+        if (key == null) {
+            if (!GenerateKey(resp)) {
+                // Unrecoverable failure, error already handled
+                return null;
+            }
+            key = TryGetKey(resp);
+            if (key == null) {
+                // Unrecoverable failure
+                final String msg = "Cannot get android keystore";
+                log.log(Level.SEVERE, msg);
+                resp.onError(msg, null);
+                return null;
+            }
+        }
+
+        Cipher c;
+        try {
+            c = InitCipher(key, ivForDecoding);
+        }
+        catch (InvalidKeyException e) {
+            return null;
+        }
+        catch (KeyStoreException | UnrecoverableKeyException | NoSuchPaddingException | NoSuchAlgorithmException | InvalidAlgorithmParameterException e) {
+            final String msg = "Cannot create Cipher";
+            resp.onError(msg, e);
+            log.log(Level.SEVERE, msg, e);
+            return null;
+        }
+        return new FingerprintManagerCompat.CryptoObject(c);
+    }
+
+    private static Cipher InitCipher(Key key, IvAndPayload ivForDecoding) throws NoSuchAlgorithmException, NoSuchPaddingException, KeyStoreException, InvalidKeyException, UnrecoverableKeyException, InvalidAlgorithmParameterException {
+        Cipher c = Cipher.getInstance(KEY_ALGORITHM +"/" +KEY_BLOCK_MODE +"/" +KEY_PADDING);
+        if (ivForDecoding == null)
+            c.init(Cipher.ENCRYPT_MODE, key);
+        else
+            c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivForDecoding.fIV));
+        return c;
+    }
+
+    private final class IvAndPayload {
+        public static final char SEPARATOR = '-';
+        public final byte[] fIV;
+        public final byte[] fPayload;
+
+        public IvAndPayload(Cipher c, byte[] payload) {
+            fIV = c.getIV();
+            fPayload = payload;
+        }
+
+        public IvAndPayload(String raw) throws Base64DataException {
+            int sep = raw.indexOf(SEPARATOR);
+            if (sep < 0)
+                throw new Base64DataException("Cannot find separator");
+            fIV = Base64.decode(raw.substring(0, sep), Base64.NO_WRAP);
+            fPayload = Base64.decode(raw.substring(sep +1), Base64.NO_WRAP);
+        }
+
+        public String toString() {
+            return Base64.encodeToString(fIV, Base64.NO_WRAP) +SEPARATOR +Base64.encodeToString(fPayload, Base64.NO_WRAP);
+        }
+    }
+
+    private void DoWriteFile(FingerprintManagerCompat.CryptoObject cryptoObject, String content) {
+        try {
+            Cipher cipher = cryptoObject.getCipher();
+            byte[] encrypted = cipher.doFinal(content.getBytes());
+            Writer writer = new FileWriter(fFile);
+            writer.write((new IvAndPayload(cipher, encrypted)).toString());
+            writer.close();
+        }
+        catch (IOException | BadPaddingException | IllegalBlockSizeException | NullPointerException e) {
+            log.log(Level.SEVERE, e.getMessage(), e);
+            return;
+        }
+    }
+
+    private void DisplayPrompt(IvAndPayload ivForDecoding, OnResponseListener<FingerprintManagerCompat.CryptoObject> resp) {
+        final CancellationSignal signal = new CancellationSignal();
+        final FingerprintView fingerprintView = new FingerprintView(fContext);
+        final AlertPrompt prompt = fAlertFactory.Generate(fContext)
+                .setView(fingerprintView)
+                .setTitle(R.string.pushfinger)
+                .setNegativeButton(R.string.cancel, (dialogInterface, view) -> {
+                    signal.cancel();
+                    resp.onError(fContext.getString(R.string.cancelled), null);
+                })
+                .show();
+        final FingerprintManagerCompat.CryptoObject cryptoObject = InitCryptoObject(ivForDecoding, resp::onError);
+        if (cryptoObject == null)
+            return; // Error already handled
+
+        GetFingerprintManager().authenticate(cryptoObject, 0, signal, new FingerprintManagerCompat.AuthenticationCallback() {
+            @Override
+            public void onAuthenticationError(int errMsgId, CharSequence errString) {
+                resp.onError(errString.toString(), null);
+            }
+
+            @Override
+            public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+                log.severe("Authentification help " +helpString);
+                super.onAuthenticationHelp(helpMsgId, helpString);
+            }
+
+            @Override
+            public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
+                prompt.close();
+                resp.onResponse(cryptoObject);
+            }
+
+            @Override
+            public void onAuthenticationFailed() {
+                ((FingerprintView)(prompt.getView())).onAuthFailed();
+            }
+        }, null);
+    }
+
+    private void ReadAllPasswords(final FileInterfaceFactory.OnPasswordEnteredListener onResp) {
+        try {
+            String raw = FileUtils.ReadAllFile(fFile);
+            fEncrypted = new IvAndPayload(raw);
+        } catch (IOException e) {
+            // Fallback password
+            SuperGetPassword(onResp);
+            return;
+        }
+
+        DisplayPrompt(fEncrypted, new OnResponseListener<FingerprintManagerCompat.CryptoObject>() {
+            @Override
+            public void onResponse(FingerprintManagerCompat.CryptoObject cryptoObject) {
+                try {
+                    byte[] decoded = cryptoObject.getCipher().doFinal(fEncrypted.fPayload);
+                    String[] passwords = (new String(decoded, "UTF-8")).split("\n");
+                    fPassword = new ArrayDeque<>();
+                    fPasswordIterator = new ArrayDeque<>();
+                    // Try all password in keystore
+                    for (String i: passwords)
+                        if (i.length() > 0)
+                            fPassword.add(i);
+                    fPasswordIterator.addAll(fPassword);
+                    if (fPasswordIterator.isEmpty())
+                        SuperGetPassword(onResp);
+                    else
+                        onResp.onResponse(fPasswordIterator.remove());
+                }
+                catch (BadPaddingException | IllegalBlockSizeException | UnsupportedEncodingException e) {
+                    onError(e.getMessage(), e);
+                }
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                // FIXME Toast error
+                // Fallback password
+                fPassword = new ArrayDeque<>();
+                fPasswordIterator = new ArrayDeque<>();
+                SuperGetPassword(onResp);
+            }
+        });
+    }
+
+    private void SuperGetPassword(FileInterfaceFactory.OnPasswordEnteredListener onResp) {
+        FingerprintPicker.super.GetPassword(new FileInterfaceFactory.OnPasswordEnteredListener() {
+            @Override
+            public boolean onResponse(String result) {
+                // TODO checkbox save password ?
+                if (result != null) {
+                    AddPassword(result);
+                }
+                return onResp.onResponse(result);
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+                onResp.onError(msg, e);
+            }
+        });
+    }
+
+    @Override
+    public void GetPassword(FileInterfaceFactory.OnPasswordEnteredListener onResp) {
+        if (fPassword == null)
+            ReadAllPasswords(onResp);
+        else if (fPasswordIterator.isEmpty())
+            SuperGetPassword(onResp);
+        else
+            onResp.onResponse(fPasswordIterator.remove());
+    }
+
+    @Override
+    public void WrongPassword() {
+
+    }
+
+    private void AddPassword(String newPass) {
+        fEncrypted = null;
+        DisplayPrompt(null, new OnResponseListener<FingerprintManagerCompat.CryptoObject>() {
+            @Override
+            public void onResponse(FingerprintManagerCompat.CryptoObject cryptoObject) {
+                StringBuilder prev = new StringBuilder();
+                if (fPassword != null)
+                    for (String i: fPassword)
+                        prev.append(i).append('\n');
+                prev.append(newPass);
+                DoWriteFile(cryptoObject, prev.toString());
+            }
+
+            @Override
+            public void onError(String msg, Throwable e) {
+            }
+        });
+    }
+}

+ 31 - 38
app/src/main/java/info/knacki/pass/ui/passwordPicker/PasswordPicker.java

@@ -1,25 +1,20 @@
 package info.knacki.pass.ui.passwordPicker;
 
 import android.content.Context;
-import android.content.DialogInterface;
-import android.view.View;
 import android.widget.Toast;
 
 import info.knacki.pass.R;
 import info.knacki.pass.io.FileInterfaceFactory;
-import info.knacki.pass.io.OnResponseListener;
-import info.knacki.pass.ui.alertPrompt.AlertPrompt;
+import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
 import info.knacki.pass.ui.alertPrompt.views.PasswordTextEdit;
 
-public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
+class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
     protected final Context fContext;
+    protected final AlertPromptGenerator fAlertFactory;
 
-    public PasswordPicker(Context context) {
+    public PasswordPicker(Context context, AlertPromptGenerator alertFactory) {
         fContext = context;
-    }
-
-    protected AlertPrompt AlertFactory() {
-        return new AlertPrompt(fContext);
+        fAlertFactory = alertFactory;
     }
 
     public int GetTitle() {
@@ -27,27 +22,22 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
     }
 
     @Override
-    public void GetPassword(final OnResponseListener<String> onPassword) {
-        AlertFactory()
+    public void GetPassword(final FileInterfaceFactory.OnPasswordEnteredListener onPassword) {
+        fAlertFactory.Generate(fContext)
             .setCancelable(true)
                 .setView(new PasswordTextEdit(fContext))
-            .setPositiveButton(R.string.ok, new AlertPrompt.OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialogInterface, View view) {
-                    onPassword.onResponse(((PasswordTextEdit) view).getStr());
-                }
+            .setPositiveButton(R.string.ok, (dialogInterface, view) -> {
+                if (!onPassword.onResponse(((PasswordTextEdit) view).getStr()))
+                    WrongPassword();
             })
-            .setNegativeButton(R.string.cancel, new AlertPrompt.OnClickListener() {
-                @Override
-                public void onClick(DialogInterface dialogInterface, View s) {
-                    onPassword.onResponse(null);
-                }
+            .setNegativeButton(R.string.cancel, (dialogInterface, s) -> {
+                if (!onPassword.onResponse(null))
+                    WrongPassword();
             })
                 .setTitle(GetTitle())
             .show();
     }
 
-    @Override
     public void WrongPassword() {
         Toast.makeText(fContext, fContext.getResources().getString(R.string.wrongPassword), Toast.LENGTH_LONG).show();
     }
@@ -55,10 +45,10 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
     public static class ChangePasswordPicker extends PasswordPicker implements FileInterfaceFactory.ChangePasswordGetter {
         protected FileInterfaceFactory.ChangePasswordGetter.eStep fCurrentStep = FileInterfaceFactory.ChangePasswordGetter.eStep.eOldPassword;
         private String fFirstPassword = "";
-        private OnResponseListener<String> fListener;
+        private FileInterfaceFactory.OnPasswordEnteredListener fListener;
 
-        public ChangePasswordPicker(Context context) {
-            super(context);
+        public ChangePasswordPicker(Context context, AlertPromptGenerator alertFactory) {
+            super(context, alertFactory);
         }
 
         @Override
@@ -74,15 +64,16 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
             return R.string.enter_password;
         }
 
-        private OnResponseListener<String> fOnFirstPassEntered = new OnResponseListener<String>() {
+        private FileInterfaceFactory.OnPasswordEnteredListener fOnFirstPassEntered = new FileInterfaceFactory.OnPasswordEnteredListener() {
             @Override
-            public void onResponse(String result) {
-                if (result != null) {
-                    fFirstPassword = result;
-                    SetStep(eStep.eRetypePassword).GetPassword(fListener);
-                } else {
+            public boolean onResponse(String result) {
+                if (result == null) {
                     fListener.onResponse(null);
+                    return false;
                 }
+                fFirstPassword = result;
+                SetStep(eStep.eRetypePassword).GetPassword(fListener);
+                return true;
             }
 
             @Override
@@ -91,17 +82,19 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
             }
         };
 
-        private OnResponseListener<String> fOnRetypePassEntered = new OnResponseListener<String>() {
+        private FileInterfaceFactory.OnPasswordEnteredListener fOnRetypePassEntered = new FileInterfaceFactory.OnPasswordEnteredListener() {
             @Override
-            public void onResponse(String result) {
+            public boolean onResponse(String result) {
                 if (result == null) {
                     SetStep(eStep.eNewPassword).GetPassword(fListener);
-                } else if (fFirstPassword.equals(result)) {
-                    fListener.onResponse(result);
-                } else {
+                    return false;
+                } else if (!fFirstPassword.equals(result)) {
                     Toast.makeText(fContext, fContext.getResources().getString(R.string.password_missmatch), Toast.LENGTH_LONG).show();
                     SetStep(eStep.eNewPassword).GetPassword(fListener);
+                    return false;
                 }
+                fListener.onResponse(result);
+                return true;
             }
 
             @Override
@@ -111,7 +104,7 @@ public class PasswordPicker implements FileInterfaceFactory.PasswordGetter {
         };
 
         @Override
-        public void GetPassword(final OnResponseListener<String> onPassword) {
+        public void GetPassword(final FileInterfaceFactory.OnPasswordEnteredListener onPassword) {
             fListener = onPassword;
             switch (fCurrentStep) {
                 case eOldPassword:

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

@@ -0,0 +1,32 @@
+package info.knacki.pass.ui.passwordPicker;
+
+import android.app.Service;
+import android.content.Context;
+import android.os.Build;
+import android.view.View;
+
+import info.knacki.pass.io.FileInterfaceFactory;
+import info.knacki.pass.ui.alertPrompt.AlertPromptGenerator;
+import info.knacki.pass.ui.alertPrompt.ServiceAlertPromptGenerator;
+
+public class PasswordPickerFactory {
+    public static FileInterfaceFactory.PasswordGetter GetPasswordPicker(Context ctx) {
+        final AlertPromptGenerator generator = new AlertPromptGenerator();
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) // FIXME config !
+            return new FingerprintPicker(ctx, generator);
+        return new PasswordPicker(ctx, generator);
+    }
+
+    public static FileInterfaceFactory.PasswordGetter GetPasswordPicker(Service ctx, View v) {
+        final AlertPromptGenerator generator = new ServiceAlertPromptGenerator(v);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) // FIXME config !
+            return new FingerprintPicker(ctx, generator);
+        return new PasswordPicker(ctx, generator);
+    }
+
+    public static FileInterfaceFactory.ChangePasswordGetter GetPasswordEditor(Context ctx) {
+        return new PasswordPicker.ChangePasswordPicker(ctx, new AlertPromptGenerator());
+    }
+}

+ 0 - 21
app/src/main/java/info/knacki/pass/ui/passwordPicker/ServicePasswordPicker.java

@@ -1,21 +0,0 @@
-package info.knacki.pass.ui.passwordPicker;
-
-import android.content.Context;
-import android.view.View;
-
-import info.knacki.pass.ui.alertPrompt.AlertPrompt;
-import info.knacki.pass.ui.alertPrompt.ServiceAlertPrompt;
-
-public class ServicePasswordPicker extends PasswordPicker {
-    protected final View fInputView;
-
-    public ServicePasswordPicker(Context context, View inputView) {
-        super(context);
-        fInputView = inputView;
-    }
-
-    @Override
-    protected AlertPrompt AlertFactory() {
-        return new ServiceAlertPrompt(fContext, fInputView);
-    }
-}

+ 7 - 0
app/src/main/res/layout/fingerprint_picker.xml

@@ -0,0 +1,7 @@
+<?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="match_parent"
+    android:orientation="horizontal">
+
+</LinearLayout>

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

@@ -16,6 +16,7 @@
     <string name="edit_ShowPassword">Montrer le mot de passe</string>
     <string name="edit_Save">Sauvegarder</string>
     <string name="cancel">Annuler</string>
+    <string name="cancelled">Annulé</string>
     <string name="ChangeKeyboard">Revenir au clavier</string>
     <string name="settings">Configuration</string>
     <string name="pref_title_system_keyboard_settings">Paramètres système du clavier</string>
@@ -35,7 +36,6 @@
     <string name="pref_header_GPG">Configuration de GPG</string>
     <array name="pref_enctype_title">
         <item>Pas de chiffrement</item>
-        <item>Empreinte digitale</item>
         <item>GPG</item>
         <item>Mot de passe</item>
     </array>
@@ -80,4 +80,5 @@
     <string name="pref_header_gpg_export">Export GPG key</string>
     <string name="sync">Synchroniser</string>
     <string name="close">Fermer</string>
+    <string name="pushfinger">Posez votre doigt</string>
 </resources>

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

@@ -16,6 +16,7 @@
     <string name="edit_ShowPassword">Show password</string>
     <string name="edit_Save">Save</string>
     <string name="cancel">Cancel</string>
+    <string name="cancelled">Cancelled</string>
     <string name="ChangeKeyboard">Change keyboard</string>
     <string name="settings">Settings</string>
     <string name="pref_title_system_keyboard_settings">Keyboard system settings</string>
@@ -25,7 +26,6 @@
     <string name="pref_title_enctype">Encryption type</string>
     <array name="pref_enctype_title">
         <item>No encryption</item>
-        <item>Fingerprint</item>
         <item>GPG</item>
         <item>Password</item>
     </array>
@@ -80,4 +80,5 @@
     <string name="pref_header_gpg_export">Export GPG key</string>
     <string name="sync">Synchronize</string>
     <string name="close">Close</string>
+    <string name="pushfinger">Fingerprint reader</string>
 </resources>

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

@@ -4,7 +4,6 @@
         <item>0</item>
         <item>1</item>
         <item>2</item>
-        <item>3</item>
     </string-array>
     <string-array name="pref_vcs_list_values">
         <item>0</item>

+ 1 - 1
build.gradle

@@ -7,7 +7,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.2.0'
+        classpath 'com.android.tools.build:gradle:3.2.1'
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
     }