1 /*
2  * Copyright (C) 2019 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_FINGERPRINT;
20 import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode;
21 
22 import android.annotation.IntDef;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.content.Context;
26 import android.graphics.PixelFormat;
27 import android.hardware.biometrics.BiometricAuthenticator.Modality;
28 import android.hardware.biometrics.BiometricConstants;
29 import android.hardware.biometrics.PromptInfo;
30 import android.hardware.face.FaceSensorPropertiesInternal;
31 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
32 import android.os.Binder;
33 import android.os.Bundle;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.Looper;
37 import android.os.UserManager;
38 import android.util.Log;
39 import android.view.Display;
40 import android.view.Gravity;
41 import android.view.KeyEvent;
42 import android.view.LayoutInflater;
43 import android.view.Surface;
44 import android.view.View;
45 import android.view.ViewGroup;
46 import android.view.WindowInsets;
47 import android.view.WindowManager;
48 import android.view.animation.Interpolator;
49 import android.widget.FrameLayout;
50 import android.widget.ImageView;
51 import android.widget.LinearLayout;
52 import android.widget.ScrollView;
53 
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.systemui.Dependency;
56 import com.android.systemui.R;
57 import com.android.systemui.animation.Interpolators;
58 import com.android.systemui.keyguard.WakefulnessLifecycle;
59 
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 import java.util.List;
63 
64 /**
65  * Top level container/controller for the BiometricPrompt UI.
66  */
67 public class AuthContainerView extends LinearLayout
68         implements AuthDialog, WakefulnessLifecycle.Observer {
69 
70     private static final String TAG = "BiometricPrompt/AuthContainerView";
71     private static final int ANIMATION_DURATION_SHOW_MS = 250;
72     private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
73 
74     static final int STATE_UNKNOWN = 0;
75     static final int STATE_ANIMATING_IN = 1;
76     static final int STATE_PENDING_DISMISS = 2;
77     static final int STATE_SHOWING = 3;
78     static final int STATE_ANIMATING_OUT = 4;
79     static final int STATE_GONE = 5;
80 
81     @Retention(RetentionPolicy.SOURCE)
82     @IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING,
83             STATE_ANIMATING_OUT, STATE_GONE})
84     @interface ContainerState {}
85 
86     final Config mConfig;
87     final int mEffectiveUserId;
88     @Nullable private final List<FingerprintSensorPropertiesInternal> mFpProps;
89     @Nullable private final List<FaceSensorPropertiesInternal> mFaceProps;
90     private final Handler mHandler;
91     private final Injector mInjector;
92     private final IBinder mWindowToken = new Binder();
93     private final WindowManager mWindowManager;
94     private final AuthPanelController mPanelController;
95     private final Interpolator mLinearOutSlowIn;
96     @VisibleForTesting final BiometricCallback mBiometricCallback;
97     private final CredentialCallback mCredentialCallback;
98 
99     @VisibleForTesting final FrameLayout mFrameLayout;
100     @VisibleForTesting @Nullable AuthBiometricView mBiometricView;
101     @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
102 
103     @VisibleForTesting final ImageView mBackgroundView;
104     @VisibleForTesting final ScrollView mBiometricScrollView;
105     private final View mPanelView;
106 
107     private final float mTranslationY;
108 
109     @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
110 
111     @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN;
112 
113     // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
114     @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
115     // HAT received from LockSettingsService when credential is verified.
116     @Nullable byte[] mCredentialAttestation;
117 
118     static class Config {
119         Context mContext;
120         AuthDialogCallback mCallback;
121         PromptInfo mPromptInfo;
122         boolean mRequireConfirmation;
123         int mUserId;
124         String mOpPackageName;
125         int[] mSensorIds;
126         boolean mCredentialAllowed;
127         boolean mSkipIntro;
128         long mOperationId;
129         long mRequestId;
130         @BiometricMultiSensorMode int mMultiSensorConfig;
131     }
132 
133     public static class Builder {
134         Config mConfig;
135 
Builder(Context context)136         public Builder(Context context) {
137             mConfig = new Config();
138             mConfig.mContext = context;
139         }
140 
setCallback(AuthDialogCallback callback)141         public Builder setCallback(AuthDialogCallback callback) {
142             mConfig.mCallback = callback;
143             return this;
144         }
145 
setPromptInfo(PromptInfo promptInfo)146         public Builder setPromptInfo(PromptInfo promptInfo) {
147             mConfig.mPromptInfo = promptInfo;
148             return this;
149         }
150 
setRequireConfirmation(boolean requireConfirmation)151         public Builder setRequireConfirmation(boolean requireConfirmation) {
152             mConfig.mRequireConfirmation = requireConfirmation;
153             return this;
154         }
155 
setUserId(int userId)156         public Builder setUserId(int userId) {
157             mConfig.mUserId = userId;
158             return this;
159         }
160 
setOpPackageName(String opPackageName)161         public Builder setOpPackageName(String opPackageName) {
162             mConfig.mOpPackageName = opPackageName;
163             return this;
164         }
165 
setSkipIntro(boolean skip)166         public Builder setSkipIntro(boolean skip) {
167             mConfig.mSkipIntro = skip;
168             return this;
169         }
170 
setOperationId(long operationId)171         public Builder setOperationId(long operationId) {
172             mConfig.mOperationId = operationId;
173             return this;
174         }
175 
176         /** Unique id for this request. */
setRequestId(long requestId)177         public Builder setRequestId(long requestId) {
178             mConfig.mRequestId = requestId;
179             return this;
180         }
181 
182         /** The multi-sensor mode. */
setMultiSensorConfig(@iometricMultiSensorMode int multiSensorConfig)183         public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) {
184             mConfig.mMultiSensorConfig = multiSensorConfig;
185             return this;
186         }
187 
build(int[] sensorIds, boolean credentialAllowed, @Nullable List<FingerprintSensorPropertiesInternal> fpProps, @Nullable List<FaceSensorPropertiesInternal> faceProps)188         public AuthContainerView build(int[] sensorIds, boolean credentialAllowed,
189                 @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
190                 @Nullable List<FaceSensorPropertiesInternal> faceProps) {
191             mConfig.mSensorIds = sensorIds;
192             mConfig.mCredentialAllowed = credentialAllowed;
193             return new AuthContainerView(mConfig, new Injector(), fpProps, faceProps);
194         }
195     }
196 
197     public static class Injector {
getBiometricScrollView(FrameLayout parent)198         ScrollView getBiometricScrollView(FrameLayout parent) {
199             return parent.findViewById(R.id.biometric_scrollview);
200         }
201 
inflateContainerView(LayoutInflater factory, ViewGroup root)202         FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
203             return (FrameLayout) factory.inflate(
204                     R.layout.auth_container_view, root, false /* attachToRoot */);
205         }
206 
getPanelController(Context context, View panelView)207         AuthPanelController getPanelController(Context context, View panelView) {
208             return new AuthPanelController(context, panelView);
209         }
210 
getBackgroundView(FrameLayout parent)211         ImageView getBackgroundView(FrameLayout parent) {
212             return parent.findViewById(R.id.background);
213         }
214 
getPanelView(FrameLayout parent)215         View getPanelView(FrameLayout parent) {
216             return parent.findViewById(R.id.panel);
217         }
218 
getAnimateCredentialStartDelayMs()219         int getAnimateCredentialStartDelayMs() {
220             return AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS;
221         }
222 
getUserManager(Context context)223         UserManager getUserManager(Context context) {
224             return UserManager.get(context);
225         }
226 
getCredentialType(Context context, int effectiveUserId)227         int getCredentialType(Context context, int effectiveUserId) {
228             return Utils.getCredentialType(context, effectiveUserId);
229         }
230     }
231 
232     @VisibleForTesting
233     final class BiometricCallback implements AuthBiometricView.Callback {
234         @Override
onAction(int action)235         public void onAction(int action) {
236             switch (action) {
237                 case AuthBiometricView.Callback.ACTION_AUTHENTICATED:
238                     animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
239                     break;
240                 case AuthBiometricView.Callback.ACTION_USER_CANCELED:
241                     sendEarlyUserCanceled();
242                     animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
243                     break;
244                 case AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE:
245                     animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
246                     break;
247                 case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
248                     mConfig.mCallback.onTryAgainPressed();
249                     break;
250                 case AuthBiometricView.Callback.ACTION_ERROR:
251                     animateAway(AuthDialogCallback.DISMISSED_ERROR);
252                     break;
253                 case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL:
254                     mConfig.mCallback.onDeviceCredentialPressed();
255                     mHandler.postDelayed(() -> {
256                         addCredentialView(false /* animatePanel */, true /* animateContents */);
257                     }, mInjector.getAnimateCredentialStartDelayMs());
258                     break;
259                 case AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR:
260                     mConfig.mCallback.onStartFingerprintNow();
261                     break;
262                 default:
263                     Log.e(TAG, "Unhandled action: " + action);
264             }
265         }
266     }
267 
268     final class CredentialCallback implements AuthCredentialView.Callback {
269         @Override
onCredentialMatched(byte[] attestation)270         public void onCredentialMatched(byte[] attestation) {
271             mCredentialAttestation = attestation;
272             animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
273         }
274     }
275 
276     @VisibleForTesting
AuthContainerView(Config config, Injector injector, @Nullable List<FingerprintSensorPropertiesInternal> fpProps, @Nullable List<FaceSensorPropertiesInternal> faceProps)277     AuthContainerView(Config config, Injector injector,
278             @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
279             @Nullable List<FaceSensorPropertiesInternal> faceProps) {
280         super(config.mContext);
281 
282         mConfig = config;
283         mInjector = injector;
284         mFpProps = fpProps;
285         mFaceProps = faceProps;
286 
287         mEffectiveUserId = mInjector.getUserManager(mContext)
288                 .getCredentialOwnerProfile(mConfig.mUserId);
289 
290         mHandler = new Handler(Looper.getMainLooper());
291         mWindowManager = mContext.getSystemService(WindowManager.class);
292         mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
293 
294         mTranslationY = getResources()
295                 .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
296         mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
297         mBiometricCallback = new BiometricCallback();
298         mCredentialCallback = new CredentialCallback();
299 
300         final LayoutInflater factory = LayoutInflater.from(mContext);
301         mFrameLayout = mInjector.inflateContainerView(factory, this);
302 
303         mPanelView = mInjector.getPanelView(mFrameLayout);
304         mPanelController = mInjector.getPanelController(mContext, mPanelView);
305 
306         // Inflate biometric view only if necessary.
307         final int sensorCount = config.mSensorIds.length;
308         if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
309             if (sensorCount == 1) {
310                 final int singleSensorAuthId = config.mSensorIds[0];
311                 if (Utils.containsSensorId(mFpProps, singleSensorAuthId)) {
312                     FingerprintSensorPropertiesInternal sensorProps = null;
313                     for (FingerprintSensorPropertiesInternal prop : mFpProps) {
314                         if (prop.sensorId == singleSensorAuthId) {
315                             sensorProps = prop;
316                             break;
317                         }
318                     }
319 
320                     if (sensorProps.isAnyUdfpsType()) {
321                         AuthBiometricUdfpsView udfpsView = (AuthBiometricUdfpsView) factory
322                                 .inflate(R.layout.auth_biometric_udfps_view, null, false);
323                         udfpsView.setSensorProps(sensorProps);
324                         mBiometricView = udfpsView;
325                     } else {
326                         mBiometricView = (AuthBiometricFingerprintView) factory
327                                 .inflate(R.layout.auth_biometric_fingerprint_view, null, false);
328                     }
329                 } else if (Utils.containsSensorId(mFaceProps, singleSensorAuthId)) {
330                     mBiometricView = (AuthBiometricFaceView)
331                             factory.inflate(R.layout.auth_biometric_face_view, null, false);
332                 } else {
333                     // Unknown sensorId
334                     Log.e(TAG, "Unknown sensorId: " + singleSensorAuthId);
335                     mBiometricView = null;
336                     mBackgroundView = null;
337                     mBiometricScrollView = null;
338                     return;
339                 }
340             } else if (sensorCount == 2) {
341                 final int[] allSensors = findFaceAndFingerprintSensors();
342                 final int faceSensorId = allSensors[0];
343                 final int fingerprintSensorId = allSensors[1];
344 
345                 if (fingerprintSensorId == -1 || faceSensorId == -1) {
346                     Log.e(TAG, "Missing fingerprint or face for dual-sensor config");
347                     mBiometricView = null;
348                     mBackgroundView = null;
349                     mBiometricScrollView = null;
350                     return;
351                 }
352 
353                 FingerprintSensorPropertiesInternal fingerprintSensorProps = null;
354                 for (FingerprintSensorPropertiesInternal prop : mFpProps) {
355                     if (prop.sensorId == fingerprintSensorId) {
356                         fingerprintSensorProps = prop;
357                         break;
358                     }
359                 }
360 
361                 if (fingerprintSensorProps != null) {
362                     final AuthBiometricFaceToFingerprintView faceToFingerprintView =
363                             (AuthBiometricFaceToFingerprintView) factory.inflate(
364                                     R.layout.auth_biometric_face_to_fingerprint_view, null, false);
365                     faceToFingerprintView.setFingerprintSensorProps(fingerprintSensorProps);
366                     faceToFingerprintView.setModalityListener(new ModalityListener() {
367                         @Override
368                         public void onModalitySwitched(int oldModality, int newModality) {
369                             maybeUpdatePositionForUdfps(true /* invalidate */);
370                         }
371                     });
372                     mBiometricView = faceToFingerprintView;
373                 } else {
374                     Log.e(TAG, "Fingerprint props not found for sensor ID: " + fingerprintSensorId);
375                     mBiometricView = null;
376                     mBackgroundView = null;
377                     mBiometricScrollView = null;
378                     return;
379                 }
380             } else {
381                 Log.e(TAG, "Unsupported sensor array, length: " + sensorCount);
382                 mBiometricView = null;
383                 mBackgroundView = null;
384                 mBiometricScrollView = null;
385                 return;
386             }
387         }
388 
389         mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout);
390         mBackgroundView = mInjector.getBackgroundView(mFrameLayout);
391 
392         addView(mFrameLayout);
393 
394         // init view before showing
395         if (mBiometricView != null) {
396             mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
397             mBiometricView.setPanelController(mPanelController);
398             mBiometricView.setPromptInfo(mConfig.mPromptInfo);
399             mBiometricView.setCallback(mBiometricCallback);
400             mBiometricView.setBackgroundView(mBackgroundView);
401             mBiometricView.setUserId(mConfig.mUserId);
402             mBiometricView.setEffectiveUserId(mEffectiveUserId);
403         }
404 
405         // TODO: De-dupe the logic with AuthCredentialPasswordView
406         setOnKeyListener((v, keyCode, event) -> {
407             if (keyCode != KeyEvent.KEYCODE_BACK) {
408                 return false;
409             }
410             if (event.getAction() == KeyEvent.ACTION_UP) {
411                 sendEarlyUserCanceled();
412                 animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
413             }
414             return true;
415         });
416 
417         setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
418         setFocusableInTouchMode(true);
419         requestFocus();
420     }
421 
sendEarlyUserCanceled()422     void sendEarlyUserCanceled() {
423         mConfig.mCallback.onSystemEvent(
424                 BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL);
425     }
426 
427     @Override
isAllowDeviceCredentials()428     public boolean isAllowDeviceCredentials() {
429         return Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo);
430     }
431 
addBiometricView()432     private void addBiometricView() {
433         mBiometricScrollView.addView(mBiometricView);
434     }
435 
436     /**
437      * Adds the credential view. When going from biometric to credential view, the biometric
438      * view starts the panel expansion animation. If the credential view is being shown first,
439      * it should own the panel expansion.
440      * @param animatePanel if the credential view needs to own the panel expansion animation
441      */
addCredentialView(boolean animatePanel, boolean animateContents)442     private void addCredentialView(boolean animatePanel, boolean animateContents) {
443         final LayoutInflater factory = LayoutInflater.from(mContext);
444 
445         final @Utils.CredentialType int credentialType = mInjector.getCredentialType(
446                 mContext, mEffectiveUserId);
447 
448         switch (credentialType) {
449             case Utils.CREDENTIAL_PATTERN:
450                 mCredentialView = (AuthCredentialView) factory.inflate(
451                         R.layout.auth_credential_pattern_view, null, false);
452                 break;
453             case Utils.CREDENTIAL_PIN:
454             case Utils.CREDENTIAL_PASSWORD:
455                 mCredentialView = (AuthCredentialView) factory.inflate(
456                         R.layout.auth_credential_password_view, null, false);
457                 break;
458             default:
459                 throw new IllegalStateException("Unknown credential type: " + credentialType);
460         }
461 
462         // The background is used for detecting taps / cancelling authentication. Since the
463         // credential view is full-screen and should not be canceled from background taps,
464         // disable it.
465         mBackgroundView.setOnClickListener(null);
466         mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
467 
468         mCredentialView.setContainerView(this);
469         mCredentialView.setUserId(mConfig.mUserId);
470         mCredentialView.setOperationId(mConfig.mOperationId);
471         mCredentialView.setEffectiveUserId(mEffectiveUserId);
472         mCredentialView.setCredentialType(credentialType);
473         mCredentialView.setCallback(mCredentialCallback);
474         mCredentialView.setPromptInfo(mConfig.mPromptInfo);
475         mCredentialView.setPanelController(mPanelController, animatePanel);
476         mCredentialView.setShouldAnimateContents(animateContents);
477         mFrameLayout.addView(mCredentialView);
478     }
479 
480     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)481     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
482         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
483         mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight());
484     }
485 
486     @Override
onOrientationChanged()487     public void onOrientationChanged() {
488         maybeUpdatePositionForUdfps(true /* invalidate */);
489     }
490 
491     @Override
onAttachedToWindow()492     public void onAttachedToWindow() {
493         super.onAttachedToWindow();
494         onAttachedToWindowInternal();
495     }
496 
497     @VisibleForTesting
onAttachedToWindowInternal()498     void onAttachedToWindowInternal() {
499         mWakefulnessLifecycle.addObserver(this);
500 
501         if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
502             addBiometricView();
503         } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) {
504             addCredentialView(true /* animatePanel */, false /* animateContents */);
505         } else {
506             throw new IllegalStateException("Unknown configuration: "
507                     + mConfig.mPromptInfo.getAuthenticators());
508         }
509 
510         maybeUpdatePositionForUdfps(false /* invalidate */);
511 
512         if (mConfig.mSkipIntro) {
513             mContainerState = STATE_SHOWING;
514         } else {
515             mContainerState = STATE_ANIMATING_IN;
516             // The background panel and content are different views since we need to be able to
517             // animate them separately in other places.
518             mPanelView.setY(mTranslationY);
519             mBiometricScrollView.setY(mTranslationY);
520 
521             setAlpha(0f);
522             postOnAnimation(() -> {
523                 mPanelView.animate()
524                         .translationY(0)
525                         .setDuration(ANIMATION_DURATION_SHOW_MS)
526                         .setInterpolator(mLinearOutSlowIn)
527                         .withLayer()
528                         .withEndAction(this::onDialogAnimatedIn)
529                         .start();
530                 mBiometricScrollView.animate()
531                         .translationY(0)
532                         .setDuration(ANIMATION_DURATION_SHOW_MS)
533                         .setInterpolator(mLinearOutSlowIn)
534                         .withLayer()
535                         .start();
536                 if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
537                     mCredentialView.setY(mTranslationY);
538                     mCredentialView.animate()
539                             .translationY(0)
540                             .setDuration(ANIMATION_DURATION_SHOW_MS)
541                             .setInterpolator(mLinearOutSlowIn)
542                             .withLayer()
543                             .start();
544                 }
545                 animate()
546                         .alpha(1f)
547                         .setDuration(ANIMATION_DURATION_SHOW_MS)
548                         .setInterpolator(mLinearOutSlowIn)
549                         .withLayer()
550                         .start();
551             });
552         }
553     }
554 
shouldUpdatePositionForUdfps(@onNull View view)555     private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
556         if (view instanceof AuthBiometricUdfpsView) {
557             return true;
558         }
559 
560         if (view instanceof AuthBiometricFaceToFingerprintView) {
561             AuthBiometricFaceToFingerprintView faceToFingerprintView =
562                     (AuthBiometricFaceToFingerprintView) view;
563             return faceToFingerprintView.getActiveSensorType() == TYPE_FINGERPRINT
564                     && faceToFingerprintView.isFingerprintUdfps();
565         }
566 
567         return false;
568     }
569 
maybeUpdatePositionForUdfps(boolean invalidate)570     private boolean maybeUpdatePositionForUdfps(boolean invalidate) {
571         final Display display = getDisplay();
572         if (display == null) {
573             return false;
574         }
575         if (!shouldUpdatePositionForUdfps(mBiometricView)) {
576             return false;
577         }
578 
579         final int displayRotation = display.getRotation();
580         switch (displayRotation) {
581             case Surface.ROTATION_0:
582                 mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
583                 setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
584                 break;
585 
586             case Surface.ROTATION_90:
587                 mPanelController.setPosition(AuthPanelController.POSITION_RIGHT);
588                 setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
589                 break;
590 
591             case Surface.ROTATION_270:
592                 mPanelController.setPosition(AuthPanelController.POSITION_LEFT);
593                 setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
594                 break;
595 
596             case Surface.ROTATION_180:
597             default:
598                 Log.e(TAG, "Unsupported display rotation: " + displayRotation);
599                 mPanelController.setPosition(AuthPanelController.POSITION_BOTTOM);
600                 setScrollViewGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM);
601                 break;
602         }
603 
604         if (invalidate) {
605             mPanelView.invalidateOutline();
606             mBiometricView.requestLayout();
607         }
608 
609         return true;
610     }
611 
setScrollViewGravity(int gravity)612     private void setScrollViewGravity(int gravity) {
613         final FrameLayout.LayoutParams params =
614                 (FrameLayout.LayoutParams) mBiometricScrollView.getLayoutParams();
615         params.gravity = gravity;
616         mBiometricScrollView.setLayoutParams(params);
617     }
618 
619     @Override
onDetachedFromWindow()620     public void onDetachedFromWindow() {
621         super.onDetachedFromWindow();
622         mWakefulnessLifecycle.removeObserver(this);
623     }
624 
625     @Override
onStartedGoingToSleep()626     public void onStartedGoingToSleep() {
627         animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
628     }
629 
630     @Override
show(WindowManager wm, @Nullable Bundle savedState)631     public void show(WindowManager wm, @Nullable Bundle savedState) {
632         if (mBiometricView != null) {
633             mBiometricView.restoreState(savedState);
634         }
635         wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
636     }
637 
638     @Override
dismissWithoutCallback(boolean animate)639     public void dismissWithoutCallback(boolean animate) {
640         if (animate) {
641             animateAway(false /* sendReason */, 0 /* reason */);
642         } else {
643             removeWindowIfAttached();
644         }
645     }
646 
647     @Override
dismissFromSystemServer()648     public void dismissFromSystemServer() {
649         animateAway(false /* sendReason */, 0 /* reason */);
650     }
651 
652     @Override
onAuthenticationSucceeded()653     public void onAuthenticationSucceeded() {
654         mBiometricView.onAuthenticationSucceeded();
655     }
656 
657     @Override
onAuthenticationFailed(@odality int modality, String failureReason)658     public void onAuthenticationFailed(@Modality int modality, String failureReason) {
659         mBiometricView.onAuthenticationFailed(modality, failureReason);
660     }
661 
662     @Override
onHelp(@odality int modality, String help)663     public void onHelp(@Modality int modality, String help) {
664         mBiometricView.onHelp(modality, help);
665     }
666 
667     @Override
onError(@odality int modality, String error)668     public void onError(@Modality int modality, String error) {
669         mBiometricView.onError(modality, error);
670     }
671 
672     @Override
onSaveState(@onNull Bundle outState)673     public void onSaveState(@NonNull Bundle outState) {
674         outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
675         // In the case where biometric and credential are both allowed, we can assume that
676         // biometric isn't showing if credential is showing since biometric is shown first.
677         outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
678                 mBiometricView != null && mCredentialView == null);
679         outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
680 
681         if (mBiometricView != null) {
682             mBiometricView.onSaveState(outState);
683         }
684     }
685 
686     @Override
getOpPackageName()687     public String getOpPackageName() {
688         return mConfig.mOpPackageName;
689     }
690 
691     @Override
animateToCredentialUI()692     public void animateToCredentialUI() {
693         mBiometricView.startTransitionToCredentialUI();
694     }
695 
696     @VisibleForTesting
animateAway(int reason)697     void animateAway(int reason) {
698         animateAway(true /* sendReason */, reason);
699     }
700 
animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason)701     private void animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason) {
702         if (mContainerState == STATE_ANIMATING_IN) {
703             Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
704             mContainerState = STATE_PENDING_DISMISS;
705             return;
706         }
707 
708         if (mContainerState == STATE_ANIMATING_OUT) {
709             Log.w(TAG, "Already dismissing, sendReason: " + sendReason + " reason: " + reason);
710             return;
711         }
712         mContainerState = STATE_ANIMATING_OUT;
713 
714         if (sendReason) {
715             mPendingCallbackReason = reason;
716         } else {
717             mPendingCallbackReason = null;
718         }
719 
720         final Runnable endActionRunnable = () -> {
721             setVisibility(View.INVISIBLE);
722             removeWindowIfAttached();
723         };
724 
725         postOnAnimation(() -> {
726             mPanelView.animate()
727                     .translationY(mTranslationY)
728                     .setDuration(ANIMATION_DURATION_AWAY_MS)
729                     .setInterpolator(mLinearOutSlowIn)
730                     .withLayer()
731                     .withEndAction(endActionRunnable)
732                     .start();
733             mBiometricScrollView.animate()
734                     .translationY(mTranslationY)
735                     .setDuration(ANIMATION_DURATION_AWAY_MS)
736                     .setInterpolator(mLinearOutSlowIn)
737                     .withLayer()
738                     .start();
739             if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
740                 mCredentialView.animate()
741                         .translationY(mTranslationY)
742                         .setDuration(ANIMATION_DURATION_AWAY_MS)
743                         .setInterpolator(mLinearOutSlowIn)
744                         .withLayer()
745                         .start();
746             }
747             animate()
748                     .alpha(0f)
749                     .setDuration(ANIMATION_DURATION_AWAY_MS)
750                     .setInterpolator(mLinearOutSlowIn)
751                     .withLayer()
752                     .start();
753         });
754     }
755 
sendPendingCallbackIfNotNull()756     private void sendPendingCallbackIfNotNull() {
757         Log.d(TAG, "pendingCallback: " + mPendingCallbackReason);
758         if (mPendingCallbackReason != null) {
759             mConfig.mCallback.onDismissed(mPendingCallbackReason, mCredentialAttestation);
760             mPendingCallbackReason = null;
761         }
762     }
763 
removeWindowIfAttached()764     private void removeWindowIfAttached() {
765         sendPendingCallbackIfNotNull();
766 
767         if (mContainerState == STATE_GONE) {
768             return;
769         }
770         mContainerState = STATE_GONE;
771         mWindowManager.removeView(this);
772     }
773 
774     @VisibleForTesting
onDialogAnimatedIn()775     void onDialogAnimatedIn() {
776         if (mContainerState == STATE_PENDING_DISMISS) {
777             Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
778             animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
779             return;
780         }
781         mContainerState = STATE_SHOWING;
782         if (mBiometricView != null) {
783             mConfig.mCallback.onDialogAnimatedIn();
784             mBiometricView.onDialogAnimatedIn();
785         }
786     }
787 
788     @VisibleForTesting
getLayoutParams(IBinder windowToken, CharSequence title)789     static WindowManager.LayoutParams getLayoutParams(IBinder windowToken,
790             CharSequence title) {
791         final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
792                 | WindowManager.LayoutParams.FLAG_SECURE;
793         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
794                 ViewGroup.LayoutParams.MATCH_PARENT,
795                 ViewGroup.LayoutParams.MATCH_PARENT,
796                 WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
797                 windowFlags,
798                 PixelFormat.TRANSLUCENT);
799         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
800         lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~WindowInsets.Type.ime());
801         lp.setTitle("BiometricPrompt");
802         lp.accessibilityTitle = title;
803         lp.token = windowToken;
804         return lp;
805     }
806 
807     // returns [face, fingerprint] sensor ids (id is -1 if not present)
findFaceAndFingerprintSensors()808     private int[] findFaceAndFingerprintSensors() {
809         int faceSensorId = -1;
810         int fingerprintSensorId = -1;
811 
812         for (final int sensorId : mConfig.mSensorIds) {
813             if (Utils.containsSensorId(mFpProps, sensorId)) {
814                 fingerprintSensorId = sensorId;
815             } else if (Utils.containsSensorId(mFaceProps, sensorId)) {
816                 faceSensorId = sensorId;
817             }
818 
819             if (fingerprintSensorId != -1 && faceSensorId != -1) {
820                 break;
821             }
822         }
823 
824         return new int[] {faceSensorId, fingerprintSensorId};
825     }
826 }
827