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());