From 1c9a06276adbb880bd878e7ec8f2eb052e2abbf0 Mon Sep 17 00:00:00 2001 From: Joe Bolinger Date: Thu, 3 Feb 2022 10:08:24 -0800 Subject: [PATCH] Remove face to fingerprint multi-sensor behavior. Refactor existing tests to remove unnecessary mocks and boilerplate code to cover more of the real behavior. This is prep for adding new tests in another change to add the new face and fingerprint behavior. Bug: 217393533 Test: atest AuthBiometricViewTest AuthContainerViewTest Test: manual (authenticate using test app) Change-Id: If984d1c07fee98d2d7a20cb0025ad434ad9977f4 --- .../hardware/biometrics/BiometricManager.java | 6 +- .../biometrics/IBiometricSysuiReceiver.aidl | 2 - core/proto/android/server/biometrics.proto | 14 - ...h_biometric_fingerprint_and_face_view.xml} | 4 +- .../res/layout/auth_biometric_view.xml | 26 ++ .../AuthBiometricFaceIconController.java | 158 ++++++++ .../AuthBiometricFaceToFingerprintView.java | 245 ------------- .../biometrics/AuthBiometricFaceView.java | 140 +------ .../AuthBiometricFingerprintAndFaceView.kt | 29 ++ .../AuthBiometricFingerprintView.java | 10 +- .../biometrics/AuthBiometricView.java | 216 +++-------- .../biometrics/AuthContainerView.java | 325 +++++------------ .../systemui/biometrics/AuthController.java | 43 +-- .../systemui/biometrics/AuthDialog.java | 4 +- .../biometrics/AuthDialogCallback.java | 5 - .../systemui/biometrics/UdfpsBpView.kt | 2 +- .../android/systemui/biometrics/Utils.java | 130 ------- .../com/android/systemui/biometrics/Utils.kt | 115 ++++++ ...uthBiometricFaceToFingerprintViewTest.java | 344 ------------------ .../biometrics/AuthBiometricFaceViewTest.java | 9 +- .../biometrics/AuthBiometricViewTest.java | 324 ++++------------- .../biometrics/AuthContainerViewTest.java | 329 ----------------- .../biometrics/AuthContainerViewTest.kt | 323 ++++++++++++++++ .../biometrics/AuthControllerTest.java | 32 +- .../biometrics/SidefpsControllerTest.kt | 4 +- .../biometrics/UdfpsControllerOverlayTest.kt | 2 +- .../systemui/biometrics/UdfpsViewTest.kt | 4 +- .../server/biometrics/AuthSession.java | 56 +-- .../server/biometrics/BiometricService.java | 21 -- .../server/biometrics/AuthSessionTest.java | 12 +- 30 files changed, 952 insertions(+), 1982 deletions(-) rename packages/SystemUI/res/layout/{auth_biometric_face_to_fingerprint_view.xml => auth_biometric_fingerprint_and_face_view.xml} (86%) create mode 100644 packages/SystemUI/res/layout/auth_biometric_view.xml create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.java delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt delete mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/Utils.java create mode 100644 packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java delete mode 100644 packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java index ada51559a38d7..3f139f02efaf6 100644 --- a/core/java/android/hardware/biometrics/BiometricManager.java +++ b/core/java/android/hardware/biometrics/BiometricManager.java @@ -104,16 +104,16 @@ public class BiometricManager { public static final int BIOMETRIC_MULTI_SENSOR_DEFAULT = 0; /** - * Prefer the face sensor and fall back to fingerprint when needed. + * Use face and fingerprint sensors together. * @hide */ - public static final int BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT = 1; + public static final int BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE = 1; /** * @hide */ @IntDef({BIOMETRIC_MULTI_SENSOR_DEFAULT, - BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT}) + BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE}) @Retention(RetentionPolicy.SOURCE) public @interface BiometricMultiSensorMode {} diff --git a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl index 5d9b5f3bcc056..450c5ceab04cd 100644 --- a/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl +++ b/core/java/android/hardware/biometrics/IBiometricSysuiReceiver.aidl @@ -30,6 +30,4 @@ oneway interface IBiometricSysuiReceiver { void onSystemEvent(int event); // Notifies that the dialog has finished animating. void onDialogAnimatedIn(); - // For multi-sensor devices, notifies that the fingerprint should start now. - void onStartFingerprintNow(); } diff --git a/core/proto/android/server/biometrics.proto b/core/proto/android/server/biometrics.proto index fc9da90ccd59d..ac9e3e001d504 100644 --- a/core/proto/android/server/biometrics.proto +++ b/core/proto/android/server/biometrics.proto @@ -91,23 +91,9 @@ message BiometricServiceStateProto { STATE_CLIENT_DIED_CANCELLING = 10; } - enum MultiSensorState { - // Initializing or not yet started. - MULTI_SENSOR_STATE_UNKNOWN = 0; - // Sensors are in the process of being transitioned and there is no active sensor. - MULTI_SENSOR_STATE_SWITCHING = 1; - // Face sensor is being used as the primary input. - MULTI_SENSOR_STATE_FACE_SCANNING = 2; - // Fingerprint sensor is being used as the primary input. - MULTI_SENSOR_STATE_FP_SCANNING = 3; - } - repeated SensorServiceStateProto sensor_service_states = 1; optional AuthSessionState auth_session_state = 2; - - // Additional session state information, when the device has multiple sensors. - optional MultiSensorState auth_session_multi_sensor_state = 3; } // Overall state for an instance of a Service, for example FingerprintService or diff --git a/packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml similarity index 86% rename from packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml rename to packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml index 7cf1789961d3f..7c2b60e221f06 100644 --- a/packages/SystemUI/res/layout/auth_biometric_face_to_fingerprint_view.xml +++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml @@ -14,7 +14,7 @@ ~ limitations under the License. --> - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_biometric_view.xml b/packages/SystemUI/res/layout/auth_biometric_view.xml new file mode 100644 index 0000000000000..ee4da25f22842 --- /dev/null +++ b/packages/SystemUI/res/layout/auth_biometric_view.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.java new file mode 100644 index 0000000000000..0058cc1e0a7f3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2022 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.Log; +import android.widget.ImageView; +import android.widget.TextView; + +import com.android.systemui.R; + +class AuthBiometricFaceIconController extends Animatable2.AnimationCallback { + + private static final String TAG = "AuthBiometricFaceIconController"; + + protected Context mContext; + protected ImageView mIconView; + protected TextView mTextView; + protected Handler mHandler; + protected boolean mLastPulseLightToDark; // false = dark to light, true = light to dark + @AuthBiometricView.BiometricState protected int mState; + protected boolean mDeactivated; + + protected AuthBiometricFaceIconController(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); + } + + protected void animateOnce(int iconRes) { + animateIcon(iconRes, false); + } + + protected void showStaticDrawable(int iconRes) { + mIconView.setImageDrawable(mContext.getDrawable(iconRes)); + } + + protected void animateIcon(int iconRes, boolean repeat) { + Log.d(TAG, "animateIcon, state: " + mState + ", deactivated: " + mDeactivated); + if (mDeactivated) { + return; + } + + final AnimatedVectorDrawable icon = + (AnimatedVectorDrawable) mContext.getDrawable(iconRes); + mIconView.setImageDrawable(icon); + icon.forceAnimationOnUI(); + if (repeat) { + icon.registerAnimationCallback(this); + } + icon.start(); + } + + protected void startPulsing() { + mLastPulseLightToDark = false; + animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true); + } + + protected 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); + Log.d(TAG, "onAnimationEnd, mState: " + mState + ", deactivated: " + mDeactivated); + if (mDeactivated) { + return; + } + + if (mState == AuthBiometricView.STATE_AUTHENTICATING + || mState == AuthBiometricView.STATE_HELP) { + pulseInNextDirection(); + } + } + + protected void deactivate() { + mDeactivated = true; + } + + protected void updateState(int lastState, int newState) { + if (mDeactivated) { + Log.w(TAG, "Ignoring updateState when deactivated: " + newState); + return; + } + + final boolean lastStateIsErrorIcon = + lastState == AuthBiometricView.STATE_ERROR + || lastState == AuthBiometricView.STATE_HELP; + + if (newState == AuthBiometricView.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 == AuthBiometricView.STATE_AUTHENTICATING) { + startPulsing(); + mIconView.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_authenticating)); + } else if (lastState == AuthBiometricView.STATE_PENDING_CONFIRMATION + && newState == AuthBiometricView.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 == AuthBiometricView.STATE_IDLE) { + animateOnce(R.drawable.face_dialog_error_to_idle); + mIconView.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_idle)); + } else if (lastStateIsErrorIcon && newState == AuthBiometricView.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 == AuthBiometricView.STATE_ERROR + && lastState != AuthBiometricView.STATE_ERROR) { + animateOnce(R.drawable.face_dialog_dark_to_error); + } else if (lastState == AuthBiometricView.STATE_AUTHENTICATING + && newState == AuthBiometricView.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 == AuthBiometricView.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 == AuthBiometricView.STATE_IDLE) { + showStaticDrawable(R.drawable.face_dialog_idle_static); + mIconView.setContentDescription(mContext.getString( + R.string.biometric_dialog_face_icon_description_idle)); + } else { + Log.w(TAG, "Unhandled state: " + newState); + } + mState = newState; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java deleted file mode 100644 index ae3e94b9a1cb0..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintView.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2021 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 android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; -import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; - -import android.content.Context; -import android.hardware.biometrics.BiometricAuthenticator.Modality; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.os.Bundle; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.R; - -/** - * Manages the layout of an auth dialog for devices with both a face sensor and a fingerprint - * sensor. Face authentication is attempted first, followed by fingerprint if the initial attempt is - * unsuccessful. - */ -public class AuthBiometricFaceToFingerprintView extends AuthBiometricFaceView { - private static final String TAG = "BiometricPrompt/AuthBiometricFaceToFingerprintView"; - - protected static class UdfpsIconController extends IconController { - @BiometricState private int mIconState = STATE_IDLE; - - protected UdfpsIconController( - @NonNull Context context, @NonNull ImageView iconView, @NonNull TextView textView) { - super(context, iconView, textView); - } - - void updateState(@BiometricState int newState) { - updateState(mIconState, newState); - } - - @Override - protected void updateState(int lastState, int newState) { - final boolean lastStateIsErrorIcon = - lastState == STATE_ERROR || lastState == STATE_HELP; - - switch (newState) { - case STATE_IDLE: - case STATE_AUTHENTICATING_ANIMATING_IN: - case STATE_AUTHENTICATING: - case STATE_PENDING_CONFIRMATION: - case STATE_AUTHENTICATED: - if (lastStateIsErrorIcon) { - animateOnce(R.drawable.fingerprint_dialog_error_to_fp); - } else { - showStaticDrawable(R.drawable.fingerprint_dialog_fp_to_error); - } - mIconView.setContentDescription(mContext.getString( - R.string.accessibility_fingerprint_dialog_fingerprint_icon)); - break; - - case STATE_ERROR: - case STATE_HELP: - if (!lastStateIsErrorIcon) { - animateOnce(R.drawable.fingerprint_dialog_fp_to_error); - } else { - showStaticDrawable(R.drawable.fingerprint_dialog_error_to_fp); - } - mIconView.setContentDescription(mContext.getString( - R.string.biometric_dialog_try_again)); - break; - - default: - Log.e(TAG, "Unknown biometric dialog state: " + newState); - break; - } - - mState = newState; - mIconState = newState; - } - } - - @Modality private int mActiveSensorType = TYPE_FACE; - @Nullable private ModalityListener mModalityListener; - @Nullable private FingerprintSensorPropertiesInternal mFingerprintSensorProps; - @Nullable private UdfpsDialogMeasureAdapter mUdfpsMeasureAdapter; - @Nullable @VisibleForTesting UdfpsIconController mUdfpsIconController; - - - public AuthBiometricFaceToFingerprintView(Context context) { - super(context); - } - - public AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @VisibleForTesting - AuthBiometricFaceToFingerprintView(Context context, AttributeSet attrs, Injector injector) { - super(context, attrs, injector); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mUdfpsIconController = new UdfpsIconController(mContext, mIconView, mIndicatorView); - } - - @Modality - int getActiveSensorType() { - return mActiveSensorType; - } - - boolean isFingerprintUdfps() { - return mFingerprintSensorProps.isAnyUdfpsType(); - } - - void setModalityListener(@NonNull ModalityListener listener) { - mModalityListener = listener; - } - - void setFingerprintSensorProps(@NonNull FingerprintSensorPropertiesInternal sensorProps) { - mFingerprintSensorProps = sensorProps; - } - - @Override - protected int getDelayAfterAuthenticatedDurationMs() { - return mActiveSensorType == TYPE_FINGERPRINT ? 0 - : super.getDelayAfterAuthenticatedDurationMs(); - } - - @Override - protected boolean supportsManualRetry() { - return false; - } - - @Override - public void onAuthenticationFailed( - @Modality int modality, @Nullable String failureReason) { - super.onAuthenticationFailed(modality, checkErrorForFallback(failureReason)); - } - - @Override - public void onError(int modality, String error) { - super.onError(modality, checkErrorForFallback(error)); - } - - private String checkErrorForFallback(String message) { - if (mActiveSensorType == TYPE_FACE) { - Log.d(TAG, "Falling back to fingerprint: " + message); - - // switching from face -> fingerprint mode, suppress root error messages - mCallback.onAction(Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR); - return mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead); - } - return message; - } - - @Override - @BiometricState - protected int getStateForAfterError() { - if (mActiveSensorType == TYPE_FACE) { - return STATE_AUTHENTICATING; - } - - return super.getStateForAfterError(); - } - - @Override - public void updateState(@BiometricState int newState) { - if (mActiveSensorType == TYPE_FACE) { - if (newState == STATE_HELP || newState == STATE_ERROR) { - mActiveSensorType = TYPE_FINGERPRINT; - - setRequireConfirmation(false); - mConfirmButton.setEnabled(false); - mConfirmButton.setVisibility(View.GONE); - - if (mModalityListener != null) { - mModalityListener.onModalitySwitched(TYPE_FACE, mActiveSensorType); - } - - // Deactivate the face icon controller so it stops drawing to the view - mFaceIconController.deactivate(); - // Then, activate this icon controller. We need to start in the "idle" state - mUdfpsIconController.updateState(STATE_IDLE); - } - } else { // Fingerprint - mUdfpsIconController.updateState(newState); - } - - super.updateState(newState); - } - - @Override - @NonNull - AuthDialog.LayoutParams onMeasureInternal(int width, int height) { - final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height); - return isFingerprintUdfps() - ? getUdfpsMeasureAdapter().onMeasureInternal(width, height, layoutParams) - : layoutParams; - } - - @NonNull - private UdfpsDialogMeasureAdapter getUdfpsMeasureAdapter() { - if (mUdfpsMeasureAdapter == null - || mUdfpsMeasureAdapter.getSensorProps() != mFingerprintSensorProps) { - mUdfpsMeasureAdapter = new UdfpsDialogMeasureAdapter(this, mFingerprintSensorProps); - } - return mUdfpsMeasureAdapter; - } - - @Override - public void onSaveState(@NonNull Bundle outState) { - super.onSaveState(outState); - outState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, mActiveSensorType); - outState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS, mFingerprintSensorProps); - } - - @Override - public void restoreState(@Nullable Bundle savedState) { - super.restoreState(savedState); - if (savedState != null) { - mActiveSensorType = savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FACE); - mFingerprintSensorProps = - savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS); - } - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java index 48f6431aec69b..b002c0060971e 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java @@ -17,152 +17,23 @@ 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.hardware.biometrics.BiometricAuthenticator.Modality; -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 androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; -import com.android.systemui.R; public class AuthBiometricFaceView extends AuthBiometricView { - private static final String TAG = "BiometricPrompt/AuthBiometricFaceView"; + private static final String TAG = "AuthBiometricFaceView"; // Delay before dismissing after being authenticated/confirmed. private static final int HIDE_DELAY_MS = 500; - protected static class IconController extends Animatable2.AnimationCallback { - protected Context mContext; - protected ImageView mIconView; - protected TextView mTextView; - protected Handler mHandler; - protected boolean mLastPulseLightToDark; // false = dark to light, true = light to dark - protected @BiometricState int mState; - protected boolean mDeactivated; - - protected 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); - } - - protected void animateOnce(int iconRes) { - animateIcon(iconRes, false); - } - - protected void showStaticDrawable(int iconRes) { - mIconView.setImageDrawable(mContext.getDrawable(iconRes)); - } - - protected void animateIcon(int iconRes, boolean repeat) { - Log.d(TAG, "animateIcon, state: " + mState + ", deactivated: " + mDeactivated); - if (mDeactivated) { - return; - } - - final AnimatedVectorDrawable icon = - (AnimatedVectorDrawable) mContext.getDrawable(iconRes); - mIconView.setImageDrawable(icon); - icon.forceAnimationOnUI(); - if (repeat) { - icon.registerAnimationCallback(this); - } - icon.start(); - } - - protected void startPulsing() { - mLastPulseLightToDark = false; - animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true); - } - - protected 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); - Log.d(TAG, "onAnimationEnd, mState: " + mState + ", deactivated: " + mDeactivated); - if (mDeactivated) { - return; - } - - if (mState == STATE_AUTHENTICATING || mState == STATE_HELP) { - pulseInNextDirection(); - } - } - - protected void deactivate() { - mDeactivated = true; - } - - protected void updateState(int lastState, int newState) { - if (mDeactivated) { - Log.w(TAG, "Ignoring updateState when deactivated: " + newState); - return; - } - - 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); - mIconView.setContentDescription(mContext.getString( - R.string.biometric_dialog_face_icon_description_idle)); - } else if (lastStateIsErrorIcon && 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_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); - mIconView.setContentDescription(mContext.getString( - R.string.biometric_dialog_face_icon_description_idle)); - } else { - Log.w(TAG, "Unhandled state: " + newState); - } - mState = newState; - } - } - - @Nullable @VisibleForTesting IconController mFaceIconController; + @Nullable @VisibleForTesting AuthBiometricFaceIconController mFaceIconController; @NonNull private final OnAttachStateChangeListener mOnAttachStateChangeListener = new OnAttachStateChangeListener() { @Override @@ -184,15 +55,10 @@ public class AuthBiometricFaceView extends AuthBiometricView { super(context, attrs); } - @VisibleForTesting - AuthBiometricFaceView(Context context, AttributeSet attrs, Injector injector) { - super(context, attrs, injector); - } - @Override protected void onFinishInflate() { super.onFinishInflate(); - mFaceIconController = new IconController(mContext, mIconView, mIndicatorView); + mFaceIconController = new AuthBiometricFaceIconController(mContext, mIconView, mIndicatorView); addOnAttachStateChangeListener(mOnAttachStateChangeListener); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt new file mode 100644 index 0000000000000..45e6c0460bcef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2022 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.util.AttributeSet + +class AuthBiometricFingerprintAndFaceView( + context: Context, + attrs: AttributeSet? +) : AuthBiometricFingerprintView(context, attrs) { + + constructor (context: Context) : this(context, null) + +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java index 4242c9bbfff6d..48993ef1ed5ba 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java @@ -33,7 +33,7 @@ import com.android.systemui.R; public class AuthBiometricFingerprintView extends AuthBiometricView { - private static final String TAG = "BiometricPrompt/AuthBiometricFingerprintView"; + private static final String TAG = "AuthBiometricFingerprintView"; private boolean mIsUdfps = false; @Nullable private UdfpsDialogMeasureAdapter mUdfpsAdapter; @@ -65,8 +65,8 @@ public class AuthBiometricFingerprintView extends AuthBiometricView { } @Override - void onLayoutInternal() { - super.onLayoutInternal(); + public void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); if (mUdfpsAdapter != null) { // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen @@ -124,8 +124,8 @@ public class AuthBiometricFingerprintView extends AuthBiometricView { } @Override - void onAttachedToWindowInternal() { - super.onAttachedToWindowInternal(); + protected void onAttachedToWindow() { + super.onAttachedToWindow(); showTouchSensorString(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index 1496f170dffee..330336ddd08a6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -44,6 +44,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import java.lang.annotation.Retention; @@ -52,11 +53,11 @@ import java.util.ArrayList; import java.util.List; /** - * Contains the Biometric views (title, subtitle, icon, buttons, etc) and its controllers. + * Contains the Biometric views (title, subtitle, icon, buttons, etc.) and its controllers. */ -public abstract class AuthBiometricView extends LinearLayout { +public class AuthBiometricView extends LinearLayout { - private static final String TAG = "BiometricPrompt/AuthBiometricView"; + private static final String TAG = "AuthBiometricView"; /** * Authentication hardware idle. @@ -102,13 +103,6 @@ public abstract class AuthBiometricView extends LinearLayout { int ACTION_BUTTON_TRY_AGAIN = 4; int ACTION_ERROR = 5; int ACTION_USE_DEVICE_CREDENTIAL = 6; - /** - * Notify the receiver to start the fingerprint sensor. - * - * This is only applicable to multi-sensor devices that need to delay fingerprint auth - * (i.e face -> fingerprint). - */ - int ACTION_START_DELAYED_FINGERPRINT_SENSOR = 7; /** * When an action has occurred. The caller will only invoke this when the callback should @@ -118,66 +112,9 @@ public abstract class AuthBiometricView extends LinearLayout { void onAction(int action); } - @VisibleForTesting - static class Injector { - AuthBiometricView mBiometricView; - - public Button getNegativeButton() { - return mBiometricView.findViewById(R.id.button_negative); - } - - public Button getCancelButton() { - return mBiometricView.findViewById(R.id.button_cancel); - } - - public Button getUseCredentialButton() { - return mBiometricView.findViewById(R.id.button_use_credential); - } - - public Button getConfirmButton() { - return mBiometricView.findViewById(R.id.button_confirm); - } - - 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 getIndicatorView() { - return mBiometricView.findViewById(R.id.indicator); - } - - public ImageView getIconView() { - return mBiometricView.findViewById(R.id.biometric_icon); - } - - public View getIconHolderView() { - return mBiometricView.findViewById(R.id.biometric_icon_frame); - } - - public int getDelayAfterError() { - return BiometricPrompt.HIDE_DIALOG_DELAY; - } - - public int getMediumToLargeAnimationDurationMs() { - return AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS; - } - } - - private final Injector mInjector; protected final Handler mHandler; private final AccessibilityManager mAccessibilityManager; + private final LockPatternUtils mLockPatternUtils; protected final int mTextColorError; protected final int mTextColorHint; @@ -195,6 +132,10 @@ public abstract class AuthBiometricView extends LinearLayout { protected ImageView mIconView; protected TextView mIndicatorView; + @VisibleForTesting int mAnimationDurationShort = AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS; + @VisibleForTesting int mAnimationDurationLong = AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS; + @VisibleForTesting int mAnimationDurationHideDialog = BiometricPrompt.HIDE_DIALOG_DELAY; + // Negative button position, exclusively for the app-specified behavior @VisibleForTesting Button mNegativeButton; // Negative button position, exclusively for cancelling auth after passive auth success @@ -217,30 +158,7 @@ public abstract class AuthBiometricView extends LinearLayout { protected boolean mDialogSizeAnimating; protected Bundle mSavedState; - /** - * 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(); - - /** - * @return true if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL} - */ - protected abstract boolean supportsSmallDialog(); - private final Runnable mResetErrorRunnable; - private final Runnable mResetHelpRunnable; private final OnClickListener mBackgroundClickListener = (view) -> { @@ -262,11 +180,6 @@ public abstract class AuthBiometricView extends LinearLayout { } 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( @@ -274,10 +187,8 @@ public abstract class AuthBiometricView extends LinearLayout { mTextColorHint = getResources().getColor( R.color.biometric_dialog_gray, context.getTheme()); - mInjector = injector; - mInjector.mBiometricView = this; - mAccessibilityManager = context.getSystemService(AccessibilityManager.class); + mLockPatternUtils = new LockPatternUtils(context); mResetErrorRunnable = () -> { updateState(getStateForAfterError()); @@ -292,31 +203,52 @@ public abstract class AuthBiometricView extends LinearLayout { }; } - public void setPanelController(AuthPanelController panelController) { + /** Delay after authentication is confirmed, before the dialog should be animated away. */ + protected int getDelayAfterAuthenticatedDurationMs() { + return 0; + } + + /** State that the dialog/icon should be in after showing a help message. */ + protected int getStateForAfterError() { + return STATE_IDLE; + } + + /** Invoked when the error message is being cleared. */ + protected void handleResetAfterError() {} + + /** Invoked when the help message is being cleared. */ + protected void handleResetAfterHelp() {} + + /** True if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}. */ + protected boolean supportsSmallDialog() { + return false; + } + + void setPanelController(AuthPanelController panelController) { mPanelController = panelController; } - public void setPromptInfo(PromptInfo promptInfo) { + void setPromptInfo(PromptInfo promptInfo) { mPromptInfo = promptInfo; } - public void setCallback(Callback callback) { + void setCallback(Callback callback) { mCallback = callback; } - public void setBackgroundView(View backgroundView) { + void setBackgroundView(View backgroundView) { backgroundView.setOnClickListener(mBackgroundClickListener); } - public void setUserId(int userId) { + void setUserId(int userId) { mUserId = userId; } - public void setEffectiveUserId(int effectiveUserId) { + void setEffectiveUserId(int effectiveUserId) { mEffectiveUserId = effectiveUserId; } - public void setRequireConfirmation(boolean requireConfirmation) { + void setRequireConfirmation(boolean requireConfirmation) { mRequireConfirmation = requireConfirmation; } @@ -376,7 +308,7 @@ public abstract class AuthBiometricView extends LinearLayout { // Choreograph together final AnimatorSet as = new AnimatorSet(); - as.setDuration(AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS); + as.setDuration(mAnimationDurationShort); as.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -429,7 +361,7 @@ public abstract class AuthBiometricView extends LinearLayout { // Translate at full duration final ValueAnimator translationAnimator = ValueAnimator.ofFloat( biometricView.getY(), biometricView.getY() - translationY); - translationAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs()); + translationAnimator.setDuration(mAnimationDurationLong); translationAnimator.addUpdateListener((animation) -> { final float translation = (float) animation.getAnimatedValue(); biometricView.setTranslationY(translation); @@ -438,7 +370,7 @@ public abstract class AuthBiometricView extends LinearLayout { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); - if (biometricView.getParent() != null) { + if (biometricView.getParent() instanceof ViewGroup) { ((ViewGroup) biometricView.getParent()).removeView(biometricView); } mSize = newSize; @@ -447,7 +379,7 @@ public abstract class AuthBiometricView extends LinearLayout { // Opacity to 0 in half duration final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(1, 0); - opacityAnimator.setDuration(mInjector.getMediumToLargeAnimationDurationMs() / 2); + opacityAnimator.setDuration(mAnimationDurationLong / 2); opacityAnimator.addUpdateListener((animation) -> { final float opacity = (float) animation.getAnimatedValue(); biometricView.setAlpha(opacity); @@ -457,7 +389,7 @@ public abstract class AuthBiometricView extends LinearLayout { mPanelController.updateForContentDimensions( mPanelController.getContainerWidth(), mPanelController.getContainerHeight(), - mInjector.getMediumToLargeAnimationDurationMs()); + mAnimationDurationLong); // Start the animations together AnimatorSet as = new AnimatorSet(); @@ -466,7 +398,7 @@ public abstract class AuthBiometricView extends LinearLayout { animators.add(opacityAnimator); as.playTogether(animators); - as.setDuration(mInjector.getMediumToLargeAnimationDurationMs() * 2 / 3); + as.setDuration(mAnimationDurationLong * 2 / 3); as.start(); } else { Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize); @@ -567,9 +499,8 @@ public abstract class AuthBiometricView extends LinearLayout { showTemporaryMessage(error, mResetErrorRunnable); updateState(STATE_ERROR); - mHandler.postDelayed(() -> { - mCallback.onAction(Callback.ACTION_ERROR); - }, mInjector.getDelayAfterError()); + mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_ERROR), + mAnimationDurationHideDialog); } /** @@ -639,7 +570,7 @@ public abstract class AuthBiometricView extends LinearLayout { // select to enable marquee unless a screen reader is enabled mIndicatorView.setSelected(!mAccessibilityManager.isEnabled() || !mAccessibilityManager.isTouchExplorationEnabled()); - mHandler.postDelayed(resetMessageRunnable, mInjector.getDelayAfterError()); + mHandler.postDelayed(resetMessageRunnable, mAnimationDurationHideDialog); Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this); } @@ -647,29 +578,22 @@ public abstract class AuthBiometricView extends LinearLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - onFinishInflateInternal(); - } - /** - * After inflation, but before things like restoreState, onAttachedToWindow, etc. - */ - @VisibleForTesting - void onFinishInflateInternal() { - mTitleView = mInjector.getTitleView(); - mSubtitleView = mInjector.getSubtitleView(); - mDescriptionView = mInjector.getDescriptionView(); - mIconView = mInjector.getIconView(); - mIconHolderView = mInjector.getIconHolderView(); - mIndicatorView = mInjector.getIndicatorView(); + mTitleView = findViewById(R.id.title); + mSubtitleView = findViewById(R.id.subtitle); + mDescriptionView = findViewById(R.id.description); + mIconView = findViewById(R.id.biometric_icon); + mIconHolderView = findViewById(R.id.biometric_icon_frame); + mIndicatorView = findViewById(R.id.indicator); // Negative-side (left) buttons - mNegativeButton = mInjector.getNegativeButton(); - mCancelButton = mInjector.getCancelButton(); - mUseCredentialButton = mInjector.getUseCredentialButton(); + mNegativeButton = findViewById(R.id.button_negative); + mCancelButton = findViewById(R.id.button_cancel); + mUseCredentialButton = findViewById(R.id.button_use_credential); // Positive-side (right) buttons - mConfirmButton = mInjector.getConfirmButton(); - mTryAgainButton = mInjector.getTryAgainButton(); + mConfirmButton = findViewById(R.id.button_confirm); + mTryAgainButton = findViewById(R.id.button_try_again); mNegativeButton.setOnClickListener((view) -> { mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE); @@ -706,21 +630,13 @@ public abstract class AuthBiometricView extends LinearLayout { @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - onAttachedToWindowInternal(); - } - /** - * Contains all the testable logic that should be invoked when {@link #onAttachedToWindow()} is - * invoked. - */ - @VisibleForTesting - void onAttachedToWindowInternal() { mTitleView.setText(mPromptInfo.getTitle()); if (isDeviceCredentialAllowed()) { final CharSequence credentialButtonText; - final @Utils.CredentialType int credentialType = - Utils.getCredentialType(mContext, mEffectiveUserId); + @Utils.CredentialType final int credentialType = + Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId); switch (credentialType) { case Utils.CREDENTIAL_PIN: credentialButtonText = @@ -731,9 +647,6 @@ public abstract class AuthBiometricView extends LinearLayout { getResources().getString(R.string.biometric_dialog_use_pattern); break; case Utils.CREDENTIAL_PASSWORD: - credentialButtonText = - getResources().getString(R.string.biometric_dialog_use_password); - break; default: credentialButtonText = getResources().getString(R.string.biometric_dialog_use_password); @@ -749,7 +662,6 @@ public abstract class AuthBiometricView extends LinearLayout { } setTextOrHide(mSubtitleView, mPromptInfo.getSubtitle()); - setTextOrHide(mDescriptionView, mPromptInfo.getDescription()); if (mSavedState == null) { @@ -856,15 +768,7 @@ public abstract class AuthBiometricView extends LinearLayout { @Override public void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); - onLayoutInternal(); - } - /** - * Contains all the testable logic that should be invoked when - * {@link #onLayout(boolean, int, int, int, int)}, is invoked. - */ - @VisibleForTesting - void onLayoutInternal() { // Start with initial size only once. Subsequent layout changes don't matter since we // only care about the initial icon position. if (mIconOriginalY == 0) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index c63aa7ac648a8..72ac1543c89e6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -16,9 +16,10 @@ package com.android.systemui.biometrics; -import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; +import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; import static android.hardware.biometrics.BiometricManager.BiometricMultiSensorMode; +import android.annotation.DurationMillisLong; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -52,6 +53,7 @@ import android.widget.LinearLayout; import android.widget.ScrollView; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.widget.LockPatternUtils; import com.android.systemui.R; import com.android.systemui.animation.Interpolators; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -66,54 +68,51 @@ import java.util.List; 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 String TAG = "AuthContainerView"; - static final int STATE_UNKNOWN = 0; - static final int STATE_ANIMATING_IN = 1; - static final int STATE_PENDING_DISMISS = 2; - static final int STATE_SHOWING = 3; - static final int STATE_ANIMATING_OUT = 4; - static final int STATE_GONE = 5; + private static final int ANIMATION_DURATION_SHOW_MS = 250; + private static final int ANIMATION_DURATION_AWAY_MS = 350; + + 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 {} + private @interface ContainerState {} - final Config mConfig; - final int mEffectiveUserId; - @Nullable private final List mFpProps; - @Nullable private final List mFaceProps; + private final Config mConfig; + private final int mEffectiveUserId; private final Handler mHandler; - private final Injector mInjector; private final IBinder mWindowToken = new Binder(); private final WindowManager mWindowManager; - private final AuthPanelController mPanelController; private final Interpolator mLinearOutSlowIn; - @VisibleForTesting final BiometricCallback mBiometricCallback; private final CredentialCallback mCredentialCallback; - - @VisibleForTesting final FrameLayout mFrameLayout; - @VisibleForTesting @Nullable AuthBiometricView mBiometricView; - @VisibleForTesting @Nullable AuthCredentialView mCredentialView; - - @VisibleForTesting final ImageView mBackgroundView; - @VisibleForTesting final ScrollView mBiometricScrollView; - private final View mPanelView; - - private final float mTranslationY; - + private final LockPatternUtils mLockPatternUtils; private final WakefulnessLifecycle mWakefulnessLifecycle; - @VisibleForTesting @ContainerState int mContainerState = STATE_UNKNOWN; + @VisibleForTesting final BiometricCallback mBiometricCallback; + + @Nullable private AuthBiometricView mBiometricView; + @Nullable private AuthCredentialView mCredentialView; + private final AuthPanelController mPanelController; + private final FrameLayout mFrameLayout; + private final ImageView mBackgroundView; + private final ScrollView mBiometricScrollView; + private final View mPanelView; + private final float mTranslationY; + @ContainerState private int mContainerState = STATE_UNKNOWN; // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet. - @Nullable @AuthDialogCallback.DismissedReason Integer mPendingCallbackReason; + @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason; // HAT received from LockSettingsService when credential is verified. - @Nullable byte[] mCredentialAttestation; + @Nullable private byte[] mCredentialAttestation; + @VisibleForTesting static class Config { Context mContext; AuthDialogCallback mCallback; @@ -122,11 +121,11 @@ public class AuthContainerView extends LinearLayout int mUserId; String mOpPackageName; int[] mSensorIds; - boolean mCredentialAllowed; boolean mSkipIntro; long mOperationId; long mRequestId; - @BiometricMultiSensorMode int mMultiSensorConfig; + boolean mSkipAnimation = false; + @BiometricMultiSensorMode int mMultiSensorConfig = BIOMETRIC_MULTI_SENSOR_DEFAULT; } public static class Builder { @@ -167,7 +166,7 @@ public class AuthContainerView extends LinearLayout return this; } - public Builder setOperationId(long operationId) { + public Builder setOperationId(@DurationMillisLong long operationId) { mConfig.mOperationId = operationId; return this; } @@ -178,55 +177,27 @@ public class AuthContainerView extends LinearLayout return this; } + @VisibleForTesting + public Builder setSkipAnimationDuration(boolean skip) { + mConfig.mSkipAnimation = skip; + return this; + } + /** The multi-sensor mode. */ public Builder setMultiSensorConfig(@BiometricMultiSensorMode int multiSensorConfig) { mConfig.mMultiSensorConfig = multiSensorConfig; return this; } - public AuthContainerView build(int[] sensorIds, boolean credentialAllowed, + public AuthContainerView build(int[] sensorIds, @Nullable List fpProps, @Nullable List faceProps, - WakefulnessLifecycle wakefulnessLifecycle) { + @NonNull WakefulnessLifecycle wakefulnessLifecycle, + @NonNull UserManager userManager, + @NonNull LockPatternUtils lockPatternUtils) { mConfig.mSensorIds = sensorIds; - mConfig.mCredentialAllowed = credentialAllowed; - return new AuthContainerView( - mConfig, new Injector(), fpProps, faceProps, wakefulnessLifecycle); - } - } - - public static class Injector { - ScrollView getBiometricScrollView(FrameLayout parent) { - return parent.findViewById(R.id.biometric_scrollview); - } - - FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) { - return (FrameLayout) factory.inflate( - R.layout.auth_container_view, root, false /* attachToRoot */); - } - - AuthPanelController getPanelController(Context context, View panelView) { - return new AuthPanelController(context, panelView); - } - - ImageView getBackgroundView(FrameLayout parent) { - return parent.findViewById(R.id.background); - } - - View getPanelView(FrameLayout parent) { - return parent.findViewById(R.id.panel); - } - - int getAnimateCredentialStartDelayMs() { - return AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS; - } - - UserManager getUserManager(Context context) { - return UserManager.get(context); - } - - int getCredentialType(Context context, int effectiveUserId) { - return Utils.getCredentialType(context, effectiveUserId); + return new AuthContainerView(mConfig, fpProps, faceProps, wakefulnessLifecycle, + userManager, lockPatternUtils, new Handler(Looper.getMainLooper())); } } @@ -255,10 +226,7 @@ public class AuthContainerView extends LinearLayout mConfig.mCallback.onDeviceCredentialPressed(); mHandler.postDelayed(() -> { addCredentialView(false /* animatePanel */, true /* animateContents */); - }, mInjector.getAnimateCredentialStartDelayMs()); - break; - case AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR: - mConfig.mCallback.onStartFingerprintNow(); + }, mConfig.mSkipAnimation ? 0 : AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS); break; default: Log.e(TAG, "Unhandled action: " + action); @@ -275,21 +243,19 @@ public class AuthContainerView extends LinearLayout } @VisibleForTesting - AuthContainerView(Config config, Injector injector, + AuthContainerView(Config config, @Nullable List fpProps, @Nullable List faceProps, - WakefulnessLifecycle wakefulnessLifecycle) { + @NonNull WakefulnessLifecycle wakefulnessLifecycle, + @NonNull UserManager userManager, + @NonNull LockPatternUtils lockPatternUtils, + @NonNull Handler mainHandler) { super(config.mContext); mConfig = config; - mInjector = injector; - mFpProps = fpProps; - mFaceProps = faceProps; - - mEffectiveUserId = mInjector.getUserManager(mContext) - .getCredentialOwnerProfile(mConfig.mUserId); - - mHandler = new Handler(Looper.getMainLooper()); + mLockPatternUtils = lockPatternUtils; + mEffectiveUserId = userManager.getCredentialOwnerProfile(mConfig.mUserId); + mHandler = mainHandler; mWindowManager = mContext.getSystemService(WindowManager.class); mWakefulnessLifecycle = wakefulnessLifecycle; @@ -299,96 +265,42 @@ public class AuthContainerView extends LinearLayout mBiometricCallback = new BiometricCallback(); mCredentialCallback = new CredentialCallback(); - final LayoutInflater factory = LayoutInflater.from(mContext); - mFrameLayout = mInjector.inflateContainerView(factory, this); - - mPanelView = mInjector.getPanelView(mFrameLayout); - mPanelController = mInjector.getPanelController(mContext, mPanelView); + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); + mFrameLayout = (FrameLayout) layoutInflater.inflate( + R.layout.auth_container_view, this, false /* attachToRoot */); + addView(mFrameLayout); + mBiometricScrollView = mFrameLayout.findViewById(R.id.biometric_scrollview); + mBackgroundView = mFrameLayout.findViewById(R.id.background); + mPanelView = mFrameLayout.findViewById(R.id.panel); + mPanelController = new AuthPanelController(mContext, mPanelView); // Inflate biometric view only if necessary. - final int sensorCount = config.mSensorIds.length; if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) { - if (sensorCount == 1) { - final int singleSensorAuthId = config.mSensorIds[0]; - if (Utils.containsSensorId(mFpProps, singleSensorAuthId)) { - FingerprintSensorPropertiesInternal sensorProps = null; - for (FingerprintSensorPropertiesInternal prop : mFpProps) { - if (prop.sensorId == singleSensorAuthId) { - sensorProps = prop; - break; - } - } + final FingerprintSensorPropertiesInternal fpProperties = + Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds); + final FaceSensorPropertiesInternal faceProperties = + Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds); - - final AuthBiometricFingerprintView fpView = (AuthBiometricFingerprintView) - factory.inflate(R.layout.auth_biometric_fingerprint_view, null, false); - fpView.setSensorProperties(sensorProps); - mBiometricView = fpView; - } else if (Utils.containsSensorId(mFaceProps, singleSensorAuthId)) { - mBiometricView = (AuthBiometricFaceView) - factory.inflate(R.layout.auth_biometric_face_view, null, false); - } else { - // Unknown sensorId - Log.e(TAG, "Unknown sensorId: " + singleSensorAuthId); - mBiometricView = null; - mBackgroundView = null; - mBiometricScrollView = null; - return; - } - } else if (sensorCount == 2) { - final int[] allSensors = findFaceAndFingerprintSensors(); - final int faceSensorId = allSensors[0]; - final int fingerprintSensorId = allSensors[1]; - - if (fingerprintSensorId == -1 || faceSensorId == -1) { - Log.e(TAG, "Missing fingerprint or face for dual-sensor config"); - mBiometricView = null; - mBackgroundView = null; - mBiometricScrollView = null; - return; - } - - FingerprintSensorPropertiesInternal fingerprintSensorProps = null; - for (FingerprintSensorPropertiesInternal prop : mFpProps) { - if (prop.sensorId == fingerprintSensorId) { - fingerprintSensorProps = prop; - break; - } - } - - if (fingerprintSensorProps != null) { - final AuthBiometricFaceToFingerprintView faceToFingerprintView = - (AuthBiometricFaceToFingerprintView) factory.inflate( - R.layout.auth_biometric_face_to_fingerprint_view, null, false); - faceToFingerprintView.setFingerprintSensorProps(fingerprintSensorProps); - faceToFingerprintView.setModalityListener(new ModalityListener() { - @Override - public void onModalitySwitched(int oldModality, int newModality) { - maybeUpdatePositionForUdfps(true /* invalidate */); - } - }); - mBiometricView = faceToFingerprintView; - } else { - Log.e(TAG, "Fingerprint props not found for sensor ID: " + fingerprintSensorId); - mBiometricView = null; - mBackgroundView = null; - mBiometricScrollView = null; - return; - } + if (fpProperties != null && faceProperties != null) { + final AuthBiometricFingerprintAndFaceView fingerprintAndFaceView = + (AuthBiometricFingerprintAndFaceView) layoutInflater.inflate( + R.layout.auth_biometric_fingerprint_and_face_view, null, false); + fingerprintAndFaceView.setSensorProperties(fpProperties); + mBiometricView = fingerprintAndFaceView; + } else if (fpProperties != null) { + final AuthBiometricFingerprintView fpView = + (AuthBiometricFingerprintView) layoutInflater.inflate( + R.layout.auth_biometric_fingerprint_view, null, false); + fpView.setSensorProperties(fpProperties); + mBiometricView = fpView; + } else if (faceProperties != null) { + mBiometricView = (AuthBiometricFaceView) layoutInflater.inflate( + R.layout.auth_biometric_face_view, null, false); } else { - Log.e(TAG, "Unsupported sensor array, length: " + sensorCount); - mBiometricView = null; - mBackgroundView = null; - mBiometricScrollView = null; - return; + Log.e(TAG, "No sensors found!"); } } - mBiometricScrollView = mInjector.getBiometricScrollView(mFrameLayout); - mBackgroundView = mInjector.getBackgroundView(mFrameLayout); - - addView(mFrameLayout); - // init view before showing if (mBiometricView != null) { mBiometricView.setRequireConfirmation(mConfig.mRequireConfirmation); @@ -427,10 +339,6 @@ public class AuthContainerView extends LinearLayout return Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo); } - private void addBiometricView() { - mBiometricScrollView.addView(mBiometricView); - } - /** * Adds the credential view. When going from biometric to credential view, the biometric * view starts the panel expansion animation. If the credential view is being shown first, @@ -440,8 +348,8 @@ public class AuthContainerView extends LinearLayout private void addCredentialView(boolean animatePanel, boolean animateContents) { final LayoutInflater factory = LayoutInflater.from(mContext); - final @Utils.CredentialType int credentialType = mInjector.getCredentialType( - mContext, mEffectiveUserId); + @Utils.CredentialType final int credentialType = Utils.getCredentialType( + mLockPatternUtils, mEffectiveUserId); switch (credentialType) { case Utils.CREDENTIAL_PATTERN: @@ -489,15 +397,11 @@ public class AuthContainerView extends LinearLayout @Override public void onAttachedToWindow() { super.onAttachedToWindow(); - onAttachedToWindowInternal(); - } - @VisibleForTesting - void onAttachedToWindowInternal() { mWakefulnessLifecycle.addObserver(this); if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) { - addBiometricView(); + mBiometricScrollView.addView(mBiometricView); } else if (Utils.isDeviceCredentialAllowed(mConfig.mPromptInfo)) { addCredentialView(true /* animatePanel */, false /* animateContents */); } else { @@ -517,17 +421,18 @@ public class AuthContainerView extends LinearLayout mBiometricScrollView.setY(mTranslationY); setAlpha(0f); + final long animateDuration = mConfig.mSkipAnimation ? 0 : ANIMATION_DURATION_SHOW_MS; postOnAnimation(() -> { mPanelView.animate() .translationY(0) - .setDuration(ANIMATION_DURATION_SHOW_MS) + .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) .withLayer() .withEndAction(this::onDialogAnimatedIn) .start(); mBiometricScrollView.animate() .translationY(0) - .setDuration(ANIMATION_DURATION_SHOW_MS) + .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) .withLayer() .start(); @@ -535,14 +440,14 @@ public class AuthContainerView extends LinearLayout mCredentialView.setY(mTranslationY); mCredentialView.animate() .translationY(0) - .setDuration(ANIMATION_DURATION_SHOW_MS) + .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) .withLayer() .start(); } animate() .alpha(1f) - .setDuration(ANIMATION_DURATION_SHOW_MS) + .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) .withLayer() .start(); @@ -555,13 +460,6 @@ public class AuthContainerView extends LinearLayout return ((AuthBiometricFingerprintView) view).isUdfps(); } - if (view instanceof AuthBiometricFaceToFingerprintView) { - AuthBiometricFaceToFingerprintView faceToFingerprintView = - (AuthBiometricFaceToFingerprintView) view; - return faceToFingerprintView.getActiveSensorType() == TYPE_FINGERPRINT - && faceToFingerprintView.isFingerprintUdfps(); - } - return false; } @@ -669,7 +567,8 @@ public class AuthContainerView extends LinearLayout @Override public void onSaveState(@NonNull Bundle outState) { - outState.putInt(AuthDialog.KEY_CONTAINER_STATE, mContainerState); + outState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, + mContainerState == STATE_ANIMATING_OUT); // In the case where biometric and credential are both allowed, we can assume that // biometric isn't showing if credential is showing since biometric is shown first. outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING, @@ -691,8 +590,7 @@ public class AuthContainerView extends LinearLayout mBiometricView.startTransitionToCredentialUI(); } - @VisibleForTesting - void animateAway(int reason) { + void animateAway(@AuthDialogCallback.DismissedReason int reason) { animateAway(true /* sendReason */, reason); } @@ -720,31 +618,32 @@ public class AuthContainerView extends LinearLayout removeWindowIfAttached(); }; + final long animateDuration = mConfig.mSkipAnimation ? 0 : ANIMATION_DURATION_AWAY_MS; postOnAnimation(() -> { mPanelView.animate() .translationY(mTranslationY) - .setDuration(ANIMATION_DURATION_AWAY_MS) + .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) .withLayer() .withEndAction(endActionRunnable) .start(); mBiometricScrollView.animate() .translationY(mTranslationY) - .setDuration(ANIMATION_DURATION_AWAY_MS) + .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) .withLayer() .start(); if (mCredentialView != null && mCredentialView.isAttachedToWindow()) { mCredentialView.animate() .translationY(mTranslationY) - .setDuration(ANIMATION_DURATION_AWAY_MS) + .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) .withLayer() .start(); } animate() .alpha(0f) - .setDuration(ANIMATION_DURATION_AWAY_MS) + .setDuration(animateDuration) .setInterpolator(mLinearOutSlowIn) .withLayer() .start(); @@ -769,8 +668,7 @@ public class AuthContainerView extends LinearLayout mWindowManager.removeView(this); } - @VisibleForTesting - void onDialogAnimatedIn() { + private void onDialogAnimatedIn() { if (mContainerState == STATE_PENDING_DISMISS) { Log.d(TAG, "onDialogAnimatedIn(): mPendingDismissDialog=true, dismissing now"); animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED); @@ -784,8 +682,7 @@ public class AuthContainerView extends LinearLayout } @VisibleForTesting - static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, - CharSequence title) { + static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) { final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED | WindowManager.LayoutParams.FLAG_SECURE; final WindowManager.LayoutParams lp = new WindowManager.LayoutParams( @@ -801,24 +698,4 @@ public class AuthContainerView extends LinearLayout lp.token = windowToken; return lp; } - - // returns [face, fingerprint] sensor ids (id is -1 if not present) - private int[] findFaceAndFingerprintSensors() { - int faceSensorId = -1; - int fingerprintSensorId = -1; - - for (final int sensorId : mConfig.mSensorIds) { - if (Utils.containsSensorId(mFpProps, sensorId)) { - fingerprintSensorId = sensorId; - } else if (Utils.containsSensorId(mFaceProps, sensorId)) { - faceSensorId = sensorId; - } - - if (fingerprintSensorId != -1 && faceSensorId != -1) { - break; - } - } - - return new int[] {faceSensorId, fingerprintSensorId}; - } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index dfb8c18b4ece1..8db779c09aa9a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -51,6 +51,7 @@ import android.hardware.fingerprint.IUdfpsHbmListener; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; +import android.os.UserManager; import android.util.Log; import android.util.SparseBooleanArray; import android.view.MotionEvent; @@ -59,6 +60,7 @@ import android.view.WindowManager; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; +import com.android.internal.widget.LockPatternUtils; import com.android.systemui.CoreStartable; import com.android.systemui.assist.ui.DisplayUtils; import com.android.systemui.dagger.SysUISingleton; @@ -137,6 +139,8 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @NonNull private final SensorPrivacyManager mSensorPrivacyManager; private final WakefulnessLifecycle mWakefulnessLifecycle; private boolean mAllAuthenticatorsRegistered; + @NonNull private final UserManager mUserManager; + @NonNull private final LockPatternUtils mLockPatternUtils; private class BiometricTaskStackListener extends TaskStackListener { @Override @@ -359,20 +363,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } } - @Override - public void onStartFingerprintNow() { - if (mReceiver == null) { - Log.e(TAG, "onStartUdfpsNow: Receiver is null"); - return; - } - - try { - mReceiver.onStartFingerprintNow(); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException when sending onDialogAnimatedIn", e); - } - } - @Override public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) { switch (reason) { @@ -503,12 +493,16 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba Provider udfpsControllerFactory, Provider sidefpsControllerFactory, @NonNull DisplayManager displayManager, - WakefulnessLifecycle wakefulnessLifecycle, + @NonNull WakefulnessLifecycle wakefulnessLifecycle, + @NonNull UserManager userManager, + @NonNull LockPatternUtils lockPatternUtils, @NonNull StatusBarStateController statusBarStateController, @Main Handler handler) { super(context); mExecution = execution; mWakefulnessLifecycle = wakefulnessLifecycle; + mUserManager = userManager; + mLockPatternUtils = lockPatternUtils; mHandler = handler; mCommandQueue = commandQueue; mActivityTaskManager = activityTaskManager; @@ -827,7 +821,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba final String opPackageName = (String) args.arg6; final long operationId = args.argl1; final long requestId = args.argl2; - final @BiometricMultiSensorMode int multiSensorConfig = args.argi2; + @BiometricMultiSensorMode final int multiSensorConfig = args.argi2; // Create a new dialog but do not replace the current one yet. final AuthDialog newDialog = buildDialog( @@ -835,13 +829,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba requireConfirmation, userId, sensorIds, - credentialAllowed, opPackageName, skipAnimation, operationId, requestId, multiSensorConfig, - mWakefulnessLifecycle); + mWakefulnessLifecycle, + mUserManager, + mLockPatternUtils); if (newDialog == null) { Log.e(TAG, "Unsupported type configuration"); @@ -902,8 +897,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba // Only show the dialog if necessary. If it was animating out, the dialog is supposed // to send its pending callback immediately. - if (savedState.getInt(AuthDialog.KEY_CONTAINER_STATE) - != AuthContainerView.STATE_ANIMATING_OUT) { + if (!savedState.getBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false)) { final boolean credentialShowing = savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING); if (credentialShowing) { @@ -927,10 +921,12 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } protected AuthDialog buildDialog(PromptInfo promptInfo, boolean requireConfirmation, - int userId, int[] sensorIds, boolean credentialAllowed, String opPackageName, + int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, @BiometricMultiSensorMode int multiSensorConfig, - WakefulnessLifecycle wakefulnessLifecycle) { + @NonNull WakefulnessLifecycle wakefulnessLifecycle, + @NonNull UserManager userManager, + @NonNull LockPatternUtils lockPatternUtils) { return new AuthContainerView.Builder(mContext) .setCallback(this) .setPromptInfo(promptInfo) @@ -941,7 +937,8 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba .setOperationId(operationId) .setRequestId(requestId) .setMultiSensorConfig(multiSensorConfig) - .build(sensorIds, credentialAllowed, mFpProps, mFaceProps, wakefulnessLifecycle); + .build(sensorIds, mFpProps, mFaceProps, wakefulnessLifecycle, userManager, + lockPatternUtils); } /** diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java index fa5213e940817..e21229a3e8d58 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java @@ -31,7 +31,7 @@ import java.lang.annotation.RetentionPolicy; */ public interface AuthDialog { - String KEY_CONTAINER_STATE = "container_state"; + String KEY_CONTAINER_GOING_AWAY = "container_going_away"; String KEY_BIOMETRIC_SHOWING = "biometric_showing"; String KEY_CREDENTIAL_SHOWING = "credential_showing"; @@ -64,7 +64,7 @@ public interface AuthDialog { @interface DialogSize {} /** - * Parameters used when laying out {@link AuthBiometricView}, its sublclasses, and + * Parameters used when laying out {@link AuthBiometricView}, its subclasses, and * {@link AuthPanelController}. */ class LayoutParams { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java index 9f40ca7b03460..a7d2901b21c3f 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java @@ -70,9 +70,4 @@ public interface AuthDialogCallback { * Notifies when the dialog has finished animating. */ void onDialogAnimatedIn(); - - /** - * Notifies that the fingerprint sensor should be started now. - */ - void onStartFingerprintNow(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt index cc4e2af9dfa0d..242601d46fa47 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt @@ -23,7 +23,7 @@ import android.util.AttributeSet * * Currently doesn't draw anything. * - * Note that [AuthBiometricFingerprintView] also shows UDFPS animations. At some point we should + * Note that [AuthBiometricFingerprintViewController] also shows UDFPS animations. At some point we should * de-dupe this if necessary. */ class UdfpsBpView(context: Context, attrs: AttributeSet?) : UdfpsAnimationView(context, attrs) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java deleted file mode 100644 index 6989547dce526..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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 android.Manifest.permission.USE_BIOMETRIC_INTERNAL; -import static android.hardware.biometrics.BiometricManager.Authenticators; -import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.admin.DevicePolicyManager; -import android.content.Context; -import android.content.pm.PackageManager; -import android.hardware.biometrics.PromptInfo; -import android.hardware.biometrics.SensorPropertiesInternal; -import android.os.UserManager; -import android.util.DisplayMetrics; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityEvent; -import android.view.accessibility.AccessibilityManager; - -import com.android.internal.widget.LockPatternUtils; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.List; - -public class Utils { - - public static final int CREDENTIAL_PIN = 1; - public static final int CREDENTIAL_PATTERN = 2; - public static final int CREDENTIAL_PASSWORD = 3; - - /** Base set of layout flags for fingerprint overlay widgets. */ - public static final int FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS = - WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD}) - @interface CredentialType {} - - static float dpToPixels(Context context, float dp) { - return dp * ((float) context.getResources().getDisplayMetrics().densityDpi - / DisplayMetrics.DENSITY_DEFAULT); - } - - static void notifyAccessibilityContentChanged(AccessibilityManager am, ViewGroup view) { - if (!am.isEnabled()) { - return; - } - AccessibilityEvent event = AccessibilityEvent.obtain(); - event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED); - event.setContentChangeTypes(CONTENT_CHANGE_TYPE_SUBTREE); - view.sendAccessibilityEventUnchecked(event); - view.notifySubtreeAccessibilityStateChanged(view, view, CONTENT_CHANGE_TYPE_SUBTREE); - } - - static boolean isDeviceCredentialAllowed(PromptInfo promptInfo) { - @Authenticators.Types final int authenticators = promptInfo.getAuthenticators(); - return (authenticators & Authenticators.DEVICE_CREDENTIAL) != 0; - } - - static boolean isBiometricAllowed(PromptInfo promptInfo) { - @Authenticators.Types final int authenticators = promptInfo.getAuthenticators(); - return (authenticators & Authenticators.BIOMETRIC_WEAK) != 0; - } - - static @CredentialType int getCredentialType(Context context, int userId) { - final LockPatternUtils lpu = new LockPatternUtils(context); - switch (lpu.getKeyguardStoredPasswordQuality(userId)) { - case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: - return CREDENTIAL_PATTERN; - case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: - case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: - return CREDENTIAL_PIN; - case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: - case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: - case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: - case DevicePolicyManager.PASSWORD_QUALITY_MANAGED: - return CREDENTIAL_PASSWORD; - default: - return CREDENTIAL_PASSWORD; - } - } - - static boolean isManagedProfile(Context context, int userId) { - final UserManager userManager = context.getSystemService(UserManager.class); - return userManager.isManagedProfile(userId); - } - - static boolean containsSensorId(@Nullable List properties, - int sensorId) { - if (properties == null) { - return false; - } - - for (SensorPropertiesInternal prop : properties) { - if (prop.sensorId == sensorId) { - return true; - } - } - - return false; - } - - static boolean isSystem(@NonNull Context context, @Nullable String clientPackage) { - final boolean hasPermission = context.checkCallingOrSelfPermission(USE_BIOMETRIC_INTERNAL) - == PackageManager.PERMISSION_GRANTED; - return hasPermission && "android".equals(clientPackage); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt new file mode 100644 index 0000000000000..37634562f0dc9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt @@ -0,0 +1,115 @@ +/* + * 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.Manifest +import android.annotation.IntDef +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_MANAGED +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX +import android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_SOMETHING +import android.content.Context +import android.content.pm.PackageManager +import android.hardware.biometrics.BiometricManager.Authenticators +import android.hardware.biometrics.PromptInfo +import android.hardware.biometrics.SensorPropertiesInternal +import android.os.UserManager +import android.util.DisplayMetrics +import android.view.ViewGroup +import android.view.WindowManager +import android.view.accessibility.AccessibilityEvent +import android.view.accessibility.AccessibilityManager +import com.android.internal.widget.LockPatternUtils +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy + +object Utils { + const val CREDENTIAL_PIN = 1 + const val CREDENTIAL_PATTERN = 2 + const val CREDENTIAL_PASSWORD = 3 + + /** Base set of layout flags for fingerprint overlay widgets. */ + const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS = + (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN + or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) + + @JvmStatic + fun dpToPixels(context: Context, dp: Float): Float { + return dp * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT) + } + + @JvmStatic + fun notifyAccessibilityContentChanged(am: AccessibilityManager, view: ViewGroup) { + if (!am.isEnabled) { + return + } + val event = AccessibilityEvent.obtain() + event.eventType = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED + event.contentChangeTypes = + AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE + view.sendAccessibilityEventUnchecked(event) + view.notifySubtreeAccessibilityStateChanged( + view, + view, + AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE + ) + } + + @JvmStatic + fun isDeviceCredentialAllowed(promptInfo: PromptInfo): Boolean = + (promptInfo.authenticators and Authenticators.DEVICE_CREDENTIAL) != 0 + + @JvmStatic + fun isBiometricAllowed(promptInfo: PromptInfo): Boolean = + (promptInfo.authenticators and Authenticators.BIOMETRIC_WEAK) != 0 + + @JvmStatic + @CredentialType + fun getCredentialType(utils: LockPatternUtils, userId: Int): Int = + when (utils.getKeyguardStoredPasswordQuality(userId)) { + PASSWORD_QUALITY_SOMETHING -> CREDENTIAL_PATTERN + PASSWORD_QUALITY_NUMERIC, PASSWORD_QUALITY_NUMERIC_COMPLEX -> CREDENTIAL_PIN + PASSWORD_QUALITY_ALPHABETIC, PASSWORD_QUALITY_ALPHANUMERIC, PASSWORD_QUALITY_COMPLEX, PASSWORD_QUALITY_MANAGED -> CREDENTIAL_PASSWORD + else -> CREDENTIAL_PASSWORD + } + + @JvmStatic + fun isManagedProfile(context: Context, userId: Int): Boolean = + context.getSystemService(UserManager::class.java)?.isManagedProfile(userId) ?: false + + @JvmStatic + fun findFirstSensorProperties( + properties: List?, + sensorIds: IntArray + ): T? = properties?.firstOrNull { sensorIds.contains(it.sensorId) } + + @JvmStatic + fun isSystem(context: Context, clientPackage: String?): Boolean { + val hasPermission = + (context.checkCallingOrSelfPermission(Manifest.permission.USE_BIOMETRIC_INTERNAL) + == PackageManager.PERMISSION_GRANTED) + return hasPermission && "android" == clientPackage + } + + @Retention(RetentionPolicy.SOURCE) + @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD) + internal annotation class CredentialType +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java deleted file mode 100644 index 619d48d1e3068..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceToFingerprintViewTest.java +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Copyright (C) 2021 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 android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; -import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.hardware.biometrics.ComponentInfoInternal; -import android.hardware.biometrics.SensorLocationInternal; -import android.hardware.biometrics.SensorProperties; -import android.hardware.fingerprint.FingerprintSensorProperties; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.os.Bundle; -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; -import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; - -import com.android.systemui.R; -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Ignore; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper -@SmallTest -public class AuthBiometricFaceToFingerprintViewTest extends SysuiTestCase { - - @Mock AuthBiometricView.Callback mCallback; - - private AuthBiometricFaceToFingerprintView mFaceToFpView; - - @Mock private Button mNegativeButton; - @Mock private Button mCancelButton; - @Mock private Button mConfirmButton; - @Mock private Button mUseCredentialButton; - @Mock private Button mTryAgainButton; - - @Mock private TextView mTitleView; - @Mock private TextView mSubtitleView; - @Mock private TextView mDescriptionView; - @Mock private TextView mIndicatorView; - @Mock private ImageView mIconView; - @Mock private View mIconHolderView; - @Mock private AuthBiometricFaceView.IconController mFaceIconController; - @Mock private AuthBiometricFaceToFingerprintView.UdfpsIconController mUdfpsIconController; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - - mFaceToFpView = new TestableView(mContext); - mFaceToFpView.mFaceIconController = mFaceIconController; - mFaceToFpView.mUdfpsIconController = mUdfpsIconController; - mFaceToFpView.setCallback(mCallback); - - mFaceToFpView.mNegativeButton = mNegativeButton; - mFaceToFpView.mCancelButton = mCancelButton; - mFaceToFpView.mUseCredentialButton = mUseCredentialButton; - mFaceToFpView.mConfirmButton = mConfirmButton; - mFaceToFpView.mTryAgainButton = mTryAgainButton; - mFaceToFpView.mIndicatorView = mIndicatorView; - } - - @Test - public void testStateUpdated_whenDialogAnimatedIn() { - mFaceToFpView.onDialogAnimatedIn(); - verify(mFaceToFpView.mFaceIconController) - .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING)); - verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt()); - } - - @Test - public void testIconUpdatesState_whenDialogStateUpdated() { - mFaceToFpView.onDialogAnimatedIn(); - verify(mFaceToFpView.mFaceIconController) - .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING)); - verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt()); - - mFaceToFpView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED); - verify(mFaceToFpView.mFaceIconController).updateState( - eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING), - eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED)); - verify(mFaceToFpView.mUdfpsIconController, never()).updateState(anyInt()); - - assertEquals(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATED, mFaceToFpView.mState); - } - - @Test - public void testStateUpdated_whenSwitchToFingerprint() { - mFaceToFpView.onDialogAnimatedIn(); - verify(mFaceToFpView.mFaceIconController) - .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING)); - - mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR); - - verify(mFaceToFpView.mFaceIconController).deactivate(); - verify(mFaceToFpView.mUdfpsIconController).updateState( - eq(AuthBiometricFaceToFingerprintView.STATE_IDLE)); - verify(mConfirmButton).setVisibility(eq(View.GONE)); - - mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING); - - verify(mFaceToFpView.mUdfpsIconController).updateState( - eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING)); - } - - @Test - public void testStateUpdated_whenSwitchToFingerprint_invokesCallbacks() { - class TestModalityListener implements ModalityListener { - public int switchCount = 0; - - @Override - public void onModalitySwitched(int oldModality, int newModality) { - assertEquals(TYPE_FINGERPRINT, newModality); - assertEquals(TYPE_FACE, oldModality); - switchCount++; - } - } - final TestModalityListener modalityListener = new TestModalityListener(); - - mFaceToFpView.onDialogAnimatedIn(); - mFaceToFpView.setModalityListener(modalityListener); - - assertEquals(0, modalityListener.switchCount); - - mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR); - mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING); - - assertEquals(1, modalityListener.switchCount); - } - - @Test - @Ignore("flaky, b/189031816") - public void testModeUpdated_onSoftError_whenSwitchToFingerprint() { - mFaceToFpView.onDialogAnimatedIn(); - mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face"); - waitForIdleSync(); - - verify(mIndicatorView).setText( - eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead))); - verify(mCallback).onAction( - eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR)); - - // First we enter the error state, since we need to show the error animation/text. The - // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING. - assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState); - } - - @Test - @Ignore("flaky, b/189031816") - public void testModeUpdated_onHardError_whenSwitchToFingerprint() { - mFaceToFpView.onDialogAnimatedIn(); - mFaceToFpView.onError(TYPE_FACE, "oh no!"); - waitForIdleSync(); - - verify(mIndicatorView).setText( - eq(mContext.getString(R.string.fingerprint_dialog_use_fingerprint_instead))); - verify(mCallback).onAction( - eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR)); - - // First we enter the error state, since we need to show the error animation/text. The - // error state is later cleared based on a timer, and we enter STATE_AUTHENTICATING. - assertEquals(AuthBiometricFaceToFingerprintView.STATE_ERROR, mFaceToFpView.mState); - } - - @Test - public void testFingerprintOnlyStartsOnFirstError() { - mFaceToFpView.onDialogAnimatedIn(); - verify(mFaceToFpView.mFaceIconController) - .updateState(anyInt(), eq(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING)); - - mFaceToFpView.onDialogAnimatedIn(); - mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_ERROR); - mFaceToFpView.updateState(AuthBiometricFaceToFingerprintView.STATE_AUTHENTICATING); - - reset(mCallback); - - mFaceToFpView.onError(TYPE_FACE, "oh no!"); - mFaceToFpView.onAuthenticationFailed(TYPE_FACE, "no face"); - - verify(mCallback, never()).onAction( - eq(AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR)); - } - - @Test - public void testOnSaveState() { - final FingerprintSensorPropertiesInternal sensorProps = createFingerprintSensorProps(); - mFaceToFpView.setFingerprintSensorProps(sensorProps); - - final Bundle savedState = new Bundle(); - mFaceToFpView.onSaveState(savedState); - - assertEquals(savedState.getInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE), - mFaceToFpView.getActiveSensorType()); - assertEquals(savedState.getParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS), sensorProps); - } - - @Test - public void testRestoreState() { - final Bundle savedState = new Bundle(); - savedState.putInt(AuthDialog.KEY_BIOMETRIC_SENSOR_TYPE, TYPE_FINGERPRINT); - savedState.putParcelable(AuthDialog.KEY_BIOMETRIC_SENSOR_PROPS, - createFingerprintSensorProps()); - - mFaceToFpView.restoreState(savedState); - - assertEquals(mFaceToFpView.getActiveSensorType(), TYPE_FINGERPRINT); - assertTrue(mFaceToFpView.isFingerprintUdfps()); - } - - @NonNull - private static FingerprintSensorPropertiesInternal createFingerprintSensorProps() { - final List componentInfo = new ArrayList<>(); - componentInfo.add(new ComponentInfoInternal("componentId", "hardwareVersion", - "firmwareVersion", "serialNumber", "softwareVersion")); - - return new FingerprintSensorPropertiesInternal( - 0 /* sensorId */, - SensorProperties.STRENGTH_STRONG, - 5 /* maxEnrollmentsPerUser */, - componentInfo, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, - true /* resetLockoutRequiresHardwareAuthToken */, - List.of(new SensorLocationInternal("" /* displayId */, - 540 /* sensorLocationX */, - 1600 /* sensorLocationY */, - 100 /* sensorRadius */))); - } - - public class TestableView extends AuthBiometricFaceToFingerprintView { - public TestableView(Context context) { - super(context, null, new MockInjector()); - } - - @Override - protected int getDelayAfterAuthenticatedDurationMs() { - return 0; - } - } - - private class MockInjector extends AuthBiometricView.Injector { - @Override - public Button getNegativeButton() { - return mNegativeButton; - } - - @Override - public Button getCancelButton() { - return mCancelButton; - } - - @Override - public Button getUseCredentialButton() { - return mUseCredentialButton; - } - - @Override - public Button getConfirmButton() { - return mConfirmButton; - } - - @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 getIndicatorView() { - return mIndicatorView; - } - - @Override - public ImageView getIconView() { - return mIconView; - } - - @Override - public View getIconHolderView() { - return mIconHolderView; - } - - @Override - public int getDelayAfterError() { - return 0; - } - - @Override - public int getMediumToLargeAnimationDurationMs() { - return 0; - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java index b93381d2b5c9b..bdde1710d6a74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java @@ -37,8 +37,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import com.android.systemui.R; - @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest @@ -58,11 +56,14 @@ public class AuthBiometricFaceViewTest extends SysuiTestCase { @Mock private TextView mErrorView; + @Mock + private TestableFaceView.TestableIconController mIconController; + @Before public void setup() { MockitoAnnotations.initMocks(this); mFaceView = new TestableFaceView(mContext); - mFaceView.mFaceIconController = mock(TestableFaceView.TestableIconController.class); + mFaceView.mFaceIconController = mIconController; mFaceView.setCallback(mCallback); mFaceView.mNegativeButton = mNegativeButton; @@ -96,7 +97,7 @@ public class AuthBiometricFaceViewTest extends SysuiTestCase { public class TestableFaceView extends AuthBiometricFaceView { - public class TestableIconController extends IconController { + public class TestableIconController extends AuthBiometricFaceIconController { TestableIconController(Context context, ImageView iconView) { super(context, iconView, mock(TextView.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java index f8e38e4994bce..91a8d8a1b5aee 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java @@ -22,67 +22,50 @@ import static android.hardware.biometrics.BiometricManager.Authenticators; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; 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.hardware.biometrics.BiometricPrompt; import android.hardware.biometrics.PromptInfo; import android.os.Bundle; import android.test.suitebuilder.annotation.SmallTest; import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; -import android.util.AttributeSet; +import android.testing.ViewUtils; +import android.view.LayoutInflater; 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.Ignore; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; @RunWith(AndroidTestingRunner.class) @RunWithLooper @SmallTest public class AuthBiometricViewTest extends SysuiTestCase { - @Mock private AuthBiometricView.Callback mCallback; - @Mock private AuthPanelController mPanelController; + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); - @Mock private Button mNegativeButton; - @Mock private Button mCancelButton; - @Mock private Button mUseCredentialButton; + @Mock + private AuthBiometricView.Callback mCallback; + @Mock + private AuthPanelController mPanelController; - @Mock private Button mPositiveButton; - @Mock private Button mTryAgainButton; - - @Mock private TextView mTitleView; - @Mock private TextView mSubtitleView; - @Mock private TextView mDescriptionView; - @Mock private TextView mIndicatorView; - @Mock private ImageView mIconView; - @Mock private View mIconHolderView; - - private TestableBiometricView mBiometricView; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } + private AuthBiometricView mBiometricView; @Test public void testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() { - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); + initDialog(false /* allowDeviceCredential */, mCallback); // The onAuthenticated runnable is posted when authentication succeeds. mBiometricView.onAuthenticationSucceeded(); @@ -93,19 +76,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() { - final Button negativeButton = new Button(mContext); - final Button cancelButton = new Button(mContext); - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { - @Override - public Button getNegativeButton() { - return negativeButton; - } - - @Override - public Button getCancelButton() { - return cancelButton; - } - }); + initDialog(false /* allowDeviceCredential */, mCallback); mBiometricView.setRequireConfirmation(true); mBiometricView.onAuthenticationSucceeded(); @@ -113,26 +84,21 @@ public class AuthBiometricViewTest extends SysuiTestCase { assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState); verify(mCallback, never()).onAction(anyInt()); - assertEquals(View.GONE, negativeButton.getVisibility()); - assertEquals(View.VISIBLE, cancelButton.getVisibility()); - assertTrue(cancelButton.isEnabled()); + assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility()); + assertEquals(View.VISIBLE, mBiometricView.mCancelButton.getVisibility()); + assertTrue(mBiometricView.mCancelButton.isEnabled()); - verify(mBiometricView.mConfirmButton).setEnabled(eq(true)); - verify(mIndicatorView).setText(eq(R.string.biometric_dialog_tap_confirm)); - verify(mIndicatorView).setVisibility(eq(View.VISIBLE)); + assertTrue(mBiometricView.mConfirmButton.isEnabled()); + assertEquals(mContext.getText(R.string.biometric_dialog_tap_confirm), + mBiometricView.mIndicatorView.getText()); + assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility()); } @Test public void testPositiveButton_sendsActionAuthenticated() { - Button button = new Button(mContext); - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { - @Override - public Button getConfirmButton() { - return button; - } - }); + initDialog(false /* allowDeviceCredential */, mCallback); - button.performClick(); + mBiometricView.mConfirmButton.performClick(); waitForIdleSync(); verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED); @@ -141,16 +107,10 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testNegativeButton_beforeAuthentication_sendsActionButtonNegative() { - Button button = new Button(mContext); - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { - @Override - public Button getNegativeButton() { - return button; - } - }); + initDialog(false /* allowDeviceCredential */, mCallback); mBiometricView.onDialogAnimatedIn(); - button.performClick(); + mBiometricView.mNegativeButton.performClick(); waitForIdleSync(); verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE); @@ -158,25 +118,14 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testCancelButton_whenPendingConfirmation_sendsActionUserCanceled() { - Button cancelButton = new Button(mContext); - Button negativeButton = new Button(mContext); - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { - @Override - public Button getNegativeButton() { - return negativeButton; - } - @Override - public Button getCancelButton() { - return cancelButton; - } - }); + initDialog(false /* allowDeviceCredential */, mCallback); mBiometricView.setRequireConfirmation(true); mBiometricView.onAuthenticationSucceeded(); - assertEquals(View.GONE, negativeButton.getVisibility()); + assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility()); - cancelButton.performClick(); + mBiometricView.mCancelButton.performClick(); waitForIdleSync(); verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED); @@ -184,15 +133,9 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testTryAgainButton_sendsActionTryAgain() { - Button button = new Button(mContext); - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { - @Override - public Button getTryAgainButton() { - return button; - } - }); + initDialog(false /* allowDeviceCredential */, mCallback); - button.performClick(); + mBiometricView.mTryAgainButton.performClick(); waitForIdleSync(); verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN); @@ -202,7 +145,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test @Ignore("flaky, b/189031816") public void testError_sendsActionError() { - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); + initDialog(false /* allowDeviceCredential */, mCallback); final String testError = "testError"; mBiometricView.onError(TYPE_FACE, testError); waitForIdleSync(); @@ -213,7 +156,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testBackgroundClicked_sendsActionUserCanceled() { - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); + initDialog(false /* allowDeviceCredential */, mCallback); View view = new View(mContext); mBiometricView.setBackgroundView(view); @@ -223,7 +166,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() { - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); + initDialog(false /* allowDeviceCredential */, mCallback); View view = new View(mContext); mBiometricView.setBackgroundView(view); @@ -234,7 +177,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() { - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); + initDialog(false /* allowDeviceCredential */, mCallback); mBiometricView.mLayoutParams = new AuthDialog.LayoutParams(0, 0); mBiometricView.updateSize(AuthDialog.SIZE_SMALL); @@ -246,7 +189,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { @Test public void testIgnoresUselessHelp() { - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector()); + initDialog(false /* allowDeviceCredential */, mCallback); mBiometricView.onDialogAnimatedIn(); waitForIdleSync(); @@ -256,33 +199,16 @@ public class AuthBiometricViewTest extends SysuiTestCase { mBiometricView.onHelp(TYPE_FINGERPRINT, ""); waitForIdleSync(); - verify(mIndicatorView, never()).setText(any()); + assertEquals("", mBiometricView.mIndicatorView.getText()); verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR)); assertEquals(AuthBiometricView.STATE_AUTHENTICATING, mBiometricView.mState); } @Test public void testRestoresState() { - final boolean requireConfirmation = true; // set/init from AuthController + final boolean requireConfirmation = true; - Button tryAgainButton = new Button(mContext); - TextView indicatorView = new TextView(mContext); - initDialog(mContext, false /* allowDeviceCredential */, mCallback, new MockInjector() { - @Override - public Button getTryAgainButton() { - return tryAgainButton; - } - @Override - public TextView getIndicatorView() { - return indicatorView; - } - - @Override - public int getDelayAfterError() { - // keep a real delay to test saving in the error state - return BiometricPrompt.HIDE_DIALOG_DELAY; - } - }); + initDialog(false /* allowDeviceCredential */, mCallback, null, 10000); final String failureMessage = "testFailureMessage"; mBiometricView.setRequireConfirmation(requireConfirmation); @@ -292,8 +218,8 @@ public class AuthBiometricViewTest extends SysuiTestCase { Bundle state = new Bundle(); mBiometricView.onSaveState(state); - assertEquals(View.VISIBLE, tryAgainButton.getVisibility()); - assertEquals(View.VISIBLE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY)); + assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility()); + assertEquals(View.GONE, state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY)); assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState); assertEquals(AuthBiometricView.STATE_ERROR, state.getInt(AuthDialog.KEY_BIOMETRIC_STATE)); @@ -307,25 +233,12 @@ public class AuthBiometricViewTest extends SysuiTestCase { // TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle // Create new dialog and restore the previous state into it - Button tryAgainButton2 = new Button(mContext); - TextView indicatorView2 = new TextView(mContext); - initDialog(mContext, false /* allowDeviceCredential */, mCallback, state, - new MockInjector() { - @Override - public Button getTryAgainButton() { - return tryAgainButton2; - } - - @Override - public TextView getIndicatorView() { - return indicatorView2; - } - }); + initDialog(false /* allowDeviceCredential */, mCallback, state, 10000); + mBiometricView.mAnimationDurationHideDialog = 10000; mBiometricView.setRequireConfirmation(requireConfirmation); waitForIdleSync(); - // Test restored state - assertEquals(View.VISIBLE, tryAgainButton.getVisibility()); + assertEquals(View.GONE, mBiometricView.mTryAgainButton.getVisibility()); assertEquals(AuthBiometricView.STATE_ERROR, mBiometricView.mState); assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility()); @@ -334,23 +247,12 @@ public class AuthBiometricViewTest extends SysuiTestCase { } @Test - public void testCredentialButton_whenDeviceCredentialAllowed() { - final Button negativeButton = new Button(mContext); - final Button useCredentialButton = new Button(mContext); - initDialog(mContext, true /* allowDeviceCredential */, mCallback, new MockInjector() { - @Override - public Button getNegativeButton() { - return negativeButton; - } + public void testCredentialButton_whenDeviceCredentialAllowed() throws InterruptedException { + initDialog(true /* allowDeviceCredential */, mCallback); - @Override - public Button getUseCredentialButton() { - return useCredentialButton; - } - }); - - assertEquals(View.GONE, negativeButton.getVisibility()); - useCredentialButton.performClick(); + assertEquals(View.VISIBLE, mBiometricView.mUseCredentialButton.getVisibility()); + assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility()); + mBiometricView.mUseCredentialButton.performClick(); waitForIdleSync(); verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL); @@ -369,120 +271,30 @@ public class AuthBiometricViewTest extends SysuiTestCase { return promptInfo; } - private void initDialog(Context context, boolean allowDeviceCredential, - AuthBiometricView.Callback callback, - Bundle savedState, MockInjector injector) { - mBiometricView = new TestableBiometricView(context, null, injector); + private void initDialog(boolean allowDeviceCredential, AuthBiometricView.Callback callback) { + initDialog(allowDeviceCredential, callback, + null /* savedState */, 0 /* hideDelay */); + } + + private void initDialog(boolean allowDeviceCredential, + AuthBiometricView.Callback callback, Bundle savedState, int hideDelay) { + final LayoutInflater inflater = LayoutInflater.from(mContext); + mBiometricView = (AuthBiometricView) inflater.inflate( + R.layout.auth_biometric_view, null, false); + mBiometricView.mAnimationDurationLong = 0; + mBiometricView.mAnimationDurationShort = 0; + mBiometricView.mAnimationDurationHideDialog = hideDelay; mBiometricView.setPromptInfo(buildPromptInfo(allowDeviceCredential)); mBiometricView.setCallback(callback); mBiometricView.restoreState(savedState); - mBiometricView.onFinishInflateInternal(); - mBiometricView.onAttachedToWindowInternal(); - + ViewUtils.attachView(mBiometricView); mBiometricView.setPanelController(mPanelController); + waitForIdleSync(); } - private void initDialog(Context context, boolean allowDeviceCredential, - AuthBiometricView.Callback callback, MockInjector injector) { - initDialog(context, allowDeviceCredential, callback, null /* savedState */, injector); - } - - private class MockInjector extends AuthBiometricView.Injector { - @Override - public Button getNegativeButton() { - return mNegativeButton; - } - - @Override - public Button getCancelButton() { - return mCancelButton; - } - - @Override - public Button getUseCredentialButton() { - return mUseCredentialButton; - } - - @Override - public Button getConfirmButton() { - 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 getIndicatorView() { - return mIndicatorView; - } - - @Override - public ImageView getIconView() { - return mIconView; - } - - @Override - public View getIconHolderView() { - return mIconHolderView; - } - - @Override - public int getDelayAfterError() { - return 0; // Keep this at 0 for tests to invoke callback immediately. - } - - @Override - public int getMediumToLargeAnimationDurationMs() { - return 0; - } - } - - 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() { - - } - - @Override - protected boolean supportsSmallDialog() { - return false; - } + @Override + protected void waitForIdleSync() { + TestableLooper.get(this).processAllMessages(); + super.waitForIdleSync(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java deleted file mode 100644 index ae1268d48af90..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * 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 android.hardware.biometrics.BiometricManager.Authenticators; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.Assert.assertTrue; - -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.annotation.Nullable; -import android.content.Context; -import android.hardware.biometrics.BiometricConstants; -import android.hardware.biometrics.ComponentInfoInternal; -import android.hardware.biometrics.PromptInfo; -import android.hardware.biometrics.SensorProperties; -import android.hardware.face.FaceSensorPropertiesInternal; -import android.hardware.fingerprint.FingerprintSensorProperties; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.os.IBinder; -import android.os.UserManager; -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.ScrollView; - -import com.android.systemui.SysuiTestCase; -import com.android.systemui.keyguard.WakefulnessLifecycle; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.ArrayList; -import java.util.List; - -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -@SmallTest -public class AuthContainerViewTest extends SysuiTestCase { - - private TestableAuthContainer mAuthContainer; - - private @Mock AuthDialogCallback mCallback; - private @Mock UserManager mUserManager; - private @Mock WakefulnessLifecycle mWakefulnessLifecycle; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - } - - @Test - public void testActionAuthenticated_sendsDismissedAuthenticated() { - initializeContainer(Authenticators.BIOMETRIC_WEAK); - - mAuthContainer.mBiometricCallback.onAction( - AuthBiometricView.Callback.ACTION_AUTHENTICATED); - verify(mCallback).onDismissed( - eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED), - eq(null) /* credentialAttestation */); - } - - @Test - public void testActionUserCanceled_sendsDismissedUserCanceled() { - initializeContainer(Authenticators.BIOMETRIC_WEAK); - - mAuthContainer.mBiometricCallback.onAction( - AuthBiometricView.Callback.ACTION_USER_CANCELED); - verify(mCallback).onSystemEvent(eq( - BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL)); - verify(mCallback).onDismissed( - eq(AuthDialogCallback.DISMISSED_USER_CANCELED), - eq(null) /* credentialAttestation */); - } - - @Test - public void testActionButtonNegative_sendsDismissedButtonNegative() { - initializeContainer(Authenticators.BIOMETRIC_WEAK); - - mAuthContainer.mBiometricCallback.onAction( - AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE); - verify(mCallback).onDismissed( - eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE), - eq(null) /* credentialAttestation */); - } - - @Test - public void testActionTryAgain_sendsTryAgain() { - initializeContainer(Authenticators.BIOMETRIC_WEAK); - - mAuthContainer.mBiometricCallback.onAction( - AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN); - verify(mCallback).onTryAgainPressed(); - } - - @Test - public void testActionError_sendsDismissedError() { - initializeContainer(Authenticators.BIOMETRIC_WEAK); - - mAuthContainer.mBiometricCallback.onAction( - AuthBiometricView.Callback.ACTION_ERROR); - verify(mCallback).onDismissed( - eq(AuthDialogCallback.DISMISSED_ERROR), - eq(null) /* credentialAttestation */); - } - - @Test - public void testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() { - initializeContainer( - Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL); - - mAuthContainer.mBiometricCallback.onAction( - AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL); - verify(mCallback).onDeviceCredentialPressed(); - - // Credential view is attached to the frame layout - waitForIdleSync(); - assertNotNull(mAuthContainer.mCredentialView); - verify(mAuthContainer.mFrameLayout).addView(eq(mAuthContainer.mCredentialView)); - } - - @Test - public void testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() { - initializeContainer( - Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL); - - mAuthContainer.mBiometricView = mock(AuthBiometricView.class); - mAuthContainer.animateToCredentialUI(); - verify(mAuthContainer.mBiometricView).startTransitionToCredentialUI(); - } - - @Test - public void testShowBiometricUI() { - initializeContainer(Authenticators.BIOMETRIC_WEAK); - - assertNotEquals(null, mAuthContainer.mBiometricView); - - mAuthContainer.onAttachedToWindowInternal(); - verify(mAuthContainer.mBiometricScrollView).addView(mAuthContainer.mBiometricView); - // Credential view is not added - verify(mAuthContainer.mFrameLayout, never()).addView(any()); - } - - @Test - public void testShowCredentialUI_doesNotInflateBiometricUI() { - initializeContainer(Authenticators.DEVICE_CREDENTIAL); - - mAuthContainer.onAttachedToWindowInternal(); - - assertNull(null, mAuthContainer.mBiometricView); - assertNotNull(mAuthContainer.mCredentialView); - verify(mAuthContainer.mFrameLayout).addView(mAuthContainer.mCredentialView); - } - - @Test - public void testCredentialViewUsesEffectiveUserId() { - final int dummyEffectiveUserId = 200; - when(mUserManager.getCredentialOwnerProfile(anyInt())).thenReturn(dummyEffectiveUserId); - - initializeContainer(Authenticators.DEVICE_CREDENTIAL); - mAuthContainer.onAttachedToWindowInternal(); - assertTrue(mAuthContainer.mCredentialView instanceof AuthCredentialPatternView); - assertEquals(dummyEffectiveUserId, mAuthContainer.mCredentialView.mEffectiveUserId); - assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType); - } - - @Test - public void testCredentialUI_disablesClickingOnBackground() { - // In the credential view, clicking on the background (to cancel authentication) is not - // valid. Thus, the listener should be null, and it should not be in the accessibility - // hierarchy. - initializeContainer(Authenticators.DEVICE_CREDENTIAL); - - mAuthContainer.onAttachedToWindowInternal(); - - verify(mAuthContainer.mBackgroundView).setOnClickListener(eq(null)); - verify(mAuthContainer.mBackgroundView).setImportantForAccessibility( - eq(View.IMPORTANT_FOR_ACCESSIBILITY_NO)); - } - - @Test - public void testOnDialogAnimatedIn_sendsCancelReason_whenPendingDismiss() { - initializeContainer(Authenticators.BIOMETRIC_WEAK); - mAuthContainer.mContainerState = AuthContainerView.STATE_PENDING_DISMISS; - mAuthContainer.onDialogAnimatedIn(); - verify(mCallback).onDismissed( - eq(AuthDialogCallback.DISMISSED_USER_CANCELED), - eq(null) /* credentialAttestation */); - } - - @Test - public void testLayoutParams_hasSecureWindowFlag() { - final IBinder windowToken = mock(IBinder.class); - final WindowManager.LayoutParams layoutParams = - AuthContainerView.getLayoutParams(windowToken, ""); - assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0); - } - - @Test - public void testLayoutParams_excludesImeInsets() { - final IBinder windowToken = mock(IBinder.class); - final WindowManager.LayoutParams layoutParams = - AuthContainerView.getLayoutParams(windowToken, ""); - assertTrue((layoutParams.getFitInsetsTypes() & WindowInsets.Type.ime()) == 0); - } - - private void initializeContainer(int authenticators) { - AuthContainerView.Config config = new AuthContainerView.Config(); - config.mContext = mContext; - config.mCallback = mCallback; - config.mSensorIds = new int[] {0}; - config.mCredentialAllowed = false; - - PromptInfo promptInfo = new PromptInfo(); - promptInfo.setAuthenticators(authenticators); - config.mPromptInfo = promptInfo; - - final List fpProps = new ArrayList<>(); - - final List componentInfo = new ArrayList<>(); - componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, - "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, - "00000001" /* serialNumber */, "" /* softwareVersion */)); - componentInfo.add(new ComponentInfoInternal("matchingAlgorithm" /* componentId */, - "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, - "vendor/version/revision" /* softwareVersion */)); - - fpProps.add(new FingerprintSensorPropertiesInternal(0, - SensorProperties.STRENGTH_STRONG, - 5 /* maxEnrollmentsPerUser */, - componentInfo, - FingerprintSensorProperties.TYPE_REAR, - false /* resetLockoutRequiresHardwareAuthToken */)); - mAuthContainer = new TestableAuthContainer(config, fpProps, null /* faceProps */, - mWakefulnessLifecycle); - } - - private class TestableAuthContainer extends AuthContainerView { - TestableAuthContainer(AuthContainerView.Config config, - @Nullable List fpProps, - @Nullable List faceProps, - WakefulnessLifecycle wakefulnessLifecycle) { - - super(config, new MockInjector(), fpProps, faceProps, wakefulnessLifecycle); - } - - @Override - public void animateAway(int reason) { - // TODO: Credential attestation should be testable/tested - mConfig.mCallback.onDismissed(reason, null /* credentialAttestation */); - } - } - - private final class MockInjector extends AuthContainerView.Injector { - @Override - public ScrollView getBiometricScrollView(FrameLayout parent) { - return mock(ScrollView.class); - } - - @Override - public FrameLayout inflateContainerView(LayoutInflater factory, ViewGroup root) { - return mock(FrameLayout.class); - } - - @Override - public AuthPanelController getPanelController(Context context, View view) { - return mock(AuthPanelController.class); - } - - @Override - public ImageView getBackgroundView(FrameLayout parent) { - return mock(ImageView.class); - } - - @Override - public View getPanelView(FrameLayout parent) { - return mock(View.class); - } - - @Override - public int getAnimateCredentialStartDelayMs() { - return 0; - } - - @Override - public UserManager getUserManager(Context context) { - return mUserManager; - } - - @Override - public @Utils.CredentialType int getCredentialType(Context context, int effectiveUserId) { - return Utils.CREDENTIAL_PATTERN; - } - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt new file mode 100644 index 0000000000000..5832795c0055c --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2022 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.app.admin.DevicePolicyManager +import android.hardware.biometrics.BiometricConstants +import android.hardware.biometrics.BiometricManager +import android.hardware.biometrics.ComponentInfoInternal +import android.hardware.biometrics.PromptInfo +import android.hardware.biometrics.SensorProperties +import android.hardware.face.FaceSensorPropertiesInternal +import android.hardware.fingerprint.FingerprintSensorProperties +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.os.Handler +import android.os.IBinder +import android.os.UserManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.testing.TestableLooper.RunWithLooper +import android.testing.ViewUtils +import android.view.View +import android.view.WindowInsets +import android.view.WindowManager +import android.widget.ScrollView +import androidx.test.filters.SmallTest +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.google.common.truth.Truth.assertThat +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.eq +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever + + +@RunWith(AndroidTestingRunner::class) +@RunWithLooper +@SmallTest +class AuthContainerViewTest : SysuiTestCase() { + + @JvmField @Rule + var rule = MockitoJUnit.rule() + + @Mock + lateinit var callback: AuthDialogCallback + @Mock + lateinit var userManager: UserManager + @Mock + lateinit var lockPatternUtils: LockPatternUtils + @Mock + lateinit var wakefulnessLifecycle: WakefulnessLifecycle + @Mock + lateinit var windowToken: IBinder + + private lateinit var authContainer: TestAuthContainerView + + @Test + fun testActionAuthenticated_sendsDismissedAuthenticated() { + initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK) + authContainer.mBiometricCallback.onAction( + AuthBiometricView.Callback.ACTION_AUTHENTICATED + ) + waitForIdleSync() + + verify(callback).onDismissed( + eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED), + eq(null) /* credentialAttestation */ + ) + assertThat(authContainer.parent).isNull() + } + + @Test + fun testActionUserCanceled_sendsDismissedUserCanceled() { + initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK) + authContainer.mBiometricCallback.onAction( + AuthBiometricView.Callback.ACTION_USER_CANCELED + ) + waitForIdleSync() + + verify(callback).onSystemEvent( + eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL) + ) + verify(callback).onDismissed( + eq(AuthDialogCallback.DISMISSED_USER_CANCELED), + eq(null) /* credentialAttestation */ + ) + assertThat(authContainer.parent).isNull() + } + + @Test + fun testActionButtonNegative_sendsDismissedButtonNegative() { + initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK) + authContainer.mBiometricCallback.onAction( + AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE + ) + waitForIdleSync() + + verify(callback).onDismissed( + eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE), + eq(null) /* credentialAttestation */ + ) + assertThat(authContainer.parent).isNull() + } + + @Test + fun testActionTryAgain_sendsTryAgain() { + initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK) + authContainer.mBiometricCallback.onAction( + AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN + ) + waitForIdleSync() + + verify(callback).onTryAgainPressed() + } + + @Test + fun testActionError_sendsDismissedError() { + initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK) + authContainer.mBiometricCallback.onAction( + AuthBiometricView.Callback.ACTION_ERROR + ) + waitForIdleSync() + + verify(callback).onDismissed( + eq(AuthDialogCallback.DISMISSED_ERROR), + eq(null) /* credentialAttestation */ + ) + assertThat(authContainer.parent).isNull() + } + + @Test + fun testActionUseDeviceCredential_sendsOnDeviceCredentialPressed() { + initializeContainer( + BiometricManager.Authenticators.BIOMETRIC_WEAK or + BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) + authContainer.mBiometricCallback.onAction( + AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL + ) + waitForIdleSync() + + verify(callback).onDeviceCredentialPressed() + assertThat(authContainer.hasCredentialView()).isTrue() + } + + @Test + fun testAnimateToCredentialUI_invokesStartTransitionToCredentialUI() { + initializeContainer( + BiometricManager.Authenticators.BIOMETRIC_WEAK or + BiometricManager.Authenticators.DEVICE_CREDENTIAL + ) + authContainer.animateToCredentialUI() + waitForIdleSync() + + assertThat(authContainer.hasCredentialView()).isTrue() + } + + @Test + fun testShowBiometricUI() { + initializeContainer(BiometricManager.Authenticators.BIOMETRIC_WEAK) + + waitForIdleSync() + + assertThat(authContainer.hasCredentialView()).isFalse() + assertThat(authContainer.hasBiometricPrompt()).isTrue() + } + + @Test + fun testShowCredentialUI() { + initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL) + waitForIdleSync() + + assertThat(authContainer.hasCredentialView()).isTrue() + assertThat(authContainer.hasBiometricPrompt()).isFalse() + } + + @Test + fun testCredentialViewUsesEffectiveUserId() { + whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(200) + whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(200))).thenReturn( + DevicePolicyManager.PASSWORD_QUALITY_SOMETHING + ) + + initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL) + waitForIdleSync() + + assertThat(authContainer.hasCredentialPatternView()).isTrue() + assertThat(authContainer.hasBiometricPrompt()).isFalse() + } + + @Test + fun testCredentialUI_disablesClickingOnBackground() { + whenever(userManager.getCredentialOwnerProfile(anyInt())).thenReturn(20) + whenever(lockPatternUtils.getKeyguardStoredPasswordQuality(eq(20))).thenReturn( + DevicePolicyManager.PASSWORD_QUALITY_NUMERIC + ) + + // In the credential view, clicking on the background (to cancel authentication) is not + // valid. Thus, the listener should be null, and it should not be in the accessibility + // hierarchy. + initializeContainer(BiometricManager.Authenticators.DEVICE_CREDENTIAL) + waitForIdleSync() + + assertThat(authContainer.hasCredentialPasswordView()).isTrue() + assertThat(authContainer.hasBiometricPrompt()).isFalse() + assertThat( + authContainer.findViewById(R.id.background)?.isImportantForAccessibility + ).isFalse() + + authContainer.findViewById(R.id.background)?.performClick() + waitForIdleSync() + + assertThat(authContainer.hasCredentialPasswordView()).isTrue() + assertThat(authContainer.hasBiometricPrompt()).isFalse() + } + + @Test + fun testLayoutParams_hasSecureWindowFlag() { + val layoutParams = AuthContainerView.getLayoutParams(windowToken, "") + assertThat((layoutParams.flags and WindowManager.LayoutParams.FLAG_SECURE) != 0).isTrue() + } + + @Test + fun testLayoutParams_excludesImeInsets() { + val layoutParams = AuthContainerView.getLayoutParams(windowToken, "") + assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue() + } + + private fun initializeContainer(authenticators: Int) { + val config = AuthContainerView.Config() + config.mContext = mContext + config.mCallback = callback + config.mSensorIds = intArrayOf(0) + config.mSkipAnimation = true + config.mPromptInfo = PromptInfo() + config.mPromptInfo.authenticators = authenticators + val componentInfo = listOf( + ComponentInfoInternal( + "faceSensor" /* componentId */, + "vendor/model/revision" /* hardwareVersion */, "1.01" /* firmwareVersion */, + "00000001" /* serialNumber */, "" /* softwareVersion */ + ), + ComponentInfoInternal( + "matchingAlgorithm" /* componentId */, + "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */, + "vendor/version/revision" /* softwareVersion */ + ) + ) + val fpProps = listOf( + FingerprintSensorPropertiesInternal( + 0, + SensorProperties.STRENGTH_STRONG, + 5 /* maxEnrollmentsPerUser */, + componentInfo, + FingerprintSensorProperties.TYPE_REAR, + false /* resetLockoutRequiresHardwareAuthToken */ + ) + ) + authContainer = TestAuthContainerView( + config, + fpProps, + listOf(), + wakefulnessLifecycle, + userManager, + lockPatternUtils, + Handler(TestableLooper.get(this).looper) + ) + ViewUtils.attachView(authContainer) + } + + private inner class TestAuthContainerView( + config: Config, + fpProps: List, + faceProps: List, + wakefulnessLifecycle: WakefulnessLifecycle, + userManager: UserManager, + lockPatternUtils: LockPatternUtils, + mainHandler: Handler + ) : AuthContainerView( + config, fpProps, faceProps, + wakefulnessLifecycle, userManager, lockPatternUtils, mainHandler + ) { + override fun postOnAnimation(runnable: Runnable) { + runnable.run() + } + } + + override fun waitForIdleSync() { + TestableLooper.get(this).processAllMessages() + super.waitForIdleSync() + } +} + +private fun AuthContainerView.hasBiometricPrompt() = + (findViewById(R.id.biometric_scrollview)?.childCount ?: 0) > 0 + +private fun AuthContainerView.hasCredentialView() = + hasCredentialPatternView() || hasCredentialPasswordView() + +private fun AuthContainerView.hasCredentialPatternView() = + findViewById(R.id.lockPattern) != null + +private fun AuthContainerView.hasCredentialPasswordView() = + findViewById(R.id.lockPassword) != null diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java index c37e966f35404..e7993930e922e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -17,7 +17,7 @@ package com.android.systemui.biometrics; import static android.hardware.biometrics.BiometricManager.Authenticators; -import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT; +import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNull; @@ -62,6 +62,7 @@ import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; +import android.os.UserManager; import android.testing.AndroidTestingRunner; import android.testing.TestableContext; import android.testing.TestableLooper; @@ -71,6 +72,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; import com.android.internal.R; +import com.android.internal.widget.LockPatternUtils; import com.android.systemui.SysuiTestCase; import com.android.systemui.keyguard.WakefulnessLifecycle; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -79,6 +81,7 @@ import com.android.systemui.util.concurrency.Execution; import com.android.systemui.util.concurrency.FakeExecution; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.AdditionalMatchers; @@ -86,7 +89,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.InOrder; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.List; @@ -99,6 +103,9 @@ import javax.inject.Provider; @SmallTest public class AuthControllerTest extends SysuiTestCase { + @Rule + public final MockitoRule mMockitoRule = MockitoJUnit.rule(); + @Mock private PackageManager mPackageManager; @Mock @@ -128,6 +135,10 @@ public class AuthControllerTest extends SysuiTestCase { @Mock private WakefulnessLifecycle mWakefulnessLifecycle; @Mock + private UserManager mUserManager; + @Mock + private LockPatternUtils mLockPatternUtils; + @Mock private StatusBarStateController mStatusBarStateController; @Captor ArgumentCaptor mAuthenticatorsRegisteredCaptor; @@ -144,8 +155,6 @@ public class AuthControllerTest extends SysuiTestCase { @Before public void setup() throws RemoteException { - MockitoAnnotations.initMocks(this); - mContextSpy = spy(mContext); mExecution = new FakeExecution(); mTestableLooper = TestableLooper.get(this); @@ -528,8 +537,7 @@ public class AuthControllerTest extends SysuiTestCase { doAnswer(invocation -> { Object[] args = invocation.getArguments(); Bundle savedState = (Bundle) args[0]; - savedState.putInt( - AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING); + savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false); return null; // onSaveState returns void }).when(mDialog1).onSaveState(any()); @@ -558,8 +566,7 @@ public class AuthControllerTest extends SysuiTestCase { doAnswer(invocation -> { Object[] args = invocation.getArguments(); Bundle savedState = (Bundle) args[0]; - savedState.putInt( - AuthDialog.KEY_CONTAINER_STATE, AuthContainerView.STATE_SHOWING); + savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false); savedState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, true); return null; // onSaveState returns void }).when(mDialog1).onSaveState(any()); @@ -697,7 +704,7 @@ public class AuthControllerTest extends SysuiTestCase { 0 /* operationId */, "testPackage", 1 /* requestId */, - BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT); + BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE); } private PromptInfo createTestPromptInfo() { @@ -739,15 +746,16 @@ public class AuthControllerTest extends SysuiTestCase { super(context, execution, commandQueue, activityTaskManager, windowManager, fingerprintManager, faceManager, udfpsControllerFactory, sidefpsControllerFactory, mDisplayManager, mWakefulnessLifecycle, - statusBarStateController, mHandler); + mUserManager, mLockPatternUtils, statusBarStateController, mHandler); } @Override protected AuthDialog buildDialog(PromptInfo promptInfo, - boolean requireConfirmation, int userId, int[] sensorIds, boolean credentialAllowed, + boolean requireConfirmation, int userId, int[] sensorIds, String opPackageName, boolean skipIntro, long operationId, long requestId, @BiometricManager.BiometricMultiSensorMode int multiSensorConfig, - WakefulnessLifecycle wakefulnessLifecycle) { + WakefulnessLifecycle wakefulnessLifecycle, UserManager userManager, + LockPatternUtils lockPatternUtils) { mLastBiometricPromptInfo = promptInfo; diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt index 254fc5945522e..839c0ab1318f0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt @@ -17,10 +17,10 @@ package com.android.systemui.biometrics import android.animation.Animator -import android.graphics.Insets import android.app.ActivityManager import android.app.ActivityTaskManager import android.content.ComponentName +import android.graphics.Insets import android.graphics.Rect import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS @@ -65,8 +65,8 @@ import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.Mockito.any import org.mockito.Mockito.anyFloat -import org.mockito.Mockito.anyLong import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyLong import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.reset diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index 066a866118dd4..ef82c3ec3322f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -57,9 +57,9 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.mock -import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index 2cd470e49d0c0..3d8d1282e184c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt @@ -38,11 +38,11 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.anyInt -import org.mockito.Mockito.nullable import org.mockito.Mockito.never -import org.mockito.Mockito.`when` as whenever +import org.mockito.Mockito.nullable import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever private const val DISPLAY_ID = "" // default display id private const val SENSOR_X = 50 diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index 79705a32c2643..986a4a11e324a 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -20,12 +20,8 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; -import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT; +import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; -import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FACE_SCANNING; -import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_FP_SCANNING; -import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_SWITCHING; -import static com.android.server.biometrics.BiometricServiceStateProto.MULTI_SENSOR_STATE_UNKNOWN; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTHENTICATED_PENDING_SYSUI; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_CALLED; import static com.android.server.biometrics.BiometricServiceStateProto.STATE_AUTH_IDLE; @@ -100,14 +96,6 @@ public final class AuthSession implements IBinder.DeathRecipient { @Retention(RetentionPolicy.SOURCE) @interface SessionState {} - /** Defined in biometrics.proto */ - @IntDef({ - MULTI_SENSOR_STATE_UNKNOWN, - MULTI_SENSOR_STATE_FACE_SCANNING, - MULTI_SENSOR_STATE_FP_SCANNING}) - @Retention(RetentionPolicy.SOURCE) - @interface MultiSensorState {} - /** * Notify the holder of the AuthSession that the caller/client's binder has died. The * holder (BiometricService) should schedule {@link AuthSession#onClientDied()} to be run @@ -143,7 +131,6 @@ public final class AuthSession implements IBinder.DeathRecipient { // The current state, which can be either idle, called, or started private @SessionState int mState = STATE_AUTH_IDLE; private @BiometricMultiSensorMode int mMultiSensorMode; - private @MultiSensorState int mMultiSensorState; private int[] mSensors; // TODO(b/197265902): merge into state private boolean mCancelled; @@ -254,7 +241,6 @@ public final class AuthSession implements IBinder.DeathRecipient { mState = STATE_SHOWING_DEVICE_CREDENTIAL; mSensors = new int[0]; mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT; - mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN; mStatusBarService.showAuthenticationDialog( mPromptInfo, @@ -307,7 +293,6 @@ public final class AuthSession implements IBinder.DeathRecipient { } mMultiSensorMode = getMultiSensorModeForNewSession( mPreAuthInfo.eligibleSensors); - mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN; mStatusBarService.showAuthenticationDialog(mPromptInfo, mSysuiReceiver, @@ -415,7 +400,7 @@ public final class AuthSession implements IBinder.DeathRecipient { mErrorEscrow = error; mVendorCodeEscrow = vendorCode; - final @BiometricAuthenticator.Modality int modality = sensorIdToModality(sensorId); + @Modality final int modality = sensorIdToModality(sensorId); switch (mState) { case STATE_AUTH_CALLED: { @@ -430,7 +415,6 @@ public final class AuthSession implements IBinder.DeathRecipient { mState = STATE_SHOWING_DEVICE_CREDENTIAL; mMultiSensorMode = BIOMETRIC_MULTI_SENSOR_DEFAULT; - mMultiSensorState = MULTI_SENSOR_STATE_UNKNOWN; mSensors = new int[0]; mStatusBarService.showAuthenticationDialog( @@ -468,12 +452,6 @@ public final class AuthSession implements IBinder.DeathRecipient { return true; } else { mState = STATE_ERROR_PENDING_SYSUI; - if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT - && mMultiSensorState == MULTI_SENSOR_STATE_FACE_SCANNING) { - // wait for the UI to signal when modality should switch - Slog.d(TAG, "onErrorReceived: waiting for modality switch callback"); - mMultiSensorState = MULTI_SENSOR_STATE_SWITCHING; - } mStatusBarService.onBiometricError(modality, error, vendorCode); } break; @@ -538,34 +516,6 @@ public final class AuthSession implements IBinder.DeathRecipient { mState = STATE_AUTH_STARTED_UI_SHOWING; - if (mMultiSensorMode == BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) { - mMultiSensorState = MULTI_SENSOR_STATE_FACE_SCANNING; - } else { - startFingerprintSensorsNow(); - } - } - - // call anytime after onDialogAnimatedIn() to indicate it's appropriate to start the - // fingerprint sensor (i.e. face auth has failed or is not available) - void onStartFingerprint() { - if (mMultiSensorMode != BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT) { - Slog.e(TAG, "onStartFingerprint, unexpected mode: " + mMultiSensorMode); - return; - } - - if (mState != STATE_AUTH_STARTED - && mState != STATE_AUTH_STARTED_UI_SHOWING - && mState != STATE_AUTH_PAUSED - && mState != STATE_ERROR_PENDING_SYSUI) { - Slog.w(TAG, "onStartFingerprint, started from unexpected state: " + mState); - } - - mMultiSensorState = MULTI_SENSOR_STATE_FP_SCANNING; - startFingerprintSensorsNow(); - } - - // unguarded helper for the above methods only - private void startFingerprintSensorsNow() { startAllPreparedFingerprintSensors(); mState = STATE_AUTH_STARTED_UI_SHOWING; } @@ -919,7 +869,7 @@ public final class AuthSession implements IBinder.DeathRecipient { } if (hasFace && hasFingerprint) { - return BIOMETRIC_MULTI_SENSOR_FACE_THEN_FINGERPRINT; + return BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; } return BIOMETRIC_MULTI_SENSOR_DEFAULT; } diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 758cf7a7d4303..6af283968d897 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -106,7 +106,6 @@ public class BiometricService extends SystemService { private static final int MSG_ON_SYSTEM_EVENT = 13; private static final int MSG_CLIENT_DIED = 14; private static final int MSG_ON_DIALOG_ANIMATED_IN = 15; - private static final int MSG_ON_START_FINGERPRINT_NOW = 16; private final Injector mInjector; private final DevicePolicyManager mDevicePolicyManager; @@ -244,11 +243,6 @@ public class BiometricService extends SystemService { break; } - case MSG_ON_START_FINGERPRINT_NOW: { - handleOnStartFingerprintNow(); - break; - } - default: Slog.e(TAG, "Unknown message: " + msg); break; @@ -630,11 +624,6 @@ public class BiometricService extends SystemService { public void onDialogAnimatedIn() { mHandler.obtainMessage(MSG_ON_DIALOG_ANIMATED_IN).sendToTarget(); } - - @Override - public void onStartFingerprintNow() { - mHandler.obtainMessage(MSG_ON_START_FINGERPRINT_NOW).sendToTarget(); - } }; private final AuthSession.ClientDeathReceiver mClientDeathReceiver = () -> { @@ -1344,16 +1333,6 @@ public class BiometricService extends SystemService { mCurrentAuthSession.onDialogAnimatedIn(); } - private void handleOnStartFingerprintNow() { - Slog.d(TAG, "handleOnStartFingerprintNow"); - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleOnStartFingerprintNow: AuthSession is null"); - return; - } - - mCurrentAuthSession.onStartFingerprint(); - } - /** * Invoked when each service has notified that its client is ready to be started. When * all biometrics are ready, this invokes the SystemUI dialog through StatusBar. diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java index b255a35c512e5..25cf8a86baadd 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthSessionTest.java @@ -219,7 +219,7 @@ public class AuthSessionTest { public void testMultiAuth_singleSensor_fingerprintSensorStartsAfterDialogAnimationCompletes() throws Exception { setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); - testMultiAuth_fingerprintSensorStartsAfter(false /* fingerprintStartsAfterDelay */); + testMultiAuth_fingerprintSensorStartsAfterUINotifies(); } @Test @@ -227,10 +227,10 @@ public class AuthSessionTest { throws Exception { setupFingerprint(0 /* id */, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); setupFace(1 /* id */, false, mock(IBiometricAuthenticator.class)); - testMultiAuth_fingerprintSensorStartsAfter(true /* fingerprintStartsAfterDelay */); + testMultiAuth_fingerprintSensorStartsAfterUINotifies(); } - public void testMultiAuth_fingerprintSensorStartsAfter(boolean fingerprintStartsAfterDelay) + public void testMultiAuth_fingerprintSensorStartsAfterUINotifies() throws Exception { final long operationId = 123; final int userId = 10; @@ -274,12 +274,6 @@ public class AuthSessionTest { // Notify AuthSession that the UI is shown. Then, fingerprint sensor should be started. session.onDialogAnimatedIn(); - if (fingerprintStartsAfterDelay) { - assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState()); - assertEquals(BiometricSensor.STATE_COOKIE_RETURNED, - session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState()); - session.onStartFingerprint(); - } assertEquals(STATE_AUTH_STARTED_UI_SHOWING, session.getState()); assertEquals(BiometricSensor.STATE_AUTHENTICATING, session.mPreAuthInfo.eligibleSensors.get(fingerprintSensorId).getSensorState());