· 7 years ago · Dec 19, 2018, 03:08 PM
1mFingerprintManager
2 .authenticate(cryptoObject, 0 /* flags */, mCancellationSignal, this, null);
3
4/**
5 * Small helper class to manage text/icon around fingerprint authentication UI.
6 */
7public class FingerprintUiHelper extends FingerprintManagerCompat.AuthenticationCallback {
8 private static final String TAG = "FingerprintUiHelper";
9
10 private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
11 private static final String DEFAULT_KEY_NAME = "hello world key name";
12
13 private int configShortAnimTime;
14
15 private final FingerprintManagerCompat mFingerprintManager;
16 private final ImageView mIcon;
17 private final Callback mCallback;
18 private CancellationSignal mCancellationSignal;
19
20 private boolean mSelfCancelled;
21
22 private final ResetErrorRunnable resetErrorRunnable = new ResetErrorRunnable();
23
24 private final int mSuccessColor;
25 private final int mAlertColor;
26
27 private class ResetErrorRunnable implements Runnable {
28
29 @Override
30 public void run() {
31 resetError();
32 }
33 }
34
35 public static FingerprintUiHelper newInstance(ImageView icon, Callback callback, int successColor, int alertColor) {
36 FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(WeNoteApplication.instance());
37 return new FingerprintUiHelper(fingerprintManagerCompat, icon, callback, successColor, alertColor);
38 }
39
40 private void initResource() {
41 configShortAnimTime = WeNoteApplication.instance().getResources().getInteger(android.R.integer.config_shortAnimTime);
42 }
43
44 /**
45 * Constructor for {@link FingerprintUiHelper}.
46 */
47 private FingerprintUiHelper(FingerprintManagerCompat fingerprintManager,
48 ImageView icon, Callback callback, int successColor, int alertColor) {
49 initResource();
50
51 mFingerprintManager = fingerprintManager;
52 mIcon = icon;
53 mCallback = callback;
54 mSuccessColor = successColor;
55 mAlertColor = alertColor;
56 }
57
58 public boolean isFingerprintAuthAvailable() {
59 // The line below prevents the false positive inspection from Android Studio
60 // noinspection ResourceType
61 return mFingerprintManager.isHardwareDetected()
62 && mFingerprintManager.hasEnrolledFingerprints();
63 }
64
65 /**
66 * Initialize the {@link Cipher} instance with the created key in the
67 * {@link #createKey(String, boolean)} method.
68 *
69 * @param keyName the key name to init the cipher
70 * @return {@code true} if initialization is successful, {@code false} if the lock screen has
71 * been disabled or reset after the key was generated, or if a fingerprint got enrolled after
72 * the key was generated.
73 */
74 private boolean initCipher(Cipher cipher, String keyName) {
75 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
76 return false;
77 }
78
79 KeyStore keyStore;
80 KeyGenerator keyGenerator;
81
82 try {
83 keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
84 } catch (KeyStoreException e) {
85 Log.e(TAG, "", e);
86 return false;
87 }
88
89 try {
90 keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
91 } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
92 Log.e(TAG, "", e);
93 return false;
94 }
95
96 // The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint
97 // for your flow. Use of keys is necessary if you need to know if the set of
98 // enrolled fingerprints has changed.
99 try {
100 keyStore.load(null);
101 // Set the alias of the entry in Android KeyStore where the key will appear
102 // and the constrains (purposes) in the constructor of the Builder
103
104 KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
105 KeyProperties.PURPOSE_ENCRYPT |
106 KeyProperties.PURPOSE_DECRYPT)
107 .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
108 // Require the user to authenticate with a fingerprint to authorize every use
109 // of the key
110 .setUserAuthenticationRequired(true)
111 .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
112
113 // This is a workaround to avoid crashes on devices whose API level is < 24
114 // because KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment is only
115 // visible on API level +24.
116 // Ideally there should be a compat library for KeyGenParameterSpec.Builder but
117 // which isn't available yet.
118 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
119 builder.setInvalidatedByBiometricEnrollment(true);
120 }
121 keyGenerator.init(builder.build());
122 keyGenerator.generateKey();
123 } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
124 | CertificateException | IOException e) {
125 Log.e(TAG, "", e);
126 return false;
127 }
128
129 try {
130 keyStore.load(null);
131 SecretKey key = (SecretKey) keyStore.getKey(keyName, null);
132 cipher.init(Cipher.ENCRYPT_MODE, key);
133 return true;
134 } catch (Exception e) {
135 Log.e(TAG, "", e);
136 return false;
137 }
138 }
139
140 public void startListening() {
141 if (!isFingerprintAuthAvailable()) {
142 return;
143 }
144
145 Cipher defaultCipher;
146 try {
147 defaultCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
148 + KeyProperties.BLOCK_MODE_CBC + "/"
149 + KeyProperties.ENCRYPTION_PADDING_PKCS7);
150 } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
151 Log.e(TAG, "", e);
152 return;
153 }
154
155 if (false == initCipher(defaultCipher, DEFAULT_KEY_NAME)) {
156 return;
157 }
158
159 FingerprintManagerCompat.CryptoObject cryptoObject = new FingerprintManagerCompat.CryptoObject(defaultCipher);
160
161 startListening(cryptoObject);
162
163 showIcon();
164 }
165
166 private void startListening(FingerprintManagerCompat.CryptoObject cryptoObject) {
167 if (!isFingerprintAuthAvailable()) {
168 return;
169 }
170 mCancellationSignal = new CancellationSignal();
171 mSelfCancelled = false;
172
173 // The line below prevents the false positive inspection from Android Studio
174 // noinspection ResourceType
175 mFingerprintManager
176 .authenticate(cryptoObject, 0 /* flags */, mCancellationSignal, this, null);
177 }
178
179 public void stopListening() {
180 if (mCancellationSignal != null) {
181 mSelfCancelled = true;
182 mCancellationSignal.cancel();
183 mCancellationSignal = null;
184 }
185 }
186
187 @Override
188 public void onAuthenticationError(int errMsgId, CharSequence errString) {
189 if (!mSelfCancelled) {
190 if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
191 mIcon.removeCallbacks(resetErrorRunnable);
192 showError();
193 return;
194 }
195
196 if (errMsgId == FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST) {
197 return;
198 }
199
200 showError();
201 mIcon.postDelayed(resetErrorRunnable, configShortAnimTime);
202 }
203 }
204
205 @Override
206 public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
207 showError();
208 mIcon.postDelayed(resetErrorRunnable, configShortAnimTime);
209 }
210
211 @Override
212 public void onAuthenticationFailed() {
213 showError();
214 mIcon.postDelayed(resetErrorRunnable, configShortAnimTime);
215 }
216
217 @Override
218 public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
219 mIcon.setColorFilter(mSuccessColor);
220
221 mIcon.postDelayed(() -> mCallback.onAuthenticated(), configShortAnimTime);
222 }
223
224 private void showIcon() {
225 mIcon.setVisibility(View.VISIBLE);
226 }
227
228 private void showError() {
229 mIcon.setColorFilter(mAlertColor);
230 }
231
232 private void resetError() {
233 mIcon.clearColorFilter();
234 }
235
236 public interface Callback {
237
238 void onAuthenticated();
239 }
240}