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