summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kevin Chyn <kchyn@google.com> 2019-09-03 17:49:46 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2019-09-03 17:49:46 +0000
commitb63ff32f531946a13d80f9340aaa8acf692cf145 (patch)
tree64cef608d2a14285091108ff5587590a44d9af60
parent2a17568e0ab3dce15b674a339168d4722d846863 (diff)
parentf8688a0a1e66d185016332a35b915047d57c668e (diff)
Merge changes from topic "biometric-ui-refactor"
* changes: 4/n: Rename files to make more sense 3/n: Tapping outside of the dialog should cancel authentication 2/n: Start plumbing authentication signals to the UI 1/n: Refactor BiometricPrompt UI hierarchy
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_contents.xml118
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_face_view.xml26
-rw-r--r--packages/SystemUI/res/layout/auth_container_view.xml43
-rw-r--r--packages/SystemUI/res/values/config.xml2
-rw-r--r--packages/SystemUI/res/values/dimens.xml2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java201
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java533
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java400
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java (renamed from packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java)69
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java (renamed from packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java)22
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java (renamed from packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java)4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java (renamed from packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java)50
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java (renamed from packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java)7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java (renamed from packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java)7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/Utils.java (renamed from packages/SystemUI/src/com/android/systemui/biometrics/ui/Utils.java)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java271
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java)39
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogViewTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java)9
21 files changed, 2021 insertions, 90 deletions
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
new file mode 100644
index 000000000000..aed200f69bc3
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -0,0 +1,118 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<merge xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <TextView
+ android:id="@+id/title"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="24dp"
+ android:paddingTop="24dp"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:textSize="20sp"
+ android:textColor="?android:attr/textColorPrimary"/>
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingHorizontal="24dp"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:textSize="16sp"
+ android:textColor="?android:attr/textColorPrimary"/>
+
+ <TextView
+ android:id="@+id/description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="24dp"
+ android:paddingBottom="48dp"
+ android:paddingTop="8dp"
+ android:gravity="@integer/biometric_dialog_text_gravity"
+ android:textSize="16sp"
+ android:textColor="?android:attr/textColorPrimary"/>
+
+ <ImageView
+ android:id="@+id/biometric_icon"
+ android:layout_width="@dimen/biometric_dialog_biometric_icon_size"
+ android:layout_height="@dimen/biometric_dialog_biometric_icon_size"
+ android:layout_gravity="center_horizontal"
+ android:scaleType="fitXY" />
+
+ <TextView
+ android:id="@+id/error"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="24dp"
+ android:paddingTop="16dp"
+ android:paddingBottom="24dp"
+ android:textSize="12sp"
+ android:gravity="center_horizontal"
+ android:accessibilityLiveRegion="polite"
+ android:textColor="@color/biometric_dialog_gray"/>
+
+ <LinearLayout
+ android:id="@+id/button_bar"
+ android:layout_width="match_parent"
+ android:layout_height="72dip"
+ android:paddingTop="24dp"
+ android:layout_gravity="center_vertical"
+ style="?android:attr/buttonBarStyle"
+ android:orientation="horizontal">
+ <Space android:id="@+id/leftSpacer"
+ android:layout_width="12dp"
+ android:layout_height="match_parent"
+ android:visibility="visible" />
+ <!-- Negative Button -->
+ <Button android:id="@+id/button_negative"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
+ android:gravity="center"
+ android:maxLines="2"/>
+ <Space android:id="@+id/middleSpacer"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:visibility="visible"/>
+ <!-- Positive Button -->
+ <Button android:id="@+id/button_positive"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:gravity="center"
+ android:maxLines="2"
+ android:text="@string/biometric_dialog_confirm"
+ android:visibility="gone"/>
+ <!-- Try Again Button -->
+ <Button android:id="@+id/button_try_again"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ style="@*android:style/Widget.DeviceDefault.Button.Colored"
+ android:gravity="center"
+ android:maxLines="2"
+ android:text="@string/biometric_dialog_try_again"
+ android:visibility="gone"/>
+ <Space android:id="@+id/rightSpacer"
+ android:layout_width="12dip"
+ android:layout_height="match_parent"
+ android:visibility="visible" />
+ </LinearLayout>
+
+</merge> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_face_view.xml
new file mode 100644
index 000000000000..be30f21af536
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_face_view.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<com.android.systemui.biometrics.AuthBiometricFaceView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/contents"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <include layout="@layout/auth_biometric_contents"/>
+
+</com.android.systemui.biometrics.AuthBiometricFaceView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_container_view.xml b/packages/SystemUI/res/layout/auth_container_view.xml
new file mode 100644
index 000000000000..d83776b74fd1
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_container_view.xml
@@ -0,0 +1,43 @@
+<!--
+ ~ Copyright (C) 2019 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ImageView
+ android:id="@+id/background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@color/biometric_dialog_dim_color"/>
+
+ <View
+ android:id="@+id/panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?android:attr/colorBackgroundFloating"
+ android:elevation="@dimen/biometric_dialog_elevation"/>
+
+ <ScrollView
+ android:id="@+id/scrollview"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:layout_margin="@dimen/biometric_dialog_border_padding"
+ android:elevation="@dimen/biometric_dialog_elevation"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 78318cb7e858..38293bf2defd 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -291,7 +291,7 @@
<item>com.android.systemui.LatencyTester</item>
<item>com.android.systemui.globalactions.GlobalActionsComponent</item>
<item>com.android.systemui.ScreenDecorations</item>
- <item>com.android.systemui.biometrics.BiometricDialogImpl</item>
+ <item>com.android.systemui.biometrics.AuthController</item>
<item>com.android.systemui.SliceBroadcastRelayHandler</item>
<item>com.android.systemui.SizeCompatModeActivityController</item>
<item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 2e1799168b5d..3a1f7a37729f 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1010,6 +1010,8 @@
<dimen name="biometric_dialog_corner_size">4dp</dimen>
<dimen name="biometric_dialog_animation_translation_offset">350dp</dimen>
<dimen name="biometric_dialog_border_padding">4dp</dimen>
+ <dimen name="biometric_dialog_elevation">1dp</dimen>
+ <dimen name="biometric_dialog_icon_padding">16dp</dimen>
<!-- Wireless Charging Animation values -->
<dimen name="wireless_charging_dots_radius_start">0dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
new file mode 100644
index 000000000000..74cc9c39741e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.content.Context;
+import android.graphics.drawable.Animatable2;
+import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+public class AuthBiometricFaceView extends AuthBiometricView {
+
+ private static final String TAG = "BiometricPrompt/AuthBiometricFaceView";
+
+ // Delay before dismissing after being authenticated/confirmed.
+ private static final int HIDE_DELAY_MS = 500;
+
+ public static class IconController extends Animatable2.AnimationCallback {
+ Context mContext;
+ ImageView mIconView;
+ TextView mTextView;
+ Handler mHandler;
+ boolean mLastPulseLightToDark; // false = dark to light, true = light to dark
+ @BiometricState int mState;
+
+ IconController(Context context, ImageView iconView, TextView textView) {
+ mContext = context;
+ mIconView = iconView;
+ mTextView = textView;
+ mHandler = new Handler(Looper.getMainLooper());
+ showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
+ }
+
+ void animateOnce(int iconRes) {
+ animateIcon(iconRes, false);
+ }
+
+ public void showStaticDrawable(int iconRes) {
+ mIconView.setImageDrawable(mContext.getDrawable(iconRes));
+ }
+
+ void animateIcon(int iconRes, boolean repeat) {
+ final AnimatedVectorDrawable icon =
+ (AnimatedVectorDrawable) mContext.getDrawable(iconRes);
+ mIconView.setImageDrawable(icon);
+ icon.forceAnimationOnUI();
+ if (repeat) {
+ icon.registerAnimationCallback(this);
+ }
+ icon.start();
+ }
+
+ void startPulsing() {
+ mLastPulseLightToDark = false;
+ animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true);
+ }
+
+ void pulseInNextDirection() {
+ int iconRes = mLastPulseLightToDark ? R.drawable.face_dialog_pulse_dark_to_light
+ : R.drawable.face_dialog_pulse_light_to_dark;
+ animateIcon(iconRes, true /* repeat */);
+ mLastPulseLightToDark = !mLastPulseLightToDark;
+ }
+
+ @Override
+ public void onAnimationEnd(Drawable drawable) {
+ super.onAnimationEnd(drawable);
+ if (mState == STATE_AUTHENTICATING || mState == STATE_HELP) {
+ pulseInNextDirection();
+ }
+ }
+
+ public void updateState(int lastState, int newState) {
+ final boolean lastStateIsErrorIcon =
+ lastState == STATE_ERROR || lastState == STATE_HELP;
+
+ if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+ showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light);
+ mIconView.setContentDescription(mContext.getString(
+ R.string.biometric_dialog_face_icon_description_authenticating));
+ } else if (newState == STATE_AUTHENTICATING) {
+ startPulsing();
+ mIconView.setContentDescription(mContext.getString(
+ R.string.biometric_dialog_face_icon_description_authenticating));
+ } else if (lastState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
+ animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+ mIconView.setContentDescription(mContext.getString(
+ R.string.biometric_dialog_face_icon_description_confirmed));
+ } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
+ animateOnce(R.drawable.face_dialog_error_to_idle);
+ } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
+ animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+ } else if (newState == STATE_ERROR && lastState != STATE_ERROR) {
+ animateOnce(R.drawable.face_dialog_dark_to_error);
+ } else if (lastState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
+ animateOnce(R.drawable.face_dialog_dark_to_checkmark);
+ mIconView.setContentDescription(mContext.getString(
+ R.string.biometric_dialog_face_icon_description_authenticated));
+ } else if (newState == STATE_PENDING_CONFIRMATION) {
+ animateOnce(R.drawable.face_dialog_wink_from_dark);
+ mIconView.setContentDescription(mContext.getString(
+ R.string.biometric_dialog_face_icon_description_authenticated));
+ } else if (newState == STATE_IDLE) {
+ showStaticDrawable(R.drawable.face_dialog_idle_static);
+ } else {
+ Log.w(TAG, "Unhandled state: " + newState);
+ }
+ mState = newState;
+ }
+ }
+
+ @VisibleForTesting IconController mIconController;
+
+ public AuthBiometricFaceView(Context context) {
+ this(context, null);
+ }
+
+ public AuthBiometricFaceView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected int getDelayAfterAuthenticatedDurationMs() {
+ return HIDE_DELAY_MS;
+ }
+
+ @Override
+ protected int getStateForAfterError() {
+ return STATE_IDLE;
+ }
+
+ @Override
+ protected void handleResetAfterError() {
+ resetErrorView(mContext, mErrorView);
+ }
+
+ @Override
+ protected void handleResetAfterHelp() {
+ resetErrorView(mContext, mErrorView);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mIconController = new IconController(mContext, mIconView, mErrorView);
+ }
+
+ @Override
+ public void updateState(@BiometricState int newState) {
+ mIconController.updateState(mState, newState);
+
+ if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
+ (newState == STATE_AUTHENTICATING && mSize == AuthDialog.SIZE_MEDIUM)) {
+ resetErrorView(mContext, mErrorView);
+ }
+
+ // Do this last since the state variable gets updated.
+ super.updateState(newState);
+ }
+
+ @Override
+ public void onAuthenticationFailed(String failureReason) {
+ if (mSize == AuthDialog.SIZE_MEDIUM) {
+ mTryAgainButton.setVisibility(View.VISIBLE);
+ mPositiveButton.setVisibility(View.GONE);
+ }
+
+ // Do this last since wa want to know if the button is being animated (in the case of
+ // small -> medium dialog)
+ super.onAuthenticationFailed(failureReason);
+ }
+
+ static void resetErrorView(Context context, TextView textView) {
+ textView.setTextColor(context.getResources().getColor(
+ R.color.biometric_dialog_gray, context.getTheme()));
+ textView.setVisibility(View.INVISIBLE);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
new file mode 100644
index 000000000000..f26083998c76
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -0,0 +1,533 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.annotation.IntDef;
+import android.content.Context;
+import android.hardware.biometrics.BiometricPrompt;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Contains the Biometric views (title, subtitle, icon, buttons, etc) and its controllers.
+ */
+public abstract class AuthBiometricView extends LinearLayout {
+
+ private static final String TAG = "BiometricPrompt/AuthBiometricView";
+
+ /**
+ * Authentication hardware idle.
+ */
+ protected static final int STATE_IDLE = 0;
+ /**
+ * UI animating in, authentication hardware active.
+ */
+ protected static final int STATE_AUTHENTICATING_ANIMATING_IN = 1;
+ /**
+ * UI animated in, authentication hardware active.
+ */
+ protected static final int STATE_AUTHENTICATING = 2;
+ /**
+ * UI animated in, authentication hardware active.
+ */
+ protected static final int STATE_HELP = 3;
+ /**
+ * Hard error, e.g. ERROR_TIMEOUT. Authentication hardware idle.
+ */
+ protected static final int STATE_ERROR = 4;
+ /**
+ * Authenticated, waiting for user confirmation. Authentication hardware idle.
+ */
+ protected static final int STATE_PENDING_CONFIRMATION = 5;
+ /**
+ * Authenticated, dialog animating away soon.
+ */
+ protected static final int STATE_AUTHENTICATED = 6;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_IDLE, STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING, STATE_HELP,
+ STATE_ERROR, STATE_PENDING_CONFIRMATION, STATE_AUTHENTICATED})
+ @interface BiometricState {}
+
+ /**
+ * Callback to the parent when a user action has occurred.
+ */
+ interface Callback {
+ int ACTION_AUTHENTICATED = 1;
+ int ACTION_USER_CANCELED = 2;
+ int ACTION_BUTTON_NEGATIVE = 3;
+ int ACTION_BUTTON_TRY_AGAIN = 4;
+
+ /**
+ * When an action has occurred. The caller will only invoke this when the callback should
+ * be propagated. e.g. the caller will handle any necessary delay.
+ * @param action
+ */
+ void onAction(int action);
+ }
+
+ @VisibleForTesting
+ static class Injector {
+ AuthBiometricView mBiometricView;
+
+ public Button getNegativeButton() {
+ return mBiometricView.findViewById(R.id.button_negative);
+ }
+
+ public Button getPositiveButton() {
+ return mBiometricView.findViewById(R.id.button_positive);
+ }
+
+ public Button getTryAgainButton() {
+ return mBiometricView.findViewById(R.id.button_try_again);
+ }
+
+ public TextView getTitleView() {
+ return mBiometricView.findViewById(R.id.title);
+ }
+
+ public TextView getSubtitleView() {
+ return mBiometricView.findViewById(R.id.subtitle);
+ }
+
+ public TextView getDescriptionView() {
+ return mBiometricView.findViewById(R.id.description);
+ }
+
+ public TextView getErrorView() {
+ return mBiometricView.findViewById(R.id.error);
+ }
+
+ public ImageView getIconView() {
+ return mBiometricView.findViewById(R.id.biometric_icon);
+ }
+ }
+
+ private final Injector mInjector;
+ private final Handler mHandler;
+ private final int mTextColorError;
+ private final int mTextColorHint;
+
+ private AuthPanelController mPanelController;
+ private Bundle mBundle;
+ private boolean mRequireConfirmation;
+ @AuthDialog.DialogSize int mSize = AuthDialog.SIZE_UNKNOWN;
+
+ private TextView mTitleView;
+ private TextView mSubtitleView;
+ private TextView mDescriptionView;
+ protected ImageView mIconView;
+ @VisibleForTesting protected TextView mErrorView;
+ @VisibleForTesting Button mNegativeButton;
+ @VisibleForTesting Button mPositiveButton;
+ @VisibleForTesting Button mTryAgainButton;
+
+ // Measurements when biometric view is showing text, buttons, etc.
+ private int mMediumHeight;
+ private int mMediumWidth;
+
+ private Callback mCallback;
+ protected @BiometricState int mState;
+
+ private float mIconOriginalY;
+
+ protected boolean mDialogSizeAnimating;
+
+ /**
+ * Delay after authentication is confirmed, before the dialog should be animated away.
+ */
+ protected abstract int getDelayAfterAuthenticatedDurationMs();
+ /**
+ * State that the dialog/icon should be in after showing a help message.
+ */
+ protected abstract int getStateForAfterError();
+ /**
+ * Invoked when the error message is being cleared.
+ */
+ protected abstract void handleResetAfterError();
+ /**
+ * Invoked when the help message is being cleared.
+ */
+ protected abstract void handleResetAfterHelp();
+
+ private final Runnable mResetErrorRunnable = () -> {
+ updateState(getStateForAfterError());
+ handleResetAfterError();
+ };
+
+ private final Runnable mResetHelpRunnable = () -> {
+ updateState(STATE_AUTHENTICATING);
+ handleResetAfterHelp();
+ };
+
+ private final OnClickListener mBackgroundClickListener = (view) -> {
+ if (mState == STATE_AUTHENTICATED) {
+ Log.w(TAG, "Ignoring background click after authenticated");
+ return;
+ } else if (mSize == AuthDialog.SIZE_SMALL) {
+ Log.w(TAG, "Ignoring background click during small dialog");
+ return;
+ }
+ mCallback.onAction(Callback.ACTION_USER_CANCELED);
+ };
+
+ public AuthBiometricView(Context context) {
+ this(context, null);
+ }
+
+ public AuthBiometricView(Context context, AttributeSet attrs) {
+ this(context, attrs, new Injector());
+ }
+
+ @VisibleForTesting
+ AuthBiometricView(Context context, AttributeSet attrs, Injector injector) {
+ super(context, attrs);
+ mHandler = new Handler(Looper.getMainLooper());
+ mTextColorError = getResources().getColor(
+ R.color.biometric_dialog_error, context.getTheme());
+ mTextColorHint = getResources().getColor(
+ R.color.biometric_dialog_gray, context.getTheme());
+
+ mInjector = injector;
+ mInjector.mBiometricView = this;
+ }
+
+ public void setPanelController(AuthPanelController panelController) {
+ mPanelController = panelController;
+ }
+
+ public void setBiometricPromptBundle(Bundle bundle) {
+ mBundle = bundle;
+ }
+
+ public void setCallback(Callback callback) {
+ mCallback = callback;
+ }
+
+ public void setBackgroundView(View backgroundView) {
+ backgroundView.setOnClickListener(mBackgroundClickListener);
+ }
+
+ public void setRequireConfirmation(boolean requireConfirmation) {
+ mRequireConfirmation = requireConfirmation;
+ }
+
+ @VisibleForTesting
+ void updateSize(@AuthDialog.DialogSize int newSize) {
+ Log.v(TAG, "Current: " + mSize + " New: " + newSize);
+ if (newSize == AuthDialog.SIZE_SMALL) {
+ mTitleView.setVisibility(View.GONE);
+ mSubtitleView.setVisibility(View.GONE);
+ mDescriptionView.setVisibility(View.GONE);
+ mErrorView.setVisibility(View.GONE);
+ mNegativeButton.setVisibility(View.GONE);
+
+ final float iconPadding = getResources()
+ .getDimension(R.dimen.biometric_dialog_icon_padding);
+ mIconView.setY(getHeight() - mIconView.getHeight() - iconPadding);
+
+ final int newHeight = mIconView.getHeight() + 2 * (int) iconPadding;
+ mPanelController.updateForContentDimensions(mMediumWidth, newHeight,
+ false /* animate */);
+
+ mSize = newSize;
+ } else if (mSize == AuthDialog.SIZE_SMALL && newSize == AuthDialog.SIZE_MEDIUM) {
+ if (mDialogSizeAnimating) {
+ return;
+ }
+ mDialogSizeAnimating = true;
+
+ // Animate the icon back to original position
+ final ValueAnimator iconAnimator =
+ ValueAnimator.ofFloat(mIconView.getY(), mIconOriginalY);
+ iconAnimator.addUpdateListener((animation) -> {
+ mIconView.setY((float) animation.getAnimatedValue());
+ });
+
+ // Animate the text
+ final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
+ opacityAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS);
+ opacityAnimator.addUpdateListener((animation) -> {
+ final float opacity = (float) animation.getAnimatedValue();
+
+ mTitleView.setAlpha(opacity);
+ mErrorView.setAlpha(opacity);
+ mNegativeButton.setAlpha(opacity);
+ mTryAgainButton.setAlpha(opacity);
+
+ if (!TextUtils.isEmpty(mSubtitleView.getText())) {
+ mSubtitleView.setAlpha(opacity);
+ }
+ if (!TextUtils.isEmpty(mDescriptionView.getText())) {
+ mDescriptionView.setAlpha(opacity);
+ }
+ });
+
+ // Choreograph together
+ final AnimatorSet as = new AnimatorSet();
+ as.setDuration(AuthDialog.ANIMATE_DURATION_MS);
+ as.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ super.onAnimationStart(animation);
+ mTitleView.setVisibility(View.VISIBLE);
+ mErrorView.setVisibility(View.VISIBLE);
+ mNegativeButton.setVisibility(View.VISIBLE);
+ mTryAgainButton.setVisibility(View.VISIBLE);
+
+ if (!TextUtils.isEmpty(mSubtitleView.getText())) {
+ mSubtitleView.setVisibility(View.VISIBLE);
+ }
+ if (!TextUtils.isEmpty(mDescriptionView.getText())) {
+ mDescriptionView.setVisibility(View.VISIBLE);
+ }
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ mSize = newSize;
+ mDialogSizeAnimating = false;
+ }
+ });
+
+ as.play(iconAnimator).with(opacityAnimator);
+ as.start();
+ // Animate the panel
+ mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight,
+ true /* animate */);
+ } else if (newSize == AuthDialog.SIZE_MEDIUM) {
+ mPanelController.updateForContentDimensions(mMediumWidth, mMediumHeight,
+ false /* animate */);
+ mSize = newSize;
+ } else {
+ Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize);
+ }
+ }
+
+ public void updateState(@BiometricState int newState) {
+ Log.v(TAG, "newState: " + newState);
+ switch (newState) {
+ case STATE_AUTHENTICATING_ANIMATING_IN:
+ case STATE_AUTHENTICATING:
+ removePendingAnimations();
+ if (mRequireConfirmation) {
+ mPositiveButton.setEnabled(false);
+ mPositiveButton.setVisibility(View.VISIBLE);
+ }
+ break;
+
+ case STATE_AUTHENTICATED:
+ if (mSize != AuthDialog.SIZE_SMALL) {
+ mPositiveButton.setVisibility(View.GONE);
+ mNegativeButton.setVisibility(View.GONE);
+ mErrorView.setVisibility(View.INVISIBLE);
+ }
+ mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_AUTHENTICATED),
+ getDelayAfterAuthenticatedDurationMs());
+ break;
+
+ case STATE_PENDING_CONFIRMATION:
+ removePendingAnimations();
+ mNegativeButton.setText(R.string.cancel);
+ mNegativeButton.setContentDescription(getResources().getString(R.string.cancel));
+ mPositiveButton.setEnabled(true);
+ mErrorView.setTextColor(mTextColorHint);
+ mErrorView.setText(R.string.biometric_dialog_tap_confirm);
+ mErrorView.setVisibility(View.VISIBLE);
+ break;
+
+ case STATE_ERROR:
+ if (mSize == AuthDialog.SIZE_SMALL) {
+ updateSize(AuthDialog.SIZE_MEDIUM);
+ }
+ break;
+
+ default:
+ Log.w(TAG, "Unhandled state: " + newState);
+ break;
+ }
+
+ mState = newState;
+ }
+
+ public void onDialogAnimatedIn() {
+ updateState(STATE_AUTHENTICATING);
+ }
+
+ public void onAuthenticationSucceeded() {
+ removePendingAnimations();
+ if (mRequireConfirmation) {
+ updateState(STATE_PENDING_CONFIRMATION);
+ } else {
+ updateState(STATE_AUTHENTICATED);
+ }
+ }
+
+ public void onAuthenticationFailed(String failureReason) {
+ showTemporaryMessage(failureReason, mResetErrorRunnable);
+ updateState(STATE_ERROR);
+ }
+
+ public void onHelp(String help) {
+ if (mSize != AuthDialog.SIZE_MEDIUM) {
+ return;
+ }
+ showTemporaryMessage(help, mResetHelpRunnable);
+ updateState(STATE_HELP);
+ }
+
+ private void setTextOrHide(TextView view, String string) {
+ if (TextUtils.isEmpty(string)) {
+ view.setVisibility(View.GONE);
+ } else {
+ view.setText(string);
+ }
+ }
+
+ private void setText(TextView view, String string) {
+ view.setText(string);
+ }
+
+ // Remove all pending icon and text animations
+ private void removePendingAnimations() {
+ mHandler.removeCallbacks(mResetHelpRunnable);
+ mHandler.removeCallbacks(mResetErrorRunnable);
+ }
+
+ private void showTemporaryMessage(String message, Runnable resetMessageRunnable) {
+ removePendingAnimations();
+ mErrorView.setText(message);
+ mErrorView.setTextColor(mTextColorError);
+ mErrorView.setVisibility(View.VISIBLE);
+ mHandler.postDelayed(resetMessageRunnable, BiometricPrompt.HIDE_DIALOG_DELAY);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ initializeViews();
+ }
+
+ @VisibleForTesting
+ void initializeViews() {
+ mTitleView = mInjector.getTitleView();
+ mSubtitleView = mInjector.getSubtitleView();
+ mDescriptionView = mInjector.getDescriptionView();
+ mIconView = mInjector.getIconView();
+ mErrorView = mInjector.getErrorView();
+ mNegativeButton = mInjector.getNegativeButton();
+ mPositiveButton = mInjector.getPositiveButton();
+ mTryAgainButton = mInjector.getTryAgainButton();
+
+ mNegativeButton.setOnClickListener((view) -> {
+ if (mState == STATE_PENDING_CONFIRMATION) {
+ mCallback.onAction(Callback.ACTION_USER_CANCELED);
+ } else {
+ mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
+ }
+ });
+
+ mPositiveButton.setOnClickListener((view) -> {
+ updateState(STATE_AUTHENTICATED);
+ });
+
+ mTryAgainButton.setOnClickListener((view) -> {
+ updateState(STATE_AUTHENTICATING);
+ mCallback.onAction(Callback.ACTION_BUTTON_TRY_AGAIN);
+ mTryAgainButton.setVisibility(View.GONE);
+ });
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ setText(mTitleView, mBundle.getString(BiometricPrompt.KEY_TITLE));
+ setText(mNegativeButton, mBundle.getString(BiometricPrompt.KEY_NEGATIVE_TEXT));
+
+ setTextOrHide(mSubtitleView, mBundle.getString(BiometricPrompt.KEY_SUBTITLE));
+ setTextOrHide(mDescriptionView, mBundle.getString(BiometricPrompt.KEY_DESCRIPTION));
+
+ updateState(STATE_AUTHENTICATING_ANIMATING_IN);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ final int height = MeasureSpec.getSize(heightMeasureSpec);
+ final int newWidth = Math.min(width, height);
+
+ int totalHeight = 0;
+ final int numChildren = getChildCount();
+ for (int i = 0; i < numChildren; i++) {
+ final View child = getChildAt(i);
+
+ if (child.getId() == R.id.biometric_icon) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.AT_MOST),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+ } else if (child.getId() == R.id.button_bar) {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
+ MeasureSpec.EXACTLY));
+ } else {
+ child.measure(
+ MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
+ }
+ totalHeight += child.getMeasuredHeight();
+ }
+
+ // Use the new width so it's centered horizontally
+ setMeasuredDimension(newWidth, totalHeight);
+
+ mMediumHeight = totalHeight;
+ mMediumWidth = getMeasuredWidth();
+ }
+
+ @Override
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ // Start with initial size only once. Subsequent layout changes don't matter since we
+ // only care about the initial icon position.
+ if (mIconOriginalY == 0) {
+ mIconOriginalY = mIconView.getY();
+ updateSize(mRequireConfirmation ? AuthDialog.SIZE_MEDIUM
+ : AuthDialog.SIZE_SMALL);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
new file mode 100644
index 000000000000..9cb5fcf4de00
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -0,0 +1,400 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.keyguard.WakefulnessLifecycle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Top level container/controller for the BiometricPrompt UI.
+ */
+public class AuthContainerView extends LinearLayout
+ implements AuthDialog, WakefulnessLifecycle.Observer {
+
+ private static final String TAG = "BiometricPrompt/AuthContainerView";
+ private static final int ANIMATION_DURATION_SHOW_MS = 250;
+ private static final int ANIMATION_DURATION_AWAY_MS = 350; // ms
+
+ private static final int STATE_UNKNOWN = 0;
+ private static final int STATE_ANIMATING_IN = 1;
+ private static final int STATE_PENDING_DISMISS = 2;
+ private static final int STATE_SHOWING = 3;
+ private static final int STATE_ANIMATING_OUT = 4;
+ private static final int STATE_GONE = 5;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({STATE_UNKNOWN, STATE_ANIMATING_IN, STATE_PENDING_DISMISS, STATE_SHOWING,
+ STATE_ANIMATING_OUT, STATE_GONE})
+ @interface ContainerState {}
+
+ final Config mConfig;
+ private final IBinder mWindowToken = new Binder();
+ private final WindowManager mWindowManager;
+ private final AuthPanelController mPanelController;
+ private final Interpolator mLinearOutSlowIn;
+ @VisibleForTesting final BiometricCallback mBiometricCallback;
+
+ private final ViewGroup mContainerView;
+ private final AuthBiometricView mBiometricView;
+
+ private final ImageView mBackgroundView;
+ private final ScrollView mScrollView;
+ private final View mPanelView;
+
+ private final float mTranslationY;
+
+ @VisibleForTesting final WakefulnessLifecycle mWakefulnessLifecycle;
+
+ private @ContainerState int mContainerState = STATE_UNKNOWN;
+
+ static class Config {
+ Context mContext;
+ AuthDialogCallback mCallback;
+ Bundle mBiometricPromptBundle;
+ boolean mRequireConfirmation;
+ int mUserId;
+ String mOpPackageName;
+ int mModalityMask;
+ boolean mSkipIntro;
+ }
+
+ public static class Builder {
+ Config mConfig;
+
+ public Builder(Context context) {
+ mConfig = new Config();
+ mConfig.mContext = context;
+ }
+
+ public Builder setCallback(AuthDialogCallback callback) {
+ mConfig.mCallback = callback;
+ return this;
+ }
+
+ public Builder setBiometricPromptBundle(Bundle bundle) {
+ mConfig.mBiometricPromptBundle = bundle;
+ return this;
+ }
+
+ public Builder setRequireConfirmation(boolean requireConfirmation) {
+ mConfig.mRequireConfirmation = requireConfirmation;
+ return this;
+ }
+
+ public Builder setUserId(int userId) {
+ mConfig.mUserId = userId;
+ return this;
+ }
+
+ public Builder setOpPackageName(String opPackageName) {
+ mConfig.mOpPackageName = opPackageName;
+ return this;
+ }
+
+ public Builder setSkipIntro(boolean skip) {
+ mConfig.mSkipIntro = skip;
+ return this;
+ }
+
+ public AuthContainerView build(int modalityMask) { // TODO
+ return new AuthContainerView(mConfig);
+ }
+ }
+
+ @VisibleForTesting
+ final class BiometricCallback implements AuthBiometricView.Callback {
+ @Override
+ public void onAction(int action) {
+ switch (action) {
+ case AuthBiometricView.Callback.ACTION_AUTHENTICATED:
+ animateAway(AuthDialogCallback.DISMISSED_AUTHENTICATED);
+ break;
+ case AuthBiometricView.Callback.ACTION_USER_CANCELED:
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ break;
+ case AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE:
+ animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
+ break;
+ case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
+ mConfig.mCallback.onTryAgainPressed();
+ break;
+ default:
+ Log.e(TAG, "Unhandled action: " + action);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ AuthContainerView(Config config) {
+ super(config.mContext);
+
+ mConfig = config;
+ mWindowManager = mContext.getSystemService(WindowManager.class);
+ mWakefulnessLifecycle = Dependency.get(WakefulnessLifecycle.class);
+
+ mTranslationY = getResources()
+ .getDimension(R.dimen.biometric_dialog_animation_translation_offset);
+ mLinearOutSlowIn = Interpolators.LINEAR_OUT_SLOW_IN;
+ mBiometricCallback = new BiometricCallback();
+
+ final LayoutInflater factory = LayoutInflater.from(mContext);
+ mContainerView = (ViewGroup) factory.inflate(
+ R.layout.auth_container_view, this, false /* attachToRoot */);
+
+ // TODO: Depends on modality
+ mBiometricView = (AuthBiometricFaceView)
+ factory.inflate(R.layout.auth_biometric_face_view, null, false);
+
+ mBackgroundView = mContainerView.findViewById(R.id.background);
+
+ mPanelView = mContainerView.findViewById(R.id.panel);
+ mPanelController = new AuthPanelController(mContext, mPanelView);
+
+ mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation);
+ mBiometricView.setPanelController(mPanelController);
+ mBiometricView.setBiometricPromptBundle(config.mBiometricPromptBundle);
+ mBiometricView.setCallback(mBiometricCallback);
+ mBiometricView.setBackgroundView(mBackgroundView);
+
+ mScrollView = mContainerView.findViewById(R.id.scrollview);
+ mScrollView.addView(mBiometricView);
+ addView(mContainerView);
+
+ setOnKeyListener((v, keyCode, event) -> {
+ if (keyCode != KeyEvent.KEYCODE_BACK) {
+ return false;
+ }
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ }
+ return true;
+ });
+
+ setFocusableInTouchMode(true);
+ requestFocus();
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ mPanelController.setContainerDimensions(getMeasuredWidth(), getMeasuredHeight());
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mWakefulnessLifecycle.addObserver(this);
+
+ if (mConfig.mSkipIntro) {
+ mContainerState = STATE_SHOWING;
+ } else {
+ mContainerState = STATE_ANIMATING_IN;
+ // The background panel and content are different views since we need to be able to
+ // animate them separately in other places.
+ mPanelView.setY(mTranslationY);
+ mScrollView.setY(mTranslationY);
+
+ setAlpha(0f);
+ postOnAnimation(() -> {
+ mPanelView.animate()
+ .translationY(0)
+ .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .withEndAction(this::onDialogAnimatedIn)
+ .start();
+ mScrollView.animate()
+ .translationY(0)
+ .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .start();
+ animate()
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_SHOW_MS)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .start();
+ });
+ }
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mWakefulnessLifecycle.removeObserver(this);
+ }
+
+ @Override
+ public void onStartedGoingToSleep() {
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+ }
+
+ @Override
+ public void show(WindowManager wm) {
+ wm.addView(this, getLayoutParams(mWindowToken));
+ }
+
+ @Override
+ public void dismissWithoutCallback(boolean animate) {
+ if (animate) {
+ animateAway(false /* sendReason */, 0 /* reason */);
+ } else {
+ mWindowManager.removeView(this);
+ }
+ }
+
+ @Override
+ public void dismissFromSystemServer() {
+ mWindowManager.removeView(this);
+ }
+
+ @Override
+ public void onAuthenticationSucceeded() {
+ mBiometricView.onAuthenticationSucceeded();
+ }
+
+ @Override
+ public void onAuthenticationFailed(String failureReason) {
+ mBiometricView.onAuthenticationFailed(failureReason);
+ }
+
+ @Override
+ public void onHelp(String help) {
+ mBiometricView.onHelp(help);
+ }
+
+ @Override
+ public void onError(String error) {
+
+ }
+
+ @Override
+ public void onSaveState(Bundle outState) {
+
+ }
+
+ @Override
+ public void restoreState(Bundle savedState) {
+
+ }
+
+ @Override
+ public String getOpPackageName() {
+ return mConfig.mOpPackageName;
+ }
+
+ @VisibleForTesting
+ void animateAway(int reason) {
+ animateAway(true /* sendReason */, reason);
+ }
+
+ private void animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason) {
+ if (mContainerState == STATE_ANIMATING_IN) {
+ Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
+ mContainerState = STATE_PENDING_DISMISS;
+ return;
+ }
+
+ if (mContainerState == STATE_ANIMATING_OUT) {
+ Log.w(TAG, "Already dismissing, sendReason: " + sendReason + " reason: " + reason);
+ return;
+ }
+ mContainerState = STATE_ANIMATING_OUT;
+
+ final Runnable endActionRunnable = () -> {
+ setVisibility(View.INVISIBLE);
+ mWindowManager.removeView(this);
+ if (sendReason) {
+ mConfig.mCallback.onDismissed(reason);
+ }
+ };
+
+ postOnAnimation(() -> {
+ mPanelView.animate()
+ .translationY(mTranslationY)
+ .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .withEndAction(endActionRunnable)
+ .start();
+ mScrollView.animate()
+ .translationY(mTranslationY)
+ .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .start();
+ animate()
+ .alpha(0f)
+ .setDuration(ANIMATION_DURATION_AWAY_MS)
+ .setInterpolator(mLinearOutSlowIn)
+ .withLayer()
+ .start();
+ });
+ }
+
+ private void onDialogAnimatedIn() {
+ if (mContainerState == STATE_PENDING_DISMISS) {
+ Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now");
+ animateAway(false /* sendReason */, 0);
+ return;
+ }
+ mContainerState = STATE_SHOWING;
+ mBiometricView.onDialogAnimatedIn();
+ }
+
+ /**
+ * @param windowToken token for the window
+ * @return
+ */
+ public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
+ final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+ WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+ PixelFormat.TRANSLUCENT);
+ lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+ lp.setTitle("BiometricPrompt");
+ lp.token = windowToken;
+ return lp;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a8e572216315..dcd01c682726 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.systemui.biometrics;
@@ -29,13 +29,13 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.provider.Settings;
import android.util.Log;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import com.android.systemui.SystemUI;
-import com.android.systemui.biometrics.ui.BiometricDialogView;
import com.android.systemui.statusbar.CommandQueue;
import java.util.List;
@@ -44,9 +44,12 @@ import java.util.List;
* Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
* appropriate biometric UI (e.g. BiometricDialogView).
*/
-public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callbacks,
- DialogViewCallback {
- private static final String TAG = "BiometricDialogImpl";
+public class AuthController extends SystemUI implements CommandQueue.Callbacks,
+ AuthDialogCallback {
+ private static final String USE_NEW_DIALOG =
+ "com.android.systemui.biometrics.AuthController.USE_NEW_DIALOG";
+
+ private static final String TAG = "BiometricPrompt/AuthController";
private static final boolean DEBUG = true;
private final Injector mInjector;
@@ -54,7 +57,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
// TODO: These should just be saved from onSaveState
private SomeArgs mCurrentDialogArgs;
@VisibleForTesting
- BiometricDialog mCurrentDialog;
+ AuthDialog mCurrentDialog;
private Handler mHandler = new Handler(Looper.getMainLooper());
private WindowManager mWindowManager;
@@ -107,27 +110,27 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
@Override
public void onDismissed(@DismissedReason int reason) {
switch (reason) {
- case DialogViewCallback.DISMISSED_USER_CANCELED:
+ case AuthDialogCallback.DISMISSED_USER_CANCELED:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
break;
- case DialogViewCallback.DISMISSED_BUTTON_NEGATIVE:
+ case AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
break;
- case DialogViewCallback.DISMISSED_BUTTON_POSITIVE:
+ case AuthDialogCallback.DISMISSED_BUTTON_POSITIVE:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
break;
- case DialogViewCallback.DISMISSED_AUTHENTICATED:
+ case AuthDialogCallback.DISMISSED_AUTHENTICATED:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
break;
- case DialogViewCallback.DISMISSED_ERROR:
+ case AuthDialogCallback.DISMISSED_ERROR:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_ERROR);
break;
- case DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER:
+ case AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
break;
@@ -156,12 +159,12 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
}
}
- public BiometricDialogImpl() {
+ public AuthController() {
this(new Injector());
}
@VisibleForTesting
- BiometricDialogImpl(Injector injector) {
+ AuthController(Injector injector) {
mInjector = injector;
}
@@ -248,12 +251,13 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
final String opPackageName = (String) args.arg4;
// Create a new dialog but do not replace the current one yet.
- final BiometricDialog newDialog = buildDialog(
+ final AuthDialog newDialog = buildDialog(
biometricPromptBundle,
requireConfirmation,
userId,
type,
- opPackageName);
+ opPackageName,
+ skipAnimation);
if (newDialog == null) {
Log.e(TAG, "Unsupported type: " + type);
@@ -282,7 +286,7 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
mReceiver = (IBiometricServiceReceiverInternal) args.arg2;
mCurrentDialog = newDialog;
- mCurrentDialog.show(mWindowManager, skipAnimation);
+ mCurrentDialog.show(mWindowManager);
}
private void onDialogDismissed(@DismissedReason int reason) {
@@ -309,14 +313,27 @@ public class BiometricDialogImpl extends SystemUI implements CommandQueue.Callba
}
}
- protected BiometricDialog buildDialog(Bundle biometricPromptBundle,
- boolean requireConfirmation, int userId, int type, String opPackageName) {
- return new BiometricDialogView.Builder(mContext)
- .setCallback(this)
- .setBiometricPromptBundle(biometricPromptBundle)
- .setRequireConfirmation(requireConfirmation)
- .setUserId(userId)
- .setOpPackageName(opPackageName)
- .build(type);
+ protected AuthDialog buildDialog(Bundle biometricPromptBundle, boolean requireConfirmation,
+ int userId, int type, String opPackageName, boolean skipIntro) {
+ if (Settings.Secure.getIntForUser(
+ mContext.getContentResolver(), USE_NEW_DIALOG, userId, 0) != 0) {
+ return new AuthContainerView.Builder(mContext)
+ .setCallback(this)
+ .setBiometricPromptBundle(biometricPromptBundle)
+ .setRequireConfirmation(requireConfirmation)
+ .setUserId(userId)
+ .setOpPackageName(opPackageName)
+ .setSkipIntro(skipIntro)
+ .build(type);
+ } else {
+ return new BiometricDialogView.Builder(mContext)
+ .setCallback(this)
+ .setBiometricPromptBundle(biometricPromptBundle)
+ .setRequireConfirmation(requireConfirmation)
+ .setUserId(userId)
+ .setOpPackageName(opPackageName)
+ .setSkipIntro(skipIntro)
+ .build(type);
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index d4baefd64512..a6a857ca5945 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -16,16 +16,18 @@
package com.android.systemui.biometrics;
+import android.annotation.IntDef;
import android.hardware.biometrics.BiometricPrompt;
import android.os.Bundle;
import android.view.WindowManager;
-import com.android.systemui.biometrics.ui.BiometricDialogView;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
* Interface for the biometric dialog UI.
*/
-public interface BiometricDialog {
+public interface AuthDialog {
// TODO: Clean up save/restore state
String[] KEYS_TO_BACKUP = {
@@ -49,12 +51,24 @@ public interface BiometricDialog {
BiometricDialogView.KEY_ERROR_TEXT_COLOR,
};
+ int SIZE_UNKNOWN = 0;
+ int SIZE_SMALL = 1;
+ int SIZE_MEDIUM = 2;
+ int SIZE_LARGE = 3;
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SIZE_UNKNOWN, SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE})
+ @interface DialogSize {}
+
+ /**
+ * Animation duration, e.g. small to medium dialog, icon translation, etc.
+ */
+ int ANIMATE_DURATION_MS = 150;
+
/**
* Show the dialog.
* @param wm
- * @param skipIntroAnimation
*/
- void show(WindowManager wm, boolean skipIntroAnimation);
+ void show(WindowManager wm);
/**
* Dismiss the dialog without sending a callback.
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
index b65d1e823a9b..70752f5f860e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/DialogViewCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.systemui.biometrics;
@@ -22,7 +22,7 @@ import android.annotation.IntDef;
* Callback interface for dialog views. These should be implemented by the controller (e.g.
* FingerprintDialogImpl) and passed into their views (e.g. FingerprintDialogView).
*/
-public interface DialogViewCallback {
+public interface AuthDialogCallback {
int DISMISSED_USER_CANCELED = 1;
int DISMISSED_BUTTON_NEGATIVE = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
new file mode 100644
index 000000000000..55ba0491dc1e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+
+import com.android.systemui.R;
+import com.android.systemui.biometrics.AuthDialog;
+
+/**
+ * Controls the back panel and its animations for the BiometricPrompt UI.
+ */
+public class AuthPanelController extends ViewOutlineProvider {
+
+ private static final String TAG = "BiometricPrompt/AuthPanelController";
+ private static final boolean DEBUG = false;
+
+ private final Context mContext;
+ private final View mPanelView;
+ private final float mCornerRadius;
+ private final int mBiometricMargin;
+
+ private int mContainerWidth;
+ private int mContainerHeight;
+
+ private int mContentWidth;
+ private int mContentHeight;
+
+ @Override
+ public void getOutline(View view, Outline outline) {
+ final int left = (mContainerWidth - mContentWidth) / 2;
+ final int right = mContainerWidth - left;
+ final int top = mContentHeight < mContainerHeight
+ ? mContainerHeight - mContentHeight - mBiometricMargin
+ : mBiometricMargin;
+ final int bottom = mContainerHeight - mBiometricMargin;
+ outline.setRoundRect(left, top, right, bottom, mCornerRadius);
+ }
+
+ public void setContainerDimensions(int containerWidth, int containerHeight) {
+ if (DEBUG) {
+ Log.v(TAG, "Container Width: " + containerWidth + " Height: " + containerHeight);
+ }
+ mContainerWidth = containerWidth;
+ mContainerHeight = containerHeight;
+ }
+
+ public void updateForContentDimensions(int contentWidth, int contentHeight, boolean animate) {
+ if (DEBUG) {
+ Log.v(TAG, "Content Width: " + contentWidth
+ + " Height: " + contentHeight
+ + " Animate: " + animate);
+ }
+
+ if (mContainerWidth == 0 || mContainerHeight == 0) {
+ Log.w(TAG, "Not done measuring yet");
+ return;
+ }
+
+ if (animate) {
+ ValueAnimator heightAnimator = ValueAnimator.ofInt(mContentHeight, contentHeight);
+ heightAnimator.setDuration(AuthDialog.ANIMATE_DURATION_MS);
+ heightAnimator.addUpdateListener((animation) -> {
+ mContentHeight = (int) animation.getAnimatedValue();
+ mPanelView.invalidateOutline();
+ });
+ heightAnimator.start();
+ } else {
+ mContentWidth = contentWidth;
+ mContentHeight = contentHeight;
+ mPanelView.invalidateOutline();
+ }
+ }
+
+ AuthPanelController(Context context, View panelView) {
+ mContext = context;
+ mPanelView = panelView;
+ mCornerRadius = context.getResources()
+ .getDimension(R.dimen.biometric_dialog_corner_size);
+ mBiometricMargin = (int) context.getResources()
+ .getDimension(R.dimen.biometric_dialog_border_padding);
+ mPanelView.setOutlineProvider(this);
+ mPanelView.setClipToOutline(true);
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
index 290475562893..89d08d795128 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/BiometricDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/BiometricDialogView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.ui;
+package com.android.systemui.biometrics;
import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE;
@@ -58,17 +58,15 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Dependency;
import com.android.systemui.Interpolators;
import com.android.systemui.R;
-import com.android.systemui.biometrics.BiometricDialog;
-import com.android.systemui.biometrics.DialogViewCallback;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.util.leak.RotationUtils;
/**
* Abstract base class. Shows a dialog for BiometricPrompt.
*/
-public abstract class BiometricDialogView extends LinearLayout implements BiometricDialog {
+public abstract class BiometricDialogView extends LinearLayout implements AuthDialog {
- private static final String TAG = "BiometricDialogView";
+ private static final String TAG = "BiometricPrompt/DialogView";
public static final String KEY_TRY_AGAIN_VISIBILITY = "key_try_again_visibility";
public static final String KEY_CONFIRM_VISIBILITY = "key_confirm_visibility";
@@ -112,7 +110,7 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
private final float mAnimationTranslationOffset;
private final int mErrorColor;
private final float mDialogWidth;
- protected final DialogViewCallback mCallback;
+ protected final AuthDialogCallback mCallback;
private final DialogOutlineProvider mOutlineProvider = new DialogOutlineProvider();
protected final ViewGroup mLayout;
@@ -176,7 +174,7 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
new WakefulnessLifecycle.Observer() {
@Override
public void onStartedGoingToSleep() {
- animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
}
};
@@ -226,17 +224,18 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
public static final int TYPE_FACE = BiometricAuthenticator.TYPE_FACE;
private Context mContext;
- private DialogViewCallback mCallback;
+ private AuthDialogCallback mCallback;
private Bundle mBundle;
private boolean mRequireConfirmation;
private int mUserId;
private String mOpPackageName;
+ private boolean mSkipIntro;
public Builder(Context context) {
mContext = context;
}
- public Builder setCallback(DialogViewCallback callback) {
+ public Builder setCallback(AuthDialogCallback callback) {
mCallback = callback;
return this;
}
@@ -261,6 +260,11 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
return this;
}
+ public Builder setSkipIntro(boolean skipIntro) {
+ mSkipIntro = skipIntro;
+ return this;
+ }
+
public BiometricDialogView build(int type) {
return build(type, new Injector());
}
@@ -278,6 +282,7 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
dialog.setRequireConfirmation(mRequireConfirmation);
dialog.setUserId(mUserId);
dialog.setOpPackageName(mOpPackageName);
+ dialog.setSkipIntro(mSkipIntro);
return dialog;
}
}
@@ -288,7 +293,7 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
}
}
- protected BiometricDialogView(Context context, DialogViewCallback callback, Injector injector) {
+ protected BiometricDialogView(Context context, AuthDialogCallback callback, Injector injector) {
super(context);
mWakefulnessLifecycle = injector.getWakefulnessLifecycle();
@@ -319,7 +324,7 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
return false;
}
if (event.getAction() == KeyEvent.ACTION_UP) {
- animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
}
return true;
}
@@ -348,16 +353,16 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
mNegativeButton.setOnClickListener((View v) -> {
if (mState == STATE_PENDING_CONFIRMATION || mState == STATE_AUTHENTICATED) {
- animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
} else {
- animateAway(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE);
+ animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
}
});
mPositiveButton.setOnClickListener((View v) -> {
updateState(STATE_AUTHENTICATED);
mHandler.postDelayed(() -> {
- animateAway(DialogViewCallback.DISMISSED_BUTTON_POSITIVE);
+ animateAway(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
}, getDelayAfterAuthenticatedDurationMs());
});
@@ -639,20 +644,20 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
v.setClickable(true);
v.setOnClickListener(v1 -> {
if (mState != STATE_AUTHENTICATED && shouldGrayAreaDismissDialog()) {
- animateAway(DialogViewCallback.DISMISSED_USER_CANCELED);
+ animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
}
});
}
- private void animateAway(@DialogViewCallback.DismissedReason int reason) {
+ private void animateAway(@AuthDialogCallback.DismissedReason int reason) {
animateAway(true /* sendReason */, reason);
}
/**
* Animate the dialog away
- * @param reason one of the {@link DialogViewCallback} codes
+ * @param reason one of the {@link AuthDialogCallback} codes
*/
- private void animateAway(boolean sendReason, @DialogViewCallback.DismissedReason int reason) {
+ private void animateAway(boolean sendReason, @AuthDialogCallback.DismissedReason int reason) {
if (!mCompletedAnimatingIn) {
Log.w(TAG, "startDismiss(): waiting for onDialogAnimatedIn");
mPendingDismissDialog = true;
@@ -733,8 +738,7 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
}
@Override
- public void show(WindowManager wm, boolean skipIntroAnimation) {
- setSkipIntro(skipIntroAnimation);
+ public void show(WindowManager wm) {
wm.addView(this, getLayoutParams(mWindowToken));
}
@@ -757,7 +761,7 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
@Override
public void dismissFromSystemServer() {
- animateAway(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER);
+ animateAway(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
}
@Override
@@ -768,7 +772,7 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
updateState(STATE_PENDING_CONFIRMATION);
} else {
mHandler.postDelayed(() -> {
- animateAway(DialogViewCallback.DISMISSED_AUTHENTICATED);
+ animateAway(AuthDialogCallback.DISMISSED_AUTHENTICATED);
}, getDelayAfterAuthenticatedDurationMs());
updateState(STATE_AUTHENTICATED);
@@ -810,7 +814,7 @@ public abstract class BiometricDialogView extends LinearLayout implements Biomet
showTryAgainButton(false /* show */);
mHandler.postDelayed(() -> {
- animateAway(DialogViewCallback.DISMISSED_ERROR);
+ animateAway(AuthDialogCallback.DISMISSED_ERROR);
}, BiometricPrompt.HIDE_DIALOG_DELAY);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
index 9e4fe24aec40..d5dcbf126b63 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FaceDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FaceDialogView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.ui;
+package com.android.systemui.biometrics;
import android.content.Context;
import android.graphics.drawable.Animatable2;
@@ -26,7 +26,6 @@ import android.util.Log;
import android.view.View;
import com.android.systemui.R;
-import com.android.systemui.biometrics.DialogViewCallback;
/**
* This class loads the view for the system-provided dialog. The view consists of:
@@ -35,7 +34,7 @@ import com.android.systemui.biometrics.DialogViewCallback;
*/
public class FaceDialogView extends BiometricDialogView {
- private static final String TAG = "FaceDialogView";
+ private static final String TAG = "BiometricPrompt/FaceDialogView";
private static final String KEY_DIALOG_ANIMATED_IN = "key_dialog_animated_in";
@@ -110,7 +109,7 @@ public class FaceDialogView extends BiometricDialogView {
announceAccessibilityEvent();
};
- protected FaceDialogView(Context context, DialogViewCallback callback, Injector injector) {
+ protected FaceDialogView(Context context, AuthDialogCallback callback, Injector injector) {
super(context, callback, injector);
mIconController = new IconController();
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
index 292588024646..cda217619eed 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/FingerprintDialogView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/FingerprintDialogView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.ui;
+package com.android.systemui.biometrics;
import android.content.Context;
import android.graphics.drawable.AnimatedVectorDrawable;
@@ -22,7 +22,6 @@ import android.graphics.drawable.Drawable;
import android.util.Log;
import com.android.systemui.R;
-import com.android.systemui.biometrics.DialogViewCallback;
/**
* This class loads the view for the system-provided dialog. The view consists of:
@@ -31,9 +30,9 @@ import com.android.systemui.biometrics.DialogViewCallback;
*/
public class FingerprintDialogView extends BiometricDialogView {
- private static final String TAG = "FingerprintDialogView";
+ private static final String TAG = "BiometricPrompt/FingerprintDialogView";
- protected FingerprintDialogView(Context context, DialogViewCallback callback,
+ protected FingerprintDialogView(Context context, AuthDialogCallback callback,
Injector injector) {
super(context, callback, injector);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
index 028b1aa5b513..edd8089c35f9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.ui;
+package com.android.systemui.biometrics;
import android.content.Context;
import android.util.DisplayMetrics;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
new file mode 100644
index 000000000000..128e819ccedd
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import com.android.systemui.R;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthBiometricFaceViewTest extends SysuiTestCase {
+
+ @Mock
+ AuthBiometricView.Callback mCallback;
+
+ private TestableFaceView mFaceView;
+
+ @Mock private Button mNegativeButton;
+ @Mock private Button mPositiveButton;
+ @Mock private Button mTryAgainButton;
+ @Mock private TextView mErrorView;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mFaceView = new TestableFaceView(mContext);
+ mFaceView.mIconController = mock(TestableFaceView.TestableIconController.class);
+ mFaceView.setCallback(mCallback);
+ mFaceView.mNegativeButton = mNegativeButton;
+ mFaceView.mPositiveButton = mPositiveButton;
+ mFaceView.mTryAgainButton = mTryAgainButton;
+ mFaceView.mErrorView = mErrorView;
+ }
+
+ @Test
+ public void testStateUpdated_whenDialogAnimatedIn() {
+ mFaceView.onDialogAnimatedIn();
+ verify(mFaceView.mIconController)
+ .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
+ }
+
+ @Test
+ public void testIconUpdatesState_whenDialogStateUpdated() {
+ mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATING);
+ verify(mFaceView.mIconController)
+ .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING));
+
+ mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED);
+ verify(mFaceView.mIconController).updateState(
+ eq(AuthBiometricFaceView.STATE_AUTHENTICATING),
+ eq(AuthBiometricFaceView.STATE_AUTHENTICATED));
+ }
+
+ public class TestableFaceView extends AuthBiometricFaceView {
+
+ public class TestableIconController extends IconController {
+ TestableIconController(Context context, ImageView iconView) {
+ super(context, iconView, mock(TextView.class));
+ }
+
+ public void startPulsing() {
+ // Stub for testing
+ }
+ }
+
+ @Override
+ protected int getDelayAfterAuthenticatedDurationMs() {
+ return 0; // Keep this at 0 for tests to invoke callback immediately.
+ }
+
+ public TestableFaceView(Context context) {
+ super(context);
+ }
+ }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
new file mode 100644
index 000000000000..ffcb293ba398
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthBiometricViewTest extends SysuiTestCase {
+
+ @Mock private AuthBiometricView.Callback mCallback;
+ @Mock private AuthPanelController mPanelController;
+
+ @Mock private Button mNegativeButton;
+ @Mock private Button mPositiveButton;
+ @Mock private Button mTryAgainButton;
+ @Mock private TextView mTitleView;
+ @Mock private TextView mSubtitleView;
+ @Mock private TextView mDescriptionView;
+ @Mock private TextView mErrorView;
+ @Mock private ImageView mIconView;
+
+ TestableBiometricView mBiometricView;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
+ initDialog(mContext, mCallback, new MockInjector());
+
+ // The onAuthenticated runnable is posted when authentication succeeds.
+ mBiometricView.onAuthenticationSucceeded();
+ waitForIdleSync();
+ assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
+ verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+ }
+
+ @Test
+ public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
+ initDialog(mContext, mCallback, new MockInjector());
+
+ mBiometricView.setRequireConfirmation(true);
+ mBiometricView.onAuthenticationSucceeded();
+ waitForIdleSync();
+ assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState);
+ verify(mCallback, never()).onAction(anyInt());
+ verify(mBiometricView.mNegativeButton).setText(eq(R.string.cancel));
+ verify(mBiometricView.mPositiveButton).setEnabled(eq(true));
+ verify(mErrorView).setText(eq(R.string.biometric_dialog_tap_confirm));
+ verify(mErrorView).setVisibility(eq(View.VISIBLE));
+ }
+
+ @Test
+ public void testPositiveButton_sendsActionAuthenticated() {
+ Button button = new Button(mContext);
+ initDialog(mContext, mCallback, new MockInjector() {
+ @Override
+ public Button getPositiveButton() {
+ return button;
+ }
+ });
+
+ button.performClick();
+ waitForIdleSync();
+
+ verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+ assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState);
+ }
+
+ @Test
+ public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
+ Button button = new Button(mContext);
+ initDialog(mContext, mCallback, new MockInjector() {
+ @Override
+ public Button getNegativeButton() {
+ return button;
+ }
+ });
+
+ mBiometricView.onDialogAnimatedIn();
+ button.performClick();
+ waitForIdleSync();
+
+ verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
+ }
+
+ @Test
+ public void testNegativeButton_whenPendingConfirmation_sendsActionUserCanceled() {
+ Button button = new Button(mContext);
+ initDialog(mContext, mCallback, new MockInjector() {
+ @Override
+ public Button getNegativeButton() {
+ return button;
+ }
+ });
+
+ mBiometricView.setRequireConfirmation(true);
+ mBiometricView.onAuthenticationSucceeded();
+ button.performClick();
+ waitForIdleSync();
+
+ verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED);
+ }
+
+ @Test
+ public void testTryAgainButton_sendsActionTryAgain() {
+ Button button = new Button(mContext);
+ initDialog(mContext, mCallback, new MockInjector() {
+ @Override
+ public Button getTryAgainButton() {
+ return button;
+ }
+ });
+
+ button.performClick();
+ waitForIdleSync();
+
+ verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+ assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState);
+ }
+
+ @Test
+ public void testBackgroundClicked_sendsActionUserCanceled() {
+ initDialog(mContext, mCallback, new MockInjector());
+
+ View view = new View(mContext);
+ mBiometricView.setBackgroundView(view);
+ view.performClick();
+ verify(mCallback).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
+ }
+
+ @Test
+ public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() {
+ initDialog(mContext, mCallback, new MockInjector());
+
+ View view = new View(mContext);
+ mBiometricView.setBackgroundView(view);
+ mBiometricView.onAuthenticationSucceeded();
+ view.performClick();
+ verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
+ }
+
+ @Test
+ public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
+ initDialog(mContext, mCallback, new MockInjector());
+ mBiometricView.setPanelController(mPanelController);
+ mBiometricView.updateSize(AuthDialog.SIZE_SMALL);
+
+ View view = new View(mContext);
+ mBiometricView.setBackgroundView(view);
+ view.performClick();
+ verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED));
+ }
+
+ private void initDialog(Context context, AuthBiometricView.Callback callback,
+ MockInjector injector) {
+ mBiometricView = new TestableBiometricView(context, null, injector);
+ mBiometricView.setCallback(callback);
+ mBiometricView.initializeViews();
+ }
+
+ private class MockInjector extends AuthBiometricView.Injector {
+ @Override
+ public Button getNegativeButton() {
+ return mNegativeButton;
+ }
+
+ @Override
+ public Button getPositiveButton() {
+ return mPositiveButton;
+ }
+
+ @Override
+ public Button getTryAgainButton() {
+ return mTryAgainButton;
+ }
+
+ @Override
+ public TextView getTitleView() {
+ return mTitleView;
+ }
+
+ @Override
+ public TextView getSubtitleView() {
+ return mSubtitleView;
+ }
+
+ @Override
+ public TextView getDescriptionView() {
+ return mDescriptionView;
+ }
+
+ @Override
+ public TextView getErrorView() {
+ return mErrorView;
+ }
+
+ @Override
+ public ImageView getIconView() {
+ return mIconView;
+ }
+ }
+
+ private class TestableBiometricView extends AuthBiometricView {
+ TestableBiometricView(Context context, AttributeSet attrs,
+ Injector injector) {
+ super(context, attrs, injector);
+ }
+
+ @Override
+ protected int getDelayAfterAuthenticatedDurationMs() {
+ return 0; // Keep this at 0 for tests to invoke callback immediately.
+ }
+
+ @Override
+ protected int getStateForAfterError() {
+ return 0;
+ }
+
+ @Override
+ protected void handleResetAfterError() {
+
+ }
+
+ @Override
+ protected void handleResetAfterHelp() {
+
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
new file mode 100644
index 000000000000..25e27ef6b781
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@RunWithLooper
+@SmallTest
+public class AuthContainerViewTest extends SysuiTestCase {
+
+ private TestableAuthContainer mAuthContainer;
+
+ private @Mock AuthDialogCallback mCallback;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+
+ AuthContainerView.Config config = new AuthContainerView.Config();
+ config.mContext = mContext;
+ config.mCallback = mCallback;
+ mAuthContainer = new TestableAuthContainer(config);
+ }
+
+ @Test
+ public void testActionAuthenticated_sendsDismissedAuthenticated() {
+ mAuthContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_AUTHENTICATED);
+ verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_AUTHENTICATED));
+ }
+
+ @Test
+ public void testActionUserCanceled_sendsDismissedUserCanceled() {
+ mAuthContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_USER_CANCELED);
+ verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_USER_CANCELED));
+ }
+
+ @Test
+ public void testActionButtonNegative_sendsDismissedButtonNegative() {
+ mAuthContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE);
+ verify(mCallback).onDismissed(eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE));
+ }
+
+ @Test
+ public void testActionTryAgain_sendsTryAgain() {
+ mAuthContainer.mBiometricCallback.onAction(
+ AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+ verify(mCallback).onTryAgainPressed();
+ }
+
+ private class TestableAuthContainer extends AuthContainerView {
+ TestableAuthContainer(AuthContainerView.Config config) {
+ super(config);
+ }
+
+ @Override
+ public void animateAway(int reason) {
+ mConfig.mCallback.onDismissed(reason);
+ }
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 8f2f8b1c0e63..a5e468e0545d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -58,16 +58,16 @@ import java.util.List;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
-public class BiometricDialogImplTest extends SysuiTestCase {
+public class AuthControllerTest extends SysuiTestCase {
@Mock
private PackageManager mPackageManager;
@Mock
private IBiometricServiceReceiverInternal mReceiver;
@Mock
- private BiometricDialog mDialog1;
+ private AuthDialog mDialog1;
@Mock
- private BiometricDialog mDialog2;
+ private AuthDialog mDialog2;
private TestableBiometricDialogImpl mBiometricDialogImpl;
@@ -102,42 +102,42 @@ public class BiometricDialogImplTest extends SysuiTestCase {
@Test
public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_USER_CANCELED);
+ mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_USER_CANCEL);
}
@Test
public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_NEGATIVE);
+ mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
}
@Test
public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BUTTON_POSITIVE);
+ mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRMED);
}
@Test
public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_AUTHENTICATED);
+ mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_AUTHENTICATED);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_CONFIRM_NOT_REQUIRED);
}
@Test
public void testSendsReasonError_whenDismissedByError() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_ERROR);
+ mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_ERROR);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_ERROR);
}
@Test
public void testSendsReasonDismissedBySystemServer_whenDismissedByServer() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
- mBiometricDialogImpl.onDismissed(DialogViewCallback.DISMISSED_BY_SYSTEM_SERVER);
+ mBiometricDialogImpl.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER);
verify(mReceiver).onDialogDismissed(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED);
}
@@ -147,7 +147,7 @@ public class BiometricDialogImplTest extends SysuiTestCase {
public void testShowInvoked_whenSystemRequested()
throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
- verify(mDialog1).show(any(), eq(false) /* skipIntro */);
+ verify(mDialog1).show(any());
}
@Test
@@ -215,7 +215,7 @@ public class BiometricDialogImplTest extends SysuiTestCase {
@Test
public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
- verify(mDialog1).show(any(), eq(false) /* skipIntro */);
+ verify(mDialog1).show(any());
showDialog(BiometricPrompt.TYPE_FACE);
@@ -223,13 +223,13 @@ public class BiometricDialogImplTest extends SysuiTestCase {
verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
// Second dialog should be shown without animation
- verify(mDialog2).show(any(), eq(true)) /* skipIntro */;
+ verify(mDialog2).show(any());
}
@Test
public void testConfigurationPersists_whenOnConfigurationChanged() throws Exception {
showDialog(BiometricPrompt.TYPE_FACE);
- verify(mDialog1).show(any(), eq(false) /* skipIntro */);
+ verify(mDialog1).show(any());
mBiometricDialogImpl.onConfigurationChanged(new Configuration());
@@ -244,7 +244,7 @@ public class BiometricDialogImplTest extends SysuiTestCase {
verify(mDialog2).restoreState(captor2.capture());
// Dialog for new configuration skips intro
- verify(mDialog2).show(any(), eq(true) /* skipIntro */);
+ verify(mDialog2).show(any());
// TODO: This should check all values we want to save/restore
assertEquals(captor.getValue(), captor2.getValue());
@@ -296,7 +296,7 @@ public class BiometricDialogImplTest extends SysuiTestCase {
return bundle;
}
- private final class TestableBiometricDialogImpl extends BiometricDialogImpl {
+ private final class TestableBiometricDialogImpl extends AuthController {
private int mBuildCount = 0;
public TestableBiometricDialogImpl(Injector injector) {
@@ -304,9 +304,10 @@ public class BiometricDialogImplTest extends SysuiTestCase {
}
@Override
- protected BiometricDialog buildDialog(Bundle biometricPromptBundle,
- boolean requireConfirmation, int userId, int type, String opPackageName) {
- BiometricDialog dialog;
+ protected AuthDialog buildDialog(Bundle biometricPromptBundle,
+ boolean requireConfirmation, int userId, int type, String opPackageName,
+ boolean skipIntro) {
+ AuthDialog dialog;
if (mBuildCount == 0) {
dialog = mDialog1;
} else if (mBuildCount == 1) {
@@ -319,7 +320,7 @@ public class BiometricDialogImplTest extends SysuiTestCase {
}
}
- private final class MockInjector extends BiometricDialogImpl.Injector {
+ private final class MockInjector extends AuthController.Injector {
@Override
IActivityTaskManager getActivityTaskManager() {
return mock(IActivityTaskManager.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogViewTest.java
index bbdd837bb446..3ff1f383fdee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/BiometricDialogViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricDialogViewTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.biometrics.ui;
+package com.android.systemui.biometrics;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotSame;
@@ -32,12 +32,9 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityManager;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.DialogViewCallback;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import org.junit.Before;
@@ -62,7 +59,7 @@ public class BiometricDialogViewTest extends SysuiTestCase {
TestableContext mTestableContext;
@Mock
- private DialogViewCallback mCallback;
+ private AuthDialogCallback mCallback;
@Mock
private UserManager mUserManager;
@Mock
@@ -176,7 +173,7 @@ public class BiometricDialogViewTest extends SysuiTestCase {
assertEquals(View.VISIBLE, mFaceDialogView.mTryAgainButton.getVisibility());
}
- private FaceDialogView buildFaceDialogView(Context context, DialogViewCallback callback,
+ private FaceDialogView buildFaceDialogView(Context context, AuthDialogCallback callback,
boolean requireConfirmation) {
return (FaceDialogView) new BiometricDialogView.Builder(context)
.setCallback(callback)