blob: baf972b265739f941dc98911ed719b4213fcab4f [file] [log] [blame]
Kevin Chynaae4a152018-01-18 11:48:09 -08001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Vishwath Mohancf87df12018-03-20 22:57:17 -070017package android.hardware.biometrics;
Kevin Chynaae4a152018-01-18 11:48:09 -080018
Vishwath Mohancf87df12018-03-20 22:57:17 -070019import static android.Manifest.permission.USE_BIOMETRIC;
Kevin Chyn3a0187192018-10-08 15:40:05 -070020import static android.Manifest.permission.USE_BIOMETRIC_INTERNAL;
Kevin Chynaae4a152018-01-18 11:48:09 -080021
22import android.annotation.CallbackExecutor;
23import android.annotation.NonNull;
Kevin Chyna24e9fd2018-08-27 12:39:17 -070024import android.annotation.Nullable;
Kevin Chynaae4a152018-01-18 11:48:09 -080025import android.annotation.RequiresPermission;
Kevin Chyn45d1f9d2019-02-07 13:38:04 -080026import android.app.KeyguardManager;
Kevin Chynaae4a152018-01-18 11:48:09 -080027import android.content.Context;
28import android.content.DialogInterface;
Kevin Chyna24e9fd2018-08-27 12:39:17 -070029import android.os.Binder;
Kevin Chynaae4a152018-01-18 11:48:09 -080030import android.os.Bundle;
31import android.os.CancellationSignal;
Kevin Chyna24e9fd2018-08-27 12:39:17 -070032import android.os.IBinder;
33import android.os.RemoteException;
34import android.os.ServiceManager;
Kevin Chynaae4a152018-01-18 11:48:09 -080035import android.text.TextUtils;
Kevin Chyna24e9fd2018-08-27 12:39:17 -070036import android.util.Log;
37
38import com.android.internal.R;
Kevin Chynaae4a152018-01-18 11:48:09 -080039
Kevin Chyn66682562018-01-25 18:26:46 -080040import java.security.Signature;
Kevin Chynaae4a152018-01-18 11:48:09 -080041import java.util.concurrent.Executor;
42
Kevin Chyn66682562018-01-25 18:26:46 -080043import javax.crypto.Cipher;
44import javax.crypto.Mac;
45
Kevin Chynaae4a152018-01-18 11:48:09 -080046/**
Vishwath Mohancf87df12018-03-20 22:57:17 -070047 * A class that manages a system-provided biometric dialog.
Kevin Chynaae4a152018-01-18 11:48:09 -080048 */
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070049public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants {
Kevin Chynaae4a152018-01-18 11:48:09 -080050
Kevin Chyna24e9fd2018-08-27 12:39:17 -070051 private static final String TAG = "BiometricPrompt";
52
Kevin Chynaae4a152018-01-18 11:48:09 -080053 /**
54 * @hide
55 */
56 public static final String KEY_TITLE = "title";
57 /**
58 * @hide
59 */
Kevin Chyn3a0187192018-10-08 15:40:05 -070060 public static final String KEY_USE_DEFAULT_TITLE = "use_default_title";
61 /**
62 * @hide
63 */
Kevin Chynaae4a152018-01-18 11:48:09 -080064 public static final String KEY_SUBTITLE = "subtitle";
65 /**
66 * @hide
67 */
68 public static final String KEY_DESCRIPTION = "description";
69 /**
70 * @hide
71 */
72 public static final String KEY_POSITIVE_TEXT = "positive_text";
73 /**
74 * @hide
75 */
76 public static final String KEY_NEGATIVE_TEXT = "negative_text";
Kevin Chyn158fefb2019-01-03 18:59:05 -080077 /**
78 * @hide
79 */
80 public static final String KEY_REQUIRE_CONFIRMATION = "require_confirmation";
Kevin Chyn1b2137c2019-01-24 16:32:38 -080081 /**
82 * @hide
83 */
Kevin Chyn45d1f9d2019-02-07 13:38:04 -080084 public static final String KEY_ALLOW_DEVICE_CREDENTIAL = "allow_device_credential";
Kevin Chynaae4a152018-01-18 11:48:09 -080085
86 /**
87 * Error/help message will show for this amount of time.
88 * For error messages, the dialog will also be dismissed after this amount of time.
89 * Error messages will be propagated back to the application via AuthenticationCallback
90 * after this amount of time.
91 * @hide
92 */
Kevin Chyndba919a2018-03-16 14:35:10 -070093 public static final int HIDE_DIALOG_DELAY = 2000; // ms
Kevin Chyned941802018-08-14 19:00:57 -070094
Kevin Chynaae4a152018-01-18 11:48:09 -080095 /**
96 * @hide
97 */
98 public static final int DISMISSED_REASON_POSITIVE = 1;
99
100 /**
101 * @hide
102 */
103 public static final int DISMISSED_REASON_NEGATIVE = 2;
104
105 /**
106 * @hide
107 */
108 public static final int DISMISSED_REASON_USER_CANCEL = 3;
109
110 private static class ButtonInfo {
111 Executor executor;
112 DialogInterface.OnClickListener listener;
113 ButtonInfo(Executor ex, DialogInterface.OnClickListener l) {
114 executor = ex;
115 listener = l;
116 }
117 }
118
119 /**
Vishwath Mohancf87df12018-03-20 22:57:17 -0700120 * A builder that collects arguments to be shown on the system-provided biometric dialog.
Kevin Chynaae4a152018-01-18 11:48:09 -0800121 **/
122 public static class Builder {
Vishwath Mohancf87df12018-03-20 22:57:17 -0700123 private final Bundle mBundle;
124 private ButtonInfo mPositiveButtonInfo;
125 private ButtonInfo mNegativeButtonInfo;
126 private Context mContext;
Kevin Chynaae4a152018-01-18 11:48:09 -0800127
128 /**
Vishwath Mohancf87df12018-03-20 22:57:17 -0700129 * Creates a builder for a biometric dialog.
130 * @param context
Kevin Chynaae4a152018-01-18 11:48:09 -0800131 */
Vishwath Mohancf87df12018-03-20 22:57:17 -0700132 public Builder(Context context) {
133 mBundle = new Bundle();
134 mContext = context;
Kevin Chynaae4a152018-01-18 11:48:09 -0800135 }
136
137 /**
138 * Required: Set the title to display.
139 * @param title
140 * @return
141 */
142 public Builder setTitle(@NonNull CharSequence title) {
Vishwath Mohancf87df12018-03-20 22:57:17 -0700143 mBundle.putCharSequence(KEY_TITLE, title);
Kevin Chynaae4a152018-01-18 11:48:09 -0800144 return this;
145 }
146
147 /**
Kevin Chyn3a0187192018-10-08 15:40:05 -0700148 * For internal use currently. Only takes effect if title is null/empty. Shows a default
149 * modality-specific title.
150 * @hide
151 */
152 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
153 public Builder setUseDefaultTitle() {
154 mBundle.putBoolean(KEY_USE_DEFAULT_TITLE, true);
155 return this;
156 }
157
158 /**
Kevin Chynaae4a152018-01-18 11:48:09 -0800159 * Optional: Set the subtitle to display.
160 * @param subtitle
161 * @return
162 */
163 public Builder setSubtitle(@NonNull CharSequence subtitle) {
Vishwath Mohancf87df12018-03-20 22:57:17 -0700164 mBundle.putCharSequence(KEY_SUBTITLE, subtitle);
Kevin Chynaae4a152018-01-18 11:48:09 -0800165 return this;
166 }
167
168 /**
169 * Optional: Set the description to display.
170 * @param description
171 * @return
172 */
173 public Builder setDescription(@NonNull CharSequence description) {
Vishwath Mohancf87df12018-03-20 22:57:17 -0700174 mBundle.putCharSequence(KEY_DESCRIPTION, description);
Kevin Chynaae4a152018-01-18 11:48:09 -0800175 return this;
176 }
177
178 /**
179 * Optional: Set the text for the positive button. If not set, the positive button
180 * will not show.
181 * @param text
182 * @return
183 * @hide
184 */
185 public Builder setPositiveButton(@NonNull CharSequence text,
186 @NonNull @CallbackExecutor Executor executor,
187 @NonNull DialogInterface.OnClickListener listener) {
188 if (TextUtils.isEmpty(text)) {
189 throw new IllegalArgumentException("Text must be set and non-empty");
190 }
191 if (executor == null) {
192 throw new IllegalArgumentException("Executor must not be null");
193 }
194 if (listener == null) {
195 throw new IllegalArgumentException("Listener must not be null");
196 }
Vishwath Mohancf87df12018-03-20 22:57:17 -0700197 mBundle.putCharSequence(KEY_POSITIVE_TEXT, text);
198 mPositiveButtonInfo = new ButtonInfo(executor, listener);
Kevin Chynaae4a152018-01-18 11:48:09 -0800199 return this;
200 }
201
202 /**
Kevin Chyndba919a2018-03-16 14:35:10 -0700203 * Required: Set the text for the negative button. This would typically be used as a
204 * "Cancel" button, but may be also used to show an alternative method for authentication,
205 * such as screen that asks for a backup password.
Kevin Chyn39ebee42019-01-31 16:24:46 -0800206 *
Kevin Chyn45d1f9d2019-02-07 13:38:04 -0800207 * Note that this should not be set if {@link #setAllowDeviceCredential(boolean)
208 * is set to true.
Kevin Chyn39ebee42019-01-31 16:24:46 -0800209 *
Kevin Chynaae4a152018-01-18 11:48:09 -0800210 * @param text
211 * @return
212 */
213 public Builder setNegativeButton(@NonNull CharSequence text,
214 @NonNull @CallbackExecutor Executor executor,
215 @NonNull DialogInterface.OnClickListener listener) {
216 if (TextUtils.isEmpty(text)) {
217 throw new IllegalArgumentException("Text must be set and non-empty");
218 }
219 if (executor == null) {
220 throw new IllegalArgumentException("Executor must not be null");
221 }
222 if (listener == null) {
223 throw new IllegalArgumentException("Listener must not be null");
224 }
Vishwath Mohancf87df12018-03-20 22:57:17 -0700225 mBundle.putCharSequence(KEY_NEGATIVE_TEXT, text);
226 mNegativeButtonInfo = new ButtonInfo(executor, listener);
Kevin Chynaae4a152018-01-18 11:48:09 -0800227 return this;
228 }
229
230 /**
Kevin Chyn158fefb2019-01-03 18:59:05 -0800231 * Optional: A hint to the system to require user confirmation after a biometric has been
232 * authenticated. For example, implicit modalities like Face and Iris authentication are
233 * passive, meaning they don't require an explicit user action to complete. When set to
234 * 'false', the user action (e.g. pressing a button) will not be required. BiometricPrompt
235 * will require confirmation by default.
236 *
237 * A typical use case for not requiring confirmation would be for low-risk transactions,
238 * such as re-authenticating a recently authenticated application. A typical use case for
239 * requiring confirmation would be for authorizing a purchase.
240 *
241 * Note that this is a hint to the system. The system may choose to ignore the flag. For
242 * example, if the user disables implicit authentication in Settings, or if it does not
243 * apply to a modality (e.g. Fingerprint). When ignored, the system will default to
244 * requiring confirmation.
245 *
246 * @param requireConfirmation
Kevin Chyn158fefb2019-01-03 18:59:05 -0800247 */
248 public Builder setRequireConfirmation(boolean requireConfirmation) {
249 mBundle.putBoolean(KEY_REQUIRE_CONFIRMATION, requireConfirmation);
250 return this;
251 }
252
253 /**
Kevin Chyn1b2137c2019-01-24 16:32:38 -0800254 * The user will first be prompted to authenticate with biometrics, but also given the
Kevin Chyn45d1f9d2019-02-07 13:38:04 -0800255 * option to authenticate with their device PIN, pattern, or password. Developers should
256 * first check {@link KeyguardManager#isDeviceSecure()} before enabling this. If the device
257 * is not secure, {@link BiometricPrompt#BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL} will be
258 * returned in {@link AuthenticationCallback#onAuthenticationError(int, CharSequence)}}
Kevin Chyn39ebee42019-01-31 16:24:46 -0800259 *
260 * Note that {@link #setNegativeButton(CharSequence, Executor,
261 * DialogInterface.OnClickListener)} should not be set if this is set to true.
262 *
Kevin Chyn1b2137c2019-01-24 16:32:38 -0800263 * @param enable When true, the prompt will fall back to ask for the user's device
264 * credentials (PIN, pattern, or password).
265 * @return
266 */
Kevin Chyn45d1f9d2019-02-07 13:38:04 -0800267 public Builder setAllowDeviceCredential(boolean enable) {
268 mBundle.putBoolean(KEY_ALLOW_DEVICE_CREDENTIAL, enable);
Kevin Chyn1b2137c2019-01-24 16:32:38 -0800269 return this;
270 }
271
272 /**
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700273 * Creates a {@link BiometricPrompt}.
274 * @return a {@link BiometricPrompt}
Kevin Chynaae4a152018-01-18 11:48:09 -0800275 * @throws IllegalArgumentException if any of the required fields are not set.
276 */
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700277 public BiometricPrompt build() {
Vishwath Mohancf87df12018-03-20 22:57:17 -0700278 final CharSequence title = mBundle.getCharSequence(KEY_TITLE);
279 final CharSequence negative = mBundle.getCharSequence(KEY_NEGATIVE_TEXT);
Kevin Chyn3a0187192018-10-08 15:40:05 -0700280 final boolean useDefaultTitle = mBundle.getBoolean(KEY_USE_DEFAULT_TITLE);
Kevin Chyn45d1f9d2019-02-07 13:38:04 -0800281 final boolean enableFallback = mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL);
Kevin Chynaae4a152018-01-18 11:48:09 -0800282
Kevin Chyn3a0187192018-10-08 15:40:05 -0700283 if (TextUtils.isEmpty(title) && !useDefaultTitle) {
Kevin Chynaae4a152018-01-18 11:48:09 -0800284 throw new IllegalArgumentException("Title must be set and non-empty");
Kevin Chyn1b2137c2019-01-24 16:32:38 -0800285 } else if (TextUtils.isEmpty(negative) && !enableFallback) {
Kevin Chynaae4a152018-01-18 11:48:09 -0800286 throw new IllegalArgumentException("Negative text must be set and non-empty");
Kevin Chyn1b2137c2019-01-24 16:32:38 -0800287 } else if (!TextUtils.isEmpty(negative) && enableFallback) {
288 throw new IllegalArgumentException("Can't have both negative button behavior"
Kevin Chyn45d1f9d2019-02-07 13:38:04 -0800289 + " and device credential enabled");
Kevin Chynaae4a152018-01-18 11:48:09 -0800290 }
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700291 return new BiometricPrompt(mContext, mBundle, mPositiveButtonInfo, mNegativeButtonInfo);
Kevin Chynaae4a152018-01-18 11:48:09 -0800292 }
293 }
294
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700295 private class OnAuthenticationCancelListener implements CancellationSignal.OnCancelListener {
296 @Override
297 public void onCancel() {
298 cancelAuthentication();
299 }
300 }
301
302 private final IBinder mToken = new Binder();
303 private final Context mContext;
Kevin Chyn352adfe2018-09-20 22:28:50 -0700304 private final IBiometricService mService;
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700305 private final Bundle mBundle;
306 private final ButtonInfo mPositiveButtonInfo;
307 private final ButtonInfo mNegativeButtonInfo;
308
309 private CryptoObject mCryptoObject;
310 private Executor mExecutor;
311 private AuthenticationCallback mAuthenticationCallback;
Kevin Chynaae4a152018-01-18 11:48:09 -0800312
Kevin Chyne92cdae2018-11-21 16:35:04 -0800313 private final IBiometricServiceReceiver mBiometricServiceReceiver =
314 new IBiometricServiceReceiver.Stub() {
315
316 @Override
317 public void onAuthenticationSucceeded() throws RemoteException {
318 mExecutor.execute(() -> {
319 final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
320 mAuthenticationCallback.onAuthenticationSucceeded(result);
321 });
322 }
323
324 @Override
Kevin Chyne92cdae2018-11-21 16:35:04 -0800325 public void onAuthenticationFailed() throws RemoteException {
326 mExecutor.execute(() -> {
327 mAuthenticationCallback.onAuthenticationFailed();
328 });
329 }
330
331 @Override
Kevin Chyn87f257a2018-11-27 16:26:07 -0800332 public void onError(int error, String message) throws RemoteException {
Kevin Chyne92cdae2018-11-21 16:35:04 -0800333 mExecutor.execute(() -> {
334 mAuthenticationCallback.onAuthenticationError(error, message);
335 });
336 }
337
338 @Override
Kevin Chyn87f257a2018-11-27 16:26:07 -0800339 public void onAcquired(int acquireInfo, String message) throws RemoteException {
Kevin Chyne92cdae2018-11-21 16:35:04 -0800340 mExecutor.execute(() -> {
341 mAuthenticationCallback.onAuthenticationHelp(acquireInfo, message);
342 });
343 }
344
Kevin Chynaae4a152018-01-18 11:48:09 -0800345 @Override
Kevin Chyn87f257a2018-11-27 16:26:07 -0800346 public void onDialogDismissed(int reason) throws RemoteException {
Kevin Chynaae4a152018-01-18 11:48:09 -0800347 // Check the reason and invoke OnClickListener(s) if necessary
348 if (reason == DISMISSED_REASON_POSITIVE) {
349 mPositiveButtonInfo.executor.execute(() -> {
350 mPositiveButtonInfo.listener.onClick(null, DialogInterface.BUTTON_POSITIVE);
351 });
352 } else if (reason == DISMISSED_REASON_NEGATIVE) {
353 mNegativeButtonInfo.executor.execute(() -> {
354 mNegativeButtonInfo.listener.onClick(null, DialogInterface.BUTTON_NEGATIVE);
355 });
356 }
357 }
358 };
359
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700360 private BiometricPrompt(Context context, Bundle bundle,
Kevin Chynaae4a152018-01-18 11:48:09 -0800361 ButtonInfo positiveButtonInfo, ButtonInfo negativeButtonInfo) {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700362 mContext = context;
Kevin Chynaae4a152018-01-18 11:48:09 -0800363 mBundle = bundle;
364 mPositiveButtonInfo = positiveButtonInfo;
365 mNegativeButtonInfo = negativeButtonInfo;
Kevin Chyn352adfe2018-09-20 22:28:50 -0700366 mService = IBiometricService.Stub.asInterface(
367 ServiceManager.getService(Context.BIOMETRIC_SERVICE));
Kevin Chynaae4a152018-01-18 11:48:09 -0800368 }
369
370 /**
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700371 * A wrapper class for the crypto objects supported by BiometricPrompt. Currently the framework
Vishwath Mohancf87df12018-03-20 22:57:17 -0700372 * supports {@link Signature}, {@link Cipher} and {@link Mac} objects.
Kevin Chyn66682562018-01-25 18:26:46 -0800373 */
374 public static final class CryptoObject extends android.hardware.biometrics.CryptoObject {
375 public CryptoObject(@NonNull Signature signature) {
376 super(signature);
377 }
378
379 public CryptoObject(@NonNull Cipher cipher) {
380 super(cipher);
381 }
382
383 public CryptoObject(@NonNull Mac mac) {
384 super(mac);
385 }
386
387 /**
388 * Get {@link Signature} object.
389 * @return {@link Signature} object or null if this doesn't contain one.
390 */
391 public Signature getSignature() {
392 return super.getSignature();
393 }
394
395 /**
396 * Get {@link Cipher} object.
397 * @return {@link Cipher} object or null if this doesn't contain one.
398 */
399 public Cipher getCipher() {
400 return super.getCipher();
401 }
402
403 /**
404 * Get {@link Mac} object.
405 * @return {@link Mac} object or null if this doesn't contain one.
406 */
407 public Mac getMac() {
408 return super.getMac();
409 }
410 }
411
412 /**
Vishwath Mohancf87df12018-03-20 22:57:17 -0700413 * Container for callback data from {@link #authenticate( CancellationSignal, Executor,
414 * AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor,
Kevin Chyn66682562018-01-25 18:26:46 -0800415 * AuthenticationCallback)}
416 */
417 public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult {
418 /**
419 * Authentication result
420 * @param crypto
Kevin Chyn66682562018-01-25 18:26:46 -0800421 * @hide
422 */
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700423 public AuthenticationResult(CryptoObject crypto) {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700424 // Identifier and userId is not used for BiometricPrompt.
425 super(crypto, null /* identifier */, 0 /* userId */);
Kevin Chyn66682562018-01-25 18:26:46 -0800426 }
427 /**
428 * Obtain the crypto object associated with this transaction
Vishwath Mohancf87df12018-03-20 22:57:17 -0700429 * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal,
430 * Executor, AuthenticationCallback)}
Kevin Chyn66682562018-01-25 18:26:46 -0800431 */
432 public CryptoObject getCryptoObject() {
433 return (CryptoObject) super.getCryptoObject();
434 }
435 }
436
437 /**
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700438 * Callback structure provided to {@link BiometricPrompt#authenticate(CancellationSignal,
439 * Executor, AuthenticationCallback)} or {@link BiometricPrompt#authenticate(CryptoObject,
Kevin Chyn66682562018-01-25 18:26:46 -0800440 * CancellationSignal, Executor, AuthenticationCallback)}. Users must provide an implementation
441 * of this for listening to authentication events.
442 */
Vishwath Mohancf87df12018-03-20 22:57:17 -0700443 public abstract static class AuthenticationCallback extends
Kevin Chyn66682562018-01-25 18:26:46 -0800444 BiometricAuthenticator.AuthenticationCallback {
445 /**
446 * Called when an unrecoverable error has been encountered and the operation is complete.
447 * No further actions will be made on this object.
448 * @param errorCode An integer identifying the error message
449 * @param errString A human-readable error string that can be shown on an UI
450 */
451 @Override
452 public void onAuthenticationError(int errorCode, CharSequence errString) {}
453
454 /**
455 * Called when a recoverable error has been encountered during authentication. The help
456 * string is provided to give the user guidance for what went wrong, such as "Sensor dirty,
457 * please clean it."
458 * @param helpCode An integer identifying the error message
459 * @param helpString A human-readable string that can be shown on an UI
460 */
461 @Override
462 public void onAuthenticationHelp(int helpCode, CharSequence helpString) {}
463
464 /**
465 * Called when a biometric is recognized.
466 * @param result An object containing authentication-related data
467 */
468 public void onAuthenticationSucceeded(AuthenticationResult result) {}
469
470 /**
471 * Called when a biometric is valid but not recognized.
472 */
473 @Override
474 public void onAuthenticationFailed() {}
475
476 /**
477 * Called when a biometric has been acquired, but hasn't been processed yet.
478 * @hide
479 */
480 @Override
481 public void onAuthenticationAcquired(int acquireInfo) {}
Kevin Chyn66682562018-01-25 18:26:46 -0800482 }
483
Kevin Chyn66682562018-01-25 18:26:46 -0800484 /**
Kevin Chyn067085a2018-11-12 15:49:19 -0800485 * Authenticates for the given user.
486 * @param cancel An object that can be used to cancel authentication
487 * @param executor An executor to handle callback events
488 * @param callback An object to receive authentication events
489 * @param userId The user to authenticate
490 * @hide
491 */
492 @RequiresPermission(USE_BIOMETRIC_INTERNAL)
493 public void authenticateUser(@NonNull CancellationSignal cancel,
494 @NonNull @CallbackExecutor Executor executor,
495 @NonNull AuthenticationCallback callback,
496 int userId) {
497 if (cancel == null) {
498 throw new IllegalArgumentException("Must supply a cancellation signal");
499 }
500 if (executor == null) {
501 throw new IllegalArgumentException("Must supply an executor");
502 }
503 if (callback == null) {
504 throw new IllegalArgumentException("Must supply a callback");
505 }
506 authenticateInternal(null /* crypto */, cancel, executor, callback, userId);
507 }
508
509 /**
Kevin Chyn33250a72018-09-27 16:24:40 -0700510 * This call warms up the biometric hardware, displays a system-provided dialog, and starts
511 * scanning for a biometric. It terminates when {@link
Vishwath Mohancf87df12018-03-20 22:57:17 -0700512 * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link
513 * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)}, or when the user
514 * dismisses the system-provided dialog, at which point the crypto object becomes invalid. This
515 * operation can be canceled by using the provided cancel object. The application will receive
516 * authentication errors through {@link AuthenticationCallback}, and button events through the
517 * corresponding callback set in {@link Builder#setNegativeButton(CharSequence, Executor,
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700518 * DialogInterface.OnClickListener)}. It is safe to reuse the {@link BiometricPrompt} object,
519 * and calling {@link BiometricPrompt#authenticate( CancellationSignal, Executor,
Vishwath Mohancf87df12018-03-20 22:57:17 -0700520 * AuthenticationCallback)} while an existing authentication attempt is occurring will stop the
521 * previous client and start a new authentication. The interrupted client will receive a
522 * cancelled notification through {@link AuthenticationCallback#onAuthenticationError(int,
Kevin Chyn66682562018-01-25 18:26:46 -0800523 * CharSequence)}.
Kevin Chynaae4a152018-01-18 11:48:09 -0800524 *
Kevin Chyn66682562018-01-25 18:26:46 -0800525 * @throws IllegalArgumentException If any of the arguments are null
Kevin Chynaae4a152018-01-18 11:48:09 -0800526 *
Kevin Chyn66682562018-01-25 18:26:46 -0800527 * @param crypto Object associated with the call
528 * @param cancel An object that can be used to cancel authentication
529 * @param executor An executor to handle callback events
530 * @param callback An object to receive authentication events
Kevin Chynaae4a152018-01-18 11:48:09 -0800531 */
Vishwath Mohancf87df12018-03-20 22:57:17 -0700532 @RequiresPermission(USE_BIOMETRIC)
Kevin Chyn66682562018-01-25 18:26:46 -0800533 public void authenticate(@NonNull CryptoObject crypto,
Kevin Chynaae4a152018-01-18 11:48:09 -0800534 @NonNull CancellationSignal cancel,
535 @NonNull @CallbackExecutor Executor executor,
Kevin Chyn66682562018-01-25 18:26:46 -0800536 @NonNull AuthenticationCallback callback) {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700537 if (crypto == null) {
538 throw new IllegalArgumentException("Must supply a crypto object");
Kevin Chyn66682562018-01-25 18:26:46 -0800539 }
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700540 if (cancel == null) {
541 throw new IllegalArgumentException("Must supply a cancellation signal");
542 }
543 if (executor == null) {
544 throw new IllegalArgumentException("Must supply an executor");
545 }
546 if (callback == null) {
547 throw new IllegalArgumentException("Must supply a callback");
548 }
Kevin Chyn45d1f9d2019-02-07 13:38:04 -0800549 if (mBundle.getBoolean(KEY_ALLOW_DEVICE_CREDENTIAL)) {
550 throw new IllegalArgumentException("Device credential not supported with crypto");
Kevin Chyn1b2137c2019-01-24 16:32:38 -0800551 }
Kevin Chyn067085a2018-11-12 15:49:19 -0800552 authenticateInternal(crypto, cancel, executor, callback, mContext.getUserId());
Kevin Chynaae4a152018-01-18 11:48:09 -0800553 }
554
555 /**
Kevin Chyn33250a72018-09-27 16:24:40 -0700556 * This call warms up the biometric hardware, displays a system-provided dialog, and starts
557 * scanning for a biometric. It terminates when {@link
Vishwath Mohancf87df12018-03-20 22:57:17 -0700558 * AuthenticationCallback#onAuthenticationError(int, CharSequence)} is called, when {@link
559 * AuthenticationCallback#onAuthenticationSucceeded( AuthenticationResult)} is called, or when
560 * the user dismisses the system-provided dialog. This operation can be canceled by using the
561 * provided cancel object. The application will receive authentication errors through {@link
562 * AuthenticationCallback}, and button events through the corresponding callback set in {@link
563 * Builder#setNegativeButton(CharSequence, Executor, DialogInterface.OnClickListener)}. It is
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700564 * safe to reuse the {@link BiometricPrompt} object, and calling {@link
565 * BiometricPrompt#authenticate(CancellationSignal, Executor, AuthenticationCallback)} while
Vishwath Mohancf87df12018-03-20 22:57:17 -0700566 * an existing authentication attempt is occurring will stop the previous client and start a new
567 * authentication. The interrupted client will receive a cancelled notification through {@link
568 * AuthenticationCallback#onAuthenticationError(int, CharSequence)}.
Kevin Chynaae4a152018-01-18 11:48:09 -0800569 *
Kevin Chyn66682562018-01-25 18:26:46 -0800570 * @throws IllegalArgumentException If any of the arguments are null
Kevin Chynaae4a152018-01-18 11:48:09 -0800571 *
Kevin Chyn66682562018-01-25 18:26:46 -0800572 * @param cancel An object that can be used to cancel authentication
573 * @param executor An executor to handle callback events
574 * @param callback An object to receive authentication events
Kevin Chynaae4a152018-01-18 11:48:09 -0800575 */
Vishwath Mohancf87df12018-03-20 22:57:17 -0700576 @RequiresPermission(USE_BIOMETRIC)
Kevin Chynaae4a152018-01-18 11:48:09 -0800577 public void authenticate(@NonNull CancellationSignal cancel,
578 @NonNull @CallbackExecutor Executor executor,
Kevin Chyn66682562018-01-25 18:26:46 -0800579 @NonNull AuthenticationCallback callback) {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700580 if (cancel == null) {
581 throw new IllegalArgumentException("Must supply a cancellation signal");
Kevin Chyn66682562018-01-25 18:26:46 -0800582 }
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700583 if (executor == null) {
584 throw new IllegalArgumentException("Must supply an executor");
585 }
586 if (callback == null) {
587 throw new IllegalArgumentException("Must supply a callback");
588 }
Kevin Chyn067085a2018-11-12 15:49:19 -0800589 authenticateInternal(null /* crypto */, cancel, executor, callback, mContext.getUserId());
Kevin Chynaae4a152018-01-18 11:48:09 -0800590 }
Kevin Chyn66682562018-01-25 18:26:46 -0800591
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700592 private void cancelAuthentication() {
593 if (mService != null) {
594 try {
595 mService.cancelAuthentication(mToken, mContext.getOpPackageName());
596 } catch (RemoteException e) {
597 Log.e(TAG, "Unable to cancel authentication", e);
598 }
Kevin Chyn66682562018-01-25 18:26:46 -0800599 }
Kevin Chyn66682562018-01-25 18:26:46 -0800600 }
601
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700602 private void authenticateInternal(@Nullable CryptoObject crypto,
603 @NonNull CancellationSignal cancel,
604 @NonNull @CallbackExecutor Executor executor,
Kevin Chyn067085a2018-11-12 15:49:19 -0800605 @NonNull AuthenticationCallback callback,
606 int userId) {
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700607 try {
608 if (cancel.isCanceled()) {
609 Log.w(TAG, "Authentication already canceled");
610 return;
611 } else {
612 cancel.setOnCancelListener(new OnAuthenticationCancelListener());
613 }
614
615 mCryptoObject = crypto;
616 mExecutor = executor;
617 mAuthenticationCallback = callback;
618 final long sessionId = crypto != null ? crypto.getOpId() : 0;
Kevin Chyne92cdae2018-11-21 16:35:04 -0800619 mService.authenticate(mToken, sessionId, userId, mBiometricServiceReceiver,
Kevin Chyn87f257a2018-11-27 16:26:07 -0800620 mContext.getOpPackageName(), mBundle);
Kevin Chyna24e9fd2018-08-27 12:39:17 -0700621 } catch (RemoteException e) {
622 Log.e(TAG, "Remote exception while authenticating", e);
623 mExecutor.execute(() -> {
624 callback.onAuthenticationError(BiometricPrompt.BIOMETRIC_ERROR_HW_UNAVAILABLE,
625 mContext.getString(R.string.biometric_error_hw_unavailable));
626 });
627 }
Kevin Chyn66682562018-01-25 18:26:46 -0800628 }
Kevin Chynaae4a152018-01-18 11:48:09 -0800629}