blob: ce67577ea48375fd99c68b68a46b7bf697066770 [file] [log] [blame]
Kevin Chyn42653e82018-01-19 14:15:46 -08001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
Kevin Chyne9275662018-07-23 16:42:06 -070017package com.android.systemui.biometrics;
Kevin Chyn42653e82018-01-19 14:15:46 -080018
joshmccloskey6cf12772019-05-30 17:09:40 -070019import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
20
Kevin Chyn1b9f8df2018-11-12 19:04:55 -080021import android.app.admin.DevicePolicyManager;
Kevin Chyn42653e82018-01-19 14:15:46 -080022import android.content.Context;
Kevin Chyn42653e82018-01-19 14:15:46 -080023import android.graphics.PixelFormat;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -080024import android.graphics.PorterDuff;
25import android.graphics.drawable.Drawable;
Vishwath Mohanecf00ce2018-04-05 10:28:24 -070026import android.hardware.biometrics.BiometricPrompt;
Kevin Chyn42653e82018-01-19 14:15:46 -080027import android.os.Binder;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.IBinder;
Kevin Chyn5906c172018-07-23 15:43:02 -070031import android.os.Message;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -080032import android.os.UserManager;
Kevin Chyna4596f32018-04-05 11:33:12 -070033import android.text.TextUtils;
Kevin Chyn27ce1942018-03-12 14:42:17 -070034import android.util.DisplayMetrics;
Kevin Chynd0544dc2018-02-06 16:30:31 -080035import android.util.Log;
Kevin Chyn42653e82018-01-19 14:15:46 -080036import android.view.KeyEvent;
37import android.view.LayoutInflater;
Kevin Chyn42653e82018-01-19 14:15:46 -080038import android.view.View;
39import android.view.ViewGroup;
Kevin Chyn42653e82018-01-19 14:15:46 -080040import android.view.WindowManager;
joshmccloskey6cf12772019-05-30 17:09:40 -070041import android.view.accessibility.AccessibilityEvent;
Kevin Chyn791f1dd2019-06-05 14:03:26 -070042import android.view.accessibility.AccessibilityManager;
Kevin Chyn42653e82018-01-19 14:15:46 -080043import android.view.animation.Interpolator;
44import android.widget.Button;
Kevin Chyn6cf54e82018-09-18 19:13:27 -070045import android.widget.ImageView;
Kevin Chyn42653e82018-01-19 14:15:46 -080046import android.widget.LinearLayout;
47import android.widget.TextView;
48
Kevin Chyne8f3e1b2018-01-23 17:33:58 -080049import com.android.systemui.Interpolators;
Kevin Chyn42653e82018-01-19 14:15:46 -080050import com.android.systemui.R;
Kevin Chynbb269142018-11-07 19:31:51 -080051import com.android.systemui.util.leak.RotationUtils;
Kevin Chyn42653e82018-01-19 14:15:46 -080052
53/**
Kevin Chyn0be1f332018-09-18 18:15:16 -070054 * Abstract base class. Shows a dialog for BiometricPrompt.
Kevin Chyn42653e82018-01-19 14:15:46 -080055 */
Kevin Chyn0be1f332018-09-18 18:15:16 -070056public abstract class BiometricDialogView extends LinearLayout {
Kevin Chyn42653e82018-01-19 14:15:46 -080057
Kevin Chyna883fb52018-09-18 18:23:22 -070058 private static final String TAG = "BiometricDialogView";
Kevin Chyn42653e82018-01-19 14:15:46 -080059
Kevin Chyne1912712019-01-04 14:22:34 -080060 private static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
61 private static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
Kevin Chynd4e0b3b2019-06-19 13:26:31 -070062 private static final String KEY_CONFIRM_ENABLED = "key_confirm_enabled";
Kevin Chyn8cfd7a42019-05-07 20:22:31 -070063 private static final String KEY_STATE = "key_state";
64 private static final String KEY_ERROR_TEXT_VISIBILITY = "key_error_text_visibility";
65 private static final String KEY_ERROR_TEXT_STRING = "key_error_text_string";
66 private static final String KEY_ERROR_TEXT_IS_TEMPORARY = "key_error_text_is_temporary";
67 private static final String KEY_ERROR_TEXT_COLOR = "key_error_text_color";
Kevin Chyne1912712019-01-04 14:22:34 -080068
Kevin Chyndba919a2018-03-16 14:35:10 -070069 private static final int ANIMATION_DURATION_SHOW = 250; // ms
70 private static final int ANIMATION_DURATION_AWAY = 350; // ms
Kevin Chyn42653e82018-01-19 14:15:46 -080071
Kevin Chyn8cfd7a42019-05-07 20:22:31 -070072 protected static final int MSG_RESET_MESSAGE = 1;
Kevin Chyn5906c172018-07-23 15:43:02 -070073
Kevin Chyne1912712019-01-04 14:22:34 -080074 protected static final int STATE_IDLE = 0;
Kevin Chyn0be1f332018-09-18 18:15:16 -070075 protected static final int STATE_AUTHENTICATING = 1;
76 protected static final int STATE_ERROR = 2;
Kevin Chyn6bb20772018-12-27 15:14:44 -080077 protected static final int STATE_PENDING_CONFIRMATION = 3;
78 protected static final int STATE_AUTHENTICATED = 4;
Kevin Chynd0544dc2018-02-06 16:30:31 -080079
Kevin Chyn791f1dd2019-06-05 14:03:26 -070080 private final AccessibilityManager mAccessibilityManager;
Kevin Chyn42653e82018-01-19 14:15:46 -080081 private final IBinder mWindowToken = new Binder();
Kevin Chyn42653e82018-01-19 14:15:46 -080082 private final Interpolator mLinearOutSlowIn;
Kevin Chyn27e1f262018-03-08 16:38:32 -080083 private final WindowManager mWindowManager;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -080084 private final UserManager mUserManager;
85 private final DevicePolicyManager mDevicePolicyManager;
Kevin Chyne8f3e1b2018-01-23 17:33:58 -080086 private final float mAnimationTranslationOffset;
Kevin Chyn178ace52018-03-30 12:36:32 -070087 private final int mErrorColor;
Kevin Chyn02129b12018-11-01 16:47:12 -070088 private final float mDialogWidth;
Kevin Chyn3b53d6f2019-05-01 11:49:05 -070089 protected final DialogViewCallback mCallback;
Kevin Chyn42653e82018-01-19 14:15:46 -080090
Kevin Chyne1912712019-01-04 14:22:34 -080091 protected final ViewGroup mLayout;
92 protected final LinearLayout mDialog;
93 protected final TextView mTitleText;
94 protected final TextView mSubtitleText;
95 protected final TextView mDescriptionText;
96 protected final ImageView mBiometricIcon;
97 protected final TextView mErrorText;
98 protected final Button mPositiveButton;
99 protected final Button mNegativeButton;
100 protected final Button mTryAgainButton;
101
Kevin Chync9744ac2019-01-11 18:49:50 -0800102 protected final int mTextColor;
103
Kevin Chyn42653e82018-01-19 14:15:46 -0800104 private Bundle mBundle;
Kevin Chyn30c76082019-05-07 19:39:44 -0700105 private Bundle mRestoredState;
Kevin Chyne1912712019-01-04 14:22:34 -0800106
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700107 private int mState = STATE_IDLE;
Kevin Chyn87df0682018-04-10 19:29:23 -0700108 private boolean mAnimatingAway;
109 private boolean mWasForceRemoved;
Kevin Chyn02129b12018-11-01 16:47:12 -0700110 private boolean mSkipIntro;
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700111 protected boolean mRequireConfirmation;
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800112 private int mUserId; // used to determine if we should show work background
Kevin Chyn42653e82018-01-19 14:15:46 -0800113
Kevin Chyn906bde52019-07-24 18:45:42 -0700114 private boolean mCompletedAnimatingIn;
115 private boolean mPendingDismissDialog;
116
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700117 protected abstract int getHintStringResourceId();
118 protected abstract int getAuthenticatedAccessibilityResourceId();
119 protected abstract int getIconDescriptionResourceId();
Kevin Chyn6bb20772018-12-27 15:14:44 -0800120 protected abstract int getDelayAfterAuthenticatedDurationMs();
Kevin Chyne1912712019-01-04 14:22:34 -0800121 protected abstract boolean shouldGrayAreaDismissDialog();
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700122 protected abstract void handleResetMessage();
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700123 protected abstract void updateIcon(int oldState, int newState);
Kevin Chyn0be1f332018-09-18 18:15:16 -0700124
Kevin Chyn87df0682018-04-10 19:29:23 -0700125 private final Runnable mShowAnimationRunnable = new Runnable() {
126 @Override
127 public void run() {
128 mLayout.animate()
129 .alpha(1f)
130 .setDuration(ANIMATION_DURATION_SHOW)
131 .setInterpolator(mLinearOutSlowIn)
132 .withLayer()
133 .start();
134 mDialog.animate()
135 .translationY(0)
136 .setDuration(ANIMATION_DURATION_SHOW)
137 .setInterpolator(mLinearOutSlowIn)
138 .withLayer()
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700139 .withEndAction(() -> onDialogAnimatedIn())
Kevin Chyn87df0682018-04-10 19:29:23 -0700140 .start();
141 }
142 };
143
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700144 protected Handler mHandler = new Handler() {
Kevin Chyn5906c172018-07-23 15:43:02 -0700145 @Override
146 public void handleMessage(Message msg) {
147 switch(msg.what) {
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700148 case MSG_RESET_MESSAGE:
149 handleResetMessage();
Kevin Chyn5906c172018-07-23 15:43:02 -0700150 break;
151 default:
152 Log.e(TAG, "Unhandled message: " + msg.what);
153 break;
154 }
155 }
156 };
157
Kevin Chyna883fb52018-09-18 18:23:22 -0700158 public BiometricDialogView(Context context, DialogViewCallback callback) {
Kevin Chyn42653e82018-01-19 14:15:46 -0800159 super(context);
Kevin Chyn5906c172018-07-23 15:43:02 -0700160 mCallback = callback;
Kevin Chyne8f3e1b2018-01-23 17:33:58 -0800161 mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
Kevin Chyn791f1dd2019-06-05 14:03:26 -0700162 mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800163 mWindowManager = mContext.getSystemService(WindowManager.class);
164 mUserManager = mContext.getSystemService(UserManager.class);
165 mDevicePolicyManager = mContext.getSystemService(DevicePolicyManager.class);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700166 mAnimationTranslationOffset = getResources()
167 .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
Kevin Chyn7f93a582019-05-07 19:15:52 -0700168 mErrorColor = getResources().getColor(R.color.biometric_dialog_error);
169 mTextColor = getResources().getColor(R.color.biometric_dialog_gray);
Kevin Chyn42653e82018-01-19 14:15:46 -0800170
Kevin Chyn27ce1942018-03-12 14:42:17 -0700171 DisplayMetrics metrics = new DisplayMetrics();
172 mWindowManager.getDefaultDisplay().getMetrics(metrics);
Kevin Chyn02129b12018-11-01 16:47:12 -0700173 mDialogWidth = Math.min(metrics.widthPixels, metrics.heightPixels);
Kevin Chyn27ce1942018-03-12 14:42:17 -0700174
Kevin Chyn42653e82018-01-19 14:15:46 -0800175 // Create the dialog
176 LayoutInflater factory = LayoutInflater.from(getContext());
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700177 mLayout = (ViewGroup) factory.inflate(R.layout.biometric_dialog, this, false);
Kevin Chyn42653e82018-01-19 14:15:46 -0800178 addView(mLayout);
179
Kevin Chyn42653e82018-01-19 14:15:46 -0800180 mLayout.setOnKeyListener(new View.OnKeyListener() {
181 boolean downPressed = false;
182 @Override
183 public boolean onKey(View v, int keyCode, KeyEvent event) {
184 if (keyCode != KeyEvent.KEYCODE_BACK) {
185 return false;
186 }
187 if (event.getAction() == KeyEvent.ACTION_DOWN && downPressed == false) {
188 downPressed = true;
189 } else if (event.getAction() == KeyEvent.ACTION_DOWN) {
190 downPressed = false;
191 } else if (event.getAction() == KeyEvent.ACTION_UP && downPressed == true) {
192 downPressed = false;
Kevin Chyn5906c172018-07-23 15:43:02 -0700193 mCallback.onUserCanceled();
Kevin Chyn42653e82018-01-19 14:15:46 -0800194 }
195 return true;
196 }
197 });
198
199 final View space = mLayout.findViewById(R.id.space);
Kevin Chyn27ce1942018-03-12 14:42:17 -0700200 final View leftSpace = mLayout.findViewById(R.id.left_space);
201 final View rightSpace = mLayout.findViewById(R.id.right_space);
Kevin Chyne1912712019-01-04 14:22:34 -0800202
203 mDialog = mLayout.findViewById(R.id.dialog);
204 mTitleText = mLayout.findViewById(R.id.title);
205 mSubtitleText = mLayout.findViewById(R.id.subtitle);
206 mDescriptionText = mLayout.findViewById(R.id.description);
207 mBiometricIcon = mLayout.findViewById(R.id.biometric_icon);
208 mErrorText = mLayout.findViewById(R.id.error);
Kevin Chyn6bb20772018-12-27 15:14:44 -0800209 mNegativeButton = mLayout.findViewById(R.id.button2);
210 mPositiveButton = mLayout.findViewById(R.id.button1);
Kevin Chyne1912712019-01-04 14:22:34 -0800211 mTryAgainButton = mLayout.findViewById(R.id.button_try_again);
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700212
Kevin Chyne1912712019-01-04 14:22:34 -0800213 mBiometricIcon.setContentDescription(
214 getResources().getString(getIconDescriptionResourceId()));
Kevin Chyn42653e82018-01-19 14:15:46 -0800215
Kevin Chyn27ce1942018-03-12 14:42:17 -0700216 setDismissesDialog(space);
217 setDismissesDialog(leftSpace);
218 setDismissesDialog(rightSpace);
Kevin Chyn42653e82018-01-19 14:15:46 -0800219
Kevin Chyn6bb20772018-12-27 15:14:44 -0800220 mNegativeButton.setOnClickListener((View v) -> {
Kevin Chyn078dcf12019-04-26 11:14:20 -0700221 if (mState == STATE_PENDING_CONFIRMATION || mState == STATE_AUTHENTICATED) {
222 mCallback.onUserCanceled();
223 } else {
224 mCallback.onNegativePressed();
225 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800226 });
227
Kevin Chyn6bb20772018-12-27 15:14:44 -0800228 mPositiveButton.setOnClickListener((View v) -> {
229 updateState(STATE_AUTHENTICATED);
230 mHandler.postDelayed(() -> {
231 mCallback.onPositivePressed();
232 }, getDelayAfterAuthenticatedDurationMs());
Kevin Chyn42653e82018-01-19 14:15:46 -0800233 });
234
Kevin Chyne1912712019-01-04 14:22:34 -0800235 mTryAgainButton.setOnClickListener((View v) -> {
Kevin Chynbb79c002019-05-14 12:53:20 -0700236 handleResetMessage();
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700237 updateState(STATE_AUTHENTICATING);
Kevin Chyn23289ef2018-11-28 16:32:36 -0800238 showTryAgainButton(false /* show */);
Kevin Chynd4e0b3b2019-06-19 13:26:31 -0700239
240 mPositiveButton.setVisibility(View.VISIBLE);
241 mPositiveButton.setEnabled(false);
242
Kevin Chyn23289ef2018-11-28 16:32:36 -0800243 mCallback.onTryAgainPressed();
244 });
Kevin Chynd1cdc3a2019-06-05 13:43:58 -0700245
246 // Must set these in order for the back button events to be received.
247 mLayout.setFocusableInTouchMode(true);
248 mLayout.requestFocus();
Kevin Chyn42653e82018-01-19 14:15:46 -0800249 }
250
Kevin Chyne1912712019-01-04 14:22:34 -0800251 public void onSaveState(Bundle bundle) {
252 bundle.putInt(KEY_TRY_AGAIN_VISIBILITY, mTryAgainButton.getVisibility());
253 bundle.putInt(KEY_CONFIRM_VISIBILITY, mPositiveButton.getVisibility());
Kevin Chynd4e0b3b2019-06-19 13:26:31 -0700254 bundle.putBoolean(KEY_CONFIRM_ENABLED, mPositiveButton.isEnabled());
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700255 bundle.putInt(KEY_STATE, mState);
256 bundle.putInt(KEY_ERROR_TEXT_VISIBILITY, mErrorText.getVisibility());
257 bundle.putCharSequence(KEY_ERROR_TEXT_STRING, mErrorText.getText());
258 bundle.putBoolean(KEY_ERROR_TEXT_IS_TEMPORARY, mHandler.hasMessages(MSG_RESET_MESSAGE));
259 bundle.putInt(KEY_ERROR_TEXT_COLOR, mErrorText.getCurrentTextColor());
Kevin Chyne1912712019-01-04 14:22:34 -0800260 }
261
Kevin Chyn42653e82018-01-19 14:15:46 -0800262 @Override
263 public void onAttachedToWindow() {
264 super.onAttachedToWindow();
265
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800266 final ImageView backgroundView = mLayout.findViewById(R.id.background);
267
268 if (mUserManager.isManagedProfile(mUserId)) {
269 final Drawable image = getResources().getDrawable(R.drawable.work_challenge_background,
270 mContext.getTheme());
271 image.setColorFilter(mDevicePolicyManager.getOrganizationColorForUser(mUserId),
272 PorterDuff.Mode.DARKEN);
Kevin Chynb4523b32019-06-21 14:16:20 -0700273 backgroundView.setScaleType(ImageView.ScaleType.CENTER_CROP);
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800274 backgroundView.setImageDrawable(image);
275 } else {
276 backgroundView.setImageDrawable(null);
277 backgroundView.setBackgroundColor(R.color.biometric_dialog_dim_color);
278 }
Kevin Chynd0544dc2018-02-06 16:30:31 -0800279
Kevin Chyn6bb20772018-12-27 15:14:44 -0800280 mNegativeButton.setVisibility(View.VISIBLE);
Kevin Chyn6bb20772018-12-27 15:14:44 -0800281
Kevin Chynbb269142018-11-07 19:31:51 -0800282 if (RotationUtils.getRotation(mContext) != RotationUtils.ROTATION_NONE) {
283 mDialog.getLayoutParams().width = (int) mDialogWidth;
284 }
285
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700286 if (mRestoredState == null) {
287 updateState(STATE_AUTHENTICATING);
joshmccloskeya1c777e2019-07-10 17:15:15 -0700288 mNegativeButton.setText(mBundle.getCharSequence(BiometricPrompt.KEY_NEGATIVE_TEXT));
Kevin Chynd4e0b3b2019-06-19 13:26:31 -0700289 final int hint = getHintStringResourceId();
290 if (hint != 0) {
291 mErrorText.setText(hint);
292 mErrorText.setContentDescription(mContext.getString(hint));
293 mErrorText.setVisibility(View.VISIBLE);
294 } else {
295 mErrorText.setVisibility(View.INVISIBLE);
296 }
297 announceAccessibilityEvent();
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700298 } else {
299 updateState(mState);
300 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800301
Kevin Chyn3a0187192018-10-08 15:40:05 -0700302 CharSequence titleText = mBundle.getCharSequence(BiometricPrompt.KEY_TITLE);
303
Kevin Chyne1912712019-01-04 14:22:34 -0800304 mTitleText.setVisibility(View.VISIBLE);
305 mTitleText.setText(titleText);
Kevin Chyndba919a2018-03-16 14:35:10 -0700306
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700307 final CharSequence subtitleText = mBundle.getCharSequence(BiometricPrompt.KEY_SUBTITLE);
Kevin Chyna4596f32018-04-05 11:33:12 -0700308 if (TextUtils.isEmpty(subtitleText)) {
Kevin Chyne1912712019-01-04 14:22:34 -0800309 mSubtitleText.setVisibility(View.GONE);
joshmccloskey6cf12772019-05-30 17:09:40 -0700310 announceAccessibilityEvent();
Kevin Chyndba919a2018-03-16 14:35:10 -0700311 } else {
Kevin Chyne1912712019-01-04 14:22:34 -0800312 mSubtitleText.setVisibility(View.VISIBLE);
313 mSubtitleText.setText(subtitleText);
Kevin Chyndba919a2018-03-16 14:35:10 -0700314 }
315
Kevin Chyn23289ef2018-11-28 16:32:36 -0800316 final CharSequence descriptionText =
317 mBundle.getCharSequence(BiometricPrompt.KEY_DESCRIPTION);
Kevin Chyna4596f32018-04-05 11:33:12 -0700318 if (TextUtils.isEmpty(descriptionText)) {
Kevin Chyne1912712019-01-04 14:22:34 -0800319 mDescriptionText.setVisibility(View.GONE);
joshmccloskey6cf12772019-05-30 17:09:40 -0700320 announceAccessibilityEvent();
Kevin Chyndba919a2018-03-16 14:35:10 -0700321 } else {
Kevin Chyne1912712019-01-04 14:22:34 -0800322 mDescriptionText.setVisibility(View.VISIBLE);
323 mDescriptionText.setText(descriptionText);
Kevin Chyndba919a2018-03-16 14:35:10 -0700324 }
325
Kevin Chyn30c76082019-05-07 19:39:44 -0700326 if (requiresConfirmation() && mRestoredState == null) {
Kevin Chyn8d39b4e2019-04-26 11:02:13 -0700327 mPositiveButton.setVisibility(View.VISIBLE);
328 mPositiveButton.setEnabled(false);
329 }
330
Kevin Chyn02129b12018-11-01 16:47:12 -0700331 if (mWasForceRemoved || mSkipIntro) {
Kevin Chyn87df0682018-04-10 19:29:23 -0700332 // Show the dialog immediately
333 mLayout.animate().cancel();
334 mDialog.animate().cancel();
335 mDialog.setAlpha(1.0f);
336 mDialog.setTranslationY(0);
337 mLayout.setAlpha(1.0f);
Kevin Chyn906bde52019-07-24 18:45:42 -0700338 mCompletedAnimatingIn = true;
Kevin Chyn02129b12018-11-01 16:47:12 -0700339 } else {
340 // Dim the background and slide the dialog up
341 mDialog.setTranslationY(mAnimationTranslationOffset);
342 mLayout.setAlpha(0f);
343 postOnAnimation(mShowAnimationRunnable);
Kevin Chyn87df0682018-04-10 19:29:23 -0700344 }
345 mWasForceRemoved = false;
Kevin Chyn02129b12018-11-01 16:47:12 -0700346 mSkipIntro = false;
Kevin Chyn42653e82018-01-19 14:15:46 -0800347 }
348
Kevin Chyn27ce1942018-03-12 14:42:17 -0700349 private void setDismissesDialog(View v) {
350 v.setClickable(true);
Kevin Chyn8ab1de62019-05-14 12:19:07 -0700351 v.setOnClickListener(v1 -> {
Kevin Chyn078dcf12019-04-26 11:14:20 -0700352 if (mState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
Kevin Chyn6bb20772018-12-27 15:14:44 -0800353 mCallback.onUserCanceled();
354 }
Kevin Chyn27ce1942018-03-12 14:42:17 -0700355 });
356 }
357
Kevin Chyn27e1f262018-03-08 16:38:32 -0800358 public void startDismiss() {
Kevin Chyn906bde52019-07-24 18:45:42 -0700359 if (!mCompletedAnimatingIn) {
360 Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
361 mPendingDismissDialog = true;
362 return;
363 }
364
Kevin Chyn87df0682018-04-10 19:29:23 -0700365 mAnimatingAway = true;
366
Kevin Chyn23289ef2018-11-28 16:32:36 -0800367 // This is where final cleanup should occur.
Kevin Chyn27e1f262018-03-08 16:38:32 -0800368 final Runnable endActionRunnable = new Runnable() {
369 @Override
370 public void run() {
Kevin Chyna883fb52018-09-18 18:23:22 -0700371 mWindowManager.removeView(BiometricDialogView.this);
Kevin Chyn87df0682018-04-10 19:29:23 -0700372 mAnimatingAway = false;
Kevin Chyn87f257a2018-11-27 16:26:07 -0800373 // Set the icons / text back to normal state
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700374 handleResetMessage();
Kevin Chyn23289ef2018-11-28 16:32:36 -0800375 showTryAgainButton(false /* show */);
Kevin Chyne1912712019-01-04 14:22:34 -0800376 updateState(STATE_IDLE);
Kevin Chyn27e1f262018-03-08 16:38:32 -0800377 }
378 };
379
380 postOnAnimation(new Runnable() {
381 @Override
382 public void run() {
383 mLayout.animate()
384 .alpha(0f)
Kevin Chyndba919a2018-03-16 14:35:10 -0700385 .setDuration(ANIMATION_DURATION_AWAY)
Kevin Chyn27e1f262018-03-08 16:38:32 -0800386 .setInterpolator(mLinearOutSlowIn)
387 .withLayer()
388 .start();
389 mDialog.animate()
390 .translationY(mAnimationTranslationOffset)
Kevin Chyndba919a2018-03-16 14:35:10 -0700391 .setDuration(ANIMATION_DURATION_AWAY)
Kevin Chyn27e1f262018-03-08 16:38:32 -0800392 .setInterpolator(mLinearOutSlowIn)
393 .withLayer()
394 .withEndAction(endActionRunnable)
395 .start();
396 }
397 });
398 }
399
Kevin Chyn87df0682018-04-10 19:29:23 -0700400 /**
401 * Force remove the window, cancelling any animation that's happening. This should only be
402 * called if we want to quickly show the dialog again (e.g. on rotation). Calling this method
403 * will cause the dialog to show without an animation the next time it's attached.
404 */
405 public void forceRemove() {
406 mLayout.animate().cancel();
407 mDialog.animate().cancel();
Kevin Chyna883fb52018-09-18 18:23:22 -0700408 mWindowManager.removeView(BiometricDialogView.this);
Kevin Chyn87df0682018-04-10 19:29:23 -0700409 mAnimatingAway = false;
410 mWasForceRemoved = true;
411 }
412
Kevin Chyn02129b12018-11-01 16:47:12 -0700413 /**
414 * Skip the intro animation
415 */
416 public void setSkipIntro(boolean skip) {
417 mSkipIntro = skip;
418 }
419
Kevin Chyn87df0682018-04-10 19:29:23 -0700420 public boolean isAnimatingAway() {
421 return mAnimatingAway;
422 }
423
Kevin Chyn42653e82018-01-19 14:15:46 -0800424 public void setBundle(Bundle bundle) {
425 mBundle = bundle;
426 }
427
Kevin Chyn6cf54e82018-09-18 19:13:27 -0700428 public void setRequireConfirmation(boolean requireConfirmation) {
429 mRequireConfirmation = requireConfirmation;
430 }
431
432 public boolean requiresConfirmation() {
433 return mRequireConfirmation;
434 }
435
Kevin Chyn1b9f8df2018-11-12 19:04:55 -0800436 public void setUserId(int userId) {
437 mUserId = userId;
438 }
439
Kevin Chyn0be1f332018-09-18 18:15:16 -0700440 public ViewGroup getLayout() {
441 return mLayout;
442 }
443
Kevin Chynd0544dc2018-02-06 16:30:31 -0800444 // Shows an error/help message
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700445 protected void showTemporaryMessage(String message) {
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700446 mHandler.removeMessages(MSG_RESET_MESSAGE);
Kevin Chyn42653e82018-01-19 14:15:46 -0800447 mErrorText.setText(message);
Kevin Chyn178ace52018-03-30 12:36:32 -0700448 mErrorText.setTextColor(mErrorColor);
Kevin Chyn16aac7a2018-01-24 11:32:46 -0800449 mErrorText.setContentDescription(message);
Kevin Chynd4e0b3b2019-06-19 13:26:31 -0700450 mErrorText.setVisibility(View.VISIBLE);
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700451 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE),
Vishwath Mohanecf00ce2018-04-05 10:28:24 -0700452 BiometricPrompt.HIDE_DIALOG_DELAY);
Kevin Chyn42653e82018-01-19 14:15:46 -0800453 }
454
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700455 /**
456 * Transient help message (acquire) is received, dialog stays showing. Sensor stays in
457 * "authenticating" state.
458 * @param message
459 */
460 public void onHelpReceived(String message) {
461 updateState(STATE_ERROR);
462 showTemporaryMessage(message);
Kevin Chyn42653e82018-01-19 14:15:46 -0800463 }
464
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700465 public void onAuthenticationFailed(String message) {
466 updateState(STATE_ERROR);
467 showTemporaryMessage(message);
468 }
469
470 /**
471 * Hard error is received, dialog will be dismissed soon.
472 * @param error
473 */
474 public void onErrorReceived(String error) {
475 updateState(STATE_ERROR);
476 showTemporaryMessage(error);
Kevin Chyn23289ef2018-11-28 16:32:36 -0800477 showTryAgainButton(false /* show */);
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700478 mCallback.onErrorShown(); // TODO: Split between fp and face
Kevin Chyn42653e82018-01-19 14:15:46 -0800479 }
480
Kevin Chyn6bb20772018-12-27 15:14:44 -0800481 public void updateState(int newState) {
482 if (newState == STATE_PENDING_CONFIRMATION) {
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700483 mHandler.removeMessages(MSG_RESET_MESSAGE);
Kevin Chynd4e0b3b2019-06-19 13:26:31 -0700484 mErrorText.setTextColor(mTextColor);
485 mErrorText.setText(R.string.biometric_dialog_tap_confirm);
joshmccloskeya1c777e2019-07-10 17:15:15 -0700486 mErrorText.setContentDescription(
487 getResources().getString(R.string.biometric_dialog_tap_confirm));
Kevin Chynd4e0b3b2019-06-19 13:26:31 -0700488 mErrorText.setVisibility(View.VISIBLE);
joshmccloskey6cf12772019-05-30 17:09:40 -0700489 announceAccessibilityEvent();
Kevin Chyn30c76082019-05-07 19:39:44 -0700490 mPositiveButton.setVisibility(View.VISIBLE);
Kevin Chyn078dcf12019-04-26 11:14:20 -0700491 mPositiveButton.setEnabled(true);
Kevin Chyn6bb20772018-12-27 15:14:44 -0800492 } else if (newState == STATE_AUTHENTICATED) {
493 mPositiveButton.setVisibility(View.GONE);
494 mNegativeButton.setVisibility(View.GONE);
495 mErrorText.setVisibility(View.INVISIBLE);
joshmccloskey6cf12772019-05-30 17:09:40 -0700496 announceAccessibilityEvent();
Kevin Chyn6bb20772018-12-27 15:14:44 -0800497 }
498
Kevin Chyn078dcf12019-04-26 11:14:20 -0700499 if (newState == STATE_PENDING_CONFIRMATION || newState == STATE_AUTHENTICATED) {
500 mNegativeButton.setText(R.string.cancel);
joshmccloskeya1c777e2019-07-10 17:15:15 -0700501 mNegativeButton.setContentDescription(getResources().getString(R.string.cancel));
Kevin Chyn078dcf12019-04-26 11:14:20 -0700502 }
503
504 updateIcon(mState, newState);
505 mState = newState;
Kevin Chynd0544dc2018-02-06 16:30:31 -0800506 }
507
Kevin Chyn23289ef2018-11-28 16:32:36 -0800508 public void showTryAgainButton(boolean show) {
Kevin Chyn23289ef2018-11-28 16:32:36 -0800509 }
510
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700511 public void onDialogAnimatedIn() {
Kevin Chyn906bde52019-07-24 18:45:42 -0700512 mCompletedAnimatingIn = true;
513
514 if (mPendingDismissDialog) {
515 Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
516 startDismiss();
517 mPendingDismissDialog = false;
518 }
Kevin Chyn3b53d6f2019-05-01 11:49:05 -0700519 }
520
Kevin Chyne1912712019-01-04 14:22:34 -0800521 public void restoreState(Bundle bundle) {
Kevin Chyn30c76082019-05-07 19:39:44 -0700522 mRestoredState = bundle;
joshmccloskey6cf12772019-05-30 17:09:40 -0700523 final int tryAgainVisibility = bundle.getInt(KEY_TRY_AGAIN_VISIBILITY);
524 mTryAgainButton.setVisibility(tryAgainVisibility);
525 final int confirmVisibility = bundle.getInt(KEY_CONFIRM_VISIBILITY);
526 mPositiveButton.setVisibility(confirmVisibility);
Kevin Chynd4e0b3b2019-06-19 13:26:31 -0700527 final boolean confirmEnabled = bundle.getBoolean(KEY_CONFIRM_ENABLED);
528 mPositiveButton.setEnabled(confirmEnabled);
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700529 mState = bundle.getInt(KEY_STATE);
530 mErrorText.setText(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
Kevin Chynbb79c002019-05-14 12:53:20 -0700531 mErrorText.setContentDescription(bundle.getCharSequence(KEY_ERROR_TEXT_STRING));
joshmccloskey6cf12772019-05-30 17:09:40 -0700532 final int errorTextVisibility = bundle.getInt(KEY_ERROR_TEXT_VISIBILITY);
533 mErrorText.setVisibility(errorTextVisibility);
534 if (errorTextVisibility == View.INVISIBLE || tryAgainVisibility == View.INVISIBLE
535 || confirmVisibility == View.INVISIBLE) {
536 announceAccessibilityEvent();
537 }
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700538 mErrorText.setTextColor(bundle.getInt(KEY_ERROR_TEXT_COLOR));
Kevin Chyn8cfd7a42019-05-07 20:22:31 -0700539 if (bundle.getBoolean(KEY_ERROR_TEXT_IS_TEMPORARY)) {
540 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_RESET_MESSAGE),
541 BiometricPrompt.HIDE_DIALOG_DELAY);
542 }
543 }
544
545 protected int getState() {
546 return mState;
Kevin Chyn23289ef2018-11-28 16:32:36 -0800547 }
548
Kevin Chyn42653e82018-01-19 14:15:46 -0800549 public WindowManager.LayoutParams getLayoutParams() {
550 final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
551 ViewGroup.LayoutParams.MATCH_PARENT,
552 ViewGroup.LayoutParams.MATCH_PARENT,
553 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
554 WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
555 PixelFormat.TRANSLUCENT);
556 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
Kevin Chyna883fb52018-09-18 18:23:22 -0700557 lp.setTitle("BiometricDialogView");
Kevin Chyn42653e82018-01-19 14:15:46 -0800558 lp.token = mWindowToken;
559 return lp;
560 }
joshmccloskey6cf12772019-05-30 17:09:40 -0700561
562 // Every time a view becomes invisible we need to announce an accessibility event.
563 // This is due to an issue in the framework, b/132298701 recommended this workaround.
564 protected void announceAccessibilityEvent() {
Kevin Chyn791f1dd2019-06-05 14:03:26 -0700565 if (!mAccessibilityManager.isEnabled()) {
566 return;
567 }
joshmccloskey6cf12772019-05-30 17:09:40 -0700568 AccessibilityEvent event = AccessibilityEvent.obtain();
569 event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
570 event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE);
571 mDialog.sendAccessibilityEventUnchecked(event);
572 mDialog.notifySubtreeAccessibilityStateChanged(mDialog, mDialog,
573 CONTENT_CHANGE_TYPE_SUBTREE);
joshmccloskey6cf12772019-05-30 17:09:40 -0700574 }
Kevin Chyn42653e82018-01-19 14:15:46 -0800575}