blob: 9d1dfa7b21b97e6bf8111d1874a4938b6d8f0d00 [file] [log] [blame]
Kevin Chynfc468262019-08-20 17:17:11 -07001/*
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
Kevin Chynf8688a02019-08-27 17:04:05 -070017package com.android.systemui.biometrics;
Kevin Chynfc468262019-08-20 17:17:11 -070018
Kevin Chyn2b1d3d82019-08-27 16:38:22 -070019import android.annotation.IntDef;
Kevin Chyn9cf89912019-08-30 13:33:58 -070020import android.annotation.NonNull;
21import android.annotation.Nullable;
Kevin Chynfc468262019-08-20 17:17:11 -070022import android.content.Context;
23import android.graphics.PixelFormat;
Kevin Chyn889de4c2019-09-05 18:17:32 -070024import android.hardware.biometrics.BiometricAuthenticator;
Kevin Chyn0a45b662020-03-27 10:15:50 -070025import android.hardware.biometrics.BiometricConstants;
Kevin Chynfc468262019-08-20 17:17:11 -070026import android.os.Binder;
27import android.os.Bundle;
Kevin Chyn656a6972019-10-25 14:16:41 -070028import android.os.Handler;
Kevin Chynfc468262019-08-20 17:17:11 -070029import android.os.IBinder;
Kevin Chyn656a6972019-10-25 14:16:41 -070030import android.os.Looper;
Kevin Chynb54bdfc2020-01-17 16:07:00 -080031import android.os.UserManager;
Kevin Chynfc468262019-08-20 17:17:11 -070032import android.util.Log;
33import android.view.KeyEvent;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.view.ViewGroup;
Tiger Huang4a7835f2019-11-06 00:07:56 +080037import android.view.WindowInsets.Type;
Kevin Chynfc468262019-08-20 17:17:11 -070038import android.view.WindowManager;
39import android.view.animation.Interpolator;
Kevin Chynff168dc2019-09-16 16:04:38 -070040import android.widget.FrameLayout;
Kevin Chynfc468262019-08-20 17:17:11 -070041import android.widget.ImageView;
42import android.widget.LinearLayout;
43import android.widget.ScrollView;
44
45import com.android.internal.annotations.VisibleForTesting;
46import com.android.systemui.Dependency;
47import com.android.systemui.Interpolators;
48import com.android.systemui.R;
Kevin Chynfc468262019-08-20 17:17:11 -070049import com.android.systemui.keyguard.WakefulnessLifecycle;
50
Kevin Chyn2b1d3d82019-08-27 16:38:22 -070051import java.lang.annotation.Retention;
52import java.lang.annotation.RetentionPolicy;
53
Kevin Chynfc468262019-08-20 17:17:11 -070054/**
55 * Top level container/controller for the BiometricPrompt UI.
56 */
57public class AuthContainerView extends LinearLayout
Kevin Chynf8688a02019-08-27 17:04:05 -070058 implements AuthDialog, WakefulnessLifecycle.Observer {
Kevin Chynfc468262019-08-20 17:17:11 -070059
60 private static final String TAG = "BiometricPrompt/AuthContainerView";
61 private static final int ANIMATION_DURATION_SHOW_MS = 250;
62 private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
63
Kevin Chyn27da7182019-09-11 12:17:55 -070064 static final int STATE_UNKNOWN = 0;
65 static final int STATE_ANIMATING_IN = 1;
66 static final int STATE_PENDING_DISMISS = 2;
67 static final int STATE_SHOWING = 3;
68 static final int STATE_ANIMATING_OUT = 4;
69 static final int STATE_GONE = 5;
Kevin Chyn2b1d3d82019-08-27 16:38:22 -070070
71 @Retention(RetentionPolicy.SOURCE)
72 @IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING,
73 STATE_ANIMATING_OUT, STATE_GONE})
74 @interface ContainerState {}
75
Kevin Chyn504f77d2019-08-26 15:36:19 -070076 final Config mConfig;
Kevin Chynb54bdfc2020-01-17 16:07:00 -080077 final int mEffectiveUserId;
Kevin Chyn656a6972019-10-25 14:16:41 -070078 private final Handler mHandler;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070079 private final Injector mInjector;
Kevin Chynfc468262019-08-20 17:17:11 -070080 private final IBinder mWindowToken = new Binder();
81 private final WindowManager mWindowManager;
82 private final AuthPanelController mPanelController;
83 private final Interpolator mLinearOutSlowIn;
Kevin Chyn504f77d2019-08-26 15:36:19 -070084 @VisibleForTesting final BiometricCallback mBiometricCallback;
Kevin Chynff168dc2019-09-16 16:04:38 -070085 private final CredentialCallback mCredentialCallback;
Kevin Chynfc468262019-08-20 17:17:11 -070086
Kevin Chynff168dc2019-09-16 16:04:38 -070087 @VisibleForTesting final FrameLayout mFrameLayout;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070088 @VisibleForTesting @Nullable AuthBiometricView mBiometricView;
89 @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
Kevin Chynfc468262019-08-20 17:17:11 -070090
Kevin Chyn517d7fb2020-02-06 11:22:28 -080091 @VisibleForTesting final ImageView mBackgroundView;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070092 @VisibleForTesting final ScrollView mBiometricScrollView;
Kevin Chynfc468262019-08-20 17:17:11 -070093 private final View mPanelView;
94
95 private final float mTranslationY;
96
97 @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
98
Kevin Chyn2b1d3d82019-08-27 16:38:22 -070099 private @ContainerState int mContainerState = STATE_UNKNOWN;
Kevin Chynfc468262019-08-20 17:17:11 -0700100
Kevin Chyn27da7182019-09-11 12:17:55 -0700101 // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
102 @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
Kevin Chync8cb6852020-03-10 18:29:15 -0700103 // HAT received from LockSettingsService when credential is verified.
104 @Nullable byte[] mCredentialAttestation;
Kevin Chyn27da7182019-09-11 12:17:55 -0700105
Kevin Chyn504f77d2019-08-26 15:36:19 -0700106 static class Config {
Kevin Chynfc468262019-08-20 17:17:11 -0700107 Context mContext;
Kevin Chynf8688a02019-08-27 17:04:05 -0700108 AuthDialogCallback mCallback;
Kevin Chynfc468262019-08-20 17:17:11 -0700109 Bundle mBiometricPromptBundle;
110 boolean mRequireConfirmation;
111 int mUserId;
112 String mOpPackageName;
113 int mModalityMask;
114 boolean mSkipIntro;
Kevin Chync8cb6852020-03-10 18:29:15 -0700115 long mOperationId;
Kevin Chynfa7069d2020-05-07 13:43:39 -0700116 int mSysUiSessionId;
Kevin Chynfc468262019-08-20 17:17:11 -0700117 }
118
119 public static class Builder {
120 Config mConfig;
121
122 public Builder(Context context) {
123 mConfig = new Config();
124 mConfig.mContext = context;
125 }
126
Kevin Chynf8688a02019-08-27 17:04:05 -0700127 public Builder setCallback(AuthDialogCallback callback) {
Kevin Chynfc468262019-08-20 17:17:11 -0700128 mConfig.mCallback = callback;
129 return this;
130 }
131
132 public Builder setBiometricPromptBundle(Bundle bundle) {
133 mConfig.mBiometricPromptBundle = bundle;
134 return this;
135 }
136
137 public Builder setRequireConfirmation(boolean requireConfirmation) {
138 mConfig.mRequireConfirmation = requireConfirmation;
139 return this;
140 }
141
142 public Builder setUserId(int userId) {
143 mConfig.mUserId = userId;
144 return this;
145 }
146
147 public Builder setOpPackageName(String opPackageName) {
148 mConfig.mOpPackageName = opPackageName;
149 return this;
150 }
151
152 public Builder setSkipIntro(boolean skip) {
153 mConfig.mSkipIntro = skip;
154 return this;
155 }
156
Kevin Chync8cb6852020-03-10 18:29:15 -0700157 public Builder setOperationId(long operationId) {
158 mConfig.mOperationId = operationId;
159 return this;
160 }
161
Kevin Chynfa7069d2020-05-07 13:43:39 -0700162 public Builder setSysUiSessionId(int sysUiSessionId) {
163 mConfig.mSysUiSessionId = sysUiSessionId;
164 return this;
165 }
166
Kevin Chyn889de4c2019-09-05 18:17:32 -0700167 public AuthContainerView build(int modalityMask) {
168 mConfig.mModalityMask = modalityMask;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700169 return new AuthContainerView(mConfig, new Injector());
170 }
171 }
172
173 public static class Injector {
174 ScrollView getBiometricScrollView(FrameLayout parent) {
175 return parent.findViewById(R.id.biometric_scrollview);
176 }
177
178 FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
179 return (FrameLayout) factory.inflate(
180 R.layout.auth_container_view, root, false /* attachToRoot */);
181 }
182
Kevin Chynbac72c42020-01-21 14:30:26 -0800183 AuthPanelController getPanelController(Context context, View panelView) {
184 return new AuthPanelController(context, panelView);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700185 }
186
187 ImageView getBackgroundView(FrameLayout parent) {
188 return parent.findViewById(R.id.background);
189 }
190
191 View getPanelView(FrameLayout parent) {
192 return parent.findViewById(R.id.panel);
Kevin Chynfc468262019-08-20 17:17:11 -0700193 }
Kevin Chyn656a6972019-10-25 14:16:41 -0700194
195 int getAnimateCredentialStartDelayMs() {
196 return AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS;
197 }
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800198
199 UserManager getUserManager(Context context) {
200 return UserManager.get(context);
201 }
202
203 int getCredentialType(Context context, int effectiveUserId) {
204 return Utils.getCredentialType(context, effectiveUserId);
205 }
Kevin Chynfc468262019-08-20 17:17:11 -0700206 }
207
Kevin Chyn504f77d2019-08-26 15:36:19 -0700208 @VisibleForTesting
209 final class BiometricCallback implements AuthBiometricView.Callback {
210 @Override
211 public void onAction(int action) {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700212 Log.d(TAG, "onAction: " + action
213 + ", sysUiSessionId: " + mConfig.mSysUiSessionId
214 + ", state: " + mContainerState);
Kevin Chyn504f77d2019-08-26 15:36:19 -0700215 switch (action) {
216 case AuthBiometricView.Callback.ACTION_AUTHENTICATED:
Kevin Chynff168dc2019-09-16 16:04:38 -0700217 animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
Kevin Chyn504f77d2019-08-26 15:36:19 -0700218 break;
219 case AuthBiometricView.Callback.ACTION_USER_CANCELED:
Kevin Chyn0a45b662020-03-27 10:15:50 -0700220 sendEarlyUserCanceled();
Kevin Chynf8688a02019-08-27 17:04:05 -0700221 animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
Kevin Chyn504f77d2019-08-26 15:36:19 -0700222 break;
223 case AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE:
Kevin Chynf8688a02019-08-27 17:04:05 -0700224 animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
Kevin Chyn504f77d2019-08-26 15:36:19 -0700225 break;
226 case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
227 mConfig.mCallback.onTryAgainPressed();
228 break;
Kevin Chyn889de4c2019-09-05 18:17:32 -0700229 case AuthBiometricView.Callback.ACTION_ERROR:
230 animateAway(AuthDialogCallback.DISMISSED_ERROR);
231 break;
Kevin Chyn396a8412019-09-16 11:35:18 -0700232 case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL:
Kevin Chynff168dc2019-09-16 16:04:38 -0700233 mConfig.mCallback.onDeviceCredentialPressed();
Kevin Chyn656a6972019-10-25 14:16:41 -0700234 mHandler.postDelayed(() -> {
235 addCredentialView(false /* animatePanel */, true /* animateContents */);
236 }, mInjector.getAnimateCredentialStartDelayMs());
Kevin Chyn396a8412019-09-16 11:35:18 -0700237 break;
Kevin Chyn504f77d2019-08-26 15:36:19 -0700238 default:
239 Log.e(TAG, "Unhandled action: " + action);
240 }
Kevin Chynfc468262019-08-20 17:17:11 -0700241 }
Kevin Chyn504f77d2019-08-26 15:36:19 -0700242 }
Kevin Chynfc468262019-08-20 17:17:11 -0700243
Kevin Chynff168dc2019-09-16 16:04:38 -0700244 final class CredentialCallback implements AuthCredentialView.Callback {
245 @Override
Kevin Chync8cb6852020-03-10 18:29:15 -0700246 public void onCredentialMatched(byte[] attestation) {
247 mCredentialAttestation = attestation;
Kevin Chynff168dc2019-09-16 16:04:38 -0700248 animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
249 }
250 }
251
Kevin Chyn504f77d2019-08-26 15:36:19 -0700252 @VisibleForTesting
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700253 AuthContainerView(Config config, Injector injector) {
Kevin Chynfc468262019-08-20 17:17:11 -0700254 super(config.mContext);
255
256 mConfig = config;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700257 mInjector = injector;
258
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800259 mEffectiveUserId = mInjector.getUserManager(mContext)
260 .getCredentialOwnerProfile(mConfig.mUserId);
261
Kevin Chyn656a6972019-10-25 14:16:41 -0700262 mHandler = new Handler(Looper.getMainLooper());
Kevin Chynfc468262019-08-20 17:17:11 -0700263 mWindowManager = mContext.getSystemService(WindowManager.class);
264 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
265
266 mTranslationY = getResources()
267 .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
268 mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
Kevin Chyn504f77d2019-08-26 15:36:19 -0700269 mBiometricCallback = new BiometricCallback();
Kevin Chynff168dc2019-09-16 16:04:38 -0700270 mCredentialCallback = new CredentialCallback();
Kevin Chynfc468262019-08-20 17:17:11 -0700271
272 final LayoutInflater factory = LayoutInflater.from(mContext);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700273 mFrameLayout = mInjector.inflateContainerView(factory, this);
Kevin Chynfc468262019-08-20 17:17:11 -0700274
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700275 mPanelView = mInjector.getPanelView(mFrameLayout);
Kevin Chynbac72c42020-01-21 14:30:26 -0800276 mPanelController = mInjector.getPanelController(mContext, mPanelView);
Kevin Chynfc468262019-08-20 17:17:11 -0700277
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700278 // Inflate biometric view only if necessary.
279 if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) {
280 if (config.mModalityMask == BiometricAuthenticator.TYPE_FINGERPRINT) {
281 mBiometricView = (AuthBiometricFingerprintView)
282 factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
283 } else if (config.mModalityMask == BiometricAuthenticator.TYPE_FACE) {
284 mBiometricView = (AuthBiometricFaceView)
285 factory.inflate(R.layout.auth_biometric_face_view, null, false);
286 } else {
287 Log.e(TAG, "Unsupported biometric modality: " + config.mModalityMask);
288 mBiometricView = null;
289 mBackgroundView = null;
290 mBiometricScrollView = null;
291 return;
292 }
Kevin Chyn889de4c2019-09-05 18:17:32 -0700293 }
294
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700295 mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout);
296 mBackgroundView = mInjector.getBackgroundView(mFrameLayout);
Kevin Chyn889de4c2019-09-05 18:17:32 -0700297
Kevin Chynff168dc2019-09-16 16:04:38 -0700298 addView(mFrameLayout);
Kevin Chynfc468262019-08-20 17:17:11 -0700299
Kevin Chyn0a45b662020-03-27 10:15:50 -0700300 // TODO: De-dupe the logic with AuthCredentialPasswordView
Kevin Chynfc468262019-08-20 17:17:11 -0700301 setOnKeyListener((v, keyCode, event) -> {
302 if (keyCode != KeyEvent.KEYCODE_BACK) {
303 return false;
304 }
305 if (event.getAction() == KeyEvent.ACTION_UP) {
Kevin Chyn0a45b662020-03-27 10:15:50 -0700306 sendEarlyUserCanceled();
Kevin Chynf8688a02019-08-27 17:04:05 -0700307 animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
Kevin Chynfc468262019-08-20 17:17:11 -0700308 }
309 return true;
310 });
311
Curtis Belmontebca44542020-05-08 13:40:31 -0700312 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
Kevin Chynfc468262019-08-20 17:17:11 -0700313 setFocusableInTouchMode(true);
314 requestFocus();
315 }
316
Kevin Chyn0a45b662020-03-27 10:15:50 -0700317 void sendEarlyUserCanceled() {
318 mConfig.mCallback.onSystemEvent(
319 BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL);
320 }
321
Kevin Chyn8429da22019-09-24 12:42:35 -0700322 @Override
323 public boolean isAllowDeviceCredentials() {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700324 return Utils.isDeviceCredentialAllowed(mConfig.mBiometricPromptBundle);
Kevin Chyn8429da22019-09-24 12:42:35 -0700325 }
326
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700327 private void addBiometricView() {
328 mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
329 mBiometricView.setPanelController(mPanelController);
330 mBiometricView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
331 mBiometricView.setCallback(mBiometricCallback);
332 mBiometricView.setBackgroundView(mBackgroundView);
333 mBiometricView.setUserId(mConfig.mUserId);
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800334 mBiometricView.setEffectiveUserId(mEffectiveUserId);
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700335 mBiometricScrollView.addView(mBiometricView);
336 }
337
338 /**
339 * Adds the credential view. When going from biometric to credential view, the biometric
340 * view starts the panel expansion animation. If the credential view is being shown first,
341 * it should own the panel expansion.
342 * @param animatePanel if the credential view needs to own the panel expansion animation
343 */
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700344 private void addCredentialView(boolean animatePanel, boolean animateContents) {
Kevin Chynff168dc2019-09-16 16:04:38 -0700345 final LayoutInflater factory = LayoutInflater.from(mContext);
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800346
347 final @Utils.CredentialType int credentialType = mInjector.getCredentialType(
348 mContext, mEffectiveUserId);
349
Kevin Chyn8110ebb2019-10-02 11:16:31 -0700350 switch (credentialType) {
351 case Utils.CREDENTIAL_PATTERN:
352 mCredentialView = (AuthCredentialView) factory.inflate(
353 R.layout.auth_credential_pattern_view, null, false);
354 break;
355 case Utils.CREDENTIAL_PIN:
356 case Utils.CREDENTIAL_PASSWORD:
357 mCredentialView = (AuthCredentialView) factory.inflate(
358 R.layout.auth_credential_password_view, null, false);
359 break;
360 default:
361 throw new IllegalStateException("Unknown credential type: " + credentialType);
362 }
363
Kevin Chyn517d7fb2020-02-06 11:22:28 -0800364 // The background is used for detecting taps / cancelling authentication. Since the
365 // credential view is full-screen and should not be canceled from background taps,
366 // disable it.
367 mBackgroundView.setOnClickListener(null);
368 mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
369
Kevin Chyn8110ebb2019-10-02 11:16:31 -0700370 mCredentialView.setContainerView(this);
Kevin Chynbc3347f2020-02-20 16:45:59 -0800371 mCredentialView.setUserId(mConfig.mUserId);
Kevin Chync8cb6852020-03-10 18:29:15 -0700372 mCredentialView.setOperationId(mConfig.mOperationId);
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800373 mCredentialView.setEffectiveUserId(mEffectiveUserId);
374 mCredentialView.setCredentialType(credentialType);
Kevin Chynff168dc2019-09-16 16:04:38 -0700375 mCredentialView.setCallback(mCredentialCallback);
376 mCredentialView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700377 mCredentialView.setPanelController(mPanelController, animatePanel);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700378 mCredentialView.setShouldAnimateContents(animateContents);
Kevin Chynff168dc2019-09-16 16:04:38 -0700379 mFrameLayout.addView(mCredentialView);
380 }
381
Kevin Chynfc468262019-08-20 17:17:11 -0700382 @Override
383 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
384 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
385 mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight());
386 }
387
388 @Override
389 public void onAttachedToWindow() {
390 super.onAttachedToWindow();
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700391 onAttachedToWindowInternal();
392 }
393
394 @VisibleForTesting
395 void onAttachedToWindowInternal() {
Kevin Chynfc468262019-08-20 17:17:11 -0700396 mWakefulnessLifecycle.addObserver(this);
397
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700398 if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) {
399 addBiometricView();
400 } else if (Utils.isDeviceCredentialAllowed(mConfig.mBiometricPromptBundle)) {
401 addCredentialView(true /* animatePanel */, false /* animateContents */);
402 } else {
403 throw new IllegalStateException("Unknown configuration: "
404 + Utils.getAuthenticators(mConfig.mBiometricPromptBundle));
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700405 }
406
Kevin Chynfc468262019-08-20 17:17:11 -0700407 if (mConfig.mSkipIntro) {
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700408 mContainerState = STATE_SHOWING;
Kevin Chynfc468262019-08-20 17:17:11 -0700409 } else {
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700410 mContainerState = STATE_ANIMATING_IN;
Kevin Chynfc468262019-08-20 17:17:11 -0700411 // The background panel and content are different views since we need to be able to
412 // animate them separately in other places.
413 mPanelView.setY(mTranslationY);
Kevin Chynff168dc2019-09-16 16:04:38 -0700414 mBiometricScrollView.setY(mTranslationY);
Kevin Chynfc468262019-08-20 17:17:11 -0700415
416 setAlpha(0f);
417 postOnAnimation(() -> {
418 mPanelView.animate()
419 .translationY(0)
420 .setDuration(ANIMATION_DURATION_SHOW_MS)
421 .setInterpolator(mLinearOutSlowIn)
422 .withLayer()
423 .withEndAction(this::onDialogAnimatedIn)
424 .start();
Kevin Chynff168dc2019-09-16 16:04:38 -0700425 mBiometricScrollView.animate()
Kevin Chynfc468262019-08-20 17:17:11 -0700426 .translationY(0)
427 .setDuration(ANIMATION_DURATION_SHOW_MS)
428 .setInterpolator(mLinearOutSlowIn)
429 .withLayer()
430 .start();
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700431 if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
432 mCredentialView.setY(mTranslationY);
433 mCredentialView.animate()
434 .translationY(0)
435 .setDuration(ANIMATION_DURATION_SHOW_MS)
436 .setInterpolator(mLinearOutSlowIn)
437 .withLayer()
438 .start();
439 }
Kevin Chynfc468262019-08-20 17:17:11 -0700440 animate()
441 .alpha(1f)
442 .setDuration(ANIMATION_DURATION_SHOW_MS)
443 .setInterpolator(mLinearOutSlowIn)
444 .withLayer()
445 .start();
446 });
447 }
448 }
449
450 @Override
451 public void onDetachedFromWindow() {
452 super.onDetachedFromWindow();
453 mWakefulnessLifecycle.removeObserver(this);
454 }
455
456 @Override
457 public void onStartedGoingToSleep() {
Kevin Chynf8688a02019-08-27 17:04:05 -0700458 animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
Kevin Chynfc468262019-08-20 17:17:11 -0700459 }
460
461 @Override
Kevin Chyn9cf89912019-08-30 13:33:58 -0700462 public void show(WindowManager wm, @Nullable Bundle savedState) {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700463 if (mBiometricView != null) {
464 mBiometricView.restoreState(savedState);
465 }
Kevin Chynfc468262019-08-20 17:17:11 -0700466 wm.addView(this, getLayoutParams(mWindowToken));
467 }
468
469 @Override
470 public void dismissWithoutCallback(boolean animate) {
471 if (animate) {
472 animateAway(false /* sendReason */, 0 /* reason */);
473 } else {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700474 removeWindowIfAttached(false /* sendReason */);
Kevin Chynfc468262019-08-20 17:17:11 -0700475 }
476 }
477
478 @Override
479 public void dismissFromSystemServer() {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700480 removeWindowIfAttached(true /* sendReason */);
Kevin Chynfc468262019-08-20 17:17:11 -0700481 }
482
483 @Override
484 public void onAuthenticationSucceeded() {
485 mBiometricView.onAuthenticationSucceeded();
486 }
487
488 @Override
489 public void onAuthenticationFailed(String failureReason) {
Kevin Chyn504f77d2019-08-26 15:36:19 -0700490 mBiometricView.onAuthenticationFailed(failureReason);
Kevin Chynfc468262019-08-20 17:17:11 -0700491 }
492
493 @Override
494 public void onHelp(String help) {
Kevin Chyn504f77d2019-08-26 15:36:19 -0700495 mBiometricView.onHelp(help);
Kevin Chynfc468262019-08-20 17:17:11 -0700496 }
497
498 @Override
499 public void onError(String error) {
Kevin Chyn889de4c2019-09-05 18:17:32 -0700500 mBiometricView.onError(error);
Kevin Chynfc468262019-08-20 17:17:11 -0700501 }
502
503 @Override
Kevin Chyn9cf89912019-08-30 13:33:58 -0700504 public void onSaveState(@NonNull Bundle outState) {
Kevin Chyn27da7182019-09-11 12:17:55 -0700505 outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700506 // In the case where biometric and credential are both allowed, we can assume that
507 // biometric isn't showing if credential is showing since biometric is shown first.
508 outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
509 mBiometricView != null && mCredentialView == null);
510 outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700511
512 if (mBiometricView != null) {
513 mBiometricView.onSaveState(outState);
514 }
Kevin Chynfc468262019-08-20 17:17:11 -0700515 }
516
517 @Override
518 public String getOpPackageName() {
519 return mConfig.mOpPackageName;
520 }
521
Kevin Chyn8429da22019-09-24 12:42:35 -0700522 @Override
523 public void animateToCredentialUI() {
524 mBiometricView.startTransitionToCredentialUI();
525 }
526
Kevin Chyn504f77d2019-08-26 15:36:19 -0700527 @VisibleForTesting
528 void animateAway(int reason) {
Kevin Chynfc468262019-08-20 17:17:11 -0700529 animateAway(true /* sendReason */, reason);
530 }
531
Kevin Chynf8688a02019-08-27 17:04:05 -0700532 private void animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason) {
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700533 if (mContainerState == STATE_ANIMATING_IN) {
Kevin Chynfc468262019-08-20 17:17:11 -0700534 Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700535 mContainerState = STATE_PENDING_DISMISS;
Kevin Chynfc468262019-08-20 17:17:11 -0700536 return;
537 }
538
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700539 if (mContainerState == STATE_ANIMATING_OUT) {
540 Log.w(TAG, "Already dismissing, sendReason: " + sendReason + " reason: " + reason);
541 return;
542 }
543 mContainerState = STATE_ANIMATING_OUT;
544
Kevin Chyn27da7182019-09-11 12:17:55 -0700545 if (sendReason) {
546 mPendingCallbackReason = reason;
547 } else {
548 mPendingCallbackReason = null;
549 }
550
Kevin Chynfc468262019-08-20 17:17:11 -0700551 final Runnable endActionRunnable = () -> {
552 setVisibility(View.INVISIBLE);
Kevin Chynfa7069d2020-05-07 13:43:39 -0700553 removeWindowIfAttached(true /* sendReason */);
Kevin Chynfc468262019-08-20 17:17:11 -0700554 };
555
556 postOnAnimation(() -> {
557 mPanelView.animate()
558 .translationY(mTranslationY)
559 .setDuration(ANIMATION_DURATION_AWAY_MS)
560 .setInterpolator(mLinearOutSlowIn)
561 .withLayer()
562 .withEndAction(endActionRunnable)
563 .start();
Kevin Chynff168dc2019-09-16 16:04:38 -0700564 mBiometricScrollView.animate()
Kevin Chynfc468262019-08-20 17:17:11 -0700565 .translationY(mTranslationY)
566 .setDuration(ANIMATION_DURATION_AWAY_MS)
567 .setInterpolator(mLinearOutSlowIn)
568 .withLayer()
569 .start();
Kevin Chynff168dc2019-09-16 16:04:38 -0700570 if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
571 mCredentialView.animate()
572 .translationY(mTranslationY)
573 .setDuration(ANIMATION_DURATION_AWAY_MS)
574 .setInterpolator(mLinearOutSlowIn)
575 .withLayer()
576 .start();
577 }
Kevin Chynfc468262019-08-20 17:17:11 -0700578 animate()
579 .alpha(0f)
580 .setDuration(ANIMATION_DURATION_AWAY_MS)
581 .setInterpolator(mLinearOutSlowIn)
582 .withLayer()
583 .start();
584 });
585 }
586
Kevin Chyn27da7182019-09-11 12:17:55 -0700587 private void sendPendingCallbackIfNotNull() {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700588 Log.d(TAG, "pendingCallback: " + mPendingCallbackReason
589 + " sysUISessionId: " + mConfig.mSysUiSessionId);
Kevin Chyn27da7182019-09-11 12:17:55 -0700590 if (mPendingCallbackReason != null) {
Kevin Chync8cb6852020-03-10 18:29:15 -0700591 mConfig.mCallback.onDismissed(mPendingCallbackReason, mCredentialAttestation);
Kevin Chyn27da7182019-09-11 12:17:55 -0700592 mPendingCallbackReason = null;
593 }
594 }
595
Kevin Chynfa7069d2020-05-07 13:43:39 -0700596 private void removeWindowIfAttached(boolean sendReason) {
597 if (sendReason) {
598 sendPendingCallbackIfNotNull();
599 }
Kevin Chyn27da7182019-09-11 12:17:55 -0700600
Kevin Chyn889de4c2019-09-05 18:17:32 -0700601 if (mContainerState == STATE_GONE) {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700602 Log.w(TAG, "Container already STATE_GONE, mSysUiSessionId: " + mConfig.mSysUiSessionId);
Kevin Chyn889de4c2019-09-05 18:17:32 -0700603 return;
604 }
Kevin Chynfa7069d2020-05-07 13:43:39 -0700605 Log.d(TAG, "Removing container, mSysUiSessionId: " + mConfig.mSysUiSessionId);
Kevin Chyn889de4c2019-09-05 18:17:32 -0700606 mContainerState = STATE_GONE;
607 mWindowManager.removeView(this);
608 }
609
Kevin Chynfc468262019-08-20 17:17:11 -0700610 private void onDialogAnimatedIn() {
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700611 if (mContainerState == STATE_PENDING_DISMISS) {
Kevin Chynfc468262019-08-20 17:17:11 -0700612 Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
613 animateAway(false /* sendReason */, 0);
Kevin Chynfc468262019-08-20 17:17:11 -0700614 return;
615 }
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700616 mContainerState = STATE_SHOWING;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700617 if (mBiometricView != null) {
618 mBiometricView.onDialogAnimatedIn();
619 }
Kevin Chynfc468262019-08-20 17:17:11 -0700620 }
621
622 /**
623 * @param windowToken token for the window
624 * @return
625 */
626 public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
Curtis Belmonte23b8d492020-02-04 14:24:39 -0800627 final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
628 | WindowManager.LayoutParams.FLAG_SECURE;
Kevin Chynfc468262019-08-20 17:17:11 -0700629 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
630 ViewGroup.LayoutParams.MATCH_PARENT,
631 ViewGroup.LayoutParams.MATCH_PARENT,
Heemin Seog2cf45dd2020-02-24 15:43:29 -0800632 WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
Curtis Belmonte23b8d492020-02-04 14:24:39 -0800633 windowFlags,
Kevin Chynfc468262019-08-20 17:17:11 -0700634 PixelFormat.TRANSLUCENT);
Roshan Piusa3f89c62019-10-11 08:50:53 -0700635 lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
Kevin Chynfc468262019-08-20 17:17:11 -0700636 lp.setTitle("BiometricPrompt");
637 lp.token = windowToken;
Tiger Huang52724442020-01-20 21:38:42 +0800638 lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~Type.statusBars());
Kevin Chynfc468262019-08-20 17:17:11 -0700639 return lp;
640 }
641}