mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-15 14:32:49 +00:00
Replace the custom fingerprint auth UI with BiometricPrompt
This patch replaces the usage of the deprecated FingerprintManager API with BiometricPrompt. This uses the Android X library, so we get the native biometric prompt on recent versions of Android and a Google-made one on older versions. By not working with custom prompts for biometric authentication like we do now, we can be sure that any issues like #70, #81, #237 are not actually our fault. Here's what it looks like:  As a nice aside, this also adds support for the new facial recognition as an authentication method on Pixel 4 phones. This is still a draft, but early feedback is welcome.
This commit is contained in:
parent
a93ced6e34
commit
3be9aecb88
39 changed files with 499 additions and 716 deletions
|
@ -4,17 +4,17 @@ import com.beemdevelopment.aegis.crypto.CryptParameters;
|
|||
|
||||
import java.util.UUID;
|
||||
|
||||
public class FingerprintSlot extends RawSlot {
|
||||
public FingerprintSlot() {
|
||||
public class BiometricSlot extends RawSlot {
|
||||
public BiometricSlot() {
|
||||
super();
|
||||
}
|
||||
|
||||
FingerprintSlot(UUID uuid, byte[] key, CryptParameters keyParams) {
|
||||
BiometricSlot(UUID uuid, byte[] key, CryptParameters keyParams) {
|
||||
super(uuid, key, keyParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getType() {
|
||||
return TYPE_FINGERPRINT;
|
||||
return TYPE_BIOMETRIC;
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@ import javax.crypto.spec.SecretKeySpec;
|
|||
public abstract class Slot extends UUIDMap.Value {
|
||||
public final static byte TYPE_RAW = 0x00;
|
||||
public final static byte TYPE_DERIVED = 0x01;
|
||||
public final static byte TYPE_FINGERPRINT = 0x02;
|
||||
public final static byte TYPE_BIOMETRIC = 0x02;
|
||||
|
||||
private byte[] _encryptedMasterKey;
|
||||
private CryptParameters _encryptedMasterKeyParams;
|
||||
|
@ -138,8 +138,8 @@ public abstract class Slot extends UUIDMap.Value {
|
|||
boolean repaired = obj.optBoolean("repaired", false);
|
||||
slot = new PasswordSlot(uuid, key, keyParams, scryptParams, repaired);
|
||||
break;
|
||||
case Slot.TYPE_FINGERPRINT:
|
||||
slot = new FingerprintSlot(uuid, key, keyParams);
|
||||
case Slot.TYPE_BIOMETRIC:
|
||||
slot = new BiometricSlot(uuid, key, keyParams);
|
||||
break;
|
||||
default:
|
||||
throw new SlotException("unrecognized slot type");
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
/**
|
||||
* A class that can prepare initialization of a BiometricSlot by generating a new
|
||||
* key in the Android KeyStore and authenticating a cipher for it through a
|
||||
* BiometricPrompt.
|
||||
*/
|
||||
public class BiometricSlotInitializer extends BiometricPrompt.AuthenticationCallback {
|
||||
private BiometricSlot _slot;
|
||||
private Listener _listener;
|
||||
private BiometricPrompt _prompt;
|
||||
|
||||
public BiometricSlotInitializer(Fragment fragment, Listener listener) {
|
||||
_listener = listener;
|
||||
_prompt = new BiometricPrompt(fragment, new UiThreadExecutor(), this);
|
||||
}
|
||||
|
||||
public BiometricSlotInitializer(FragmentActivity activity, Listener listener) {
|
||||
_listener = listener;
|
||||
_prompt = new BiometricPrompt(activity, new UiThreadExecutor(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new key in the Android KeyStore for the new BiometricSlot,
|
||||
* initializes a cipher with it and shows a BiometricPrompt to the user for
|
||||
* authentication. If authentication is successful, the new slot will be
|
||||
* initialized and delivered back through the listener.
|
||||
*/
|
||||
public void authenticate(BiometricPrompt.PromptInfo info) {
|
||||
if (_slot != null) {
|
||||
throw new IllegalStateException("Biometric authentication already in progress");
|
||||
}
|
||||
|
||||
KeyStoreHandle keyStore;
|
||||
try {
|
||||
keyStore = new KeyStoreHandle();
|
||||
} catch (KeyStoreHandleException e) {
|
||||
fail(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// generate a new Android KeyStore key
|
||||
// and assign it the UUID of the new slot as an alias
|
||||
Cipher cipher;
|
||||
BiometricSlot slot = new BiometricSlot();
|
||||
try {
|
||||
SecretKey key = keyStore.generateKey(slot.getUUID().toString());
|
||||
cipher = Slot.createEncryptCipher(key);
|
||||
} catch (KeyStoreHandleException | SlotException e) {
|
||||
fail(e);
|
||||
return;
|
||||
}
|
||||
|
||||
_slot = slot;
|
||||
_prompt.authenticate(info, new BiometricPrompt.CryptoObject(cipher));
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels the BiometricPrompt and resets the state of the initializer. It will
|
||||
* also attempt to delete the previously generated Android KeyStore key.
|
||||
*/
|
||||
public void cancelAuthentication() {
|
||||
if (_slot == null) {
|
||||
throw new IllegalStateException("Biometric authentication not in progress");
|
||||
}
|
||||
|
||||
reset();
|
||||
_prompt.cancelAuthentication();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
if (_slot != null) {
|
||||
try {
|
||||
// clean up the unused KeyStore key
|
||||
// this is non-critical, so just fail silently if an error occurs
|
||||
String uuid = _slot.getUUID().toString();
|
||||
KeyStoreHandle keyStore = new KeyStoreHandle();
|
||||
if (keyStore.containsKey(uuid)) {
|
||||
keyStore.deleteKey(uuid);
|
||||
}
|
||||
} catch (KeyStoreHandleException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
_slot = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void fail(int errorCode, CharSequence errString) {
|
||||
reset();
|
||||
_listener.onSlotInitializationFailed(errorCode, errString);
|
||||
}
|
||||
|
||||
private void fail(Exception e) {
|
||||
e.printStackTrace();
|
||||
fail(0, e.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||
super.onAuthenticationError(errorCode, errString);
|
||||
fail(errorCode, errString.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
_listener.onInitializeSlot(_slot, Objects.requireNonNull(result.getCryptoObject()).getCipher());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed();
|
||||
}
|
||||
|
||||
public interface Listener {
|
||||
void onInitializeSlot(BiometricSlot slot, Cipher cipher);
|
||||
void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.biometric.BiometricConstants;
|
||||
import androidx.biometric.BiometricManager;
|
||||
|
||||
public class BiometricsHelper {
|
||||
private BiometricsHelper() {
|
||||
|
||||
}
|
||||
|
||||
public static BiometricManager getManager(Context context) {
|
||||
BiometricManager manager = BiometricManager.from(context);
|
||||
if (manager.canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS) {
|
||||
return manager;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isCanceled(int errorCode) {
|
||||
return errorCode == BiometricConstants.ERROR_CANCELED
|
||||
|| errorCode == BiometricConstants.ERROR_USER_CANCELED
|
||||
|| errorCode == BiometricConstants.ERROR_NEGATIVE_BUTTON;
|
||||
}
|
||||
|
||||
public static boolean isAvailable(Context context) {
|
||||
return getManager(context) != null;
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
public class FingerprintHelper {
|
||||
private FingerprintHelper() {
|
||||
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public static FingerprintManager getManager(Context context) {
|
||||
if (PermissionHelper.granted(context, Manifest.permission.USE_FINGERPRINT)) {
|
||||
FingerprintManager manager = (FingerprintManager) context.getSystemService(Context.FINGERPRINT_SERVICE);
|
||||
if (manager != null && manager.isHardwareDetected() && manager.hasEnrolledFingerprints()) {
|
||||
return manager;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public static boolean isAvailable(Context context) {
|
||||
return getManager(context) != null;
|
||||
}
|
||||
|
||||
public static boolean isSupported() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
||||
}
|
||||
}
|
|
@ -1,156 +0,0 @@
|
|||
// This file was originally taken from https://github.com/googlesamples/android-FingerprintDialog/blob/2feb02945ae220ebd1bc2c2b620a1d43e30daea8/Application/src/main/java/com/example/android/fingerprintdialog/FingerprintUiHelper.java
|
||||
// It has been modified to suit Aegis' needs
|
||||
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License
|
||||
*/
|
||||
|
||||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.mattprecious.swirl.SwirlView;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
/**
|
||||
* Small helper class to manage text/icon around fingerprint authentication UI.
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class FingerprintUiHelper extends FingerprintManager.AuthenticationCallback {
|
||||
|
||||
private static final long ERROR_TIMEOUT_MILLIS = 1600;
|
||||
private static final long SUCCESS_DELAY_MILLIS = 100;
|
||||
|
||||
private final FingerprintManager mFingerprintManager;
|
||||
private final SwirlView mIcon;
|
||||
private final TextView mErrorTextView;
|
||||
private final Callback mCallback;
|
||||
private CancellationSignal mCancellationSignal;
|
||||
|
||||
private boolean mSelfCancelled;
|
||||
|
||||
/**
|
||||
* Constructor for {@link FingerprintUiHelper}.
|
||||
*/
|
||||
public FingerprintUiHelper(FingerprintManager fingerprintManager,
|
||||
SwirlView icon, TextView errorTextView, Callback callback) {
|
||||
mFingerprintManager = fingerprintManager;
|
||||
mIcon = icon;
|
||||
mErrorTextView = errorTextView;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public boolean isFingerprintAuthAvailable() {
|
||||
// The line below prevents the false positive inspection from Android Studio
|
||||
// noinspection ResourceType
|
||||
return mFingerprintManager.isHardwareDetected()
|
||||
&& mFingerprintManager.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
public void startListening(FingerprintManager.CryptoObject cryptoObject) {
|
||||
if (!isFingerprintAuthAvailable()) {
|
||||
return;
|
||||
}
|
||||
mCancellationSignal = new CancellationSignal();
|
||||
mSelfCancelled = false;
|
||||
// The line below prevents the false positive inspection from Android Studio
|
||||
// noinspection ResourceType
|
||||
mFingerprintManager
|
||||
.authenticate(cryptoObject, mCancellationSignal, 0 /* flags */, this, null);
|
||||
mIcon.setState(SwirlView.State.ON);
|
||||
}
|
||||
|
||||
public void stopListening() {
|
||||
if (mCancellationSignal != null) {
|
||||
mSelfCancelled = true;
|
||||
mCancellationSignal.cancel();
|
||||
mCancellationSignal = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationError(int errMsgId, CharSequence errString) {
|
||||
if (!mSelfCancelled) {
|
||||
showError(errString);
|
||||
mIcon.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCallback.onError();
|
||||
}
|
||||
}, ERROR_TIMEOUT_MILLIS);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
|
||||
showError(helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
showError(mIcon.getResources().getString(
|
||||
R.string.fingerprint_not_recognized));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
|
||||
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
|
||||
mIcon.setState(SwirlView.State.OFF);
|
||||
mErrorTextView.setText(
|
||||
mErrorTextView.getResources().getString(R.string.fingerprint_success));
|
||||
mIcon.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mCallback.onAuthenticated();
|
||||
}
|
||||
}, SUCCESS_DELAY_MILLIS);
|
||||
|
||||
// ugly hack to keep the fingerprint icon visible while also giving visual feedback of success to the user
|
||||
mIcon.postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mIcon.setState(SwirlView.State.ON);
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private void showError(CharSequence error) {
|
||||
mIcon.setState(SwirlView.State.ERROR);
|
||||
mErrorTextView.setText(error);
|
||||
mErrorTextView.removeCallbacks(mResetErrorTextRunnable);
|
||||
mErrorTextView.postDelayed(mResetErrorTextRunnable, ERROR_TIMEOUT_MILLIS);
|
||||
}
|
||||
|
||||
private Runnable mResetErrorTextRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mErrorTextView.setText(
|
||||
mErrorTextView.getResources().getString(R.string.fingerprint_hint));
|
||||
mIcon.setState(SwirlView.State.ON);
|
||||
}
|
||||
};
|
||||
|
||||
public interface Callback {
|
||||
|
||||
void onAuthenticated();
|
||||
|
||||
void onError();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package com.beemdevelopment.aegis.helpers;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public class UiThreadExecutor implements Executor {
|
||||
private final Handler _handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public void execute(@NonNull Runnable command) {
|
||||
_handler.post(command);
|
||||
}
|
||||
}
|
|
@ -1,61 +1,56 @@
|
|||
package com.beemdevelopment.aegis.ui;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.CancelAction;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
|
||||
import com.beemdevelopment.aegis.helpers.UiThreadExecutor;
|
||||
import com.beemdevelopment.aegis.ui.tasks.SlotListTask;
|
||||
import com.mattprecious.swirl.SwirlView;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class AuthActivity extends AegisActivity implements FingerprintUiHelper.Callback, SlotListTask.Callback {
|
||||
public class AuthActivity extends AegisActivity implements SlotListTask.Callback {
|
||||
private EditText _textPassword;
|
||||
|
||||
private CancelAction _cancelAction;
|
||||
private SlotList _slots;
|
||||
private FingerprintUiHelper _fingerHelper;
|
||||
private FingerprintManager.CryptoObject _fingerCryptoObj;
|
||||
private BiometricPrompt.CryptoObject _bioCryptoObj;
|
||||
private BiometricPrompt _bioPrompt;
|
||||
|
||||
@Override
|
||||
@SuppressLint("NewApi")
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_auth);
|
||||
_textPassword = findViewById(R.id.text_password);
|
||||
LinearLayout boxFingerprint = findViewById(R.id.box_fingerprint);
|
||||
LinearLayout boxFingerprintInfo = findViewById(R.id.box_fingerprint_info);
|
||||
TextView textFingerprint = findViewById(R.id.text_fingerprint);
|
||||
LinearLayout boxBiometricInfo = findViewById(R.id.box_biometric_info);
|
||||
Button decryptButton = findViewById(R.id.button_decrypt);
|
||||
Button biometricsButton = findViewById(R.id.button_biometrics);
|
||||
|
||||
_textPassword.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||
|
@ -64,25 +59,17 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
return false;
|
||||
});
|
||||
|
||||
SwirlView imgFingerprint = null;
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
ViewGroup insertPoint = findViewById(R.id.img_fingerprint_insert);
|
||||
imgFingerprint = new SwirlView(this);
|
||||
insertPoint.addView(imgFingerprint, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
_slots = (SlotList) intent.getSerializableExtra("slots");
|
||||
_cancelAction = (CancelAction) intent.getSerializableExtra("cancelAction");
|
||||
|
||||
// only show the fingerprint controls if the api version is new enough, permission is granted, a scanner is found and a fingerprint slot is found
|
||||
if (_slots.has(FingerprintSlot.class) && FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(this)) {
|
||||
// only show the biometric prompt if the api version is new enough, permission is granted, a scanner is found and a biometric slot is found
|
||||
if (_slots.has(BiometricSlot.class) && BiometricsHelper.isAvailable(this)) {
|
||||
boolean invalidated = false;
|
||||
FingerprintManager manager = FingerprintHelper.getManager(this);
|
||||
|
||||
try {
|
||||
// find a fingerprint slot with an id that matches an alias in the keystore
|
||||
for (FingerprintSlot slot : _slots.findAll(FingerprintSlot.class)) {
|
||||
// find a biometric slot with an id that matches an alias in the keystore
|
||||
for (BiometricSlot slot : _slots.findAll(BiometricSlot.class)) {
|
||||
String id = slot.getUUID().toString();
|
||||
KeyStoreHandle handle = new KeyStoreHandle();
|
||||
if (handle.containsKey(id)) {
|
||||
|
@ -92,10 +79,11 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
invalidated = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Cipher cipher = slot.createDecryptCipher(key);
|
||||
_fingerCryptoObj = new FingerprintManager.CryptoObject(cipher);
|
||||
_fingerHelper = new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, this);
|
||||
boxFingerprint.setVisibility(View.VISIBLE);
|
||||
_bioCryptoObj = new BiometricPrompt.CryptoObject(cipher);
|
||||
_bioPrompt = new BiometricPrompt(this, new UiThreadExecutor(), new BiometricPromptListener());
|
||||
biometricsButton.setVisibility(View.VISIBLE);
|
||||
invalidated = false;
|
||||
break;
|
||||
}
|
||||
|
@ -106,7 +94,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
|
||||
// display a help message if a matching invalidated keystore entry was found
|
||||
if (invalidated) {
|
||||
boxFingerprintInfo.setVisibility(View.VISIBLE);
|
||||
boxBiometricInfo.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,7 +109,11 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
}
|
||||
});
|
||||
|
||||
if (_fingerHelper == null) {
|
||||
biometricsButton.setOnClickListener(v -> {
|
||||
showBiometricPrompt();
|
||||
});
|
||||
|
||||
if (_bioCryptoObj == null) {
|
||||
_textPassword.requestFocus();
|
||||
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
|
||||
}
|
||||
|
@ -144,7 +136,7 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
private void selectPassword() {
|
||||
_textPassword.selectAll();
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||
}
|
||||
|
||||
|
@ -162,36 +154,31 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("NewApi")
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
if (_fingerHelper != null) {
|
||||
_fingerHelper.startListening(_fingerCryptoObj);
|
||||
if (_bioPrompt != null) {
|
||||
showBiometricPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
public void showBiometricPrompt() {
|
||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.authentication))
|
||||
.setNegativeButtonText(getString(android.R.string.cancel))
|
||||
.build();
|
||||
_bioPrompt.authenticate(info, _bioCryptoObj);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("NewApi")
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
|
||||
if (_fingerHelper != null) {
|
||||
_fingerHelper.stopListening();
|
||||
if (_bioPrompt != null) {
|
||||
_bioPrompt.cancelAuthentication();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("NewApi")
|
||||
public void onAuthenticated() {
|
||||
trySlots(FingerprintSlot.class, _fingerCryptoObj.getCipher());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTaskFinished(SlotListTask.Result result) {
|
||||
if (result != null) {
|
||||
|
@ -210,4 +197,25 @@ public class AuthActivity extends AegisActivity implements FingerprintUiHelper.C
|
|||
showError();
|
||||
}
|
||||
}
|
||||
|
||||
private class BiometricPromptListener extends BiometricPrompt.AuthenticationCallback {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
|
||||
super.onAuthenticationError(errorCode, errString);
|
||||
if (!BiometricsHelper.isCanceled(errorCode)) {
|
||||
Toast.makeText(AuthActivity.this, errString, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
trySlots(BiometricSlot.class, _bioCryptoObj.getCipher());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,8 +4,6 @@ import android.app.Activity;
|
|||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
|
@ -18,30 +16,21 @@ import android.widget.CheckBox;
|
|||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.NumberPicker;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.beemdevelopment.aegis.Preferences;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
|
||||
import com.beemdevelopment.aegis.ui.tasks.DerivationTask;
|
||||
import com.mattprecious.swirl.SwirlView;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
public class Dialogs {
|
||||
private Dialogs() {
|
||||
|
@ -195,52 +184,6 @@ public class Dialogs {
|
|||
showSecureDialog(dialog);
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public static void showFingerprintDialog(Activity activity, Dialogs.SlotListener listener) {
|
||||
View view = activity.getLayoutInflater().inflate(R.layout.dialog_fingerprint, null);
|
||||
TextView textFingerprint = view.findViewById(R.id.text_fingerprint);
|
||||
SwirlView imgFingerprint = view.findViewById(R.id.img_fingerprint);
|
||||
|
||||
FingerprintManager.CryptoObject obj;
|
||||
FingerprintSlot slot;
|
||||
final AtomicReference<FingerprintUiHelper> helper = new AtomicReference<>();
|
||||
FingerprintManager manager = FingerprintHelper.getManager(activity);
|
||||
|
||||
try {
|
||||
slot = new FingerprintSlot();
|
||||
SecretKey key = new KeyStoreHandle().generateKey(slot.getUUID().toString());
|
||||
Cipher cipher = Slot.createEncryptCipher(key);
|
||||
obj = new FingerprintManager.CryptoObject(cipher);
|
||||
} catch (KeyStoreHandleException | SlotException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(activity)
|
||||
.setTitle(R.string.register_fingerprint)
|
||||
.setView(view)
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.setOnDismissListener(d -> {
|
||||
helper.get().stopListening();
|
||||
})
|
||||
.create();
|
||||
|
||||
helper.set(new FingerprintUiHelper(manager, imgFingerprint, textFingerprint, new FingerprintUiHelper.Callback() {
|
||||
@Override
|
||||
public void onAuthenticated() {
|
||||
listener.onSlotResult(slot, obj.getCipher());
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
|
||||
}
|
||||
}));
|
||||
|
||||
helper.get().startListening(obj);
|
||||
showSecureDialog(dialog);
|
||||
}
|
||||
|
||||
public interface NumberInputListener {
|
||||
void onNumberInputResult(int number);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import android.content.Intent;
|
|||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.beemdevelopment.aegis.Preferences;
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.db.Database;
|
||||
|
@ -12,11 +14,10 @@ import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
|||
import com.beemdevelopment.aegis.db.DatabaseFileException;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManager;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManagerException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticatedSlide;
|
||||
import com.beemdevelopment.aegis.ui.slides.CustomAuthenticationSlide;
|
||||
import com.beemdevelopment.aegis.ui.tasks.DerivationTask;
|
||||
|
@ -29,8 +30,6 @@ import org.json.JSONObject;
|
|||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
public class IntroActivity extends AppIntro2 implements DerivationTask.Callback {
|
||||
public static final int RESULT_OK = 0;
|
||||
public static final int RESULT_EXCEPTION = 1;
|
||||
|
@ -135,7 +134,6 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
|||
creds = new DatabaseFileCredentials();
|
||||
}
|
||||
|
||||
SlotList slots = null;
|
||||
if (cryptType != CustomAuthenticationSlide.CRYPT_TYPE_NONE) {
|
||||
// encrypt the master key with a key derived from the user's password
|
||||
// and add it to the list of slots
|
||||
|
@ -150,18 +148,14 @@ public class IntroActivity extends AppIntro2 implements DerivationTask.Callback
|
|||
}
|
||||
}
|
||||
|
||||
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_FINGER) {
|
||||
if (cryptType == CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC) {
|
||||
BiometricSlot slot = _authenticatedSlide.getBiometricSlot();
|
||||
try {
|
||||
// encrypt the master key with the fingerprint key
|
||||
// and add it to the list of slots
|
||||
FingerprintSlot slot = _authenticatedSlide.getFingerSlot();
|
||||
Cipher cipher = _authenticatedSlide.getFingerCipher();
|
||||
slot.setKey(creds.getKey(), cipher);
|
||||
creds.getSlots().add(slot);
|
||||
slot.setKey(creds.getKey(), _authenticatedSlide.getBiometriCipher());
|
||||
} catch (SlotException e) {
|
||||
setException(e);
|
||||
return;
|
||||
}
|
||||
creds.getSlots().add(slot);
|
||||
}
|
||||
|
||||
// finally, save the database
|
||||
|
|
|
@ -12,6 +12,12 @@ import android.view.Window;
|
|||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import com.beemdevelopment.aegis.AegisApplication;
|
||||
import com.beemdevelopment.aegis.BuildConfig;
|
||||
import com.beemdevelopment.aegis.CancelAction;
|
||||
|
@ -25,12 +31,13 @@ import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
|||
import com.beemdevelopment.aegis.db.DatabaseFileException;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManager;
|
||||
import com.beemdevelopment.aegis.db.DatabaseManagerException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.beemdevelopment.aegis.helpers.PermissionHelper;
|
||||
import com.beemdevelopment.aegis.importers.AegisImporter;
|
||||
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
||||
|
@ -56,10 +63,6 @@ import java.util.concurrent.atomic.AtomicReference;
|
|||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
public class PreferencesFragment extends PreferenceFragmentCompat {
|
||||
// activity request codes
|
||||
private static final int CODE_IMPORT = 0;
|
||||
|
@ -81,7 +84,7 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
|||
private UUIDMap<DatabaseEntry> _importerEntries;
|
||||
|
||||
private SwitchPreference _encryptionPreference;
|
||||
private SwitchPreference _fingerprintPreference;
|
||||
private SwitchPreference _biometricsPreference;
|
||||
private Preference _autoLockPreference;
|
||||
private Preference _setPasswordPreference;
|
||||
private Preference _slotsPreference;
|
||||
|
@ -289,20 +292,25 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
|||
}
|
||||
});
|
||||
|
||||
_fingerprintPreference = (SwitchPreference) findPreference("pref_fingerprint");
|
||||
_fingerprintPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
_biometricsPreference = (SwitchPreference) findPreference("pref_biometrics");
|
||||
_biometricsPreference.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
DatabaseFileCredentials creds = _db.getCredentials();
|
||||
SlotList slots = creds.getSlots();
|
||||
|
||||
if (!slots.has(FingerprintSlot.class)) {
|
||||
if (FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(getContext())) {
|
||||
Dialogs.showFingerprintDialog(getActivity(), new RegisterFingerprintListener());
|
||||
if (!slots.has(BiometricSlot.class)) {
|
||||
if (BiometricsHelper.isAvailable(getContext())) {
|
||||
BiometricSlotInitializer initializer = new BiometricSlotInitializer(PreferencesFragment.this, new RegisterBiometricsListener());
|
||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.set_up_biometric))
|
||||
.setNegativeButtonText(getString(android.R.string.cancel))
|
||||
.build();
|
||||
initializer.authenticate(info);
|
||||
}
|
||||
} else {
|
||||
// remove the fingerprint slot
|
||||
FingerprintSlot slot = slots.find(FingerprintSlot.class);
|
||||
// remove the biometric slot
|
||||
BiometricSlot slot = slots.find(BiometricSlot.class);
|
||||
slots.remove(slot);
|
||||
_db.setCredentials(creds);
|
||||
|
||||
|
@ -676,24 +684,24 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
|||
boolean encrypted = _db.isEncryptionEnabled();
|
||||
_encryptionPreference.setChecked(encrypted, true);
|
||||
_setPasswordPreference.setVisible(encrypted);
|
||||
_fingerprintPreference.setVisible(encrypted);
|
||||
_biometricsPreference.setVisible(encrypted);
|
||||
_slotsPreference.setEnabled(encrypted);
|
||||
_autoLockPreference.setVisible(encrypted);
|
||||
|
||||
if (encrypted) {
|
||||
SlotList slots = _db.getCredentials().getSlots();
|
||||
boolean multiPassword = slots.findAll(PasswordSlot.class).size() > 1;
|
||||
boolean multiFinger = slots.findAll(FingerprintSlot.class).size() > 1;
|
||||
boolean showSlots = BuildConfig.DEBUG || multiPassword || multiFinger;
|
||||
boolean canUseFinger = FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(getContext());
|
||||
boolean multiBio = slots.findAll(BiometricSlot.class).size() > 1;
|
||||
boolean showSlots = BuildConfig.DEBUG || multiPassword || multiBio;
|
||||
boolean canUseBio = BiometricsHelper.isAvailable(getContext());
|
||||
_setPasswordPreference.setEnabled(!multiPassword);
|
||||
_fingerprintPreference.setEnabled(canUseFinger && !multiFinger);
|
||||
_fingerprintPreference.setChecked(slots.has(FingerprintSlot.class), true);
|
||||
_biometricsPreference.setEnabled(canUseBio && !multiBio);
|
||||
_biometricsPreference.setChecked(slots.has(BiometricSlot.class), true);
|
||||
_slotsPreference.setVisible(showSlots);
|
||||
} else {
|
||||
_setPasswordPreference.setEnabled(false);
|
||||
_fingerprintPreference.setEnabled(false);
|
||||
_fingerprintPreference.setChecked(false, true);
|
||||
_biometricsPreference.setEnabled(false);
|
||||
_biometricsPreference.setChecked(false, true);
|
||||
_slotsPreference.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
@ -730,20 +738,17 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
|||
}
|
||||
}
|
||||
|
||||
private class RegisterFingerprintListener implements Dialogs.SlotListener {
|
||||
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
||||
DatabaseFileCredentials creds = _db.getCredentials();
|
||||
SlotList slots = creds.getSlots();
|
||||
|
||||
try {
|
||||
slot.setKey(creds.getKey(), cipher);
|
||||
} catch (SlotException e) {
|
||||
onException(e);
|
||||
onSlotInitializationFailed(0, e.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
slots.add(slot);
|
||||
creds.getSlots().add(slot);
|
||||
_db.setCredentials(creds);
|
||||
|
||||
saveDatabase();
|
||||
|
@ -751,8 +756,10 @@ public class PreferencesFragment extends PreferenceFragmentCompat {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception e) {
|
||||
Toast.makeText(getActivity(), getString(R.string.encryption_enable_fingerprint_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) {
|
||||
if (!BiometricsHelper.isCanceled(errorCode)) {
|
||||
Toast.makeText(getActivity(), String.format("%s: %s", getString(R.string.encryption_enable_biometrics_error), errString), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,26 +7,29 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.DatabaseFileCredentials;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotList;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.beemdevelopment.aegis.ui.views.SlotAdapter;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener, Dialogs.SlotListener {
|
||||
public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Listener {
|
||||
private DatabaseFileCredentials _creds;
|
||||
private SlotAdapter _adapter;
|
||||
|
||||
|
@ -42,14 +45,19 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
bar.setHomeAsUpIndicator(R.drawable.ic_close);
|
||||
bar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
findViewById(R.id.button_add_fingerprint).setOnClickListener(view -> {
|
||||
if (FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(this)) {
|
||||
Dialogs.showFingerprintDialog(this ,this);
|
||||
findViewById(R.id.button_add_biometric).setOnClickListener(view -> {
|
||||
if (BiometricsHelper.isAvailable(this)) {
|
||||
BiometricSlotInitializer initializer = new BiometricSlotInitializer(SlotManagerActivity.this, new RegisterBiometricsListener());
|
||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.add_biometric_slot))
|
||||
.setNegativeButtonText(getString(android.R.string.cancel))
|
||||
.build();
|
||||
initializer.authenticate(info);
|
||||
}
|
||||
});
|
||||
|
||||
findViewById(R.id.button_add_password).setOnClickListener(view -> {
|
||||
Dialogs.showSetPasswordDialog(this, this);
|
||||
Dialogs.showSetPasswordDialog(this, new PasswordListener());
|
||||
});
|
||||
|
||||
// set up the recycler view
|
||||
|
@ -66,17 +74,17 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
_adapter.addSlot(slot);
|
||||
}
|
||||
|
||||
updateFingerprintButton();
|
||||
updateBiometricsButton();
|
||||
}
|
||||
|
||||
private void updateFingerprintButton() {
|
||||
// only show the fingerprint option if we can get an instance of the fingerprint manager
|
||||
private void updateBiometricsButton() {
|
||||
// only show the biometrics option if we can get an instance of the biometrics manager
|
||||
// and if none of the slots in the collection has a matching alias in the keystore
|
||||
int visibility = View.VISIBLE;
|
||||
if (FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(this)) {
|
||||
if (BiometricsHelper.isAvailable(this)) {
|
||||
try {
|
||||
KeyStoreHandle keyStore = new KeyStoreHandle();
|
||||
for (FingerprintSlot slot : _creds.getSlots().findAll(FingerprintSlot.class)) {
|
||||
for (BiometricSlot slot : _creds.getSlots().findAll(BiometricSlot.class)) {
|
||||
if (keyStore.containsKey(slot.getUUID().toString())) {
|
||||
visibility = View.GONE;
|
||||
break;
|
||||
|
@ -88,7 +96,7 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
} else {
|
||||
visibility = View.GONE;
|
||||
}
|
||||
findViewById(R.id.button_add_fingerprint).setVisibility(visibility);
|
||||
findViewById(R.id.button_add_biometric).setVisibility(visibility);
|
||||
}
|
||||
|
||||
private void onSave() {
|
||||
|
@ -164,29 +172,60 @@ public class SlotManagerActivity extends AegisActivity implements SlotAdapter.Li
|
|||
slots.remove(slot);
|
||||
_adapter.removeSlot(slot);
|
||||
_edited = true;
|
||||
updateFingerprintButton();
|
||||
updateBiometricsButton();
|
||||
})
|
||||
.setNegativeButton(android.R.string.no, null)
|
||||
.create());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
try {
|
||||
slot.setKey(_creds.getKey(), cipher);
|
||||
} catch (SlotException e) {
|
||||
onException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
private void addSlot(Slot slot) {
|
||||
_creds.getSlots().add(slot);
|
||||
_adapter.addSlot(slot);
|
||||
_edited = true;
|
||||
updateFingerprintButton();
|
||||
updateBiometricsButton();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception e) {
|
||||
Toast.makeText(this, getString(R.string.adding_new_slot_error) + e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
private void showSlotError(String error) {
|
||||
Toast.makeText(SlotManagerActivity.this, getString(R.string.adding_new_slot_error) + error, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
private class RegisterBiometricsListener implements BiometricSlotInitializer.Listener {
|
||||
|
||||
@Override
|
||||
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
||||
try {
|
||||
slot.setKey(_creds.getKey(), cipher);
|
||||
addSlot(slot);
|
||||
} catch (SlotException e) {
|
||||
onSlotInitializationFailed(0, e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) {
|
||||
if (!BiometricsHelper.isCanceled(errorCode)) {
|
||||
showSlotError(errString.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PasswordListener implements Dialogs.SlotListener {
|
||||
|
||||
@Override
|
||||
public void onSlotResult(Slot slot, Cipher cipher) {
|
||||
try {
|
||||
slot.setKey(_creds.getKey(), cipher);
|
||||
} catch (SlotException e) {
|
||||
onException(e);
|
||||
return;
|
||||
}
|
||||
|
||||
addSlot(slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Exception e) {
|
||||
showSlotError(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,53 +1,39 @@
|
|||
package com.beemdevelopment.aegis.ui.slides;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.method.PasswordTransformationMethod;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.biometric.BiometricPrompt;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricSlotInitializer;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintUiHelper;
|
||||
import com.github.paolorotolo.appintro.ISlidePolicy;
|
||||
import com.github.paolorotolo.appintro.ISlideSelectionListener;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
import com.mattprecious.swirl.SwirlView;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiHelper.Callback, ISlidePolicy, ISlideSelectionListener {
|
||||
public class CustomAuthenticatedSlide extends Fragment implements ISlidePolicy, ISlideSelectionListener {
|
||||
private int _cryptType;
|
||||
private EditText _textPassword;
|
||||
private EditText _textPasswordConfirm;
|
||||
private CheckBox _checkPasswordVisibility;
|
||||
private int _bgColor;
|
||||
|
||||
private LinearLayout _boxFingerprint;
|
||||
private SwirlView _imgFingerprint;
|
||||
private TextView _textFingerprint;
|
||||
private FingerprintUiHelper _fingerHelper;
|
||||
private KeyStoreHandle _storeHandle;
|
||||
private FingerprintSlot _fingerSlot;
|
||||
private FingerprintManager.CryptoObject _fingerCryptoObj;
|
||||
private boolean _fingerAuthenticated;
|
||||
private BiometricSlot _bioSlot;
|
||||
private Cipher _bioCipher;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
|
@ -55,13 +41,6 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
|||
_textPassword = view.findViewById(R.id.text_password);
|
||||
_textPasswordConfirm = view.findViewById(R.id.text_password_confirm);
|
||||
_checkPasswordVisibility = view.findViewById(R.id.check_toggle_visibility);
|
||||
_boxFingerprint = view.findViewById(R.id.box_fingerprint);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
ViewGroup insertPoint = view.findViewById(R.id.img_fingerprint_insert);
|
||||
_imgFingerprint = new SwirlView(getContext());
|
||||
insertPoint.addView(_imgFingerprint, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
}
|
||||
|
||||
_checkPasswordVisibility.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
|
@ -74,7 +53,6 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
|||
}
|
||||
});
|
||||
|
||||
_textFingerprint = view.findViewById(R.id.text_fingerprint);
|
||||
view.findViewById(R.id.main).setBackgroundColor(_bgColor);
|
||||
return view;
|
||||
}
|
||||
|
@ -83,82 +61,53 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
|||
return _cryptType;
|
||||
}
|
||||
|
||||
public BiometricSlot getBiometricSlot() {
|
||||
return _bioSlot;
|
||||
}
|
||||
|
||||
public Cipher getBiometriCipher() {
|
||||
return _bioCipher;
|
||||
}
|
||||
|
||||
public char[] getPassword() {
|
||||
return EditTextHelper.getEditTextChars(_textPassword);
|
||||
}
|
||||
|
||||
@SuppressLint("NewApi")
|
||||
public Cipher getFingerCipher() {
|
||||
return _fingerCryptoObj.getCipher();
|
||||
}
|
||||
|
||||
public FingerprintSlot getFingerSlot() {
|
||||
return _fingerSlot;
|
||||
}
|
||||
|
||||
public void setBgColor(int color) {
|
||||
_bgColor = color;
|
||||
}
|
||||
|
||||
public void showBiometricPrompt() {
|
||||
BiometricSlotInitializer initializer = new BiometricSlotInitializer(this, new BiometricsListener());
|
||||
BiometricPrompt.PromptInfo info = new BiometricPrompt.PromptInfo.Builder()
|
||||
.setTitle(getString(R.string.set_up_biometric))
|
||||
.setNegativeButtonText(getString(android.R.string.cancel))
|
||||
.build();
|
||||
initializer.authenticate(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("NewApi")
|
||||
public void onSlideSelected() {
|
||||
Intent intent = getActivity().getIntent();
|
||||
_cryptType = intent.getIntExtra("cryptType", CustomAuthenticationSlide.CRYPT_TYPE_INVALID);
|
||||
|
||||
switch(_cryptType) {
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_NONE:
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_PASS:
|
||||
break;
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_FINGER:
|
||||
_boxFingerprint.setVisibility(View.VISIBLE);
|
||||
|
||||
SecretKey key;
|
||||
try {
|
||||
if (_storeHandle == null) {
|
||||
_storeHandle = new KeyStoreHandle();
|
||||
_fingerSlot = new FingerprintSlot();
|
||||
}
|
||||
key = _storeHandle.generateKey(_fingerSlot.getUUID().toString());
|
||||
} catch (KeyStoreHandleException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (_fingerHelper == null) {
|
||||
FingerprintManager fingerManager = (FingerprintManager) getContext().getSystemService(Context.FINGERPRINT_SERVICE);
|
||||
_fingerHelper = new FingerprintUiHelper(fingerManager, _imgFingerprint, _textFingerprint, this);
|
||||
}
|
||||
|
||||
try {
|
||||
Cipher cipher = Slot.createEncryptCipher(key);
|
||||
_fingerCryptoObj = new FingerprintManager.CryptoObject(cipher);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
_fingerHelper.startListening(_fingerCryptoObj);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
if (_cryptType == CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC) {
|
||||
showBiometricPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressLint("NewApi")
|
||||
public void onSlideDeselected() {
|
||||
if (_fingerHelper != null) {
|
||||
_fingerAuthenticated = false;
|
||||
_boxFingerprint.setVisibility(View.INVISIBLE);
|
||||
_fingerHelper.stopListening();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPolicyRespected() {
|
||||
switch(_cryptType) {
|
||||
switch (_cryptType) {
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_NONE:
|
||||
return true;
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_FINGER:
|
||||
if (!_fingerAuthenticated) {
|
||||
case CustomAuthenticationSlide.CRYPT_TYPE_BIOMETRIC:
|
||||
if (_bioSlot == null) {
|
||||
return false;
|
||||
}
|
||||
// intentional fallthrough
|
||||
|
@ -169,7 +118,7 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
|||
|
||||
return false;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,26 +127,30 @@ public class CustomAuthenticatedSlide extends Fragment implements FingerprintUiH
|
|||
String message;
|
||||
if (!EditTextHelper.areEditTextsEqual(_textPassword, _textPasswordConfirm) && !_checkPasswordVisibility.isChecked()) {
|
||||
message = getString(R.string.password_equality_error);
|
||||
} else if (!_fingerAuthenticated) {
|
||||
message = getString(R.string.register_fingerprint);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
|
||||
snackbar.show();
|
||||
View view = getView();
|
||||
if (view != null) {
|
||||
Snackbar snackbar = Snackbar.make(view, message, Snackbar.LENGTH_LONG);
|
||||
snackbar.show();
|
||||
}
|
||||
} else if (_bioSlot == null) {
|
||||
showBiometricPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticated() {
|
||||
_fingerAuthenticated = true;
|
||||
}
|
||||
private class BiometricsListener implements BiometricSlotInitializer.Listener {
|
||||
|
||||
@Override
|
||||
public void onError() {
|
||||
@Override
|
||||
public void onInitializeSlot(BiometricSlot slot, Cipher cipher) {
|
||||
_bioSlot = slot;
|
||||
_bioCipher = cipher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSlotInitializationFailed(int errorCode, @NonNull CharSequence errString) {
|
||||
if (!BiometricsHelper.isCanceled(errorCode)) {
|
||||
Toast.makeText(CustomAuthenticatedSlide.this.getContext(), errString, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package com.beemdevelopment.aegis.ui.slides;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
|
@ -11,18 +9,18 @@ import android.widget.RadioButton;
|
|||
import android.widget.RadioGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
import com.github.paolorotolo.appintro.ISlidePolicy;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy, RadioGroup.OnCheckedChangeListener {
|
||||
public static final int CRYPT_TYPE_INVALID = 0;
|
||||
public static final int CRYPT_TYPE_NONE = 1;
|
||||
public static final int CRYPT_TYPE_PASS = 2;
|
||||
public static final int CRYPT_TYPE_FINGER = 3;
|
||||
public static final int CRYPT_TYPE_BIOMETRIC = 3;
|
||||
|
||||
private RadioGroup _buttonGroup;
|
||||
private int _bgColor;
|
||||
|
@ -35,9 +33,9 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
|
|||
onCheckedChanged(_buttonGroup, _buttonGroup.getCheckedRadioButtonId());
|
||||
|
||||
// only enable the fingerprint option if the api version is new enough, permission is granted and a scanner is found
|
||||
if (FingerprintHelper.isSupported() && FingerprintHelper.isAvailable(getContext())) {
|
||||
RadioButton button = view.findViewById(R.id.rb_fingerprint);
|
||||
TextView text = view.findViewById(R.id.text_rb_fingerprint);
|
||||
if (BiometricsHelper.isAvailable(getContext())) {
|
||||
RadioButton button = view.findViewById(R.id.rb_biometrics);
|
||||
TextView text = view.findViewById(R.id.text_rb_biometrics);
|
||||
button.setEnabled(true);
|
||||
text.setEnabled(true);
|
||||
}
|
||||
|
@ -75,8 +73,8 @@ public class CustomAuthenticationSlide extends Fragment implements ISlidePolicy,
|
|||
case R.id.rb_password:
|
||||
id = CRYPT_TYPE_PASS;
|
||||
break;
|
||||
case R.id.rb_fingerprint:
|
||||
id = CRYPT_TYPE_FINGER;
|
||||
case R.id.rb_biometrics:
|
||||
id = CRYPT_TYPE_BIOMETRIC;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException();
|
||||
|
|
|
@ -6,7 +6,7 @@ import android.content.Context;
|
|||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.CryptoUtils;
|
||||
import com.beemdevelopment.aegis.crypto.MasterKey;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.db.slots.SlotException;
|
||||
|
@ -38,8 +38,8 @@ public class SlotListTask<T extends Slot> extends ProgressDialogTask<SlotListTas
|
|||
if (slot instanceof PasswordSlot) {
|
||||
char[] password = (char[]) params.getObj();
|
||||
return decryptPasswordSlot((PasswordSlot) slot, password);
|
||||
} else if (slot instanceof FingerprintSlot) {
|
||||
return decryptFingerprintSlot((FingerprintSlot) slot, (Cipher) params.getObj());
|
||||
} else if (slot instanceof BiometricSlot) {
|
||||
return decryptBiometricSlot((BiometricSlot) slot, (Cipher) params.getObj());
|
||||
}
|
||||
} catch (SlotException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
@ -51,7 +51,7 @@ public class SlotListTask<T extends Slot> extends ProgressDialogTask<SlotListTas
|
|||
return null;
|
||||
}
|
||||
|
||||
private Result decryptFingerprintSlot(FingerprintSlot slot, Cipher cipher)
|
||||
private Result decryptBiometricSlot(BiometricSlot slot, Cipher cipher)
|
||||
throws SlotException, SlotIntegrityException {
|
||||
MasterKey key = slot.getKey(cipher);
|
||||
return new Result(key, slot);
|
||||
|
|
|
@ -5,16 +5,16 @@ import android.widget.ImageView;
|
|||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.beemdevelopment.aegis.R;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandle;
|
||||
import com.beemdevelopment.aegis.crypto.KeyStoreHandleException;
|
||||
import com.beemdevelopment.aegis.db.slots.FingerprintSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.BiometricSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.PasswordSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.RawSlot;
|
||||
import com.beemdevelopment.aegis.db.slots.Slot;
|
||||
import com.beemdevelopment.aegis.helpers.FingerprintHelper;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.beemdevelopment.aegis.helpers.BiometricsHelper;
|
||||
|
||||
public class SlotHolder extends RecyclerView.ViewHolder {
|
||||
private TextView _slotUsed;
|
||||
|
@ -36,10 +36,10 @@ public class SlotHolder extends RecyclerView.ViewHolder {
|
|||
if (slot instanceof PasswordSlot) {
|
||||
_slotName.setText(R.string.password);
|
||||
_slotImg.setImageResource(R.drawable.ic_create_black_24dp);
|
||||
} else if (slot instanceof FingerprintSlot) {
|
||||
_slotName.setText(R.string.authentication_method_fingerprint);
|
||||
} else if (slot instanceof BiometricSlot) {
|
||||
_slotName.setText(R.string.authentication_method_biometrics);
|
||||
_slotImg.setImageResource(R.drawable.ic_fingerprint_black_24dp);
|
||||
if (FingerprintHelper.isSupported()) {
|
||||
if (BiometricsHelper.isAvailable(itemView.getContext())) {
|
||||
try {
|
||||
KeyStoreHandle keyStore = new KeyStoreHandle();
|
||||
if (keyStore.containsKey(slot.getUUID().toString())) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue