当我们呼唤
mFingerprintManager
.authenticate(cryptoObject, 0 /* flags */, mCancellationSignal, this, null);
我注意到完全可以通过
null
对于
cryptoObject
. 根据
FingerprintManager documentation
Fingerprintmanager.CryptoObject:与调用或
如果不需要,则为空。
根据
https://github.com/googlesamples/android-FingerprintDialog
,它显示了创建
CryptoObject
.
所以,我不确定是否应该使用
密码对象
,或
无效的
用于我的用例。我读过
Why crypto object is needed for Android fingerprint authentication?
但仍然不能完全理解和决定我的案件。
我的用例如下。
我有一个记录程序的启动锁定屏幕。通常,当用户启用启动锁定屏幕时,他需要设置图案绘制。万一他忘了画图案,他可以用他的指纹作为替代品。应用程序如下所示
这是源代码。目前,我正在使用
密码对象
. 但是,根据我的用户反馈,他们中的一小部分人正面临着这个新功能的一些应用程序问题。虽然我们在Google Play控制台上看不到任何崩溃报告,但我们怀疑在
密码对象
世代。
所以,如果
密码对象
可以用空替换,我们很乐意这样做来简化代码。
我是否需要CryptoObject对象,或者在Fingerprintmanager期间为以下用例使用空值。
/**
* Small helper class to manage text/icon around fingerprint authentication UI.
*/
public class FingerprintUiHelper extends FingerprintManagerCompat.AuthenticationCallback {
private static final String TAG = "FingerprintUiHelper";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String DEFAULT_KEY_NAME = "hello world key name";
private int configShortAnimTime;
private final FingerprintManagerCompat mFingerprintManager;
private final ImageView mIcon;
private final Callback mCallback;
private CancellationSignal mCancellationSignal;
private boolean mSelfCancelled;
private final ResetErrorRunnable resetErrorRunnable = new ResetErrorRunnable();
private final int mSuccessColor;
private final int mAlertColor;
private class ResetErrorRunnable implements Runnable {
@Override
public void run() {
resetError();
}
}
public static FingerprintUiHelper newInstance(ImageView icon, Callback callback, int successColor, int alertColor) {
FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(WeNoteApplication.instance());
return new FingerprintUiHelper(fingerprintManagerCompat, icon, callback, successColor, alertColor);
}
private void initResource() {
configShortAnimTime = WeNoteApplication.instance().getResources().getInteger(android.R.integer.config_shortAnimTime);
}
/**
* Constructor for {@link FingerprintUiHelper}.
*/
private FingerprintUiHelper(FingerprintManagerCompat fingerprintManager,
ImageView icon, Callback callback, int successColor, int alertColor) {
initResource();
mFingerprintManager = fingerprintManager;
mIcon = icon;
mCallback = callback;
mSuccessColor = successColor;
mAlertColor = alertColor;
}
public boolean isFingerprintAuthAvailable() {
// The line below prevents the false positive inspection from Android Studio
// noinspection ResourceType
return mFingerprintManager.isHardwareDetected()
&& mFingerprintManager.hasEnrolledFingerprints();
}
/**
* Initialize the {@link Cipher} instance with the created key in the
* {@link #createKey(String, boolean)} method.
*
* @param keyName the key name to init the cipher
* @return {@code true} if initialization is successful, {@code false} if the lock screen has
* been disabled or reset after the key was generated, or if a fingerprint got enrolled after
* the key was generated.
*/
private boolean initCipher(Cipher cipher, String keyName) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
KeyStore keyStore;
KeyGenerator keyGenerator;
try {
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
} catch (KeyStoreException e) {
Log.e(TAG, "", e);
return false;
}
try {
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
Log.e(TAG, "", e);
return false;
}
// The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint
// for your flow. Use of keys is necessary if you need to know if the set of
// enrolled fingerprints has changed.
try {
keyStore.load(null);
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
// Require the user to authenticate with a fingerprint to authorize every use
// of the key
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
// This is a workaround to avoid crashes on devices whose API level is < 24
// because KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment is only
// visible on API level +24.
// Ideally there should be a compat library for KeyGenParameterSpec.Builder but
// which isn't available yet.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setInvalidatedByBiometricEnrollment(true);
}
keyGenerator.init(builder.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
| CertificateException | IOException e) {
Log.e(TAG, "", e);
return false;
}
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(keyName, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (Exception e) {
Log.e(TAG, "", e);
return false;
}
}
public void startListening() {
if (!isFingerprintAuthAvailable()) {
return;
}
Cipher defaultCipher;
try {
defaultCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
Log.e(TAG, "", e);
return;
}
if (false == initCipher(defaultCipher, DEFAULT_KEY_NAME)) {
return;
}
FingerprintManagerCompat.CryptoObject cryptoObject = new FingerprintManagerCompat.CryptoObject(defaultCipher);
startListening(cryptoObject);
showIcon();
}
private void startListening(FingerprintManagerCompat.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, 0 /* flags */, mCancellationSignal, this, null);
}
public void stopListening() {
if (mCancellationSignal != null) {
mSelfCancelled = true;
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
if (!mSelfCancelled) {
if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
mIcon.removeCallbacks(resetErrorRunnable);
showError();
return;
}
if (errMsgId == FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST) {
return;
}
showError();
mIcon.postDelayed(resetErrorRunnable, configShortAnimTime);
}
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
showError();
mIcon.postDelayed(resetErrorRunnable, configShortAnimTime);
}
@Override
public void onAuthenticationFailed() {
showError();
mIcon.postDelayed(resetErrorRunnable, configShortAnimTime);
}
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
mIcon.setColorFilter(mSuccessColor);
mIcon.postDelayed(() -> mCallback.onAuthenticated(), configShortAnimTime);
}
private void showIcon() {
mIcon.setVisibility(View.VISIBLE);
}
private void showError() {
mIcon.setColorFilter(mAlertColor);
}
private void resetError() {
mIcon.clearColorFilter();
}
public interface Callback {
void onAuthenticated();
}
}