• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.systemui.biometrics;
18 
19 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
20 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
21 
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.app.ActivityManager;
25 import android.app.ActivityTaskManager;
26 import android.app.TaskStackListener;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.res.Configuration;
32 import android.graphics.PointF;
33 import android.hardware.biometrics.BiometricAuthenticator.Modality;
34 import android.hardware.biometrics.BiometricConstants;
35 import android.hardware.biometrics.BiometricManager.Authenticators;
36 import android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
37 import android.hardware.biometrics.BiometricPrompt;
38 import android.hardware.biometrics.IBiometricSysuiReceiver;
39 import android.hardware.biometrics.PromptInfo;
40 import android.hardware.display.DisplayManager;
41 import android.hardware.face.FaceManager;
42 import android.hardware.face.FaceSensorPropertiesInternal;
43 import android.hardware.fingerprint.FingerprintManager;
44 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
45 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
46 import android.hardware.fingerprint.IUdfpsHbmListener;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.os.Looper;
50 import android.os.RemoteException;
51 import android.util.Log;
52 import android.view.MotionEvent;
53 import android.view.WindowManager;
54 
55 import com.android.internal.R;
56 import com.android.internal.annotations.VisibleForTesting;
57 import com.android.internal.os.SomeArgs;
58 import com.android.systemui.SystemUI;
59 import com.android.systemui.assist.ui.DisplayUtils;
60 import com.android.systemui.dagger.SysUISingleton;
61 import com.android.systemui.dagger.qualifiers.Main;
62 import com.android.systemui.doze.DozeReceiver;
63 import com.android.systemui.statusbar.CommandQueue;
64 
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.HashSet;
68 import java.util.List;
69 import java.util.Set;
70 
71 import javax.inject.Inject;
72 import javax.inject.Provider;
73 
74 import kotlin.Unit;
75 
76 /**
77  * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
78  * appropriate biometric UI (e.g. BiometricDialogView).
79  */
80 @SysUISingleton
81 public class AuthController extends SystemUI implements CommandQueue.Callbacks,
82         AuthDialogCallback, DozeReceiver {
83 
84     private static final String TAG = "AuthController";
85     private static final boolean DEBUG = true;
86 
87     private final Handler mHandler = new Handler(Looper.getMainLooper());
88     private final CommandQueue mCommandQueue;
89     private final ActivityTaskManager mActivityTaskManager;
90     @Nullable private final FingerprintManager mFingerprintManager;
91     @Nullable private final FaceManager mFaceManager;
92     private final Provider<UdfpsController> mUdfpsControllerFactory;
93     private final Provider<SidefpsController> mSidefpsControllerFactory;
94     @Nullable private final PointF mFaceAuthSensorLocation;
95     @Nullable private final PointF mFingerprintLocation;
96     private final Set<Callback> mCallbacks = new HashSet<>();
97 
98     // TODO: These should just be saved from onSaveState
99     private SomeArgs mCurrentDialogArgs;
100     @VisibleForTesting
101     AuthDialog mCurrentDialog;
102 
103     @NonNull private final WindowManager mWindowManager;
104     @Nullable private UdfpsController mUdfpsController;
105     @Nullable private IUdfpsHbmListener mUdfpsHbmListener;
106     @Nullable private SidefpsController mSidefpsController;
107     @VisibleForTesting
108     TaskStackListener mTaskStackListener;
109     @VisibleForTesting
110     IBiometricSysuiReceiver mReceiver;
111     @VisibleForTesting
112     @NonNull final BiometricOrientationEventListener mOrientationListener;
113     @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
114     @Nullable private List<FingerprintSensorPropertiesInternal> mFpProps;
115     @Nullable private List<FingerprintSensorPropertiesInternal> mUdfpsProps;
116     @Nullable private List<FingerprintSensorPropertiesInternal> mSidefpsProps;
117 
118     private class BiometricTaskStackListener extends TaskStackListener {
119         @Override
onTaskStackChanged()120         public void onTaskStackChanged() {
121             mHandler.post(AuthController.this::handleTaskStackChanged);
122         }
123     }
124 
125     @NonNull
126     private final IFingerprintAuthenticatorsRegisteredCallback
127             mFingerprintAuthenticatorsRegisteredCallback =
128             new IFingerprintAuthenticatorsRegisteredCallback.Stub() {
129                 @Override public void onAllAuthenticatorsRegistered(
130                         List<FingerprintSensorPropertiesInternal> sensors) {
131                     if (DEBUG) {
132                         Log.d(TAG, "onFingerprintProvidersAvailable | sensors: " + Arrays.toString(
133                                 sensors.toArray()));
134                     }
135                     mFpProps = sensors;
136                     List<FingerprintSensorPropertiesInternal> udfpsProps = new ArrayList<>();
137                     List<FingerprintSensorPropertiesInternal> sidefpsProps = new ArrayList<>();
138                     for (FingerprintSensorPropertiesInternal props : mFpProps) {
139                         if (props.isAnyUdfpsType()) {
140                             udfpsProps.add(props);
141                         }
142                         if (props.isAnySidefpsType()) {
143                             sidefpsProps.add(props);
144                         }
145                     }
146                     mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null;
147                     if (mUdfpsProps != null) {
148                         mUdfpsController = mUdfpsControllerFactory.get();
149                     }
150                     mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null;
151                     if (mSidefpsProps != null) {
152                         mSidefpsController = mSidefpsControllerFactory.get();
153                     }
154 
155                     for (Callback cb : mCallbacks) {
156                         cb.onAllAuthenticatorsRegistered();
157                     }
158                 }
159             };
160 
161     @VisibleForTesting final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
162         @Override
163         public void onReceive(Context context, Intent intent) {
164             if (mCurrentDialog != null
165                     && Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
166                 Log.w(TAG, "ACTION_CLOSE_SYSTEM_DIALOGS received");
167                 mCurrentDialog.dismissWithoutCallback(true /* animate */);
168                 mCurrentDialog = null;
169                 mOrientationListener.disable();
170 
171                 try {
172                     if (mReceiver != null) {
173                         mReceiver.onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
174                                 null /* credentialAttestation */);
175                         mReceiver = null;
176                     }
177                 } catch (RemoteException e) {
178                     Log.e(TAG, "Remote exception", e);
179                 }
180             }
181         }
182     };
183 
handleTaskStackChanged()184     private void handleTaskStackChanged() {
185         if (mCurrentDialog != null) {
186             try {
187                 final String clientPackage = mCurrentDialog.getOpPackageName();
188                 Log.w(TAG, "Task stack changed, current client: " + clientPackage);
189                 final List<ActivityManager.RunningTaskInfo> runningTasks =
190                         mActivityTaskManager.getTasks(1);
191                 if (!runningTasks.isEmpty()) {
192                     final String topPackage = runningTasks.get(0).topActivity.getPackageName();
193                     if (!topPackage.contentEquals(clientPackage)
194                             && !Utils.isSystem(mContext, clientPackage)) {
195                         Log.w(TAG, "Evicting client due to: " + topPackage);
196                         mCurrentDialog.dismissWithoutCallback(true /* animate */);
197                         mCurrentDialog = null;
198                         mOrientationListener.disable();
199 
200                         if (mReceiver != null) {
201                             mReceiver.onDialogDismissed(
202                                     BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
203                                     null /* credentialAttestation */);
204                             mReceiver = null;
205                         }
206                     }
207                 }
208             } catch (RemoteException e) {
209                 Log.e(TAG, "Remote exception", e);
210             }
211         }
212     }
213 
214     /**
215      * Adds a callback. See {@link Callback}.
216      */
addCallback(@onNull Callback callback)217     public void addCallback(@NonNull Callback callback) {
218         mCallbacks.add(callback);
219     }
220 
221     /**
222      * Removes a callback. See {@link Callback}.
223      */
removeCallback(@onNull Callback callback)224     public void removeCallback(@NonNull Callback callback) {
225         mCallbacks.remove(callback);
226     }
227 
228     @Override
dozeTimeTick()229     public void dozeTimeTick() {
230         if (mUdfpsController != null) {
231             mUdfpsController.dozeTimeTick();
232         }
233     }
234 
235     @Override
onTryAgainPressed()236     public void onTryAgainPressed() {
237         if (mReceiver == null) {
238             Log.e(TAG, "onTryAgainPressed: Receiver is null");
239             return;
240         }
241         try {
242             mReceiver.onTryAgainPressed();
243         } catch (RemoteException e) {
244             Log.e(TAG, "RemoteException when handling try again", e);
245         }
246     }
247 
248     @Override
onDeviceCredentialPressed()249     public void onDeviceCredentialPressed() {
250         if (mReceiver == null) {
251             Log.e(TAG, "onDeviceCredentialPressed: Receiver is null");
252             return;
253         }
254         try {
255             mReceiver.onDeviceCredentialPressed();
256         } catch (RemoteException e) {
257             Log.e(TAG, "RemoteException when handling credential button", e);
258         }
259     }
260 
261     @Override
onSystemEvent(int event)262     public void onSystemEvent(int event) {
263         if (mReceiver == null) {
264             Log.e(TAG, "onSystemEvent(" + event + "): Receiver is null");
265             return;
266         }
267         try {
268             mReceiver.onSystemEvent(event);
269         } catch (RemoteException e) {
270             Log.e(TAG, "RemoteException when sending system event", e);
271         }
272     }
273 
274     @Override
onDialogAnimatedIn()275     public void onDialogAnimatedIn() {
276         if (mReceiver == null) {
277             Log.e(TAG, "onDialogAnimatedIn: Receiver is null");
278             return;
279         }
280 
281         try {
282             mReceiver.onDialogAnimatedIn();
283         } catch (RemoteException e) {
284             Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
285         }
286     }
287 
288     @Override
onStartFingerprintNow()289     public void onStartFingerprintNow() {
290         if (mReceiver == null) {
291             Log.e(TAG, "onStartUdfpsNow: Receiver is null");
292             return;
293         }
294 
295         try {
296             mReceiver.onStartFingerprintNow();
297         } catch (RemoteException e) {
298             Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e);
299         }
300     }
301 
302     @Override
onDismissed(@ismissedReason int reason, @Nullable byte[] credentialAttestation)303     public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) {
304         switch (reason) {
305             case AuthDialogCallback.DISMISSED_USER_CANCELED:
306                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
307                         credentialAttestation);
308                 break;
309 
310             case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
311                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE,
312                         credentialAttestation);
313                 break;
314 
315             case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
316                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED,
317                         credentialAttestation);
318                 break;
319 
320             case AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED:
321                 sendResultAndCleanUp(
322                         BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED,
323                         credentialAttestation);
324                 break;
325 
326             case AuthDialogCallback.DISMISSED_ERROR:
327                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR,
328                         credentialAttestation);
329                 break;
330 
331             case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
332                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED,
333                         credentialAttestation);
334                 break;
335 
336             case AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED:
337                 sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED,
338                         credentialAttestation);
339                 break;
340 
341             default:
342                 Log.e(TAG, "Unhandled reason: " + reason);
343                 break;
344         }
345     }
346 
347     /**
348      * @return where the UDFPS exists on the screen in pixels in portrait mode.
349      */
getUdfpsSensorLocation()350     @Nullable public PointF getUdfpsSensorLocation() {
351         if (mUdfpsController == null) {
352             return null;
353         }
354         return new PointF(mUdfpsController.getSensorLocation().centerX(),
355                 mUdfpsController.getSensorLocation().centerY());
356     }
357 
358     /**
359      * @return where the fingerprint sensor exists in pixels in portrait mode. devices without an
360      * overridden value will use the default value even if they don't have a fingerprint sensor
361      */
getFingerprintSensorLocation()362     @Nullable public PointF getFingerprintSensorLocation() {
363         if (getUdfpsSensorLocation() != null) {
364             return getUdfpsSensorLocation();
365         }
366         return mFingerprintLocation;
367     }
368 
369     /**
370      * @return where the face authentication sensor exists relative to the screen in pixels in
371      * portrait mode.
372      */
getFaceAuthSensorLocation()373     @Nullable public PointF getFaceAuthSensorLocation() {
374         if (mFaceProps == null || mFaceAuthSensorLocation == null) {
375             return null;
376         }
377         return new PointF(mFaceAuthSensorLocation.x, mFaceAuthSensorLocation.y);
378     }
379 
380     /**
381      * Requests fingerprint scan.
382      *
383      * @param screenX X position of long press
384      * @param screenY Y position of long press
385      * @param major length of the major axis. See {@link MotionEvent#AXIS_TOOL_MAJOR}.
386      * @param minor length of the minor axis. See {@link MotionEvent#AXIS_TOOL_MINOR}.
387      */
onAodInterrupt(int screenX, int screenY, float major, float minor)388     public void onAodInterrupt(int screenX, int screenY, float major, float minor) {
389         if (mUdfpsController == null) {
390             return;
391         }
392         mUdfpsController.onAodInterrupt(screenX, screenY, major, minor);
393     }
394 
395     /**
396      * Cancel a fingerprint scan manually. This will get rid of the white circle on the udfps
397      * sensor area even if the user hasn't explicitly lifted their finger yet.
398      */
onCancelUdfps()399     public void onCancelUdfps() {
400         if (mUdfpsController == null) {
401             return;
402         }
403         mUdfpsController.onCancelUdfps();
404     }
405 
sendResultAndCleanUp(@ismissedReason int reason, @Nullable byte[] credentialAttestation)406     private void sendResultAndCleanUp(@DismissedReason int reason,
407             @Nullable byte[] credentialAttestation) {
408         if (mReceiver == null) {
409             Log.e(TAG, "sendResultAndCleanUp: Receiver is null");
410             return;
411         }
412         try {
413             mReceiver.onDialogDismissed(reason, credentialAttestation);
414         } catch (RemoteException e) {
415             Log.w(TAG, "Remote exception", e);
416         }
417         onDialogDismissed(reason);
418     }
419 
420     @Inject
AuthController(Context context, CommandQueue commandQueue, ActivityTaskManager activityTaskManager, @NonNull WindowManager windowManager, @Nullable FingerprintManager fingerprintManager, @Nullable FaceManager faceManager, Provider<UdfpsController> udfpsControllerFactory, Provider<SidefpsController> sidefpsControllerFactory, @NonNull DisplayManager displayManager, @Main Handler handler)421     public AuthController(Context context,
422             CommandQueue commandQueue,
423             ActivityTaskManager activityTaskManager,
424             @NonNull WindowManager windowManager,
425             @Nullable FingerprintManager fingerprintManager,
426             @Nullable FaceManager faceManager,
427             Provider<UdfpsController> udfpsControllerFactory,
428             Provider<SidefpsController> sidefpsControllerFactory,
429             @NonNull DisplayManager displayManager,
430             @Main Handler handler) {
431         super(context);
432         mCommandQueue = commandQueue;
433         mActivityTaskManager = activityTaskManager;
434         mFingerprintManager = fingerprintManager;
435         mFaceManager = faceManager;
436         mUdfpsControllerFactory = udfpsControllerFactory;
437         mSidefpsControllerFactory = sidefpsControllerFactory;
438         mWindowManager = windowManager;
439         mOrientationListener = new BiometricOrientationEventListener(context,
440                 () -> {
441                     onOrientationChanged();
442                     return Unit.INSTANCE;
443                 },
444                 displayManager,
445                 handler);
446 
447         mFaceProps = mFaceManager != null ? mFaceManager.getSensorPropertiesInternal() : null;
448 
449         int[] faceAuthLocation = context.getResources().getIntArray(
450                 com.android.systemui.R.array.config_face_auth_props);
451         if (faceAuthLocation == null || faceAuthLocation.length < 2) {
452             mFaceAuthSensorLocation = null;
453         } else {
454             mFaceAuthSensorLocation = new PointF(
455                     (float) faceAuthLocation[0],
456                     (float) faceAuthLocation[1]);
457         }
458 
459         mFingerprintLocation = new PointF(DisplayUtils.getWidth(mContext) / 2,
460                 mContext.getResources().getDimensionPixelSize(
461                 com.android.systemui.R.dimen.physical_fingerprint_sensor_center_screen_location_y));
462 
463         IntentFilter filter = new IntentFilter();
464         filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
465 
466         context.registerReceiver(mBroadcastReceiver, filter);
467     }
468 
469     @SuppressWarnings("deprecation")
470     @Override
start()471     public void start() {
472         mCommandQueue.addCallback(this);
473 
474         if (mFingerprintManager != null) {
475             mFingerprintManager.addAuthenticatorsRegisteredCallback(
476                     mFingerprintAuthenticatorsRegisteredCallback);
477         }
478 
479         mTaskStackListener = new BiometricTaskStackListener();
480         mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
481     }
482 
483     /**
484      * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}.
485      *
486      * DisplayModeDirector implements {@link IUdfpsHbmListener} and registers it with this class by
487      * calling {@link CommandQueue#setUdfpsHbmListener(IUdfpsHbmListener)}.
488      */
489     @Override
setUdfpsHbmListener(IUdfpsHbmListener listener)490     public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
491         mUdfpsHbmListener = listener;
492     }
493 
494     /**
495      * @return IUdfpsHbmListener that can be set by DisplayModeDirector.
496      */
getUdfpsHbmListener()497     @Nullable public IUdfpsHbmListener getUdfpsHbmListener() {
498         return mUdfpsHbmListener;
499     }
500 
501     @Override
showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver, int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation, int userId, String opPackageName, long operationId, @BiometricMultiSensorMode int multiSensorConfig)502     public void showAuthenticationDialog(PromptInfo promptInfo, IBiometricSysuiReceiver receiver,
503             int[] sensorIds, boolean credentialAllowed, boolean requireConfirmation,
504             int userId, String opPackageName, long operationId,
505             @BiometricMultiSensorMode int multiSensorConfig) {
506         @Authenticators.Types final int authenticators = promptInfo.getAuthenticators();
507 
508         if (DEBUG) {
509             StringBuilder ids = new StringBuilder();
510             for (int sensorId : sensorIds) {
511                 ids.append(sensorId).append(" ");
512             }
513             Log.d(TAG, "showAuthenticationDialog, authenticators: " + authenticators
514                     + ", sensorIds: " + ids.toString()
515                     + ", credentialAllowed: " + credentialAllowed
516                     + ", requireConfirmation: " + requireConfirmation
517                     + ", operationId: " + operationId
518                     + ", multiSensorConfig: " + multiSensorConfig);
519         }
520         SomeArgs args = SomeArgs.obtain();
521         args.arg1 = promptInfo;
522         args.arg2 = receiver;
523         args.arg3 = sensorIds;
524         args.arg4 = credentialAllowed;
525         args.arg5 = requireConfirmation;
526         args.argi1 = userId;
527         args.arg6 = opPackageName;
528         args.arg7 = operationId;
529         args.argi2 = multiSensorConfig;
530 
531         boolean skipAnimation = false;
532         if (mCurrentDialog != null) {
533             Log.w(TAG, "mCurrentDialog: " + mCurrentDialog);
534             skipAnimation = true;
535         }
536 
537         showDialog(args, skipAnimation, null /* savedState */);
538     }
539 
540     /**
541      * Only called via BiometricService for the biometric prompt. Will not be called for
542      * authentication directly requested through FingerprintManager. For
543      * example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}.
544      */
545     @Override
onBiometricAuthenticated()546     public void onBiometricAuthenticated() {
547         if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: ");
548 
549         if (mCurrentDialog != null) {
550             mCurrentDialog.onAuthenticationSucceeded();
551         } else {
552             Log.w(TAG, "onBiometricAuthenticated callback but dialog gone");
553         }
554     }
555 
556     @Override
onBiometricHelp(@odality int modality, String message)557     public void onBiometricHelp(@Modality int modality, String message) {
558         if (DEBUG) Log.d(TAG, "onBiometricHelp: " + message);
559 
560         if (mCurrentDialog != null) {
561             mCurrentDialog.onHelp(modality, message);
562         } else {
563             Log.w(TAG, "onBiometricHelp callback but dialog gone");
564         }
565     }
566 
567     @Nullable
getUdfpsProps()568     public List<FingerprintSensorPropertiesInternal> getUdfpsProps() {
569         return mUdfpsProps;
570     }
571 
getErrorString(@odality int modality, int error, int vendorCode)572     private String getErrorString(@Modality int modality, int error, int vendorCode) {
573         switch (modality) {
574             case TYPE_FACE:
575                 return FaceManager.getErrorString(mContext, error, vendorCode);
576 
577             case TYPE_FINGERPRINT:
578                 return FingerprintManager.getErrorString(mContext, error, vendorCode);
579 
580             default:
581                 return "";
582         }
583     }
584 
585     /**
586      * Only called via BiometricService for the biometric prompt. Will not be called for
587      * authentication directly requested through FingerprintManager. For
588      * example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}.
589      */
590     @Override
onBiometricError(@odality int modality, int error, int vendorCode)591     public void onBiometricError(@Modality int modality, int error, int vendorCode) {
592         if (DEBUG) {
593             Log.d(TAG, String.format("onBiometricError(%d, %d, %d)", modality, error, vendorCode));
594         }
595 
596         final boolean isLockout = (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT)
597                 || (error == BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT);
598 
599         // TODO(b/141025588): Create separate methods for handling hard and soft errors.
600         final boolean isSoftError = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED
601                 || error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT);
602 
603         if (mCurrentDialog != null) {
604             if (mCurrentDialog.isAllowDeviceCredentials() && isLockout) {
605                 if (DEBUG) Log.d(TAG, "onBiometricError, lockout");
606                 mCurrentDialog.animateToCredentialUI();
607             } else if (isSoftError) {
608                 final String errorMessage = (error == BiometricConstants.BIOMETRIC_PAUSED_REJECTED)
609                         ? mContext.getString(R.string.biometric_not_recognized)
610                         : getErrorString(modality, error, vendorCode);
611                 if (DEBUG) Log.d(TAG, "onBiometricError, soft error: " + errorMessage);
612                 mCurrentDialog.onAuthenticationFailed(modality, errorMessage);
613             } else {
614                 final String errorMessage = getErrorString(modality, error, vendorCode);
615                 if (DEBUG) Log.d(TAG, "onBiometricError, hard error: " + errorMessage);
616                 mCurrentDialog.onError(modality, errorMessage);
617             }
618         } else {
619             Log.w(TAG, "onBiometricError callback but dialog is gone");
620         }
621 
622         onCancelUdfps();
623     }
624 
625     @Override
hideAuthenticationDialog()626     public void hideAuthenticationDialog() {
627         if (DEBUG) Log.d(TAG, "hideAuthenticationDialog: " + mCurrentDialog);
628 
629         if (mCurrentDialog == null) {
630             // Could be possible if the caller canceled authentication after credential success
631             // but before the client was notified.
632             return;
633         }
634 
635         mCurrentDialog.dismissFromSystemServer();
636 
637         // BiometricService will have already sent the callback to the client in this case.
638         // This avoids a round trip to SystemUI. So, just dismiss the dialog and we're done.
639         mCurrentDialog = null;
640         mOrientationListener.disable();
641     }
642 
643     /**
644      * Whether the user's finger is currently on udfps attempting to authenticate.
645      */
isUdfpsFingerDown()646     public boolean isUdfpsFingerDown() {
647         if (mUdfpsController == null)  {
648             return false;
649         }
650 
651         return mUdfpsController.isFingerDown();
652     }
653 
654     /**
655      * Whether the passed userId has enrolled face auth.
656      */
isFaceAuthEnrolled(int userId)657     public boolean isFaceAuthEnrolled(int userId) {
658         if (mFaceProps == null) {
659             return false;
660         }
661 
662         return mFaceManager.hasEnrolledTemplates(userId);
663     }
664 
665     /**
666      * Whether the passed userId has enrolled UDFPS.
667      */
isUdfpsEnrolled(int userId)668     public boolean isUdfpsEnrolled(int userId) {
669         if (mUdfpsController == null) {
670             return false;
671         }
672 
673         return mFingerprintManager.hasEnrolledTemplatesForAnySensor(userId, mUdfpsProps);
674     }
675 
showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState)676     private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState) {
677         mCurrentDialogArgs = args;
678 
679         final PromptInfo promptInfo = (PromptInfo) args.arg1;
680         final int[] sensorIds = (int[]) args.arg3;
681         final boolean credentialAllowed = (boolean) args.arg4;
682         final boolean requireConfirmation = (boolean) args.arg5;
683         final int userId = args.argi1;
684         final String opPackageName = (String) args.arg6;
685         final long operationId = (long) args.arg7;
686         final @BiometricMultiSensorMode int multiSensorConfig = args.argi2;
687 
688         // Create a new dialog but do not replace the current one yet.
689         final AuthDialog newDialog = buildDialog(
690                 promptInfo,
691                 requireConfirmation,
692                 userId,
693                 sensorIds,
694                 credentialAllowed,
695                 opPackageName,
696                 skipAnimation,
697                 operationId,
698                 multiSensorConfig);
699 
700         if (newDialog == null) {
701             Log.e(TAG, "Unsupported type configuration");
702             return;
703         }
704 
705         if (DEBUG) {
706             Log.d(TAG, "userId: " + userId
707                     + " savedState: " + savedState
708                     + " mCurrentDialog: " + mCurrentDialog
709                     + " newDialog: " + newDialog);
710         }
711 
712         if (mCurrentDialog != null) {
713             // If somehow we're asked to show a dialog, the old one doesn't need to be animated
714             // away. This can happen if the app cancels and re-starts auth during configuration
715             // change. This is ugly because we also have to do things on onConfigurationChanged
716             // here.
717             mCurrentDialog.dismissWithoutCallback(false /* animate */);
718         }
719 
720         mReceiver = (IBiometricSysuiReceiver) args.arg2;
721         mCurrentDialog = newDialog;
722         mCurrentDialog.show(mWindowManager, savedState);
723         mOrientationListener.enable();
724     }
725 
onDialogDismissed(@ismissedReason int reason)726     private void onDialogDismissed(@DismissedReason int reason) {
727         if (DEBUG) Log.d(TAG, "onDialogDismissed: " + reason);
728         if (mCurrentDialog == null) {
729             Log.w(TAG, "Dialog already dismissed");
730         }
731         mReceiver = null;
732         mCurrentDialog = null;
733         mOrientationListener.disable();
734     }
735 
736     @Override
onConfigurationChanged(Configuration newConfig)737     protected void onConfigurationChanged(Configuration newConfig) {
738         super.onConfigurationChanged(newConfig);
739 
740         // Save the state of the current dialog (buttons showing, etc)
741         if (mCurrentDialog != null) {
742             final Bundle savedState = new Bundle();
743             mCurrentDialog.onSaveState(savedState);
744             mCurrentDialog.dismissWithoutCallback(false /* animate */);
745             mCurrentDialog = null;
746             mOrientationListener.disable();
747 
748             // Only show the dialog if necessary. If it was animating out, the dialog is supposed
749             // to send its pending callback immediately.
750             if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE)
751                     != AuthContainerView.STATE_ANIMATING_OUT) {
752                 final boolean credentialShowing =
753                         savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);
754                 if (credentialShowing) {
755                     // There may be a cleaner way to do this, rather than altering the current
756                     // authentication's parameters. This gets the job done and should be clear
757                     // enough for now.
758                     PromptInfo promptInfo = (PromptInfo) mCurrentDialogArgs.arg1;
759                     promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
760                 }
761 
762                 showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState);
763             }
764         }
765     }
766 
onOrientationChanged()767     private void onOrientationChanged() {
768         if (mCurrentDialog != null) {
769             mCurrentDialog.onOrientationChanged();
770         }
771     }
772 
buildDialog(PromptInfo promptInfo, boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName, boolean skipIntro, long operationId, @BiometricMultiSensorMode int multiSensorConfig)773     protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation,
774             int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName,
775             boolean skipIntro, long operationId,
776             @BiometricMultiSensorMode int multiSensorConfig) {
777         return new AuthContainerView.Builder(mContext)
778                 .setCallback(this)
779                 .setPromptInfo(promptInfo)
780                 .setRequireConfirmation(requireConfirmation)
781                 .setUserId(userId)
782                 .setOpPackageName(opPackageName)
783                 .setSkipIntro(skipIntro)
784                 .setOperationId(operationId)
785                 .setMultiSensorConfig(multiSensorConfig)
786                 .build(sensorIds, credentialAllowed, mFpProps, mFaceProps);
787     }
788 
789     interface Callback {
790         /**
791          * Called when authenticators are registered. If authenticators are already
792          * registered before this call, this callback will never be triggered.
793          */
onAllAuthenticatorsRegistered()794         void onAllAuthenticatorsRegistered();
795     }
796 }
797