blob: 1dd6409b85828e5de61e0cad544ca0e778781698 [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;
37import android.view.WindowManager;
38import android.view.animation.Interpolator;
Kevin Chynff168dc2019-09-16 16:04:38 -070039import android.widget.FrameLayout;
Kevin Chynfc468262019-08-20 17:17:11 -070040import android.widget.ImageView;
41import android.widget.LinearLayout;
42import android.widget.ScrollView;
43
44import com.android.internal.annotations.VisibleForTesting;
45import com.android.systemui.Dependency;
46import com.android.systemui.Interpolators;
47import com.android.systemui.R;
Kevin Chynfc468262019-08-20 17:17:11 -070048import com.android.systemui.keyguard.WakefulnessLifecycle;
49
Kevin Chyn2b1d3d82019-08-27 16:38:22 -070050import java.lang.annotation.Retention;
51import java.lang.annotation.RetentionPolicy;
52
Kevin Chynfc468262019-08-20 17:17:11 -070053/**
54 * Top level container/controller for the BiometricPrompt UI.
55 */
56public class AuthContainerView extends LinearLayout
Kevin Chynf8688a02019-08-27 17:04:05 -070057 implements AuthDialog, WakefulnessLifecycle.Observer {
Kevin Chynfc468262019-08-20 17:17:11 -070058
59 private static final String TAG = "BiometricPrompt/AuthContainerView";
60 private static final int ANIMATION_DURATION_SHOW_MS = 250;
61 private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
62
Kevin Chyn27da7182019-09-11 12:17:55 -070063 static final int STATE_UNKNOWN = 0;
64 static final int STATE_ANIMATING_IN = 1;
65 static final int STATE_PENDING_DISMISS = 2;
66 static final int STATE_SHOWING = 3;
67 static final int STATE_ANIMATING_OUT = 4;
68 static final int STATE_GONE = 5;
Kevin Chyn2b1d3d82019-08-27 16:38:22 -070069
70 @Retention(RetentionPolicy.SOURCE)
71 @IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING,
72 STATE_ANIMATING_OUT, STATE_GONE})
73 @interface ContainerState {}
74
Kevin Chyn504f77d2019-08-26 15:36:19 -070075 final Config mConfig;
Kevin Chynb54bdfc2020-01-17 16:07:00 -080076 final int mEffectiveUserId;
Kevin Chyn656a6972019-10-25 14:16:41 -070077 private final Handler mHandler;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070078 private final Injector mInjector;
Kevin Chynfc468262019-08-20 17:17:11 -070079 private final IBinder mWindowToken = new Binder();
80 private final WindowManager mWindowManager;
81 private final AuthPanelController mPanelController;
82 private final Interpolator mLinearOutSlowIn;
Kevin Chyn504f77d2019-08-26 15:36:19 -070083 @VisibleForTesting final BiometricCallback mBiometricCallback;
Kevin Chynff168dc2019-09-16 16:04:38 -070084 private final CredentialCallback mCredentialCallback;
Kevin Chynfc468262019-08-20 17:17:11 -070085
Kevin Chynff168dc2019-09-16 16:04:38 -070086 @VisibleForTesting final FrameLayout mFrameLayout;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070087 @VisibleForTesting @Nullable AuthBiometricView mBiometricView;
88 @VisibleForTesting @Nullable AuthCredentialView mCredentialView;
Kevin Chynfc468262019-08-20 17:17:11 -070089
Kevin Chyn517d7fb2020-02-06 11:22:28 -080090 @VisibleForTesting final ImageView mBackgroundView;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -070091 @VisibleForTesting final ScrollView mBiometricScrollView;
Kevin Chynfc468262019-08-20 17:17:11 -070092 private final View mPanelView;
93
94 private final float mTranslationY;
95
96 @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
97
Kevin Chyn2b1d3d82019-08-27 16:38:22 -070098 private @ContainerState int mContainerState = STATE_UNKNOWN;
Kevin Chynfc468262019-08-20 17:17:11 -070099
Kevin Chyn27da7182019-09-11 12:17:55 -0700100 // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet.
101 @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason;
Kevin Chync8cb6852020-03-10 18:29:15 -0700102 // HAT received from LockSettingsService when credential is verified.
103 @Nullable byte[] mCredentialAttestation;
Kevin Chyn27da7182019-09-11 12:17:55 -0700104
Kevin Chyn504f77d2019-08-26 15:36:19 -0700105 static class Config {
Kevin Chynfc468262019-08-20 17:17:11 -0700106 Context mContext;
Kevin Chynf8688a02019-08-27 17:04:05 -0700107 AuthDialogCallback mCallback;
Kevin Chynfc468262019-08-20 17:17:11 -0700108 Bundle mBiometricPromptBundle;
109 boolean mRequireConfirmation;
110 int mUserId;
111 String mOpPackageName;
112 int mModalityMask;
113 boolean mSkipIntro;
Kevin Chync8cb6852020-03-10 18:29:15 -0700114 long mOperationId;
Kevin Chynfa7069d2020-05-07 13:43:39 -0700115 int mSysUiSessionId;
Kevin Chynfc468262019-08-20 17:17:11 -0700116 }
117
118 public static class Builder {
119 Config mConfig;
120
121 public Builder(Context context) {
122 mConfig = new Config();
123 mConfig.mContext = context;
124 }
125
Kevin Chynf8688a02019-08-27 17:04:05 -0700126 public Builder setCallback(AuthDialogCallback callback) {
Kevin Chynfc468262019-08-20 17:17:11 -0700127 mConfig.mCallback = callback;
128 return this;
129 }
130
131 public Builder setBiometricPromptBundle(Bundle bundle) {
132 mConfig.mBiometricPromptBundle = bundle;
133 return this;
134 }
135
136 public Builder setRequireConfirmation(boolean requireConfirmation) {
137 mConfig.mRequireConfirmation = requireConfirmation;
138 return this;
139 }
140
141 public Builder setUserId(int userId) {
142 mConfig.mUserId = userId;
143 return this;
144 }
145
146 public Builder setOpPackageName(String opPackageName) {
147 mConfig.mOpPackageName = opPackageName;
148 return this;
149 }
150
151 public Builder setSkipIntro(boolean skip) {
152 mConfig.mSkipIntro = skip;
153 return this;
154 }
155
Kevin Chync8cb6852020-03-10 18:29:15 -0700156 public Builder setOperationId(long operationId) {
157 mConfig.mOperationId = operationId;
158 return this;
159 }
160
Kevin Chynfa7069d2020-05-07 13:43:39 -0700161 public Builder setSysUiSessionId(int sysUiSessionId) {
162 mConfig.mSysUiSessionId = sysUiSessionId;
163 return this;
164 }
165
Kevin Chyn889de4c2019-09-05 18:17:32 -0700166 public AuthContainerView build(int modalityMask) {
167 mConfig.mModalityMask = modalityMask;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700168 return new AuthContainerView(mConfig, new Injector());
169 }
170 }
171
172 public static class Injector {
173 ScrollView getBiometricScrollView(FrameLayout parent) {
174 return parent.findViewById(R.id.biometric_scrollview);
175 }
176
177 FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) {
178 return (FrameLayout) factory.inflate(
179 R.layout.auth_container_view, root, false /* attachToRoot */);
180 }
181
Kevin Chynbac72c42020-01-21 14:30:26 -0800182 AuthPanelController getPanelController(Context context, View panelView) {
183 return new AuthPanelController(context, panelView);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700184 }
185
186 ImageView getBackgroundView(FrameLayout parent) {
187 return parent.findViewById(R.id.background);
188 }
189
190 View getPanelView(FrameLayout parent) {
191 return parent.findViewById(R.id.panel);
Kevin Chynfc468262019-08-20 17:17:11 -0700192 }
Kevin Chyn656a6972019-10-25 14:16:41 -0700193
194 int getAnimateCredentialStartDelayMs() {
195 return AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS;
196 }
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800197
198 UserManager getUserManager(Context context) {
199 return UserManager.get(context);
200 }
201
202 int getCredentialType(Context context, int effectiveUserId) {
203 return Utils.getCredentialType(context, effectiveUserId);
204 }
Kevin Chynfc468262019-08-20 17:17:11 -0700205 }
206
Kevin Chyn504f77d2019-08-26 15:36:19 -0700207 @VisibleForTesting
208 final class BiometricCallback implements AuthBiometricView.Callback {
209 @Override
210 public void onAction(int action) {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700211 Log.d(TAG, "onAction: " + action
212 + ", sysUiSessionId: " + mConfig.mSysUiSessionId
213 + ", state: " + mContainerState);
Kevin Chyn504f77d2019-08-26 15:36:19 -0700214 switch (action) {
215 case AuthBiometricView.Callback.ACTION_AUTHENTICATED:
Kevin Chynff168dc2019-09-16 16:04:38 -0700216 animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
Kevin Chyn504f77d2019-08-26 15:36:19 -0700217 break;
218 case AuthBiometricView.Callback.ACTION_USER_CANCELED:
Kevin Chyn0a45b662020-03-27 10:15:50 -0700219 sendEarlyUserCanceled();
Kevin Chynf8688a02019-08-27 17:04:05 -0700220 animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
Kevin Chyn504f77d2019-08-26 15:36:19 -0700221 break;
222 case AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE:
Kevin Chynf8688a02019-08-27 17:04:05 -0700223 animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
Kevin Chyn504f77d2019-08-26 15:36:19 -0700224 break;
225 case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
226 mConfig.mCallback.onTryAgainPressed();
227 break;
Kevin Chyn889de4c2019-09-05 18:17:32 -0700228 case AuthBiometricView.Callback.ACTION_ERROR:
229 animateAway(AuthDialogCallback.DISMISSED_ERROR);
230 break;
Kevin Chyn396a8412019-09-16 11:35:18 -0700231 case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL:
Kevin Chynff168dc2019-09-16 16:04:38 -0700232 mConfig.mCallback.onDeviceCredentialPressed();
Kevin Chyn656a6972019-10-25 14:16:41 -0700233 mHandler.postDelayed(() -> {
234 addCredentialView(false /* animatePanel */, true /* animateContents */);
235 }, mInjector.getAnimateCredentialStartDelayMs());
Kevin Chyn396a8412019-09-16 11:35:18 -0700236 break;
Kevin Chyn504f77d2019-08-26 15:36:19 -0700237 default:
238 Log.e(TAG, "Unhandled action: " + action);
239 }
Kevin Chynfc468262019-08-20 17:17:11 -0700240 }
Kevin Chyn504f77d2019-08-26 15:36:19 -0700241 }
Kevin Chynfc468262019-08-20 17:17:11 -0700242
Kevin Chynff168dc2019-09-16 16:04:38 -0700243 final class CredentialCallback implements AuthCredentialView.Callback {
244 @Override
Kevin Chync8cb6852020-03-10 18:29:15 -0700245 public void onCredentialMatched(byte[] attestation) {
246 mCredentialAttestation = attestation;
Kevin Chynff168dc2019-09-16 16:04:38 -0700247 animateAway(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED);
248 }
249 }
250
Kevin Chyn504f77d2019-08-26 15:36:19 -0700251 @VisibleForTesting
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700252 AuthContainerView(Config config, Injector injector) {
Kevin Chynfc468262019-08-20 17:17:11 -0700253 super(config.mContext);
254
255 mConfig = config;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700256 mInjector = injector;
257
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800258 mEffectiveUserId = mInjector.getUserManager(mContext)
259 .getCredentialOwnerProfile(mConfig.mUserId);
260
Kevin Chyn656a6972019-10-25 14:16:41 -0700261 mHandler = new Handler(Looper.getMainLooper());
Kevin Chynfc468262019-08-20 17:17:11 -0700262 mWindowManager = mContext.getSystemService(WindowManager.class);
263 mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
264
265 mTranslationY = getResources()
266 .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
267 mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
Kevin Chyn504f77d2019-08-26 15:36:19 -0700268 mBiometricCallback = new BiometricCallback();
Kevin Chynff168dc2019-09-16 16:04:38 -0700269 mCredentialCallback = new CredentialCallback();
Kevin Chynfc468262019-08-20 17:17:11 -0700270
271 final LayoutInflater factory = LayoutInflater.from(mContext);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700272 mFrameLayout = mInjector.inflateContainerView(factory, this);
Kevin Chynfc468262019-08-20 17:17:11 -0700273
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700274 mPanelView = mInjector.getPanelView(mFrameLayout);
Kevin Chynbac72c42020-01-21 14:30:26 -0800275 mPanelController = mInjector.getPanelController(mContext, mPanelView);
Kevin Chynfc468262019-08-20 17:17:11 -0700276
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700277 // Inflate biometric view only if necessary.
278 if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) {
279 if (config.mModalityMask == BiometricAuthenticator.TYPE_FINGERPRINT) {
280 mBiometricView = (AuthBiometricFingerprintView)
281 factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false);
282 } else if (config.mModalityMask == BiometricAuthenticator.TYPE_FACE) {
283 mBiometricView = (AuthBiometricFaceView)
284 factory.inflate(R.layout.auth_biometric_face_view, null, false);
285 } else {
286 Log.e(TAG, "Unsupported biometric modality: " + config.mModalityMask);
287 mBiometricView = null;
288 mBackgroundView = null;
289 mBiometricScrollView = null;
290 return;
291 }
Kevin Chyn889de4c2019-09-05 18:17:32 -0700292 }
293
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700294 mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout);
295 mBackgroundView = mInjector.getBackgroundView(mFrameLayout);
Kevin Chyn889de4c2019-09-05 18:17:32 -0700296
Kevin Chynff168dc2019-09-16 16:04:38 -0700297 addView(mFrameLayout);
Kevin Chynfc468262019-08-20 17:17:11 -0700298
Kevin Chyn0a45b662020-03-27 10:15:50 -0700299 // TODO: De-dupe the logic with AuthCredentialPasswordView
Kevin Chynfc468262019-08-20 17:17:11 -0700300 setOnKeyListener((v, keyCode, event) -> {
301 if (keyCode != KeyEvent.KEYCODE_BACK) {
302 return false;
303 }
304 if (event.getAction() == KeyEvent.ACTION_UP) {
Kevin Chyn0a45b662020-03-27 10:15:50 -0700305 sendEarlyUserCanceled();
Kevin Chynf8688a02019-08-27 17:04:05 -0700306 animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
Kevin Chynfc468262019-08-20 17:17:11 -0700307 }
308 return true;
309 });
310
Curtis Belmontebca44542020-05-08 13:40:31 -0700311 setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
Kevin Chynfc468262019-08-20 17:17:11 -0700312 setFocusableInTouchMode(true);
313 requestFocus();
314 }
315
Kevin Chyn0a45b662020-03-27 10:15:50 -0700316 void sendEarlyUserCanceled() {
317 mConfig.mCallback.onSystemEvent(
318 BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL);
319 }
320
Kevin Chyn8429da22019-09-24 12:42:35 -0700321 @Override
322 public boolean isAllowDeviceCredentials() {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700323 return Utils.isDeviceCredentialAllowed(mConfig.mBiometricPromptBundle);
Kevin Chyn8429da22019-09-24 12:42:35 -0700324 }
325
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700326 private void addBiometricView() {
327 mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
328 mBiometricView.setPanelController(mPanelController);
329 mBiometricView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
330 mBiometricView.setCallback(mBiometricCallback);
331 mBiometricView.setBackgroundView(mBackgroundView);
332 mBiometricView.setUserId(mConfig.mUserId);
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800333 mBiometricView.setEffectiveUserId(mEffectiveUserId);
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700334 mBiometricScrollView.addView(mBiometricView);
335 }
336
337 /**
338 * Adds the credential view. When going from biometric to credential view, the biometric
339 * view starts the panel expansion animation. If the credential view is being shown first,
340 * it should own the panel expansion.
341 * @param animatePanel if the credential view needs to own the panel expansion animation
342 */
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700343 private void addCredentialView(boolean animatePanel, boolean animateContents) {
Kevin Chynff168dc2019-09-16 16:04:38 -0700344 final LayoutInflater factory = LayoutInflater.from(mContext);
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800345
346 final @Utils.CredentialType int credentialType = mInjector.getCredentialType(
347 mContext, mEffectiveUserId);
348
Kevin Chyn8110ebb2019-10-02 11:16:31 -0700349 switch (credentialType) {
350 case Utils.CREDENTIAL_PATTERN:
351 mCredentialView = (AuthCredentialView) factory.inflate(
352 R.layout.auth_credential_pattern_view, null, false);
353 break;
354 case Utils.CREDENTIAL_PIN:
355 case Utils.CREDENTIAL_PASSWORD:
356 mCredentialView = (AuthCredentialView) factory.inflate(
357 R.layout.auth_credential_password_view, null, false);
358 break;
359 default:
360 throw new IllegalStateException("Unknown credential type: " + credentialType);
361 }
362
Kevin Chyn517d7fb2020-02-06 11:22:28 -0800363 // The background is used for detecting taps / cancelling authentication. Since the
364 // credential view is full-screen and should not be canceled from background taps,
365 // disable it.
366 mBackgroundView.setOnClickListener(null);
367 mBackgroundView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
368
Kevin Chyn8110ebb2019-10-02 11:16:31 -0700369 mCredentialView.setContainerView(this);
Kevin Chynbc3347f2020-02-20 16:45:59 -0800370 mCredentialView.setUserId(mConfig.mUserId);
Kevin Chync8cb6852020-03-10 18:29:15 -0700371 mCredentialView.setOperationId(mConfig.mOperationId);
Kevin Chynb54bdfc2020-01-17 16:07:00 -0800372 mCredentialView.setEffectiveUserId(mEffectiveUserId);
373 mCredentialView.setCredentialType(credentialType);
Kevin Chynff168dc2019-09-16 16:04:38 -0700374 mCredentialView.setCallback(mCredentialCallback);
375 mCredentialView.setBiometricPromptBundle(mConfig.mBiometricPromptBundle);
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700376 mCredentialView.setPanelController(mPanelController, animatePanel);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700377 mCredentialView.setShouldAnimateContents(animateContents);
Kevin Chynff168dc2019-09-16 16:04:38 -0700378 mFrameLayout.addView(mCredentialView);
379 }
380
Kevin Chynfc468262019-08-20 17:17:11 -0700381 @Override
382 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
383 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
384 mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight());
385 }
386
387 @Override
388 public void onAttachedToWindow() {
389 super.onAttachedToWindow();
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700390 onAttachedToWindowInternal();
391 }
392
393 @VisibleForTesting
394 void onAttachedToWindowInternal() {
Kevin Chynfc468262019-08-20 17:17:11 -0700395 mWakefulnessLifecycle.addObserver(this);
396
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700397 if (Utils.isBiometricAllowed(mConfig.mBiometricPromptBundle)) {
398 addBiometricView();
399 } else if (Utils.isDeviceCredentialAllowed(mConfig.mBiometricPromptBundle)) {
400 addCredentialView(true /* animatePanel */, false /* animateContents */);
401 } else {
402 throw new IllegalStateException("Unknown configuration: "
403 + Utils.getAuthenticators(mConfig.mBiometricPromptBundle));
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700404 }
405
Kevin Chynfc468262019-08-20 17:17:11 -0700406 if (mConfig.mSkipIntro) {
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700407 mContainerState = STATE_SHOWING;
Kevin Chynfc468262019-08-20 17:17:11 -0700408 } else {
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700409 mContainerState = STATE_ANIMATING_IN;
Kevin Chynfc468262019-08-20 17:17:11 -0700410 // The background panel and content are different views since we need to be able to
411 // animate them separately in other places.
412 mPanelView.setY(mTranslationY);
Kevin Chynff168dc2019-09-16 16:04:38 -0700413 mBiometricScrollView.setY(mTranslationY);
Kevin Chynfc468262019-08-20 17:17:11 -0700414
415 setAlpha(0f);
416 postOnAnimation(() -> {
417 mPanelView.animate()
418 .translationY(0)
419 .setDuration(ANIMATION_DURATION_SHOW_MS)
420 .setInterpolator(mLinearOutSlowIn)
421 .withLayer()
422 .withEndAction(this::onDialogAnimatedIn)
423 .start();
Kevin Chynff168dc2019-09-16 16:04:38 -0700424 mBiometricScrollView.animate()
Kevin Chynfc468262019-08-20 17:17:11 -0700425 .translationY(0)
426 .setDuration(ANIMATION_DURATION_SHOW_MS)
427 .setInterpolator(mLinearOutSlowIn)
428 .withLayer()
429 .start();
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700430 if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
431 mCredentialView.setY(mTranslationY);
432 mCredentialView.animate()
433 .translationY(0)
434 .setDuration(ANIMATION_DURATION_SHOW_MS)
435 .setInterpolator(mLinearOutSlowIn)
436 .withLayer()
437 .start();
438 }
Kevin Chynfc468262019-08-20 17:17:11 -0700439 animate()
440 .alpha(1f)
441 .setDuration(ANIMATION_DURATION_SHOW_MS)
442 .setInterpolator(mLinearOutSlowIn)
443 .withLayer()
444 .start();
445 });
446 }
447 }
448
449 @Override
450 public void onDetachedFromWindow() {
451 super.onDetachedFromWindow();
452 mWakefulnessLifecycle.removeObserver(this);
453 }
454
455 @Override
456 public void onStartedGoingToSleep() {
Kevin Chynf8688a02019-08-27 17:04:05 -0700457 animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
Kevin Chynfc468262019-08-20 17:17:11 -0700458 }
459
460 @Override
Kevin Chyn9cf89912019-08-30 13:33:58 -0700461 public void show(WindowManager wm, @Nullable Bundle savedState) {
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700462 if (mBiometricView != null) {
463 mBiometricView.restoreState(savedState);
464 }
Kevin Chynfc468262019-08-20 17:17:11 -0700465 wm.addView(this, getLayoutParams(mWindowToken));
466 }
467
468 @Override
469 public void dismissWithoutCallback(boolean animate) {
470 if (animate) {
471 animateAway(false /* sendReason */, 0 /* reason */);
472 } else {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700473 removeWindowIfAttached(false /* sendReason */);
Kevin Chynfc468262019-08-20 17:17:11 -0700474 }
475 }
476
477 @Override
478 public void dismissFromSystemServer() {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700479 removeWindowIfAttached(true /* sendReason */);
Kevin Chynfc468262019-08-20 17:17:11 -0700480 }
481
482 @Override
483 public void onAuthenticationSucceeded() {
484 mBiometricView.onAuthenticationSucceeded();
485 }
486
487 @Override
488 public void onAuthenticationFailed(String failureReason) {
Kevin Chyn504f77d2019-08-26 15:36:19 -0700489 mBiometricView.onAuthenticationFailed(failureReason);
Kevin Chynfc468262019-08-20 17:17:11 -0700490 }
491
492 @Override
493 public void onHelp(String help) {
Kevin Chyn504f77d2019-08-26 15:36:19 -0700494 mBiometricView.onHelp(help);
Kevin Chynfc468262019-08-20 17:17:11 -0700495 }
496
497 @Override
498 public void onError(String error) {
Kevin Chyn889de4c2019-09-05 18:17:32 -0700499 mBiometricView.onError(error);
Kevin Chynfc468262019-08-20 17:17:11 -0700500 }
501
502 @Override
Kevin Chyn9cf89912019-08-30 13:33:58 -0700503 public void onSaveState(@NonNull Bundle outState) {
Kevin Chyn27da7182019-09-11 12:17:55 -0700504 outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState);
Kevin Chyn8cbb4882019-09-19 16:49:02 -0700505 // In the case where biometric and credential are both allowed, we can assume that
506 // biometric isn't showing if credential is showing since biometric is shown first.
507 outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
508 mBiometricView != null && mCredentialView == null);
509 outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700510
511 if (mBiometricView != null) {
512 mBiometricView.onSaveState(outState);
513 }
Kevin Chynfc468262019-08-20 17:17:11 -0700514 }
515
516 @Override
517 public String getOpPackageName() {
518 return mConfig.mOpPackageName;
519 }
520
Kevin Chyn8429da22019-09-24 12:42:35 -0700521 @Override
522 public void animateToCredentialUI() {
523 mBiometricView.startTransitionToCredentialUI();
524 }
525
Kevin Chyn504f77d2019-08-26 15:36:19 -0700526 @VisibleForTesting
527 void animateAway(int reason) {
Kevin Chynfc468262019-08-20 17:17:11 -0700528 animateAway(true /* sendReason */, reason);
529 }
530
Kevin Chynf8688a02019-08-27 17:04:05 -0700531 private void animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason) {
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700532 if (mContainerState == STATE_ANIMATING_IN) {
Kevin Chynfc468262019-08-20 17:17:11 -0700533 Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700534 mContainerState = STATE_PENDING_DISMISS;
Kevin Chynfc468262019-08-20 17:17:11 -0700535 return;
536 }
537
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700538 if (mContainerState == STATE_ANIMATING_OUT) {
539 Log.w(TAG, "Already dismissing, sendReason: " + sendReason + " reason: " + reason);
540 return;
541 }
542 mContainerState = STATE_ANIMATING_OUT;
543
Kevin Chyn27da7182019-09-11 12:17:55 -0700544 if (sendReason) {
545 mPendingCallbackReason = reason;
546 } else {
547 mPendingCallbackReason = null;
548 }
549
Kevin Chynfc468262019-08-20 17:17:11 -0700550 final Runnable endActionRunnable = () -> {
551 setVisibility(View.INVISIBLE);
Kevin Chynfa7069d2020-05-07 13:43:39 -0700552 removeWindowIfAttached(true /* sendReason */);
Kevin Chynfc468262019-08-20 17:17:11 -0700553 };
554
555 postOnAnimation(() -> {
556 mPanelView.animate()
557 .translationY(mTranslationY)
558 .setDuration(ANIMATION_DURATION_AWAY_MS)
559 .setInterpolator(mLinearOutSlowIn)
560 .withLayer()
561 .withEndAction(endActionRunnable)
562 .start();
Kevin Chynff168dc2019-09-16 16:04:38 -0700563 mBiometricScrollView.animate()
Kevin Chynfc468262019-08-20 17:17:11 -0700564 .translationY(mTranslationY)
565 .setDuration(ANIMATION_DURATION_AWAY_MS)
566 .setInterpolator(mLinearOutSlowIn)
567 .withLayer()
568 .start();
Kevin Chynff168dc2019-09-16 16:04:38 -0700569 if (mCredentialView != null && mCredentialView.isAttachedToWindow()) {
570 mCredentialView.animate()
571 .translationY(mTranslationY)
572 .setDuration(ANIMATION_DURATION_AWAY_MS)
573 .setInterpolator(mLinearOutSlowIn)
574 .withLayer()
575 .start();
576 }
Kevin Chynfc468262019-08-20 17:17:11 -0700577 animate()
578 .alpha(0f)
579 .setDuration(ANIMATION_DURATION_AWAY_MS)
580 .setInterpolator(mLinearOutSlowIn)
581 .withLayer()
582 .start();
583 });
584 }
585
Kevin Chyn27da7182019-09-11 12:17:55 -0700586 private void sendPendingCallbackIfNotNull() {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700587 Log.d(TAG, "pendingCallback: " + mPendingCallbackReason
588 + " sysUISessionId: " + mConfig.mSysUiSessionId);
Kevin Chyn27da7182019-09-11 12:17:55 -0700589 if (mPendingCallbackReason != null) {
Kevin Chync8cb6852020-03-10 18:29:15 -0700590 mConfig.mCallback.onDismissed(mPendingCallbackReason, mCredentialAttestation);
Kevin Chyn27da7182019-09-11 12:17:55 -0700591 mPendingCallbackReason = null;
592 }
593 }
594
Kevin Chynfa7069d2020-05-07 13:43:39 -0700595 private void removeWindowIfAttached(boolean sendReason) {
596 if (sendReason) {
597 sendPendingCallbackIfNotNull();
598 }
Kevin Chyn27da7182019-09-11 12:17:55 -0700599
Kevin Chyn889de4c2019-09-05 18:17:32 -0700600 if (mContainerState == STATE_GONE) {
Kevin Chynfa7069d2020-05-07 13:43:39 -0700601 Log.w(TAG, "Container already STATE_GONE, mSysUiSessionId: " + mConfig.mSysUiSessionId);
Kevin Chyn889de4c2019-09-05 18:17:32 -0700602 return;
603 }
Kevin Chynfa7069d2020-05-07 13:43:39 -0700604 Log.d(TAG, "Removing container, mSysUiSessionId: " + mConfig.mSysUiSessionId);
Kevin Chyn889de4c2019-09-05 18:17:32 -0700605 mContainerState = STATE_GONE;
606 mWindowManager.removeView(this);
607 }
608
Kevin Chynfc468262019-08-20 17:17:11 -0700609 private void onDialogAnimatedIn() {
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700610 if (mContainerState == STATE_PENDING_DISMISS) {
Kevin Chynfc468262019-08-20 17:17:11 -0700611 Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
612 animateAway(false /* sendReason */, 0);
Kevin Chynfc468262019-08-20 17:17:11 -0700613 return;
614 }
Kevin Chyn2b1d3d82019-08-27 16:38:22 -0700615 mContainerState = STATE_SHOWING;
Kevin Chyn86f1b8e2019-09-24 19:00:49 -0700616 if (mBiometricView != null) {
617 mBiometricView.onDialogAnimatedIn();
618 }
Kevin Chynfc468262019-08-20 17:17:11 -0700619 }
620
621 /**
622 * @param windowToken token for the window
623 * @return
624 */
625 public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
Curtis Belmonte23b8d492020-02-04 14:24:39 -0800626 final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
627 | WindowManager.LayoutParams.FLAG_SECURE;
Kevin Chynfc468262019-08-20 17:17:11 -0700628 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
629 ViewGroup.LayoutParams.MATCH_PARENT,
630 ViewGroup.LayoutParams.MATCH_PARENT,
Heemin Seog2cf45dd2020-02-24 15:43:29 -0800631 WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
Curtis Belmonte23b8d492020-02-04 14:24:39 -0800632 windowFlags,
Kevin Chynfc468262019-08-20 17:17:11 -0700633 PixelFormat.TRANSLUCENT);
Roshan Piusa3f89c62019-10-11 08:50:53 -0700634 lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
Kevin Chynfc468262019-08-20 17:17:11 -0700635 lp.setTitle("BiometricPrompt");
636 lp.token = windowToken;
Kevin Chynfc468262019-08-20 17:17:11 -0700637 return lp;
638 }
639}