taomenggo init
This commit is contained in:
1
fingermanager/.gitignore
vendored
Normal file
1
fingermanager/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
32
fingermanager/build.gradle
Normal file
32
fingermanager/build.gradle
Normal file
@@ -0,0 +1,32 @@
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdk 33
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
0
fingermanager/consumer-rules.pro
Normal file
0
fingermanager/consumer-rules.pro
Normal file
21
fingermanager/proguard-rules.pro
vendored
Normal file
21
fingermanager/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
@Test
|
||||
public void useAppContext() {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("com.codersun.fingerprintcompat.test", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
6
fingermanager/src/main/AndroidManifest.xml
Normal file
6
fingermanager/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.codersun.fingerprintcompat">
|
||||
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC"/>
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.app.DialogFragment;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* @author codersun
|
||||
* @time 2019/9/3 15:14
|
||||
*/
|
||||
public abstract class AFingerDialog extends DialogFragment
|
||||
{
|
||||
|
||||
private IonDismissListener mDismissListener;
|
||||
|
||||
public AFingerDialog()
|
||||
{
|
||||
super();
|
||||
setCancelable(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听返回键
|
||||
*
|
||||
* @param savedInstanceState
|
||||
* @return
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState)
|
||||
{
|
||||
Dialog dialog = super.onCreateDialog(savedInstanceState);
|
||||
dialog.setOnKeyListener(new DialogInterface.OnKeyListener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event)
|
||||
{
|
||||
if (keyCode == KeyEvent.KEYCODE_HOME || keyCode == KeyEvent.KEYCODE_BACK)
|
||||
{
|
||||
dismiss();
|
||||
cancelFingerAuth();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog)
|
||||
{
|
||||
super.onDismiss(dialog);
|
||||
if (mDismissListener != null)
|
||||
mDismissListener.onDismiss();
|
||||
}
|
||||
|
||||
public void setOnDismissListener(IonDismissListener dismissListener)
|
||||
{
|
||||
this.mDismissListener = dismissListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState)
|
||||
{
|
||||
super.onSaveInstanceState(outState);
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
|
||||
protected void cancelFingerAuth()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public interface IonDismissListener
|
||||
{
|
||||
|
||||
void onDismiss();
|
||||
}
|
||||
|
||||
public abstract void onSucceed();
|
||||
|
||||
/**
|
||||
* 当识别的手指没有注册时回调,但是可以继续验证
|
||||
*
|
||||
* @author codersun
|
||||
* @time 2019/10/16 10:36
|
||||
*/
|
||||
public abstract void onFailed();
|
||||
|
||||
/**
|
||||
* 指纹识别不对,会提示,手指不要大范围移动等信息,可以继续验证
|
||||
*
|
||||
* @author codersun
|
||||
* @time 2019/10/16 10:37
|
||||
*/
|
||||
public abstract void onHelp(String help);
|
||||
|
||||
/**
|
||||
* 指纹识别彻底失败,不能继续验证
|
||||
*
|
||||
* @author codersun
|
||||
* @time 2019/10/16 10:37
|
||||
*/
|
||||
public abstract void onError(String error);
|
||||
|
||||
public abstract void onCancelAuth();
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
/**
|
||||
* @author codersun
|
||||
* @time 2019/9/8 16:36
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public abstract class AonFingerChangeCallback
|
||||
{
|
||||
|
||||
void onChange(Context context)
|
||||
{
|
||||
SharePreferenceUtil.saveData(context, SharePreferenceUtil.KEY_IS_FINGER_CHANGE, "1");
|
||||
onFingerDataChange();
|
||||
}
|
||||
|
||||
protected abstract void onFingerDataChange();
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
/**
|
||||
* @author codersun
|
||||
* @time 2019/10/15 20:48
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class BiometricPromptImpl23 implements IBiometricPromptImpl
|
||||
{
|
||||
|
||||
private Cipher mCipher;
|
||||
|
||||
private Activity mActivity;
|
||||
|
||||
private boolean mSelfCanceled;
|
||||
|
||||
private AFingerDialog mFingerDialog;
|
||||
|
||||
private IonFingerCallback mCallback;
|
||||
|
||||
private AonFingerChangeCallback mFingerChangeCallback;
|
||||
|
||||
private FingerManagerController mFingerManagerController;
|
||||
|
||||
BiometricPromptImpl23(Activity activity, AFingerDialog fingerDialog,
|
||||
FingerManagerController fingerManagerController)
|
||||
{
|
||||
this.mActivity = activity;
|
||||
mFingerManagerController = fingerManagerController;
|
||||
mCipher = CipherHelper.getInstance().createCipher();
|
||||
mFingerChangeCallback = fingerManagerController.getFingerChangeCallback();
|
||||
this.mFingerDialog = fingerDialog == null ? DefaultFingerDialog.newInstance(fingerManagerController) : fingerDialog;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void authenticate(@NonNull final CancellationSignal cancel)
|
||||
{
|
||||
this.mCallback = mFingerManagerController.getFingerCheckCallback();
|
||||
mSelfCanceled = false;
|
||||
if (CipherHelper.getInstance().initCipher(mCipher) || SharePreferenceUtil.isFingerDataChange(mActivity))
|
||||
{
|
||||
mFingerChangeCallback.onChange(mActivity);
|
||||
return;
|
||||
}
|
||||
|
||||
mFingerDialog.setOnDismissListener(new AFingerDialog.IonDismissListener()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onDismiss()
|
||||
{
|
||||
mSelfCanceled = !cancel.isCanceled();
|
||||
if (mSelfCanceled)
|
||||
{
|
||||
cancel.cancel();
|
||||
//如果使用的是默认弹窗,就使用cancel回调,否则交给开发者自行处理
|
||||
if (mFingerDialog.getClass() == DefaultFingerDialog.class)
|
||||
{
|
||||
mCallback.onCancel();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!mFingerDialog.isAdded())
|
||||
mFingerDialog.show(mActivity.getFragmentManager(), mFingerDialog.getClass().getSimpleName());
|
||||
|
||||
FingerprintManager fingerprintManager = (FingerprintManager) mActivity.getSystemService(FingerprintManager.class);
|
||||
fingerprintManager.authenticate(new FingerprintManager.CryptoObject(mCipher), cancel, 0,
|
||||
new FingerprintManager.AuthenticationCallback()
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onAuthenticationError(int errMsgId, CharSequence errString)
|
||||
{
|
||||
super.onAuthenticationError(errMsgId, errString);
|
||||
if (!mSelfCanceled)
|
||||
{
|
||||
//当指纹识别结算手动调用cancel,用于后面判断是手动取消还是自动取消识别的
|
||||
cancel.cancel();
|
||||
mFingerDialog.onError(errString.toString());
|
||||
mCallback.onError(errString.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString)
|
||||
{
|
||||
super.onAuthenticationHelp(helpMsgId, helpString);
|
||||
mFingerDialog.onHelp(helpString.toString());
|
||||
mCallback.onHelp(helpString.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result)
|
||||
{
|
||||
super.onAuthenticationSucceeded(result);
|
||||
Cipher cipher = result.getCryptoObject().getCipher();
|
||||
if (cipher != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
byte[] bytes = cipher.doFinal();
|
||||
////当指纹识别结算手动调用cancel,用于后面判断是手动取消还是自动取消识别的
|
||||
cancel.cancel();
|
||||
mFingerDialog.onSucceed();
|
||||
mCallback.onSucceed();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
mFingerChangeCallback.onChange(mActivity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed()
|
||||
{
|
||||
super.onAuthenticationFailed();
|
||||
mFingerDialog.onFailed();
|
||||
mCallback.onFailed();
|
||||
}
|
||||
}, null);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.hardware.biometrics.BiometricPrompt;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
|
||||
/**
|
||||
* @author codersun
|
||||
* @time 2019/10/15 20:48
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
public class BiometricPromptImpl28 implements IBiometricPromptImpl {
|
||||
|
||||
private Activity mActivity;
|
||||
private BiometricPrompt mBiometricPrompt;
|
||||
private CancellationSignal mCancellationSignal;
|
||||
private Cipher cipher;
|
||||
private IonFingerCallback mCallback;
|
||||
private boolean userCancel;
|
||||
|
||||
private AonFingerChangeCallback mFingerChangeCallback;
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
BiometricPromptImpl28(Activity activity,
|
||||
FingerManagerController fingerManagerController) {
|
||||
this.mActivity = activity;
|
||||
mCallback = fingerManagerController.getFingerCheckCallback();
|
||||
mFingerChangeCallback = fingerManagerController.getFingerChangeCallback();
|
||||
mBiometricPrompt = new BiometricPrompt
|
||||
.Builder(activity)
|
||||
.setTitle(fingerManagerController.getTitle())
|
||||
.setDescription(fingerManagerController.getDes())
|
||||
.setNegativeButton(fingerManagerController.getNegativeText(),
|
||||
activity.getMainExecutor(), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
mCallback.onCancel();
|
||||
userCancel = true;
|
||||
mCancellationSignal.cancel();
|
||||
}
|
||||
})
|
||||
.build();
|
||||
|
||||
|
||||
cipher = CipherHelper.getInstance().createCipher();
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
@Override
|
||||
public void authenticate(@Nullable CancellationSignal cancel) {
|
||||
userCancel = false;
|
||||
mCancellationSignal = cancel;
|
||||
|
||||
if (CipherHelper.getInstance().initCipher(cipher) || SharePreferenceUtil.isFingerDataChange(mActivity)) {
|
||||
mFingerChangeCallback.onChange(mActivity);
|
||||
return;
|
||||
}
|
||||
|
||||
mBiometricPrompt.authenticate(new BiometricPrompt.CryptoObject(cipher),
|
||||
mCancellationSignal, mActivity.getMainExecutor(), new BiometricPromptCallbackImpl());
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
private class BiometricPromptCallbackImpl extends BiometricPrompt.AuthenticationCallback {
|
||||
@Override
|
||||
public void onAuthenticationError(int errorCode, CharSequence errString) {
|
||||
super.onAuthenticationError(errorCode, errString);
|
||||
mCancellationSignal.cancel();
|
||||
if (!userCancel) {
|
||||
mCallback.onError(errString.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
|
||||
super.onAuthenticationHelp(helpCode, helpString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result) {
|
||||
super.onAuthenticationSucceeded(result);
|
||||
mCancellationSignal.cancel();
|
||||
mCallback.onSucceed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed();
|
||||
mCallback.onFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.security.keystore.KeyGenParameterSpec;
|
||||
import android.security.keystore.KeyPermanentlyInvalidatedException;
|
||||
import android.security.keystore.KeyProperties;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
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 javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
/**
|
||||
* @author codersun
|
||||
* @time 2019/10/15 20:48
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
class CipherHelper {
|
||||
|
||||
private static CipherHelper instance;
|
||||
|
||||
private static final String DEFAULT_KEY_NAME = "defaultKey";
|
||||
|
||||
private static final String KEYSTORE_ALIAS = "keyStoreAlias";
|
||||
|
||||
private static final String HAS_FINGER_KEY = "hasFingerKey";
|
||||
|
||||
private KeyGenerator mKeyGenerator;
|
||||
|
||||
private KeyStore keyStore;
|
||||
|
||||
private CipherHelper() {
|
||||
try {
|
||||
keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
} catch (KeyStoreException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
try {
|
||||
mKeyGenerator = KeyGenerator
|
||||
.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
|
||||
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
|
||||
throw new RuntimeException("Failed to get an instance of KeyGenerator", e);
|
||||
}
|
||||
}
|
||||
|
||||
static CipherHelper getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (CipherHelper.class) {
|
||||
if (instance == null) {
|
||||
instance = new CipherHelper();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @des 创建cipher
|
||||
* @author codersun
|
||||
* @date 2019/7/3 14:24
|
||||
*/
|
||||
|
||||
Cipher createCipher() {
|
||||
try {
|
||||
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
|
||||
+ KeyProperties.BLOCK_MODE_CBC + "/"
|
||||
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @des 初始化Cipher ,并判断指纹库是否发生了变化
|
||||
* @author codersun
|
||||
* @date 2019/7/3 14:24
|
||||
*/
|
||||
|
||||
boolean initCipher(Cipher cipher) {
|
||||
try {
|
||||
keyStore.load(null);
|
||||
SecretKey key = (SecretKey) keyStore.getKey(KEYSTORE_ALIAS, null);
|
||||
if (cipher == null) {
|
||||
cipher = createCipher();
|
||||
}
|
||||
cipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
return false;
|
||||
} catch (KeyPermanentlyInvalidatedException e) {
|
||||
return true;
|
||||
} catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
|
||||
| NoSuchAlgorithmException | InvalidKeyException e) {
|
||||
throw new RuntimeException("Failed to init Cipher", e);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context
|
||||
* @param createNewKey 是否创建新的密钥
|
||||
* @des 根据当前指纹库创建一个密钥
|
||||
* @author codersun
|
||||
* @date 2019/7/3 10:35
|
||||
*/
|
||||
void createKey(Context context, boolean createNewKey) {
|
||||
if (context == null) {
|
||||
throw new RuntimeException("context can not be null");
|
||||
}
|
||||
SharedPreferences sharedPreferences = context.getSharedPreferences(DEFAULT_KEY_NAME, Context.MODE_PRIVATE);
|
||||
try {
|
||||
//首先通过标志位判断用户之前是否创建了密钥,如果已经创建过了并且不需要重新创建就跳过
|
||||
if (TextUtils.isEmpty(sharedPreferences.getString(HAS_FINGER_KEY, "")) || createNewKey) {
|
||||
//创建新密钥
|
||||
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(KEYSTORE_ALIAS,
|
||||
KeyProperties.PURPOSE_ENCRYPT |
|
||||
KeyProperties.PURPOSE_DECRYPT)
|
||||
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
|
||||
.setUserAuthenticationRequired(true)
|
||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
builder.setInvalidatedByBiometricEnrollment(true);
|
||||
}
|
||||
mKeyGenerator.init(builder.build());
|
||||
mKeyGenerator.generateKey();
|
||||
sharedPreferences.edit().putString(HAS_FINGER_KEY, "KEY").apply();
|
||||
}
|
||||
} catch (InvalidAlgorithmParameterException e) {
|
||||
// throw new RuntimeException(e);
|
||||
Log.d("cipherHelper", "message:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
/**
|
||||
* @author codersun
|
||||
* @time 2019/9/8 16:35
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class DefaultFingerDialog extends AFingerDialog implements View.OnClickListener
|
||||
{
|
||||
|
||||
private static final String TITLE = "title";
|
||||
|
||||
private static final String DES = "des";
|
||||
|
||||
private static final String NEGATIVE_TEXT = "negativeText";
|
||||
|
||||
private FingerManagerController mFingerManagerController;
|
||||
|
||||
private ObjectAnimator animator;
|
||||
|
||||
private TextView titleTv;
|
||||
|
||||
private TextView desTv;
|
||||
|
||||
public static DefaultFingerDialog newInstance(FingerManagerController fingerManagerController)
|
||||
{
|
||||
DefaultFingerDialog defaultFingerDialog = new DefaultFingerDialog();
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString(TITLE, fingerManagerController.getTitle());
|
||||
bundle.putString(DES, fingerManagerController.getDes());
|
||||
bundle.putString(NEGATIVE_TEXT, fingerManagerController.getNegativeText());
|
||||
defaultFingerDialog.setArguments(bundle);
|
||||
return defaultFingerDialog;
|
||||
}
|
||||
|
||||
@SuppressLint("ValidFragment")
|
||||
private DefaultFingerDialog()
|
||||
{
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
|
||||
@Nullable Bundle savedInstanceState)
|
||||
{
|
||||
super.onCreateView(inflater, container, savedInstanceState);
|
||||
View view = inflater.inflate(R.layout.dialog_finger, null);
|
||||
|
||||
titleTv = view.findViewById(R.id.finger_dialog_title_tv);
|
||||
desTv = view.findViewById(R.id.finger_dialog_des_tv);
|
||||
TextView cancelTv = view.findViewById(R.id.finger_dialog_cancel_tv);
|
||||
cancelTv.setOnClickListener(this);
|
||||
|
||||
if (getArguments() != null)
|
||||
{
|
||||
titleTv.setText(getArguments().getString(TITLE));
|
||||
desTv.setText(getArguments().getString(DES));
|
||||
cancelTv.setText(getArguments().getString(NEGATIVE_TEXT));
|
||||
}
|
||||
|
||||
animator = ObjectAnimator.ofFloat(desTv, View.TRANSLATION_X, 20, -20);
|
||||
animator.setRepeatCount(1);
|
||||
animator.setDuration(500);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSucceed()
|
||||
{
|
||||
dismiss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed()
|
||||
{
|
||||
titleTv.setText("请重试");
|
||||
desTv.setText("换个手指试试");
|
||||
desTv.setVisibility(View.VISIBLE);
|
||||
if (!animator.isRunning())
|
||||
{
|
||||
animator.start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHelp(String help)
|
||||
{
|
||||
titleTv.setVisibility(View.VISIBLE);
|
||||
desTv.setVisibility(View.VISIBLE);
|
||||
titleTv.setText("请重试");
|
||||
desTv.setText("换个手指试试");
|
||||
|
||||
if (!TextUtils.isEmpty(help))
|
||||
{
|
||||
if (!desTv.getText().toString().trim().equals(help.trim()))
|
||||
{
|
||||
if (!animator.isRunning())
|
||||
animator.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String error)
|
||||
{
|
||||
dismissAllowingStateLoss();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCancelAuth()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v)
|
||||
{
|
||||
dismiss();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.hardware.fingerprint.FingerprintManager;
|
||||
import android.os.Build;
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.hardware.fingerprint.FingerprintManagerCompat;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class FingerManager {
|
||||
|
||||
private static FingerManager fingerManager;
|
||||
|
||||
private static FingerManagerController mFingerManagerController;
|
||||
|
||||
private CancellationSignal cancellationSignal;
|
||||
|
||||
private IBiometricPromptImpl biometricPrompt;
|
||||
|
||||
public enum SupportResult {
|
||||
DEVICE_UNSUPPORTED,//设备不支持指纹识别
|
||||
SUPPORT_WITHOUT_DATA,//设备支持指纹识别但是没有指纹数据
|
||||
SUPPORT//设备支持且有指纹数据
|
||||
}
|
||||
|
||||
private static FingerManager getInstance() {
|
||||
if (fingerManager == null) {
|
||||
synchronized (FingerManager.class) {
|
||||
if (fingerManager == null) {
|
||||
fingerManager = new FingerManager();
|
||||
}
|
||||
}
|
||||
}
|
||||
return fingerManager;
|
||||
}
|
||||
|
||||
static FingerManager getInstance(FingerManagerController fingerManagerController) {
|
||||
mFingerManagerController = fingerManagerController;
|
||||
return getInstance();
|
||||
}
|
||||
|
||||
private FingerManager() {
|
||||
}
|
||||
|
||||
private void createImp(Activity activity, AFingerDialog fingerDialog) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
biometricPrompt = new BiometricPromptImpl28(activity, mFingerManagerController);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
biometricPrompt = new BiometricPromptImpl23(activity, fingerDialog, mFingerManagerController);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查设别是否支持指纹识别
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static SupportResult checkSupport(Context context) {
|
||||
// FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(context);
|
||||
FingerprintManager fingerprintManager = context.getSystemService(FingerprintManager.class);
|
||||
if (fingerprintManager == null) {
|
||||
return SupportResult.DEVICE_UNSUPPORTED;
|
||||
}
|
||||
if (fingerprintManager.isHardwareDetected()) {
|
||||
if (fingerprintManager.hasEnrolledFingerprints()) {
|
||||
return SupportResult.SUPPORT;
|
||||
} else {
|
||||
return SupportResult.SUPPORT_WITHOUT_DATA;
|
||||
}
|
||||
} else {
|
||||
return SupportResult.DEVICE_UNSUPPORTED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查设备是否有指纹数据
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public static boolean hasFingerprintData(Context context) {
|
||||
FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(context);
|
||||
return fingerprintManagerCompat.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始监听指纹识别器
|
||||
*/
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
private void startListener() {
|
||||
|
||||
CipherHelper.getInstance().createKey(mFingerManagerController.getApplication(), false);
|
||||
|
||||
if (cancellationSignal == null) {
|
||||
cancellationSignal = new CancellationSignal();
|
||||
}
|
||||
|
||||
if (cancellationSignal.isCanceled()) {
|
||||
cancellationSignal = new CancellationSignal();
|
||||
}
|
||||
|
||||
biometricPrompt.authenticate(cancellationSignal);
|
||||
}
|
||||
|
||||
public void startListener(Activity activity) {
|
||||
createImp(activity, mFingerManagerController.getFingerDialogApi23());
|
||||
startListener();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步指纹数据,解除 指纹数据变化 问题
|
||||
*
|
||||
* @param context
|
||||
*/
|
||||
public static void updateFingerData(Context context) {
|
||||
SharePreferenceUtil.saveData(context, SharePreferenceUtil.KEY_IS_FINGER_CHANGE, "0");
|
||||
CipherHelper.getInstance().createKey(context, true);
|
||||
}
|
||||
|
||||
public static FingerManagerController build() {
|
||||
return new FingerManagerController();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
/**
|
||||
* @author codersun
|
||||
* @time 2019/9/3 17:41
|
||||
*/
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public class FingerManagerController
|
||||
{
|
||||
|
||||
private Application mApplication;
|
||||
|
||||
//弹窗标题
|
||||
private String mTitle;
|
||||
|
||||
//弹窗描述
|
||||
private String mDes;
|
||||
|
||||
//取消按钮话术
|
||||
private String mNegativeText;
|
||||
|
||||
//Android P 以下版本的指纹识别弹窗(如需自定义样式就设置)
|
||||
private AFingerDialog mFingerDialogApi23;
|
||||
|
||||
//指纹识别回调
|
||||
private IonFingerCallback mFingerCheckCallback;
|
||||
|
||||
//指纹库发生变化时的回调
|
||||
private AonFingerChangeCallback mFingerChangeCallback;
|
||||
|
||||
public AonFingerChangeCallback getFingerChangeCallback()
|
||||
{
|
||||
return mFingerChangeCallback;
|
||||
}
|
||||
|
||||
public FingerManagerController setFingerChangeCallback(AonFingerChangeCallback fingerChangeCallback)
|
||||
{
|
||||
this.mFingerChangeCallback = fingerChangeCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IonFingerCallback getFingerCheckCallback()
|
||||
{
|
||||
return mFingerCheckCallback;
|
||||
}
|
||||
|
||||
public FingerManagerController setFingerCheckCallback(IonFingerCallback fingerCheckCallback)
|
||||
{
|
||||
this.mFingerCheckCallback = fingerCheckCallback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FingerManagerController setDes(String des)
|
||||
{
|
||||
this.mDes = des;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FingerManagerController setNegativeText(String negativeText)
|
||||
{
|
||||
this.mNegativeText = negativeText;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FingerManagerController setTitle(String title)
|
||||
{
|
||||
mTitle = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FingerManagerController setApplication(Application application)
|
||||
{
|
||||
mApplication = application;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FingerManagerController setFingerDialogApi23(@Nullable AFingerDialog fingerDialogApi23)
|
||||
{
|
||||
this.mFingerDialogApi23 = fingerDialogApi23;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Application getApplication()
|
||||
{
|
||||
return mApplication;
|
||||
}
|
||||
|
||||
public String getTitle()
|
||||
{
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
public String getDes()
|
||||
{
|
||||
return mDes;
|
||||
}
|
||||
|
||||
public String getNegativeText()
|
||||
{
|
||||
return mNegativeText;
|
||||
}
|
||||
|
||||
public AFingerDialog getFingerDialogApi23()
|
||||
{
|
||||
return mFingerDialogApi23;
|
||||
}
|
||||
|
||||
public FingerManager create()
|
||||
{
|
||||
if (mFingerCheckCallback == null){
|
||||
throw new RuntimeException("CompatFingerManager : FingerCheckCallback can not be null");
|
||||
}
|
||||
|
||||
return FingerManager.getInstance(this);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.os.CancellationSignal;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public interface IBiometricPromptImpl {
|
||||
void authenticate(@NonNull CancellationSignal cancel);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
public interface IonFingerCallback
|
||||
{
|
||||
|
||||
void onSucceed();
|
||||
|
||||
void onFailed();
|
||||
|
||||
void onHelp(String help);
|
||||
|
||||
void onError(String error);
|
||||
|
||||
void onCancel();
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
|
||||
class SharePreferenceUtil
|
||||
{
|
||||
private static final String DEFAULT_NAME = "finger";
|
||||
|
||||
public static String KEY_IS_FINGER_CHANGE = "is_finger_change";//指纹是否变化了
|
||||
|
||||
|
||||
private static SharedPreferences.Editor getSharePreferenceEditor(Context context, String fileName) {
|
||||
return getSharedPreferences(context,fileName).edit();
|
||||
}
|
||||
|
||||
private static SharedPreferences getSharedPreferences(Context context, String fileName) {
|
||||
return context.getSharedPreferences(isEmpty(fileName) ? DEFAULT_NAME : fileName, Context.MODE_PRIVATE);
|
||||
}
|
||||
|
||||
public static void saveData(Context context, String key, String value) {
|
||||
saveData(context, key, value, null);
|
||||
}
|
||||
|
||||
public static void saveData(Context context, String key, String value, String fileName) {
|
||||
SharedPreferences.Editor editor = getSharePreferenceEditor(context, fileName);
|
||||
editor.putString(key, value);
|
||||
editor.commit();
|
||||
}
|
||||
|
||||
public static String getValue(Context context,String key) {
|
||||
if (null == key)
|
||||
return "";
|
||||
SharedPreferences sharedPreferences = getSharedPreferences(context, "");
|
||||
return sharedPreferences.getString(key, "");
|
||||
}
|
||||
|
||||
public static boolean isFingerDataChange(Context context){
|
||||
String value = getValue(context, KEY_IS_FINGER_CHANGE);
|
||||
boolean result = false;
|
||||
if (TextUtils.isEmpty(value)){
|
||||
result = false;
|
||||
}else {
|
||||
result = Integer.parseInt(value) == 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
/**
|
||||
* @author codersun
|
||||
* @time 2019/9/8 16:36
|
||||
*/
|
||||
public abstract class SimpleFingerCheckCallback implements IonFingerCallback
|
||||
{
|
||||
|
||||
@Override
|
||||
public void onHelp(String help)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailed()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
19
fingermanager/src/main/res/drawable/gray_round_bg.xml
Normal file
19
fingermanager/src/main/res/drawable/gray_round_bg.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item android:state_pressed="true">
|
||||
<shape>
|
||||
<corners android:radius="5dip"/>
|
||||
|
||||
<solid android:color="#aaaaaa"/>
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape>
|
||||
<corners android:radius="5dip"/>
|
||||
|
||||
<solid android:color="#909090"/>
|
||||
</shape>
|
||||
</item>
|
||||
|
||||
</selector>
|
||||
BIN
fingermanager/src/main/res/drawable/ic_fingerprint.png
Normal file
BIN
fingermanager/src/main/res/drawable/ic_fingerprint.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners
|
||||
android:topLeftRadius="50dp"
|
||||
android:topRightRadius="50dp"/>
|
||||
<solid android:color="#00ffffff"/>
|
||||
</shape>
|
||||
75
fingermanager/src/main/res/layout/dialog_finger.xml
Normal file
75
fingermanager/src/main/res/layout/dialog_finger.xml
Normal file
@@ -0,0 +1,75 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="205dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/top_round_white_bg"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="20dp"
|
||||
android:src="@drawable/ic_fingerprint"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/finger_dialog_title_tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/icon"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textColor="#000000"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/finger_dialog_bottom_layout_rl"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/finger_dialog_title_tv">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/finger_dialog_des_tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="20dp"
|
||||
android:textColor="#000"
|
||||
android:textSize="15sp"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/finger_dialog_button_layout_rl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/finger_dialog_des_tv"
|
||||
android:layout_marginTop="30dp">
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0.5dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:background="@drawable/gray_round_bg"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/finger_dialog_cancel_tv"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="取消"/>
|
||||
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
3
fingermanager/src/main/res/values/strings.xml
Normal file
3
fingermanager/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">Fingerprint Compat</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.codersun.fingerprintcompat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user