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