diff --git a/core/java/android/hardware/biometrics/IBiometricService.aidl b/core/java/android/hardware/biometrics/IBiometricService.aidl index 2c3c8c353e8c0..42aad36e44c14 100644 --- a/core/java/android/hardware/biometrics/IBiometricService.aidl +++ b/core/java/android/hardware/biometrics/IBiometricService.aidl @@ -63,7 +63,7 @@ interface IBiometricService { // Notify BiometricService when Service is ready to start the prepared client. // Client lifecycle is still managed in Service. - void onReadyForAuthentication(int cookie); + void onReadyForAuthentication(long requestId, int cookie); // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the // specified user. This happens when enrollments have been added on devices with multiple diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 099d1fc933d22..d629d66d1c312 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -159,7 +159,7 @@ oneway interface IStatusBar /** * Used to notify the authentication dialog that a biometric has been authenticated. */ - void onBiometricAuthenticated(); + void onBiometricAuthenticated(int modality); /** * Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc. */ diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index dcc1a7626a1b5..9163b6d6215ea 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -125,7 +125,7 @@ interface IStatusBarService int multiSensorConfig); // Used to notify the authentication dialog that a biometric has been authenticated - void onBiometricAuthenticated(); + void onBiometricAuthenticated(int modality); // Used to set a temporary message, e.g. fingerprint not recognized, finger moved too fast, etc void onBiometricHelp(int modality, String message); // Used to show an error - the dialog will dismiss after a certain amount of time diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml index 0ae5dc745478b..5084ca48e6084 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_fp.xml @@ -1,254 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml new file mode 100644 index 0000000000000..c4f818146011c --- /dev/null +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_error_to_unlock.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml index fc2c7d00f3a73..c05a8d55c16ca 100644 --- a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_error.xml @@ -1,247 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml new file mode 100644 index 0000000000000..16944294a94e0 --- /dev/null +++ b/packages/SystemUI/res/drawable/fingerprint_dialog_fp_to_unlock.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml index 89690e8ff0ec0..58adb9146bd04 100644 --- a/packages/SystemUI/res/layout/auth_biometric_contents.xml +++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml @@ -49,8 +49,8 @@ diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml index 7c2b60e221f06..05ca2a786e3ab 100644 --- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml +++ b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml @@ -16,6 +16,7 @@ diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 65c17b9028e1a..5a7efca3dece2 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -876,7 +876,8 @@ 60dp - 64dp + 64dp + 80dp 160dp 136dp 4dp diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 06febbb239998..4ea6d9d7dd33b 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -316,6 +316,8 @@ Confirmed Tap Confirm to complete + + Unlocked by your face. Press to continue. Authenticated diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.java deleted file mode 100644 index 0058cc1e0a7f3..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * 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/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt new file mode 100644 index 0000000000000..55611f7d7ada4 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt @@ -0,0 +1,123 @@ +/* + * 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.Drawable +import android.util.Log +import android.widget.ImageView +import com.android.systemui.R +import com.android.systemui.biometrics.AuthBiometricView.BiometricState +import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED +import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING +import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN +import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR +import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP +import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE +import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION + +private const val TAG = "AuthBiometricFaceIconController" + +/** Face only icon animator for BiometricPrompt. */ +class AuthBiometricFaceIconController( + context: Context, + iconView: ImageView +) : AuthIconController(context, iconView) { + + // false = dark to light, true = light to dark + private var lastPulseLightToDark = false + + @BiometricState + private var state = 0 + + init { + val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size) + iconView.layoutParams.width = size + iconView.layoutParams.height = size + showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light) + } + + private fun startPulsing() { + lastPulseLightToDark = false + animateIcon(R.drawable.face_dialog_pulse_dark_to_light, true) + } + + private fun pulseInNextDirection() { + val iconRes = if (lastPulseLightToDark) { + R.drawable.face_dialog_pulse_dark_to_light + } else { + R.drawable.face_dialog_pulse_light_to_dark + } + animateIcon(iconRes, true /* repeat */) + lastPulseLightToDark = !lastPulseLightToDark + } + + override fun handleAnimationEnd(drawable: Drawable) { + if (state == STATE_AUTHENTICATING || state == STATE_HELP) { + pulseInNextDirection() + } + } + + override fun updateIcon(@BiometricState oldState: Int, @BiometricState newState: Int) { + val lastStateIsErrorIcon = (oldState == STATE_ERROR || oldState == STATE_HELP) + if (newState == STATE_AUTHENTICATING_ANIMATING_IN) { + showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light) + iconView.contentDescription = context.getString( + R.string.biometric_dialog_face_icon_description_authenticating + ) + } else if (newState == STATE_AUTHENTICATING) { + startPulsing() + iconView.contentDescription = context.getString( + R.string.biometric_dialog_face_icon_description_authenticating + ) + } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) { + animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) + iconView.contentDescription = context.getString( + R.string.biometric_dialog_face_icon_description_confirmed + ) + } else if (lastStateIsErrorIcon && newState == STATE_IDLE) { + animateIconOnce(R.drawable.face_dialog_error_to_idle) + iconView.contentDescription = context.getString( + R.string.biometric_dialog_face_icon_description_idle + ) + } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) { + animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) + iconView.contentDescription = context.getString( + R.string.biometric_dialog_face_icon_description_authenticated + ) + } else if (newState == STATE_ERROR && oldState != STATE_ERROR) { + animateIconOnce(R.drawable.face_dialog_dark_to_error) + } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) { + animateIconOnce(R.drawable.face_dialog_dark_to_checkmark) + iconView.contentDescription = context.getString( + R.string.biometric_dialog_face_icon_description_authenticated + ) + } else if (newState == STATE_PENDING_CONFIRMATION) { + animateIconOnce(R.drawable.face_dialog_wink_from_dark) + iconView.contentDescription = context.getString( + R.string.biometric_dialog_face_icon_description_authenticated + ) + } else if (newState == STATE_IDLE) { + showStaticDrawable(R.drawable.face_dialog_idle_static) + iconView.contentDescription = context.getString( + R.string.biometric_dialog_face_icon_description_idle + ) + } else { + Log.w(TAG, "Unhandled state: $newState") + } + state = newState + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java deleted file mode 100644 index b002c0060971e..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.java +++ /dev/null @@ -1,127 +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 android.content.Context; -import android.hardware.biometrics.BiometricAuthenticator.Modality; -import android.util.AttributeSet; -import android.view.View; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.android.internal.annotations.VisibleForTesting; - -public class AuthBiometricFaceView extends AuthBiometricView { - - private static final String TAG = "AuthBiometricFaceView"; - - // Delay before dismissing after being authenticated/confirmed. - private static final int HIDE_DELAY_MS = 500; - - @Nullable @VisibleForTesting AuthBiometricFaceIconController mFaceIconController; - @NonNull private final OnAttachStateChangeListener mOnAttachStateChangeListener = - new OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - - } - - @Override - public void onViewDetachedFromWindow(View v) { - mFaceIconController.deactivate(); - } - }; - - public AuthBiometricFaceView(Context context) { - this(context, null); - } - - public AuthBiometricFaceView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - mFaceIconController = new AuthBiometricFaceIconController(mContext, mIconView, mIndicatorView); - - addOnAttachStateChangeListener(mOnAttachStateChangeListener); - } - - @Override - protected int getDelayAfterAuthenticatedDurationMs() { - return HIDE_DELAY_MS; - } - - @Override - protected int getStateForAfterError() { - return STATE_IDLE; - } - - @Override - protected void handleResetAfterError() { - resetErrorView(); - } - - @Override - protected void handleResetAfterHelp() { - resetErrorView(); - } - - @Override - protected boolean supportsSmallDialog() { - return true; - } - - @Override - protected boolean supportsManualRetry() { - return true; - } - - @Override - public void updateState(@BiometricState int newState) { - mFaceIconController.updateState(mState, newState); - - if (newState == STATE_AUTHENTICATING_ANIMATING_IN || - (newState == STATE_AUTHENTICATING && getSize() == AuthDialog.SIZE_MEDIUM)) { - resetErrorView(); - } - - // Do this last since the state variable gets updated. - super.updateState(newState); - } - - @Override - public void onAuthenticationFailed(@Modality int modality, @Nullable String failureReason) { - if (getSize() == AuthDialog.SIZE_MEDIUM) { - if (supportsManualRetry()) { - mTryAgainButton.setVisibility(View.VISIBLE); - mConfirmButton.setVisibility(View.GONE); - } - } - - // Do this last since we want to know if the button is being animated (in the case of - // small -> medium dialog) - super.onAuthenticationFailed(modality, failureReason); - } - - private void resetErrorView() { - mIndicatorView.setTextColor(mTextColorHint); - mIndicatorView.setVisibility(View.INVISIBLE); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt new file mode 100644 index 0000000000000..be89d10393ddf --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.biometrics + +import android.content.Context +import android.hardware.biometrics.BiometricAuthenticator.Modality +import android.util.AttributeSet + +/** Face only view for BiometricPrompt. */ +class AuthBiometricFaceView( + context: Context, + attrs: AttributeSet? = null +) : AuthBiometricView(context, attrs) { + + override fun getDelayAfterAuthenticatedDurationMs() = HIDE_DELAY_MS + + override fun getStateForAfterError() = STATE_IDLE + + override fun handleResetAfterError() = resetErrorView() + + override fun handleResetAfterHelp() = resetErrorView() + + override fun supportsSmallDialog() = true + + override fun supportsManualRetry() = true + + override fun supportsRequireConfirmation() = true + + override fun createIconController(): AuthIconController = + AuthBiometricFaceIconController(mContext, mIconView) + + override fun updateState(@BiometricState newState: Int) { + if (newState == STATE_AUTHENTICATING_ANIMATING_IN || + newState == STATE_AUTHENTICATING && size == AuthDialog.SIZE_MEDIUM) { + resetErrorView() + } + + // Do this last since the state variable gets updated. + super.updateState(newState) + } + + override fun onAuthenticationFailed( + @Modality modality: Int, + failureReason: String? + ) { + if (size == AuthDialog.SIZE_MEDIUM) { + if (supportsManualRetry()) { + mTryAgainButton.visibility = VISIBLE + mConfirmButton.visibility = GONE + } + } + + // Do this last since we want to know if the button is being animated (in the case of + // small -> medium dialog) + super.onAuthenticationFailed(modality, failureReason) + } + + private fun resetErrorView() { + mIndicatorView.setTextColor(mTextColorHint) + mIndicatorView.visibility = INVISIBLE + } + + companion object { + /** Delay before dismissing after being authenticated/confirmed. */ + const val HIDE_DELAY_MS = 500 + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt new file mode 100644 index 0000000000000..3e4e573c9531d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt @@ -0,0 +1,60 @@ +/* + * 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.Drawable +import android.widget.ImageView +import com.android.systemui.R +import com.android.systemui.biometrics.AuthBiometricView.BiometricState +import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION +import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED +import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR +import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP + +/** Face/Fingerprint combined icon animator for BiometricPrompt. */ +class AuthBiometricFingerprintAndFaceIconController( + context: Context, + iconView: ImageView +) : AuthBiometricFingerprintIconController(context, iconView) { + + override val actsAsConfirmButton: Boolean = true + + override fun shouldAnimateForTransition( + @BiometricState oldState: Int, + @BiometricState newState: Int + ): Boolean = when (newState) { + STATE_PENDING_CONFIRMATION -> true + STATE_AUTHENTICATED -> false + else -> super.shouldAnimateForTransition(oldState, newState) + } + + override fun getAnimationForTransition( + @BiometricState oldState: Int, + @BiometricState newState: Int + ): Drawable? = when (newState) { + STATE_PENDING_CONFIRMATION -> { + if (oldState == STATE_ERROR || oldState == STATE_HELP) { + context.getDrawable(R.drawable.fingerprint_dialog_error_to_unlock) + } else { + context.getDrawable(R.drawable.fingerprint_dialog_fp_to_unlock) + } + } + STATE_AUTHENTICATED -> null + else -> super.getAnimationForTransition(oldState, newState) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt index 45e6c0460bcef..7371442bdd07b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt @@ -17,8 +17,12 @@ package com.android.systemui.biometrics import android.content.Context +import android.hardware.biometrics.BiometricAuthenticator.Modality +import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE import android.util.AttributeSet +import com.android.systemui.R +/** Face/Fingerprint combined view for BiometricPrompt. */ class AuthBiometricFingerprintAndFaceView( context: Context, attrs: AttributeSet? @@ -26,4 +30,14 @@ class AuthBiometricFingerprintAndFaceView( constructor (context: Context) : this(context, null) -} \ No newline at end of file + override fun getConfirmationPrompt() = R.string.biometric_dialog_tap_confirm_with_face + + override fun forceRequireConfirmation(@Modality modality: Int) = modality == TYPE_FACE + + override fun ignoreUnsuccessfulEventsFrom(@Modality modality: Int) = modality == TYPE_FACE + + override fun onPointerDown(failedModalities: Set) = failedModalities.contains(TYPE_FACE) + + override fun createIconController(): AuthIconController = + AuthBiometricFingerprintAndFaceIconController(mContext, mIconView) +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt new file mode 100644 index 0000000000000..cd16379cd5b4c --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt @@ -0,0 +1,112 @@ +/* + * 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.AnimatedVectorDrawable +import android.graphics.drawable.Drawable +import android.widget.ImageView +import com.android.systemui.R +import com.android.systemui.biometrics.AuthBiometricView.BiometricState +import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED +import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING +import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN +import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR +import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP +import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE +import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION + +/** Fingerprint only icon animator for BiometricPrompt. */ +open class AuthBiometricFingerprintIconController( + context: Context, + iconView: ImageView +) : AuthIconController(context, iconView) { + + init { + val size = context.resources.getDimensionPixelSize( + R.dimen.biometric_dialog_fingerprint_icon_size + ) + iconView.layoutParams.width = size + iconView.layoutParams.height = size + } + + override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) { + val icon = getAnimationForTransition(lastState, newState) ?: return + + iconView.setImageDrawable(icon) + + val iconContentDescription = getIconContentDescription(newState) + if (iconContentDescription != null) { + iconView.contentDescription = iconContentDescription + } + + (icon as? AnimatedVectorDrawable)?.apply { + reset() + if (shouldAnimateForTransition(lastState, newState)) { + forceAnimationOnUI() + start() + } + } + } + + private fun getIconContentDescription(@BiometricState newState: Int): CharSequence? { + val id = when (newState) { + STATE_IDLE, + STATE_AUTHENTICATING_ANIMATING_IN, + STATE_AUTHENTICATING, + STATE_PENDING_CONFIRMATION, + STATE_AUTHENTICATED -> R.string.accessibility_fingerprint_dialog_fingerprint_icon + STATE_ERROR, + STATE_HELP -> R.string.biometric_dialog_try_again + else -> null + } + return if (id != null) context.getString(id) else null + } + + protected open fun shouldAnimateForTransition( + @BiometricState oldState: Int, + @BiometricState newState: Int + ) = when (newState) { + STATE_HELP, + STATE_ERROR -> true + STATE_AUTHENTICATING_ANIMATING_IN, + STATE_AUTHENTICATING -> oldState == STATE_ERROR || oldState == STATE_HELP + STATE_AUTHENTICATED -> false + else -> false + } + + protected open fun getAnimationForTransition( + @BiometricState oldState: Int, + @BiometricState newState: Int + ): Drawable? { + val id = when (newState) { + STATE_HELP, + STATE_ERROR -> R.drawable.fingerprint_dialog_fp_to_error + STATE_AUTHENTICATING_ANIMATING_IN, + STATE_AUTHENTICATING -> { + if (oldState == STATE_ERROR || oldState == STATE_HELP) { + R.drawable.fingerprint_dialog_error_to_fp + } else { + R.drawable.fingerprint_dialog_fp_to_error + } + } + STATE_AUTHENTICATED -> R.drawable.fingerprint_dialog_fp_to_error + else -> return null + } + return if (id != null) context.getDrawable(id) else null + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java deleted file mode 100644 index 48993ef1ed5ba..0000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.java +++ /dev/null @@ -1,225 +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 android.annotation.NonNull; -import android.content.Context; -import android.graphics.drawable.AnimatedVectorDrawable; -import android.graphics.drawable.Drawable; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; -import android.util.AttributeSet; -import android.util.Log; -import android.widget.FrameLayout; -import android.widget.TextView; - -import androidx.annotation.Nullable; - -import com.android.systemui.R; - -public class AuthBiometricFingerprintView extends AuthBiometricView { - - private static final String TAG = "AuthBiometricFingerprintView"; - - private boolean mIsUdfps = false; - @Nullable private UdfpsDialogMeasureAdapter mUdfpsAdapter; - - public AuthBiometricFingerprintView(Context context) { - this(context, null); - } - - public AuthBiometricFingerprintView(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - AuthDialog.LayoutParams onMeasureInternal(int width, int height) { - final AuthDialog.LayoutParams layoutParams = super.onMeasureInternal(width, height); - return mUdfpsAdapter != null - ? mUdfpsAdapter.onMeasureInternal(width, height, layoutParams) - : layoutParams; - } - - /** - * Set the properties of this sensor so the view can be customized prior to layout. - * - * @param sensorProps sensor properties - */ - public void setSensorProperties(@NonNull FingerprintSensorPropertiesInternal sensorProps) { - mIsUdfps = sensorProps.isAnyUdfpsType(); - mUdfpsAdapter = mIsUdfps ? new UdfpsDialogMeasureAdapter(this, sensorProps) : null; - } - - @Override - 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 - // for devices where the UDFPS sensor is too low. - // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap - // the button bar area. - final int bottomSpacerHeight = mUdfpsAdapter.getBottomSpacerHeight(); - Log.w(TAG, "bottomSpacerHeight: " + bottomSpacerHeight); - if (bottomSpacerHeight < 0) { - FrameLayout iconFrame = findViewById(R.id.biometric_icon_frame); - iconFrame.setTranslationY(-bottomSpacerHeight); - - TextView indicator = findViewById(R.id.indicator); - indicator.setTranslationY(-bottomSpacerHeight); - } - } - } - - /** If this view is for a UDFPS sensor. */ - public boolean isUdfps() { - return mIsUdfps; - } - - @Override - protected int getDelayAfterAuthenticatedDurationMs() { - return 0; - } - - @Override - protected int getStateForAfterError() { - return STATE_AUTHENTICATING; - } - - @Override - protected void handleResetAfterError() { - showTouchSensorString(); - } - - @Override - protected void handleResetAfterHelp() { - showTouchSensorString(); - } - - @Override - protected boolean supportsSmallDialog() { - return false; - } - - @Override - public void updateState(@BiometricState int newState) { - updateIcon(mState, newState); - - // Do this last since the state variable gets updated. - super.updateState(newState); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - showTouchSensorString(); - } - - private void showTouchSensorString() { - mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor); - mIndicatorView.setTextColor(mTextColorHint); - } - - private void updateIcon(int lastState, int newState) { - final Drawable icon = getAnimationForTransition(lastState, newState); - if (icon == null) { - Log.e(TAG, "Animation not found, " + lastState + " -> " + newState); - return; - } - - final AnimatedVectorDrawable animation = icon instanceof AnimatedVectorDrawable - ? (AnimatedVectorDrawable) icon - : null; - - mIconView.setImageDrawable(icon); - - final CharSequence iconContentDescription = getIconContentDescription(newState); - if (iconContentDescription != null) { - mIconView.setContentDescription(iconContentDescription); - } - - if (animation != null && shouldAnimateForTransition(lastState, newState)) { - animation.forceAnimationOnUI(); - animation.start(); - } - } - - @Nullable - private CharSequence getIconContentDescription(int newState) { - switch (newState) { - case STATE_IDLE: - case STATE_AUTHENTICATING_ANIMATING_IN: - case STATE_AUTHENTICATING: - case STATE_PENDING_CONFIRMATION: - case STATE_AUTHENTICATED: - return mContext.getString( - R.string.accessibility_fingerprint_dialog_fingerprint_icon); - - case STATE_ERROR: - case STATE_HELP: - return mContext.getString(R.string.biometric_dialog_try_again); - - default: - return null; - } - } - - private boolean shouldAnimateForTransition(int oldState, int newState) { - switch (newState) { - case STATE_HELP: - case STATE_ERROR: - return true; - case STATE_AUTHENTICATING_ANIMATING_IN: - case STATE_AUTHENTICATING: - if (oldState == STATE_ERROR || oldState == STATE_HELP) { - return true; - } else { - return false; - } - case STATE_AUTHENTICATED: - return false; - default: - return false; - } - } - - private Drawable getAnimationForTransition(int oldState, int newState) { - int iconRes; - - switch (newState) { - case STATE_HELP: - case STATE_ERROR: - iconRes = R.drawable.fingerprint_dialog_fp_to_error; - break; - case STATE_AUTHENTICATING_ANIMATING_IN: - case STATE_AUTHENTICATING: - if (oldState == STATE_ERROR || oldState == STATE_HELP) { - iconRes = R.drawable.fingerprint_dialog_error_to_fp; - } else { - iconRes = R.drawable.fingerprint_dialog_fp_to_error; - } - break; - case STATE_AUTHENTICATED: - iconRes = R.drawable.fingerprint_dialog_fp_to_error; - break; - default: - return null; - } - - return mContext.getDrawable(iconRes); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt new file mode 100644 index 0000000000000..368bc3aadb701 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.systemui.biometrics + +import android.content.Context +import android.hardware.fingerprint.FingerprintSensorPropertiesInternal +import android.util.AttributeSet +import android.util.Log +import android.widget.FrameLayout +import android.widget.TextView +import com.android.systemui.R + +private const val TAG = "AuthBiometricFingerprintView" + +/** Fingerprint only view for BiometricPrompt. */ +open class AuthBiometricFingerprintView( + context: Context, + attrs: AttributeSet? = null +) : AuthBiometricView(context, attrs) { + /** If this view is for a UDFPS sensor. */ + var isUdfps = false + private set + + private var udfpsAdapter: UdfpsDialogMeasureAdapter? = null + + /** Set the [sensorProps] of this sensor so the view can be customized prior to layout. */ + fun setSensorProperties(sensorProps: FingerprintSensorPropertiesInternal) { + isUdfps = sensorProps.isAnyUdfpsType + udfpsAdapter = if (isUdfps) UdfpsDialogMeasureAdapter(this, sensorProps) else null + } + + override fun onMeasureInternal(width: Int, height: Int): AuthDialog.LayoutParams { + val layoutParams = super.onMeasureInternal(width, height) + return udfpsAdapter?.onMeasureInternal(width, height, layoutParams) ?: layoutParams + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + + val adapter = udfpsAdapter + if (adapter != null) { + // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen + // for devices where the UDFPS sensor is too low. + // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap + // the button bar area. + val bottomSpacerHeight = adapter.bottomSpacerHeight + Log.w(TAG, "bottomSpacerHeight: $bottomSpacerHeight") + if (bottomSpacerHeight < 0) { + val iconFrame = findViewById(R.id.biometric_icon_frame)!! + iconFrame.translationY = -bottomSpacerHeight.toFloat() + val indicator = findViewById(R.id.indicator)!! + indicator.translationY = -bottomSpacerHeight.toFloat() + } + } + } + + override fun getDelayAfterAuthenticatedDurationMs() = 0 + + override fun getStateForAfterError() = STATE_AUTHENTICATING + + override fun handleResetAfterError() = showTouchSensorString() + + override fun handleResetAfterHelp() = showTouchSensorString() + + override fun supportsSmallDialog() = false + + override fun createIconController(): AuthIconController = + AuthBiometricFingerprintIconController(mContext, mIconView) + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + showTouchSensorString() + } + + private fun showTouchSensorString() { + mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor) + mIndicatorView.setTextColor(mTextColorHint) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt new file mode 100644 index 0000000000000..ce5e600e6a77a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricIconController.kt @@ -0,0 +1,94 @@ +/* + * 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.annotation.DrawableRes +import android.content.Context +import android.graphics.drawable.Animatable2 +import android.graphics.drawable.AnimatedVectorDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import android.widget.ImageView +import com.android.systemui.biometrics.AuthBiometricView.BiometricState + +private const val TAG = "AuthIconController" + +/** Controller for animating the BiometricPrompt icon/affordance. */ +abstract class AuthIconController( + protected val context: Context, + protected val iconView: ImageView +) : Animatable2.AnimationCallback() { + + /** If this controller should ignore events and pause. */ + var deactivated: Boolean = false + + /** If the icon view should be treated as an alternate "confirm" button. */ + open val actsAsConfirmButton: Boolean = false + + final override fun onAnimationStart(drawable: Drawable) { + super.onAnimationStart(drawable) + } + + final override fun onAnimationEnd(drawable: Drawable) { + super.onAnimationEnd(drawable) + + if (!deactivated) { + handleAnimationEnd(drawable) + } + } + + /** Set the icon to a static image. */ + protected fun showStaticDrawable(@DrawableRes iconRes: Int) { + iconView.setImageDrawable(context.getDrawable(iconRes)) + } + + /** Animate a resource. */ + protected fun animateIconOnce(@DrawableRes iconRes: Int) { + animateIcon(iconRes, false) + } + + /** Animate a resource. */ + protected fun animateIcon(@DrawableRes iconRes: Int, repeat: Boolean) { + if (!deactivated) { + val icon = context.getDrawable(iconRes) as AnimatedVectorDrawable + iconView.setImageDrawable(icon) + icon.forceAnimationOnUI() + if (repeat) { + icon.registerAnimationCallback(this) + } + icon.start() + } + } + + /** Update the icon to reflect the [newState]. */ + fun updateState(@BiometricState lastState: Int, @BiometricState newState: Int) { + if (deactivated) { + Log.w(TAG, "Ignoring updateState when deactivated: $newState") + } else { + updateIcon(lastState, newState) + } + } + + /** If the icon should act as a "retry" button in the [currentState]. */ + fun iconTapSendsRetryWhen(@BiometricState currentState: Int): Boolean = false + + /** Call during [updateState] if the controller is not [deactivated]. */ + abstract fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) + + /** Called during [onAnimationEnd] if the controller is not [deactivated]. */ + open fun handleAnimationEnd(drawable: Drawable) {} +} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index 330336ddd08a6..76d4aa839ef31 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -25,6 +25,7 @@ import android.animation.ValueAnimator; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.StringRes; import android.content.Context; import android.hardware.biometrics.BiometricAuthenticator.Modality; import android.hardware.biometrics.BiometricPrompt; @@ -51,6 +52,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; +import java.util.Set; /** * Contains the Biometric views (title, subtitle, icon, buttons, etc.) and its controllers. @@ -132,6 +134,7 @@ public class AuthBiometricView extends LinearLayout { protected ImageView mIconView; protected TextView mIndicatorView; + @VisibleForTesting @NonNull AuthIconController mIconController; @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; @@ -224,6 +227,40 @@ public class AuthBiometricView extends LinearLayout { return false; } + /** The string to show when the user must tap to confirm via the button or icon. */ + @StringRes + protected int getConfirmationPrompt() { + return R.string.biometric_dialog_tap_confirm; + } + + /** True if require confirmation will be honored when set via the API. */ + protected boolean supportsRequireConfirmation() { + return false; + } + + /** True if confirmation will be required even if it was not supported/requested. */ + protected boolean forceRequireConfirmation(@Modality int modality) { + return false; + } + + /** Ignore all events from this (secondary) modality except successful authentication. */ + protected boolean ignoreUnsuccessfulEventsFrom(@Modality int modality) { + return false; + } + + /** + * Create the controller for managing the icons transitions during the prompt. + * + * Subclass should override. + */ + @NonNull + protected AuthIconController createIconController() { + return new AuthIconController(mContext, mIconView) { + @Override + public void updateIcon(int lastState, int newState) {} + }; + } + void setPanelController(AuthPanelController panelController) { mPanelController = panelController; } @@ -249,11 +286,11 @@ public class AuthBiometricView extends LinearLayout { } void setRequireConfirmation(boolean requireConfirmation) { - mRequireConfirmation = requireConfirmation; + mRequireConfirmation = requireConfirmation && supportsRequireConfirmation(); } @VisibleForTesting - void updateSize(@AuthDialog.DialogSize int newSize) { + final void updateSize(@AuthDialog.DialogSize int newSize) { Log.v(TAG, "Current size: " + mSize + " New size: " + newSize); if (newSize == AuthDialog.SIZE_SMALL) { mTitleView.setVisibility(View.GONE); @@ -413,6 +450,8 @@ public class AuthBiometricView extends LinearLayout { public void updateState(@BiometricState int newState) { Log.v(TAG, "newState: " + newState); + mIconController.updateState(mState, newState); + switch (newState) { case STATE_AUTHENTICATING_ANIMATING_IN: case STATE_AUTHENTICATING: @@ -442,10 +481,11 @@ public class AuthBiometricView extends LinearLayout { mNegativeButton.setVisibility(View.GONE); mCancelButton.setVisibility(View.VISIBLE); mUseCredentialButton.setVisibility(View.GONE); - mConfirmButton.setEnabled(true); - mConfirmButton.setVisibility(View.VISIBLE); + // forced confirmations (multi-sensor) use the icon view as the confirm button + mConfirmButton.setEnabled(mRequireConfirmation); + mConfirmButton.setVisibility(mRequireConfirmation ? View.VISIBLE : View.GONE); mIndicatorView.setTextColor(mTextColorHint); - mIndicatorView.setText(R.string.biometric_dialog_tap_confirm); + mIndicatorView.setText(getConfirmationPrompt()); mIndicatorView.setVisibility(View.VISIBLE); break; @@ -468,9 +508,9 @@ public class AuthBiometricView extends LinearLayout { updateState(STATE_AUTHENTICATING); } - public void onAuthenticationSucceeded() { + public void onAuthenticationSucceeded(@Modality int modality) { removePendingAnimations(); - if (mRequireConfirmation) { + if (mRequireConfirmation || forceRequireConfirmation(modality)) { updateState(STATE_PENDING_CONFIRMATION); } else { updateState(STATE_AUTHENTICATED); @@ -485,6 +525,10 @@ public class AuthBiometricView extends LinearLayout { */ public void onAuthenticationFailed( @Modality int modality, @Nullable String failureReason) { + if (ignoreUnsuccessfulEventsFrom(modality)) { + return; + } + showTemporaryMessage(failureReason, mResetErrorRunnable); updateState(STATE_ERROR); } @@ -496,6 +540,10 @@ public class AuthBiometricView extends LinearLayout { * @param error message */ public void onError(@Modality int modality, String error) { + if (ignoreUnsuccessfulEventsFrom(modality)) { + return; + } + showTemporaryMessage(error, mResetErrorRunnable); updateState(STATE_ERROR); @@ -503,6 +551,18 @@ public class AuthBiometricView extends LinearLayout { mAnimationDurationHideDialog); } + /** + * Fingerprint pointer down event. This does nothing by default and will not be called if the + * device does not have an appropriate sensor (UDFPS), but it may be used as an alternative + * to the "retry" button when fingerprint is used with other modalities. + * + * @param failedModalities the set of modalities that have failed + * @return true if a retry was initiated as a result of this event + */ + public boolean onPointerDown(Set failedModalities) { + return false; + } + /** * Show a help message to the user. * @@ -510,6 +570,9 @@ public class AuthBiometricView extends LinearLayout { * @param help message */ public void onHelp(@Modality int modality, String help) { + if (ignoreUnsuccessfulEventsFrom(modality)) { + return; + } if (mSize != AuthDialog.SIZE_MEDIUM) { Log.w(TAG, "Help received in size: " + mSize); return; @@ -617,6 +680,15 @@ public class AuthBiometricView extends LinearLayout { mTryAgainButton.setVisibility(View.GONE); Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this); }); + + mIconController = createIconController(); + if (mIconController.getActsAsConfirmButton()) { + mIconView.setOnClickListener((view) -> { + if (mState == STATE_PENDING_CONFIRMATION) { + updateState(STATE_AUTHENTICATED); + } + }); + } } /** @@ -686,6 +758,8 @@ public class AuthBiometricView extends LinearLayout { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); + mIconController.setDeactivated(true); + // Empty the handler, otherwise things like ACTION_AUTHENTICATED may be duplicated once // the new dialog is restored. mHandler.removeCallbacksAndMessages(null /* all */); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 72ac1543c89e6..6b6af4c7b52fc 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -60,7 +60,9 @@ import com.android.systemui.keyguard.WakefulnessLifecycle; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Top level container/controller for the BiometricPrompt UI. @@ -106,6 +108,7 @@ public class AuthContainerView extends LinearLayout private final View mPanelView; private final float mTranslationY; @ContainerState private int mContainerState = STATE_UNKNOWN; + private final Set mFailedModalities = new HashSet(); // Non-null only if the dialog is in the act of dismissing and has not sent the reason yet. @Nullable @AuthDialogCallback.DismissedReason private Integer mPendingCallbackReason; @@ -217,6 +220,7 @@ public class AuthContainerView extends LinearLayout animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE); break; case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN: + mFailedModalities.clear(); mConfig.mCallback.onTryAgainPressed(); break; case AuthBiometricView.Callback.ACTION_ERROR: @@ -546,12 +550,13 @@ public class AuthContainerView extends LinearLayout } @Override - public void onAuthenticationSucceeded() { - mBiometricView.onAuthenticationSucceeded(); + public void onAuthenticationSucceeded(@Modality int modality) { + mBiometricView.onAuthenticationSucceeded(modality); } @Override public void onAuthenticationFailed(@Modality int modality, String failureReason) { + mFailedModalities.add(modality); mBiometricView.onAuthenticationFailed(modality, failureReason); } @@ -565,6 +570,14 @@ public class AuthContainerView extends LinearLayout mBiometricView.onError(modality, error); } + @Override + public void onPointerDown() { + if (mBiometricView.onPointerDown(mFailedModalities)) { + Log.d(TAG, "retrying failed modalities (pointer down)"); + mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN); + } + } + @Override public void onSaveState(@NonNull Bundle outState) { outState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 8db779c09aa9a..64c2d2e3858eb 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -125,8 +125,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @Nullable private SidefpsController mSidefpsController; @Nullable private IBiometricContextListener mBiometricContextListener; @VisibleForTesting - TaskStackListener mTaskStackListener; - @VisibleForTesting IBiometricSysuiReceiver mReceiver; @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener; @@ -142,12 +140,13 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba @NonNull private final UserManager mUserManager; @NonNull private final LockPatternUtils mLockPatternUtils; - private class BiometricTaskStackListener extends TaskStackListener { + @VisibleForTesting + final TaskStackListener mTaskStackListener = new TaskStackListener() { @Override public void onTaskStackChanged() { mHandler.post(AuthController.this::handleTaskStackChanged); } - } + }; private final IFingerprintAuthenticatorsRegisteredCallback mFingerprintAuthenticatorsRegisteredCallback = @@ -260,6 +259,17 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mUdfpsProps = !udfpsProps.isEmpty() ? udfpsProps : null; if (mUdfpsProps != null) { mUdfpsController = mUdfpsControllerFactory.get(); + mUdfpsController.addCallback(new UdfpsController.Callback() { + @Override + public void onFingerUp() {} + + @Override + public void onFingerDown() { + if (mCurrentDialog != null) { + mCurrentDialog.onPointerDown(); + } + } + }); } mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null; if (mSidefpsProps != null) { @@ -577,7 +587,6 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba mFingerprintAuthenticatorsRegisteredCallback); } - mTaskStackListener = new BiometricTaskStackListener(); mActivityTaskManager.registerTaskStackListener(mTaskStackListener); } @@ -662,11 +671,11 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba * example, KeyguardUpdateMonitor has its own {@link FingerprintManager.AuthenticationCallback}. */ @Override - public void onBiometricAuthenticated() { + public void onBiometricAuthenticated(@Modality int modality) { if (DEBUG) Log.d(TAG, "onBiometricAuthenticated: "); if (mCurrentDialog != null) { - mCurrentDialog.onAuthenticationSucceeded(); + mCurrentDialog.onAuthenticationSucceeded(modality); } else { Log.w(TAG, "onBiometricAuthenticated callback but dialog gone"); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java index e21229a3e8d58..59ed156bce338 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java @@ -113,7 +113,7 @@ public interface AuthDialog { /** * Biometric authenticated. May be pending user confirmation, or completed. */ - void onAuthenticationSucceeded(); + void onAuthenticationSucceeded(@Modality int modality); /** * Authentication failed (reject, timeout). Dialog stays showing. @@ -136,6 +136,9 @@ public interface AuthDialog { */ void onError(@Modality int modality, String error); + /** UDFPS pointer down event. */ + void onPointerDown(); + /** * Save the current state. * @param outState diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt index 37634562f0dc9..d0d6f4cbf166a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt @@ -53,7 +53,8 @@ object Utils { @JvmStatic fun dpToPixels(context: Context, dp: Float): Float { - return dp * (context.resources.displayMetrics.densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT) + val density = context.resources.displayMetrics.densityDpi.toFloat() + return dp * (density / DisplayMetrics.DENSITY_DEFAULT) } @JvmStatic @@ -86,8 +87,12 @@ object Utils { 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 + 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 } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index 8a02e59526598..5932a64c1c714 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -310,11 +310,11 @@ public class CommandQueue extends IStatusBar.Stub implements long requestId, @BiometricMultiSensorMode int multiSensorConfig) { } - /** @see IStatusBar#onBiometricAuthenticated() */ - default void onBiometricAuthenticated() { + /** @see IStatusBar#onBiometricAuthenticated(int) */ + default void onBiometricAuthenticated(@Modality int modality) { } - /** @see IStatusBar#onBiometricHelp(String) */ + /** @see IStatusBar#onBiometricHelp(int, String) */ default void onBiometricHelp(@Modality int modality, String message) { } @@ -963,9 +963,11 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override - public void onBiometricAuthenticated() { + public void onBiometricAuthenticated(@Modality int modality) { synchronized (mLock) { - mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED).sendToTarget(); + SomeArgs args = SomeArgs.obtain(); + args.argi1 = modality; + mHandler.obtainMessage(MSG_BIOMETRIC_AUTHENTICATED, args).sendToTarget(); } } @@ -1465,9 +1467,11 @@ public class CommandQueue extends IStatusBar.Stub implements break; } case MSG_BIOMETRIC_AUTHENTICATED: { + SomeArgs someArgs = (SomeArgs) msg.obj; for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).onBiometricAuthenticated(); + mCallbacks.get(i).onBiometricAuthenticated(someArgs.argi1 /* modality */); } + someArgs.recycle(); break; } case MSG_BIOMETRIC_HELP: { diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java deleted file mode 100644 index bdde1710d6a74..0000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFaceViewTest.java +++ /dev/null @@ -1,120 +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 org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import android.content.Context; -import android.test.suitebuilder.annotation.SmallTest; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper.RunWithLooper; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; - -import com.android.systemui.SysuiTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -@RunWith(AndroidTestingRunner.class) -@RunWithLooper -@SmallTest -public class AuthBiometricFaceViewTest extends SysuiTestCase { - - @Mock - AuthBiometricView.Callback mCallback; - - private TestableFaceView mFaceView; - - @Mock private Button mNegativeButton; - @Mock private Button mCancelButton; - @Mock private Button mUseCredentialButton; - - @Mock private Button mConfirmButton; - @Mock private Button mTryAgainButton; - - @Mock private TextView mErrorView; - - @Mock - private TestableFaceView.TestableIconController mIconController; - - @Before - public void setup() { - MockitoAnnotations.initMocks(this); - mFaceView = new TestableFaceView(mContext); - mFaceView.mFaceIconController = mIconController; - mFaceView.setCallback(mCallback); - - mFaceView.mNegativeButton = mNegativeButton; - mFaceView.mCancelButton = mCancelButton; - mFaceView.mUseCredentialButton = mUseCredentialButton; - - mFaceView.mConfirmButton = mConfirmButton; - mFaceView.mTryAgainButton = mTryAgainButton; - - mFaceView.mIndicatorView = mErrorView; - } - - @Test - public void testStateUpdated_whenDialogAnimatedIn() { - mFaceView.onDialogAnimatedIn(); - verify(mFaceView.mFaceIconController) - .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING)); - } - - @Test - public void testIconUpdatesState_whenDialogStateUpdated() { - mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATING); - verify(mFaceView.mFaceIconController) - .updateState(anyInt(), eq(AuthBiometricFaceView.STATE_AUTHENTICATING)); - - mFaceView.updateState(AuthBiometricFaceView.STATE_AUTHENTICATED); - verify(mFaceView.mFaceIconController).updateState( - eq(AuthBiometricFaceView.STATE_AUTHENTICATING), - eq(AuthBiometricFaceView.STATE_AUTHENTICATED)); - } - - public class TestableFaceView extends AuthBiometricFaceView { - - public class TestableIconController extends AuthBiometricFaceIconController { - TestableIconController(Context context, ImageView iconView) { - super(context, iconView, mock(TextView.class)); - } - - public void startPulsing() { - // Stub for testing - } - } - - @Override - protected int getDelayAfterAuthenticatedDurationMs() { - return 0; // Keep this at 0 for tests to invoke callback immediately. - } - - public TestableFaceView(Context context) { - super(context); - } - } - -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java index 91a8d8a1b5aee..9a17d69677658 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricViewTest.java @@ -20,6 +20,8 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE; import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; +import static com.android.systemui.biometrics.AuthBiometricView.Callback.ACTION_AUTHENTICATED; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; @@ -68,10 +70,10 @@ public class AuthBiometricViewTest extends SysuiTestCase { initDialog(false /* allowDeviceCredential */, mCallback); // The onAuthenticated runnable is posted when authentication succeeds. - mBiometricView.onAuthenticationSucceeded(); + mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT); waitForIdleSync(); assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState); - verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED); + verify(mCallback).onAction(ACTION_AUTHENTICATED); } @Test @@ -79,19 +81,28 @@ public class AuthBiometricViewTest extends SysuiTestCase { initDialog(false /* allowDeviceCredential */, mCallback); mBiometricView.setRequireConfirmation(true); - mBiometricView.onAuthenticationSucceeded(); + mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT); waitForIdleSync(); - assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState); - verify(mCallback, never()).onAction(anyInt()); - assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility()); - assertEquals(View.VISIBLE, mBiometricView.mCancelButton.getVisibility()); - assertTrue(mBiometricView.mCancelButton.isEnabled()); + // TODO: this should be tested in the subclasses + if (mBiometricView.supportsRequireConfirmation()) { + assertEquals(AuthBiometricView.STATE_PENDING_CONFIRMATION, mBiometricView.mState); + + verify(mCallback, never()).onAction(anyInt()); + + assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility()); + assertEquals(View.VISIBLE, mBiometricView.mCancelButton.getVisibility()); + assertTrue(mBiometricView.mCancelButton.isEnabled()); + + assertTrue(mBiometricView.mConfirmButton.isEnabled()); + assertEquals(mContext.getText(R.string.biometric_dialog_tap_confirm), + mBiometricView.mIndicatorView.getText()); + assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility()); + } else { + assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState); + verify(mCallback).onAction(eq(ACTION_AUTHENTICATED)); + } - assertTrue(mBiometricView.mConfirmButton.isEnabled()); - assertEquals(mContext.getText(R.string.biometric_dialog_tap_confirm), - mBiometricView.mIndicatorView.getText()); - assertEquals(View.VISIBLE, mBiometricView.mIndicatorView.getVisibility()); } @Test @@ -101,7 +112,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { mBiometricView.mConfirmButton.performClick(); waitForIdleSync(); - verify(mCallback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED); + verify(mCallback).onAction(ACTION_AUTHENTICATED); assertEquals(AuthBiometricView.STATE_AUTHENTICATED, mBiometricView.mState); } @@ -121,7 +132,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { initDialog(false /* allowDeviceCredential */, mCallback); mBiometricView.setRequireConfirmation(true); - mBiometricView.onAuthenticationSucceeded(); + mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT); assertEquals(View.GONE, mBiometricView.mNegativeButton.getVisibility()); @@ -170,7 +181,7 @@ public class AuthBiometricViewTest extends SysuiTestCase { View view = new View(mContext); mBiometricView.setBackgroundView(view); - mBiometricView.onAuthenticationSucceeded(); + mBiometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT); view.performClick(); verify(mCallback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED)); } 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 e7993930e922e..42c3c7f899fab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java @@ -16,6 +16,7 @@ package com.android.systemui.biometrics; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_FINGERPRINT_AND_FACE; @@ -352,8 +353,8 @@ public class AuthControllerTest extends SysuiTestCase { @Test public void testOnAuthenticationSucceededInvoked_whenSystemRequested() { showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */); - mAuthController.onBiometricAuthenticated(); - verify(mDialog1).onAuthenticationSucceeded(); + mAuthController.onBiometricAuthenticated(TYPE_FINGERPRINT); + verify(mDialog1).onAuthenticationSucceeded(eq(TYPE_FINGERPRINT)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java index 6c29ecc7ae506..11f76a381ad40 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java @@ -448,9 +448,10 @@ public class CommandQueueTest extends SysuiTestCase { @Test public void testOnBiometricAuthenticated() { - mCommandQueue.onBiometricAuthenticated(); + final int id = 12; + mCommandQueue.onBiometricAuthenticated(id); waitForIdleSync(); - verify(mCallbacks).onBiometricAuthenticated(); + verify(mCallbacks).onBiometricAuthenticated(eq(id)); } @Test diff --git a/services/core/java/com/android/server/biometrics/AuthSession.java b/services/core/java/com/android/server/biometrics/AuthSession.java index 986a4a11e324a..bf69284df2f61 100644 --- a/services/core/java/com/android/server/biometrics/AuthSession.java +++ b/services/core/java/com/android/server/biometrics/AuthSession.java @@ -107,7 +107,7 @@ public final class AuthSession implements IBinder.DeathRecipient { private final Context mContext; private final IStatusBarService mStatusBarService; - private final IBiometricSysuiReceiver mSysuiReceiver; + @VisibleForTesting final IBiometricSysuiReceiver mSysuiReceiver; private final KeyStore mKeyStore; private final Random mRandom; private final ClientDeathReceiver mClientDeathReceiver; @@ -121,7 +121,7 @@ public final class AuthSession implements IBinder.DeathRecipient { private final long mRequestId; private final long mOperationId; private final int mUserId; - private final IBiometricSensorReceiver mSensorReceiver; + @VisibleForTesting final IBiometricSensorReceiver mSensorReceiver; // Original receiver from BiometricPrompt. private final IBiometricServiceReceiver mClientReceiver; private final String mOpPackageName; @@ -134,6 +134,7 @@ public final class AuthSession implements IBinder.DeathRecipient { private int[] mSensors; // TODO(b/197265902): merge into state private boolean mCancelled; + private int mAuthenticatedSensorId = -1; // For explicit confirmation, do not send to keystore until the user has confirmed // the authentication. private byte[] mTokenEscrow; @@ -219,8 +220,16 @@ public final class AuthSession implements IBinder.DeathRecipient { } } - private void setSensorsToStateWaitingForCookie() throws RemoteException { + private void setSensorsToStateWaitingForCookie(boolean isTryAgain) throws RemoteException { for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) { + @BiometricSensor.SensorState final int state = sensor.getSensorState(); + if (isTryAgain + && state != BiometricSensor.STATE_STOPPED + && state != BiometricSensor.STATE_CANCELING) { + Slog.d(TAG, "Skip retry because sensor: " + sensor.id + " is: " + state); + continue; + } + final int cookie = mRandom.nextInt(Integer.MAX_VALUE - 1) + 1; final boolean requireConfirmation = isConfirmationRequired(sensor); @@ -255,7 +264,7 @@ public final class AuthSession implements IBinder.DeathRecipient { mMultiSensorMode); } else if (!mPreAuthInfo.eligibleSensors.isEmpty()) { // Some combination of biometric or biometric|credential is requested - setSensorsToStateWaitingForCookie(); + setSensorsToStateWaitingForCookie(false /* isTryAgain */); mState = STATE_AUTH_CALLED; } else { // No authenticators requested. This should never happen - an exception should have @@ -269,6 +278,10 @@ public final class AuthSession implements IBinder.DeathRecipient { Slog.w(TAG, "Received cookie but already cancelled (ignoring): " + cookie); return; } + if (hasAuthenticated()) { + Slog.d(TAG, "onCookieReceived after successful auth"); + return; + } for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) { sensor.goToStateCookieReturnedIfCookieMatches(cookie); @@ -366,9 +379,8 @@ public final class AuthSession implements IBinder.DeathRecipient { // sending the final error callback to the application. for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) { try { - final boolean shouldCancel = filter.apply(sensor); - Slog.d(TAG, "sensorId: " + sensor.id + ", shouldCancel: " + shouldCancel); - if (shouldCancel) { + if (filter.apply(sensor)) { + Slog.d(TAG, "Cancelling sensorId: " + sensor.id); sensor.goToStateCancelling(mToken, mOpPackageName, mRequestId); } } catch (RemoteException e) { @@ -397,6 +409,12 @@ public final class AuthSession implements IBinder.DeathRecipient { } } + // do not propagate the error and let onAuthenticationSucceeded handle the new state + if (hasAuthenticated()) { + Slog.d(TAG, "onErrorReceived after successful auth (ignoring)"); + return false; + } + mErrorEscrow = error; mVendorCodeEscrow = vendorCode; @@ -483,6 +501,11 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onAcquired(int sensorId, int acquiredInfo, int vendorCode) { + if (hasAuthenticated()) { + Slog.d(TAG, "onAcquired after successful auth"); + return; + } + final String message = getAcquiredMessageForSensor(sensorId, acquiredInfo, vendorCode); Slog.d(TAG, "sensorId: " + sensorId + " acquiredInfo: " + acquiredInfo + " message: " + message); @@ -498,6 +521,10 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onSystemEvent(int event) { + if (hasAuthenticated()) { + Slog.d(TAG, "onSystemEvent after successful auth"); + return; + } if (!mPromptInfo.isReceiveSystemEvents()) { return; } @@ -521,20 +548,30 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onTryAgainPressed() { + if (hasAuthenticated()) { + Slog.d(TAG, "onTryAgainPressed after successful auth"); + return; + } + if (mState != STATE_AUTH_PAUSED) { Slog.w(TAG, "onTryAgainPressed, state: " + mState); } try { - setSensorsToStateWaitingForCookie(); + setSensorsToStateWaitingForCookie(true /* isTryAgain */); mState = STATE_AUTH_PAUSED_RESUMING; } catch (RemoteException e) { Slog.e(TAG, "RemoteException: " + e); } } - void onAuthenticationSucceeded(int sensorId, boolean strong, - byte[] token) { + void onAuthenticationSucceeded(int sensorId, boolean strong, byte[] token) { + if (hasAuthenticated()) { + Slog.d(TAG, "onAuthenticationSucceeded after successful auth"); + return; + } + + mAuthenticatedSensorId = sensorId; if (strong) { mTokenEscrow = token; } else { @@ -546,7 +583,7 @@ public final class AuthSession implements IBinder.DeathRecipient { try { // Notify SysUI that the biometric has been authenticated. SysUI already knows // the implicit/explicit state and will react accordingly. - mStatusBarService.onBiometricAuthenticated(); + mStatusBarService.onBiometricAuthenticated(sensorIdToModality(sensorId)); final boolean requireConfirmation = isConfirmationRequiredByAnyEligibleSensor(); @@ -559,20 +596,22 @@ public final class AuthSession implements IBinder.DeathRecipient { } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } + + cancelAllSensors(sensor -> sensor.id != sensorId); } - void onAuthenticationRejected() { - try { - mStatusBarService.onBiometricError(TYPE_NONE, - BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */); + void onAuthenticationRejected(int sensorId) { + if (hasAuthenticated()) { + Slog.d(TAG, "onAuthenticationRejected after successful auth"); + return; + } - // TODO: This logic will need to be updated if BP is multi-modal - if (hasPausableBiometric()) { - // Pause authentication. onBiometricAuthenticated(false) causes the - // dialog to show a "try again" button for passive modalities. + try { + mStatusBarService.onBiometricError(sensorIdToModality(sensorId), + BiometricConstants.BIOMETRIC_PAUSED_REJECTED, 0 /* vendorCode */); + if (pauseSensorIfSupported(sensorId)) { mState = STATE_AUTH_PAUSED; } - mClientReceiver.onAuthenticationFailed(); } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); @@ -580,15 +619,34 @@ public final class AuthSession implements IBinder.DeathRecipient { } void onAuthenticationTimedOut(int sensorId, int cookie, int error, int vendorCode) { + if (hasAuthenticated()) { + Slog.d(TAG, "onAuthenticationTimedOut after successful auth"); + return; + } + try { mStatusBarService.onBiometricError(sensorIdToModality(sensorId), error, vendorCode); + pauseSensorIfSupported(sensorId); mState = STATE_AUTH_PAUSED; } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } } + private boolean pauseSensorIfSupported(int sensorId) { + if (sensorIdToModality(sensorId) == TYPE_FACE) { + cancelAllSensors(sensor -> sensor.id == sensorId); + return true; + } + return false; + } + void onDeviceCredentialPressed() { + if (hasAuthenticated()) { + Slog.d(TAG, "onDeviceCredentialPressed after successful auth"); + return; + } + // Cancel authentication. Skip the token/package check since we are cancelling // from system server. The interface is permission protected so this is fine. cancelAllSensors(); @@ -616,6 +674,10 @@ public final class AuthSession implements IBinder.DeathRecipient { } } + private boolean hasAuthenticated() { + return mAuthenticatedSensorId != -1; + } + private void logOnDialogDismissed(@BiometricPrompt.DismissedReason int reason) { if (reason == BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED) { // Explicit auth, authentication confirmed. @@ -744,6 +806,11 @@ public final class AuthSession implements IBinder.DeathRecipient { * @return true if this AuthSession is finished, e.g. should be set to null */ boolean onCancelAuthSession(boolean force) { + if (hasAuthenticated()) { + Slog.d(TAG, "onCancelAuthSession after successful auth"); + return true; + } + mCancelled = true; final boolean authStarted = mState == STATE_AUTH_CALLED @@ -798,15 +865,6 @@ public final class AuthSession implements IBinder.DeathRecipient { return remainingCookies == 0; } - private boolean hasPausableBiometric() { - for (BiometricSensor sensor : mPreAuthInfo.eligibleSensors) { - if (sensor.modality == TYPE_FACE) { - return true; - } - } - return false; - } - @SessionState int getState() { return mState; } diff --git a/services/core/java/com/android/server/biometrics/BiometricSensor.java b/services/core/java/com/android/server/biometrics/BiometricSensor.java index 0333c3e247c0a..7166783f0b23b 100644 --- a/services/core/java/com/android/server/biometrics/BiometricSensor.java +++ b/services/core/java/com/android/server/biometrics/BiometricSensor.java @@ -131,8 +131,10 @@ public abstract class BiometricSensor { void goToStateCancelling(IBinder token, String opPackageName, long requestId) throws RemoteException { - impl.cancelAuthenticationFromService(token, opPackageName, requestId); - mSensorState = STATE_CANCELING; + if (mSensorState != STATE_CANCELING) { + impl.cancelAuthenticationFromService(token, opPackageName, requestId); + mSensorState = STATE_CANCELING; + } } void goToStoppedStateIfCookieMatches(int cookie, int error) { diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java index 6af283968d897..0d9b75481ea93 100644 --- a/services/core/java/com/android/server/biometrics/BiometricService.java +++ b/services/core/java/com/android/server/biometrics/BiometricService.java @@ -55,7 +55,6 @@ import android.os.DeadObjectException; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; @@ -84,6 +83,7 @@ import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; /** * System service that arbitrates the modality for BiometricPrompt to use. @@ -92,21 +92,6 @@ public class BiometricService extends SystemService { static final String TAG = "BiometricService"; - private static final int MSG_ON_AUTHENTICATION_SUCCEEDED = 2; - private static final int MSG_ON_AUTHENTICATION_REJECTED = 3; - private static final int MSG_ON_ERROR = 4; - private static final int MSG_ON_ACQUIRED = 5; - private static final int MSG_ON_DISMISSED = 6; - private static final int MSG_ON_TRY_AGAIN_PRESSED = 7; - private static final int MSG_ON_READY_FOR_AUTHENTICATION = 8; - private static final int MSG_AUTHENTICATE = 9; - private static final int MSG_CANCEL_AUTHENTICATION = 10; - private static final int MSG_ON_AUTHENTICATION_TIMED_OUT = 11; - private static final int MSG_ON_DEVICE_CREDENTIAL_PRESSED = 12; - 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 final Injector mInjector; private final DevicePolicyManager mDevicePolicyManager; @VisibleForTesting @@ -115,7 +100,7 @@ public class BiometricService extends SystemService { final SettingObserver mSettingObserver; private final List mEnabledOnKeyguardCallbacks; private final Random mRandom = new Random(); - @NonNull private final AtomicLong mRequestCounter; + @NonNull private final Supplier mRequestCounter; @VisibleForTesting IStatusBarService mStatusBarService; @@ -127,128 +112,13 @@ public class BiometricService extends SystemService { // Get and cache the available biometric authenticators and their associated info. final ArrayList mSensors = new ArrayList<>(); + @VisibleForTesting BiometricStrengthController mBiometricStrengthController; // The current authentication session, null if idle/done. @VisibleForTesting - AuthSession mCurrentAuthSession; - - @VisibleForTesting - final Handler mHandler = new Handler(Looper.getMainLooper()) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_ON_AUTHENTICATION_SUCCEEDED: { - SomeArgs args = (SomeArgs) msg.obj; - handleAuthenticationSucceeded( - args.argi1 /* sensorId */, - (byte[]) args.arg1 /* token */); - args.recycle(); - break; - } - - case MSG_ON_AUTHENTICATION_REJECTED: { - handleAuthenticationRejected(); - break; - } - - case MSG_ON_ERROR: { - SomeArgs args = (SomeArgs) msg.obj; - handleOnError( - args.argi1 /* sensorId */, - args.argi2 /* cookie */, - args.argi3 /* error */, - args.argi4 /* vendorCode */); - args.recycle(); - break; - } - - case MSG_ON_ACQUIRED: { - SomeArgs args = (SomeArgs) msg.obj; - handleOnAcquired( - args.argi1 /* sensorId */, - args.argi2 /* acquiredInfo */, - args.argi3 /* vendorCode */); - args.recycle(); - break; - } - - case MSG_ON_DISMISSED: { - handleOnDismissed(msg.arg1, (byte[]) msg.obj); - break; - } - - case MSG_ON_TRY_AGAIN_PRESSED: { - handleOnTryAgainPressed(); - break; - } - - case MSG_ON_READY_FOR_AUTHENTICATION: { - SomeArgs args = (SomeArgs) msg.obj; - handleOnReadyForAuthentication( - args.argi1 /* cookie */); - args.recycle(); - break; - } - - case MSG_AUTHENTICATE: { - SomeArgs args = (SomeArgs) msg.obj; - handleAuthenticate( - (IBinder) args.arg1 /* token */, - (long) args.arg6 /* requestId */, - (long) args.arg2 /* operationId */, - args.argi1 /* userid */, - (IBiometricServiceReceiver) args.arg3 /* receiver */, - (String) args.arg4 /* opPackageName */, - (PromptInfo) args.arg5 /* promptInfo */); - args.recycle(); - break; - } - - case MSG_CANCEL_AUTHENTICATION: { - SomeArgs args = (SomeArgs) msg.obj; - handleCancelAuthentication((long) args.arg3 /* requestId */); - args.recycle(); - break; - } - - case MSG_ON_AUTHENTICATION_TIMED_OUT: { - SomeArgs args = (SomeArgs) msg.obj; - handleAuthenticationTimedOut( - args.argi1 /* sensorId */, - args.argi2 /* cookie */, - args.argi3 /* error */, - args.argi4 /* vendorCode */); - args.recycle(); - break; - } - - case MSG_ON_DEVICE_CREDENTIAL_PRESSED: { - handleOnDeviceCredentialPressed(); - break; - } - - case MSG_ON_SYSTEM_EVENT: { - handleOnSystemEvent((int) msg.obj); - break; - } - - case MSG_CLIENT_DIED: { - handleClientDied(); - break; - } - - case MSG_ON_DIALOG_ANIMATED_IN: { - handleOnDialogAnimatedIn(); - break; - } - - default: - Slog.e(TAG, "Unknown message: " + msg); - break; - } - } - }; + AuthSession mAuthSession; + private final Handler mHandler = new Handler(Looper.getMainLooper()); /** * Tracks authenticatorId invalidation. For more details, see @@ -546,88 +416,74 @@ public class BiometricService extends SystemService { } // Receives events from individual biometric sensors. - @VisibleForTesting - final IBiometricSensorReceiver mBiometricSensorReceiver = new IBiometricSensorReceiver.Stub() { - @Override - public void onAuthenticationSucceeded(int sensorId, byte[] token) { - SomeArgs args = SomeArgs.obtain(); - args.argi1 = sensorId; - args.arg1 = token; - mHandler.obtainMessage(MSG_ON_AUTHENTICATION_SUCCEEDED, args).sendToTarget(); - } - - @Override - public void onAuthenticationFailed(int sensorId) { - Slog.v(TAG, "onAuthenticationFailed"); - mHandler.obtainMessage(MSG_ON_AUTHENTICATION_REJECTED).sendToTarget(); - } - - @Override - public void onError(int sensorId, int cookie, @BiometricConstants.Errors int error, - int vendorCode) { - // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are - // soft errors and we should allow the user to try authenticating again instead of - // dismissing BiometricPrompt. - if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) { - SomeArgs args = SomeArgs.obtain(); - args.argi1 = sensorId; - args.argi2 = cookie; - args.argi3 = error; - args.argi4 = vendorCode; - mHandler.obtainMessage(MSG_ON_AUTHENTICATION_TIMED_OUT, args).sendToTarget(); - } else { - SomeArgs args = SomeArgs.obtain(); - args.argi1 = sensorId; - args.argi2 = cookie; - args.argi3 = error; - args.argi4 = vendorCode; - mHandler.obtainMessage(MSG_ON_ERROR, args).sendToTarget(); + private IBiometricSensorReceiver createBiometricSensorReceiver(final long requestId) { + return new IBiometricSensorReceiver.Stub() { + @Override + public void onAuthenticationSucceeded(int sensorId, byte[] token) { + mHandler.post(() -> handleAuthenticationSucceeded(requestId, sensorId, token)); } - } - @Override - public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) { - SomeArgs args = SomeArgs.obtain(); - args.argi1 = sensorId; - args.argi2 = acquiredInfo; - args.argi3 = vendorCode; - mHandler.obtainMessage(MSG_ON_ACQUIRED, args).sendToTarget(); - } - }; + @Override + public void onAuthenticationFailed(int sensorId) { + Slog.v(TAG, "onAuthenticationFailed"); + mHandler.post(() -> handleAuthenticationRejected(requestId, sensorId)); + } - final IBiometricSysuiReceiver mSysuiReceiver = new IBiometricSysuiReceiver.Stub() { - @Override - public void onDialogDismissed(@BiometricPrompt.DismissedReason int reason, - @Nullable byte[] credentialAttestation) { - mHandler.obtainMessage(MSG_ON_DISMISSED, - reason, - 0 /* arg2 */, - credentialAttestation /* obj */).sendToTarget(); - } + @Override + public void onError(int sensorId, int cookie, @BiometricConstants.Errors int error, + int vendorCode) { + // Determine if error is hard or soft error. Certain errors (such as TIMEOUT) are + // soft errors and we should allow the user to try authenticating again instead of + // dismissing BiometricPrompt. + if (error == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT) { + mHandler.post(() -> handleAuthenticationTimedOut( + requestId, sensorId, cookie, error, vendorCode)); + } else { + mHandler.post(() -> handleOnError( + requestId, sensorId, cookie, error, vendorCode)); + } + } - @Override - public void onTryAgainPressed() { - mHandler.sendEmptyMessage(MSG_ON_TRY_AGAIN_PRESSED); - } + @Override + public void onAcquired(int sensorId, int acquiredInfo, int vendorCode) { + mHandler.post(() -> handleOnAcquired( + requestId, sensorId, acquiredInfo, vendorCode)); + } + }; + } - @Override - public void onDeviceCredentialPressed() { - mHandler.sendEmptyMessage(MSG_ON_DEVICE_CREDENTIAL_PRESSED); - } + private IBiometricSysuiReceiver createSysuiReceiver(final long requestId) { + return new IBiometricSysuiReceiver.Stub() { + @Override + public void onDialogDismissed(@BiometricPrompt.DismissedReason int reason, + @Nullable byte[] credentialAttestation) { + mHandler.post(() -> handleOnDismissed(requestId, reason, credentialAttestation)); + } - @Override - public void onSystemEvent(int event) { - mHandler.obtainMessage(MSG_ON_SYSTEM_EVENT, event).sendToTarget(); - } + @Override + public void onTryAgainPressed() { + mHandler.post(() -> handleOnTryAgainPressed(requestId)); + } - @Override - public void onDialogAnimatedIn() { - mHandler.obtainMessage(MSG_ON_DIALOG_ANIMATED_IN).sendToTarget(); - } - }; + @Override + public void onDeviceCredentialPressed() { + mHandler.post(() -> handleOnDeviceCredentialPressed(requestId)); + } - private final AuthSession.ClientDeathReceiver mClientDeathReceiver = () -> { - mHandler.sendEmptyMessage(MSG_CLIENT_DIED); + @Override + public void onSystemEvent(int event) { + mHandler.post(() -> handleOnSystemEvent(requestId, event)); + } + + @Override + public void onDialogAnimatedIn() { + mHandler.post(() -> handleOnDialogAnimatedIn(requestId)); + } + }; + } + + private AuthSession.ClientDeathReceiver createClientDeathReceiver(final long requestId) { + return () -> mHandler.post(() -> handleClientDied(requestId)); }; /** @@ -668,12 +524,10 @@ public class BiometricService extends SystemService { } @Override // Binder call - public void onReadyForAuthentication(int cookie) { + public void onReadyForAuthentication(long requestId, int cookie) { checkInternalPermission(); - SomeArgs args = SomeArgs.obtain(); - args.argi1 = cookie; - mHandler.obtainMessage(MSG_ON_READY_FOR_AUTHENTICATION, args).sendToTarget(); + mHandler.post(() -> handleOnReadyForAuthentication(requestId, cookie)); } @Override // Binder call @@ -700,18 +554,9 @@ public class BiometricService extends SystemService { } } - final long requestId = mRequestCounter.incrementAndGet(); - - SomeArgs args = SomeArgs.obtain(); - args.arg1 = token; - args.arg2 = operationId; - args.argi1 = userId; - args.arg3 = receiver; - args.arg4 = opPackageName; - args.arg5 = promptInfo; - args.arg6 = requestId; - - mHandler.obtainMessage(MSG_AUTHENTICATE, args).sendToTarget(); + final long requestId = mRequestCounter.get(); + mHandler.post(() -> handleAuthenticate( + token, requestId, operationId, userId, receiver, opPackageName, promptInfo)); return requestId; } @@ -725,7 +570,7 @@ public class BiometricService extends SystemService { args.arg2 = opPackageName; args.arg3 = requestId; - mHandler.obtainMessage(MSG_CANCEL_AUTHENTICATION, args).sendToTarget(); + mHandler.post(() -> handleCancelAuthentication(requestId)); } @Override // Binder call @@ -991,8 +836,7 @@ public class BiometricService extends SystemService { Slog.d(TAG, "ClearSchedulerBuffer: " + clearSchedulerBuffer); final ProtoOutputStream proto = new ProtoOutputStream(fd); proto.write(BiometricServiceStateProto.AUTH_SESSION_STATE, - mCurrentAuthSession != null ? mCurrentAuthSession.getState() - : STATE_AUTH_IDLE); + mAuthSession != null ? mAuthSession.getState() : STATE_AUTH_IDLE); for (BiometricSensor sensor : mSensors) { byte[] serviceState = sensor.impl .dumpSensorServiceStateProto(clearSchedulerBuffer); @@ -1117,8 +961,9 @@ public class BiometricService extends SystemService { CoexCoordinator.FACE_HAPTIC_DISABLE, 1) != 0; } - public AtomicLong getRequestGenerator() { - return new AtomicLong(0); + public Supplier getRequestGenerator() { + final AtomicLong generator = new AtomicLong(0); + return () -> generator.incrementAndGet(); } } @@ -1191,162 +1036,184 @@ public class BiometricService extends SystemService { return false; } - private void handleAuthenticationSucceeded(int sensorId, byte[] token) { + @Nullable + private AuthSession getAuthSessionIfCurrent(long requestId) { + final AuthSession session = mAuthSession; + if (session != null && session.getRequestId() == requestId) { + return session; + } + return null; + } + + private void handleAuthenticationSucceeded(long requestId, int sensorId, byte[] token) { Slog.v(TAG, "handleAuthenticationSucceeded(), sensorId: " + sensorId); // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded // after user dismissed/canceled dialog). - if (mCurrentAuthSession == null) { + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { Slog.e(TAG, "handleAuthenticationSucceeded: AuthSession is null"); return; } - mCurrentAuthSession.onAuthenticationSucceeded(sensorId, isStrongBiometric(sensorId), token); + session.onAuthenticationSucceeded(sensorId, isStrongBiometric(sensorId), token); } - private void handleAuthenticationRejected() { + private void handleAuthenticationRejected(long requestId, int sensorId) { Slog.v(TAG, "handleAuthenticationRejected()"); // Should never happen, log this to catch bad HAL behavior (e.g. auth rejected // after user dismissed/canceled dialog). - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleAuthenticationRejected: AuthSession is null"); + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleAuthenticationRejected: AuthSession is not current"); return; } - mCurrentAuthSession.onAuthenticationRejected(); + session.onAuthenticationRejected(sensorId); } - private void handleAuthenticationTimedOut(int sensorId, int cookie, int error, int vendorCode) { + private void handleAuthenticationTimedOut(long requestId, int sensorId, int cookie, int error, + int vendorCode) { Slog.v(TAG, "handleAuthenticationTimedOut(), sensorId: " + sensorId + ", cookie: " + cookie + ", error: " + error + ", vendorCode: " + vendorCode); // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded // after user dismissed/canceled dialog). - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleAuthenticationTimedOut: AuthSession is null"); + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleAuthenticationTimedOut: AuthSession is not current"); return; } - mCurrentAuthSession.onAuthenticationTimedOut(sensorId, cookie, error, vendorCode); + session.onAuthenticationTimedOut(sensorId, cookie, error, vendorCode); } - private void handleOnError(int sensorId, int cookie, @BiometricConstants.Errors int error, - int vendorCode) { + private void handleOnError(long requestId, int sensorId, int cookie, + @BiometricConstants.Errors int error, int vendorCode) { Slog.d(TAG, "handleOnError() sensorId: " + sensorId + ", cookie: " + cookie + ", error: " + error + ", vendorCode: " + vendorCode); - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleOnError: AuthSession is null"); + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleOnError: AuthSession is not current"); return; } try { - final boolean finished = mCurrentAuthSession - .onErrorReceived(sensorId, cookie, error, vendorCode); + final boolean finished = session.onErrorReceived(sensorId, cookie, error, vendorCode); if (finished) { Slog.d(TAG, "handleOnError: AuthSession finished"); - mCurrentAuthSession = null; + mAuthSession = null; } } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } } - private void handleOnAcquired(int sensorId, int acquiredInfo, int vendorCode) { + private void handleOnAcquired(long requestId, int sensorId, int acquiredInfo, int vendorCode) { // Should never happen, log this to catch bad HAL behavior (e.g. auth succeeded // after user dismissed/canceled dialog). - if (mCurrentAuthSession == null) { - Slog.e(TAG, "onAcquired: AuthSession is null"); + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "onAcquired: AuthSession is not current"); return; } - mCurrentAuthSession.onAcquired(sensorId, acquiredInfo, vendorCode); + session.onAcquired(sensorId, acquiredInfo, vendorCode); } - private void handleOnDismissed(@BiometricPrompt.DismissedReason int reason, + private void handleOnDismissed(long requestId, @BiometricPrompt.DismissedReason int reason, @Nullable byte[] credentialAttestation) { - if (mCurrentAuthSession == null) { - Slog.e(TAG, "onDismissed: " + reason + ", AuthSession is null"); + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.e(TAG, "onDismissed: " + reason + ", AuthSession is not current"); return; } - mCurrentAuthSession.onDialogDismissed(reason, credentialAttestation); - mCurrentAuthSession = null; + session.onDialogDismissed(reason, credentialAttestation); + mAuthSession = null; } - private void handleOnTryAgainPressed() { + private void handleOnTryAgainPressed(long requestId) { Slog.d(TAG, "onTryAgainPressed"); // No need to check permission, since it can only be invoked by SystemUI // (or system server itself). - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleOnTryAgainPressed: AuthSession is null"); + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleOnTryAgainPressed: AuthSession is not current"); return; } - mCurrentAuthSession.onTryAgainPressed(); + session.onTryAgainPressed(); } - private void handleOnDeviceCredentialPressed() { + private void handleOnDeviceCredentialPressed(long requestId) { Slog.d(TAG, "onDeviceCredentialPressed"); - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleOnDeviceCredentialPressed: AuthSession is null"); + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleOnDeviceCredentialPressed: AuthSession is not current"); return; } - mCurrentAuthSession.onDeviceCredentialPressed(); + session.onDeviceCredentialPressed(); } - private void handleOnSystemEvent(int event) { + private void handleOnSystemEvent(long requestId, int event) { Slog.d(TAG, "onSystemEvent: " + event); - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleOnSystemEvent: AuthSession is null"); + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleOnSystemEvent: AuthSession is not current"); return; } - mCurrentAuthSession.onSystemEvent(event); + session.onSystemEvent(event); } - private void handleClientDied() { - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleClientDied: AuthSession is null"); + private void handleClientDied(long requestId) { + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleClientDied: AuthSession is not current"); return; } - Slog.e(TAG, "Session: " + mCurrentAuthSession); - final boolean finished = mCurrentAuthSession.onClientDied(); + Slog.e(TAG, "Session: " + session); + final boolean finished = session.onClientDied(); if (finished) { - mCurrentAuthSession = null; + mAuthSession = null; } } - private void handleOnDialogAnimatedIn() { + private void handleOnDialogAnimatedIn(long requestId) { Slog.d(TAG, "handleOnDialogAnimatedIn"); - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleOnDialogAnimatedIn: AuthSession is null"); + + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleOnDialogAnimatedIn: AuthSession is not current"); return; } - mCurrentAuthSession.onDialogAnimatedIn(); + session.onDialogAnimatedIn(); } /** * 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. */ - private void handleOnReadyForAuthentication(int cookie) { - if (mCurrentAuthSession == null) { + private void handleOnReadyForAuthentication(long requestId, int cookie) { + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { // Only should happen if a biometric was locked out when authenticate() was invoked. // In that case, if device credentials are allowed, the UI is already showing. If not // allowed, the error has already been returned to the caller. - Slog.w(TAG, "handleOnReadyForAuthentication: AuthSession is null"); + Slog.w(TAG, "handleOnReadyForAuthentication: AuthSession is not current"); return; } - mCurrentAuthSession.onCookieReceived(cookie); + session.onCookieReceived(cookie); } private void handleAuthenticate(IBinder token, long requestId, long operationId, int userId, @@ -1407,47 +1274,41 @@ public class BiometricService extends SystemService { // No need to dismiss dialog / send error yet if we're continuing authentication, e.g. // "Try again" is showing due to something like ERROR_TIMEOUT. - if (mCurrentAuthSession != null) { + if (mAuthSession != null) { // Forcefully cancel authentication. Dismiss the UI, and immediately send // ERROR_CANCELED to the client. Note that we should/will ignore HAL ERROR_CANCELED. // Expect to see some harmless "unknown cookie" errors. - Slog.w(TAG, "Existing AuthSession: " + mCurrentAuthSession); - mCurrentAuthSession.onCancelAuthSession(true /* force */); - mCurrentAuthSession = null; + Slog.w(TAG, "Existing AuthSession: " + mAuthSession); + mAuthSession.onCancelAuthSession(true /* force */); + mAuthSession = null; } final boolean debugEnabled = mInjector.isDebugEnabled(getContext(), userId); - mCurrentAuthSession = new AuthSession(getContext(), mStatusBarService, mSysuiReceiver, - mKeyStore, mRandom, mClientDeathReceiver, preAuthInfo, token, requestId, - operationId, userId, mBiometricSensorReceiver, receiver, opPackageName, promptInfo, - debugEnabled, mInjector.getFingerprintSensorProperties(getContext())); + mAuthSession = new AuthSession(getContext(), mStatusBarService, + createSysuiReceiver(requestId), mKeyStore, mRandom, + createClientDeathReceiver(requestId), preAuthInfo, token, requestId, + operationId, userId, createBiometricSensorReceiver(requestId), receiver, + opPackageName, promptInfo, debugEnabled, + mInjector.getFingerprintSensorProperties(getContext())); try { - mCurrentAuthSession.goToInitialState(); + mAuthSession.goToInitialState(); } catch (RemoteException e) { Slog.e(TAG, "RemoteException", e); } } private void handleCancelAuthentication(long requestId) { - if (mCurrentAuthSession == null) { - Slog.e(TAG, "handleCancelAuthentication: AuthSession is null"); - return; - } - if (mCurrentAuthSession.getRequestId() != requestId) { - // TODO: actually cancel the operation - // This can happen if the operation has been queued, but is cancelled before - // it reaches the head of the scheduler. Consider it a programming error for now - // and ignore it. - Slog.e(TAG, "handleCancelAuthentication: AuthSession mismatch current requestId: " - + mCurrentAuthSession.getRequestId() + " cancel for: " + requestId - + " (ignoring cancellation)"); + final AuthSession session = getAuthSessionIfCurrent(requestId); + if (session == null) { + Slog.w(TAG, "handleCancelAuthentication: AuthSession is not current"); + // TODO: actually cancel the operation? return; } - final boolean finished = mCurrentAuthSession.onCancelAuthSession(false /* force */); + final boolean finished = session.onCancelAuthSession(false /* force */); if (finished) { Slog.d(TAG, "handleCancelAuthentication: AuthSession finished"); - mCurrentAuthSession = null; + mAuthSession = null; } } @@ -1470,7 +1331,7 @@ public class BiometricService extends SystemService { pw.println(" " + sensor); } pw.println(); - pw.println("CurrentSession: " + mCurrentAuthSession); + pw.println("CurrentSession: " + mAuthSession); pw.println(); pw.println("CoexCoordinator: " + CoexCoordinator.getInstance().toString()); pw.println(); diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java index 54b79e1f8e4af..6d687726dbe81 100644 --- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java +++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java @@ -86,6 +86,7 @@ public abstract class AuthenticationClient extends AcquisitionClient private long mStartTimeMs; private boolean mAuthAttempted; + private boolean mAuthSuccess = false; // TODO: This is currently hard to maintain, as each AuthenticationClient subclass must update // the state. We should think of a way to improve this in the future. @@ -237,6 +238,7 @@ public abstract class AuthenticationClient extends AcquisitionClient "Successful background authentication!"); } + mAuthSuccess = true; markAlreadyDone(); if (mTaskStackListener != null) { @@ -502,6 +504,11 @@ public abstract class AuthenticationClient extends AcquisitionClient return mAuthAttempted; } + /** If an auth attempt completed successfully. */ + public boolean wasAuthSuccessful() { + return mAuthSuccess; + } + protected int getShowOverlayReason() { if (isKeyguard()) { return BiometricOverlayConstants.REASON_AUTH_KEYGUARD; diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java index 1a6da94f683e5..d0ec4470d3e6d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java +++ b/services/core/java/com/android/server/biometrics/sensors/BiometricScheduler.java @@ -316,7 +316,8 @@ public class BiometricScheduler { } } else { try { - mBiometricService.onReadyForAuthentication(cookie); + mBiometricService.onReadyForAuthentication( + mCurrentOperation.getClientMonitor().getRequestId(), cookie); } catch (RemoteException e) { Slog.e(getTag(), "Remote exception when contacting BiometricService", e); } diff --git a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java index 25d4a38cd475a..5aa9b79c074ca 100644 --- a/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java +++ b/services/core/java/com/android/server/biometrics/sensors/CoexCoordinator.java @@ -173,18 +173,13 @@ public class CoexCoordinator { } // SensorType to AuthenticationClient map - private final Map> mClientMap; - @VisibleForTesting final LinkedList mSuccessfulAuths; + private final Map> mClientMap = new HashMap<>(); + @VisibleForTesting final LinkedList mSuccessfulAuths = new LinkedList<>(); private boolean mAdvancedLogicEnabled; private boolean mFaceHapticDisabledWhenNonBypass; - private final Handler mHandler; + private final Handler mHandler = new Handler(Looper.getMainLooper()); - private CoexCoordinator() { - // Singleton - mClientMap = new HashMap<>(); - mSuccessfulAuths = new LinkedList<>(); - mHandler = new Handler(Looper.getMainLooper()); - } + private CoexCoordinator() {} public void addAuthenticationClient(@BiometricScheduler.SensorType int sensorType, @NonNull AuthenticationClient client) { @@ -221,8 +216,14 @@ public class CoexCoordinator { public void onAuthenticationSucceeded(long currentTimeMillis, @NonNull AuthenticationClient client, @NonNull Callback callback) { + final boolean isUsingSingleModality = isSingleAuthOnly(client); + if (client.isBiometricPrompt()) { - callback.sendHapticFeedback(); + if (!isUsingSingleModality && hasMultipleSuccessfulAuthentications()) { + // only send feedback on the first one + } else { + callback.sendHapticFeedback(); + } // For BP, BiometricService will add the authToken to Keystore. callback.sendAuthenticationResult(false /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); @@ -234,7 +235,7 @@ public class CoexCoordinator { callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); callback.handleLifecycleAfterAuth(); } else if (mAdvancedLogicEnabled && client.isKeyguard()) { - if (isSingleAuthOnly(client)) { + if (isUsingSingleModality) { // Single sensor authentication callback.sendHapticFeedback(); callback.sendAuthenticationResult(true /* addAuthTokenIfStrong */); @@ -295,10 +296,10 @@ public class CoexCoordinator { @NonNull AuthenticationClient client, @LockoutTracker.LockoutMode int lockoutMode, @NonNull Callback callback) { - final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard(); + final boolean isUsingSingleModality = isSingleAuthOnly(client); - if (keyguardAdvancedLogic) { - if (isSingleAuthOnly(client)) { + if (mAdvancedLogicEnabled && client.isKeyguard()) { + if (isUsingSingleModality) { callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); } else { @@ -319,8 +320,7 @@ public class CoexCoordinator { // also done now. callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); - } - else { + } else { // UDFPS auth has never been attempted. if (mFaceHapticDisabledWhenNonBypass && !face.isKeyguardBypassEnabled()) { Slog.w(TAG, "Skipping face reject haptic"); @@ -360,6 +360,11 @@ public class CoexCoordinator { callback.handleLifecycleAfterAuth(); } } + } else if (client.isBiometricPrompt() && !isUsingSingleModality) { + if (!isCurrentFaceAuth(client)) { + callback.sendHapticFeedback(); + } + callback.handleLifecycleAfterAuth(); } else { callback.sendHapticFeedback(); callback.handleLifecycleAfterAuth(); @@ -380,6 +385,8 @@ public class CoexCoordinator { */ public void onAuthenticationError(@NonNull AuthenticationClient client, @BiometricConstants.Errors int error, @NonNull ErrorCallback callback) { + final boolean isUsingSingleModality = isSingleAuthOnly(client); + // Figure out non-coex state final boolean shouldUsuallyVibrate; if (isCurrentFaceAuth(client)) { @@ -401,25 +408,26 @@ public class CoexCoordinator { } // Figure out coex state - final boolean keyguardAdvancedLogic = mAdvancedLogicEnabled && client.isKeyguard(); final boolean hapticSuppressedByCoex; - - if (keyguardAdvancedLogic) { - if (isSingleAuthOnly(client)) { + if (mAdvancedLogicEnabled && client.isKeyguard()) { + if (isUsingSingleModality) { hapticSuppressedByCoex = false; } else { hapticSuppressedByCoex = isCurrentFaceAuth(client) && !client.isKeyguardBypassEnabled(); } + } else if (client.isBiometricPrompt() && !isUsingSingleModality) { + hapticSuppressedByCoex = isCurrentFaceAuth(client); } else { hapticSuppressedByCoex = false; } // Combine and send feedback if appropriate - Slog.d(TAG, "shouldUsuallyVibrate: " + shouldUsuallyVibrate - + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex); if (shouldUsuallyVibrate && !hapticSuppressedByCoex) { callback.sendHapticFeedback(); + } else { + Slog.v(TAG, "no haptic shouldUsuallyVibrate: " + shouldUsuallyVibrate + + ", hapticSuppressedByCoex: " + hapticSuppressedByCoex); } } @@ -504,6 +512,19 @@ public class CoexCoordinator { return true; } + private boolean hasMultipleSuccessfulAuthentications() { + int count = 0; + for (AuthenticationClient c : mClientMap.values()) { + if (c.wasAuthSuccessful()) { + count++; + } + if (count > 1) { + return true; + } + } + return false; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index e4a969b3ca1d5..c25de4f98fe8a 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -856,11 +856,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onBiometricAuthenticated() { + public void onBiometricAuthenticated(@Modality int modality) { enforceBiometricDialog(); if (mBar != null) { try { - mBar.onBiometricAuthenticated(); + mBar.onBiometricAuthenticated(modality); } catch (RemoteException ex) { } } diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java index b94b6908f030d..2ad5eaeb9aaff 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java @@ -16,6 +16,7 @@ package com.android.server.biometrics; +import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; import static android.hardware.biometrics.BiometricManager.Authenticators; import static android.hardware.biometrics.BiometricManager.BIOMETRIC_MULTI_SENSOR_DEFAULT; @@ -85,14 +86,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Random; -import java.util.concurrent.atomic.AtomicLong; @Presubmit @SmallTest public class BiometricServiceTest { - private static final String TAG = "BiometricServiceTest"; - private static final String TEST_PACKAGE_NAME = "test_package"; private static final long TEST_REQUEST_ID = 44; @@ -153,7 +151,7 @@ public class BiometricServiceTest { .thenReturn(mock(BiometricStrengthController.class)); when(mInjector.getTrustManager()).thenReturn(mTrustManager); when(mInjector.getDevicePolicyManager(any())).thenReturn(mDevicePolicyManager); - when(mInjector.getRequestGenerator()).thenReturn(new AtomicLong(TEST_REQUEST_ID - 1)); + when(mInjector.getRequestGenerator()).thenReturn(() -> TEST_REQUEST_ID); when(mResources.getString(R.string.biometric_error_hw_unavailable)) .thenReturn(ERROR_HW_UNAVAILABLE); @@ -178,22 +176,22 @@ public class BiometricServiceTest { invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, null /* authenticators */); waitForIdle(); - verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession), + verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mAuthSession), anyInt()); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FACE, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, 0 /* vendorCode */); waitForIdle(); - assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState()); - mBiometricService.mCurrentAuthSession.binderDied(); + mBiometricService.mAuthSession.binderDied(); waitForIdle(); - assertNull(mBiometricService.mCurrentAuthSession); + assertNull(mBiometricService.mAuthSession); verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); } @@ -205,31 +203,31 @@ public class BiometricServiceTest { invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, null /* authenticators */); waitForIdle(); - verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mCurrentAuthSession), + verify(mReceiver1.asBinder()).linkToDeath(eq(mBiometricService.mAuthSession), anyInt()); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); - mBiometricService.mCurrentAuthSession.binderDied(); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); + mBiometricService.mAuthSession.binderDied(); waitForIdle(); - assertNotNull(mBiometricService.mCurrentAuthSession); + assertNotNull(mBiometricService.mAuthSession); verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog(); assertEquals(STATE_CLIENT_DIED_CANCELLING, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); - verify(mBiometricService.mCurrentAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl) + verify(mBiometricService.mAuthSession.mPreAuthInfo.eligibleSensors.get(0).impl) .cancelAuthenticationFromService(any(), any(), anyLong()); // Simulate ERROR_CANCELED received from HAL - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FACE, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */); waitForIdle(); verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); - assertNull(mBiometricService.mCurrentAuthSession); + assertNull(mBiometricService.mAuthSession); } @Test @@ -265,12 +263,12 @@ public class BiometricServiceTest { Authenticators.DEVICE_CREDENTIAL); waitForIdle(); - assertNotNull(mBiometricService.mCurrentAuthSession); + assertNotNull(mBiometricService.mAuthSession); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); // StatusBar showBiometricDialog invoked verify(mBiometricService.mStatusBarService).showAuthenticationDialog( - eq(mBiometricService.mCurrentAuthSession.mPromptInfo), + eq(mBiometricService.mAuthSession.mPromptInfo), any(IBiometricSysuiReceiver.class), AdditionalMatchers.aryEq(new int[0]) /* sensorIds */, eq(true) /* credentialAllowed */, @@ -304,21 +302,21 @@ public class BiometricServiceTest { mBiometricService = new BiometricService(mContext, mInjector); mBiometricService.onStart(); mBiometricService.mImpl.registerAuthenticator(0 /* id */, - BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, mFingerprintAuthenticator); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); waitForIdle(); verify(mReceiver1).onError( - eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS), eq(0 /* vendorCode */)); } @Test public void testAuthenticate_notStrongEnough_returnsHardwareNotPresent() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG); @@ -335,7 +333,7 @@ public class BiometricServiceTest { // is able to proceed. final int[] modalities = new int[] { - BiometricAuthenticator.TYPE_FINGERPRINT, + TYPE_FINGERPRINT, BiometricAuthenticator.TYPE_FACE, }; @@ -356,7 +354,7 @@ public class BiometricServiceTest { // StatusBar showBiometricDialog invoked with face, which was set up to be STRONG verify(mBiometricService.mStatusBarService).showAuthenticationDialog( - eq(mBiometricService.mCurrentAuthSession.mPromptInfo), + eq(mBiometricService.mAuthSession.mPromptInfo), any(IBiometricSysuiReceiver.class), AdditionalMatchers.aryEq(new int[] {SENSOR_ID_FACE}), eq(false) /* credentialAllowed */, @@ -377,14 +375,14 @@ public class BiometricServiceTest { mBiometricService = new BiometricService(mContext, mInjector); mBiometricService.onStart(); mBiometricService.mImpl.registerAuthenticator(0 /* id */, - BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, mFingerprintAuthenticator); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); waitForIdle(); verify(mReceiver1).onError( - eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE), eq(0 /* vendorCode */)); } @@ -415,13 +413,13 @@ public class BiometricServiceTest { waitForIdle(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); final byte[] HAT = generateRandomHAT(); - mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded( + mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded( SENSOR_ID_FACE, HAT); waitForIdle(); // Confirmation is required assertEquals(STATE_AUTH_PENDING_CONFIRM, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); // Enrolled, not disabled in settings, user doesn't require confirmation in settings resetReceivers(); @@ -431,25 +429,25 @@ public class BiometricServiceTest { invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); waitForIdle(); - mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded( + mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded( SENSOR_ID_FACE, HAT); waitForIdle(); // Confirmation not required, waiting for dialog to dismiss assertEquals(STATE_AUTHENTICATED_PENDING_SYSUI, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); } @Test public void testAuthenticate_happyPathWithoutConfirmation_strongBiometric() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); testAuthenticate_happyPathWithoutConfirmation(true /* isStrongBiometric */); } @Test public void testAuthenticate_happyPathWithoutConfirmation_weakBiometric() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK); testAuthenticate_happyPathWithoutConfirmation(false /* isStrongBiometric */); } @@ -461,7 +459,7 @@ public class BiometricServiceTest { waitForIdle(); // Creates a pending auth session with the correct initial states - assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState()); // Invokes Service#prepareForAuthentication ArgumentCaptor cookieCaptor = ArgumentCaptor.forClass(Integer.class); @@ -477,19 +475,19 @@ public class BiometricServiceTest { cookieCaptor.capture() /* cookie */, anyBoolean() /* allowBackgroundAuthentication */); - // onReadyForAuthentication, mCurrentAuthSession state OK - mBiometricService.mImpl.onReadyForAuthentication(cookieCaptor.getValue()); + // onReadyForAuthentication, mAuthSession state OK + mBiometricService.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookieCaptor.getValue()); waitForIdle(); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); // startPreparedClient invoked - mBiometricService.mCurrentAuthSession.onDialogAnimatedIn(); + mBiometricService.mAuthSession.onDialogAnimatedIn(); verify(mBiometricService.mSensors.get(0).impl) .startPreparedClient(cookieCaptor.getValue()); // StatusBar showBiometricDialog invoked verify(mBiometricService.mStatusBarService).showAuthenticationDialog( - eq(mBiometricService.mCurrentAuthSession.mPromptInfo), + eq(mBiometricService.mAuthSession.mPromptInfo), any(IBiometricSysuiReceiver.class), any(), eq(false) /* credentialAllowed */, @@ -502,18 +500,18 @@ public class BiometricServiceTest { // Hardware authenticated final byte[] HAT = generateRandomHAT(); - mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded( + mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded( SENSOR_ID_FINGERPRINT, HAT); waitForIdle(); // Waiting for SystemUI to send dismissed callback assertEquals(STATE_AUTHENTICATED_PENDING_SYSUI, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); // Notify SystemUI hardware authenticated - verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(); + verify(mBiometricService.mStatusBarService).onBiometricAuthenticated(TYPE_FINGERPRINT); // SystemUI sends callback with dismissed reason - mBiometricService.mSysuiReceiver.onDialogDismissed( + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED, null /* credentialAttestation */); waitForIdle(); @@ -527,7 +525,7 @@ public class BiometricServiceTest { verify(mReceiver1).onAuthenticationSucceeded( BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC); // Current session becomes null - assertNull(mBiometricService.mCurrentAuthSession); + assertNull(mBiometricService.mAuthSession); } @Test @@ -542,11 +540,11 @@ public class BiometricServiceTest { waitForIdle(); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); assertEquals(Authenticators.DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators()); + mBiometricService.mAuthSession.mPromptInfo.getAuthenticators()); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( - eq(mBiometricService.mCurrentAuthSession.mPromptInfo), + eq(mBiometricService.mAuthSession.mPromptInfo), any(IBiometricSysuiReceiver.class), AdditionalMatchers.aryEq(new int[0]) /* sensorIds */, eq(true) /* credentialAllowed */, @@ -578,16 +576,16 @@ public class BiometricServiceTest { // Test authentication succeeded goes to PENDING_CONFIRMATION and that the HAT is not // sent to KeyStore yet final byte[] HAT = generateRandomHAT(); - mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded( + mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded( SENSOR_ID_FACE, HAT); waitForIdle(); // Waiting for SystemUI to send confirmation callback - assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_PENDING_CONFIRM, mBiometricService.mAuthSession.getState()); verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class)); // SystemUI sends confirm, HAT is sent to keystore and client is notified. - mBiometricService.mSysuiReceiver.onDialogDismissed( + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED, null /* credentialAttestation */); waitForIdle(); @@ -624,33 +622,34 @@ public class BiometricServiceTest { invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mBiometricSensorReceiver.onAuthenticationFailed(SENSOR_ID_FACE); + mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationFailed(SENSOR_ID_FACE); waitForIdle(); verify(mBiometricService.mStatusBarService).onBiometricError( - eq(BiometricAuthenticator.TYPE_NONE), + eq(BiometricAuthenticator.TYPE_FACE), eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED), eq(0 /* vendorCode */)); verify(mReceiver1).onAuthenticationFailed(); - assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState()); } @Test public void testRejectFingerprint_whenAuthenticating_notifiesAndKeepsAuthenticating() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mBiometricSensorReceiver.onAuthenticationFailed(SENSOR_ID_FINGERPRINT); + mBiometricService.mAuthSession.mSensorReceiver + .onAuthenticationFailed(SENSOR_ID_FINGERPRINT); waitForIdle(); verify(mBiometricService.mStatusBarService).onBiometricError( - eq(BiometricAuthenticator.TYPE_NONE), + eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_PAUSED_REJECTED), eq(0 /* vendorCode */)); verify(mReceiver1).onAuthenticationFailed(); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); } @Test @@ -678,14 +677,14 @@ public class BiometricServiceTest { invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FACE, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, 0 /* vendorCode */); waitForIdle(); - assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState()); verify(mBiometricService.mStatusBarService).onBiometricError( eq(BiometricAuthenticator.TYPE_FACE), eq(BiometricConstants.BIOMETRIC_ERROR_TIMEOUT), @@ -694,15 +693,15 @@ public class BiometricServiceTest { verify(mReceiver1, never()).onAuthenticationFailed(); // No auth session. Pressing try again will create one. - assertEquals(STATE_AUTH_PAUSED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_PAUSED, mBiometricService.mAuthSession.getState()); // Pressing "Try again" on SystemUI - mBiometricService.mSysuiReceiver.onTryAgainPressed(); + mBiometricService.mAuthSession.mSysuiReceiver.onTryAgainPressed(); waitForIdle(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); // AuthSession is now resuming - assertEquals(STATE_AUTH_PAUSED_RESUMING, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_PAUSED_RESUMING, mBiometricService.mAuthSession.getState()); // Test resuming when hardware becomes ready. SystemUI should not be requested to // show another dialog since it's already showing. @@ -728,14 +727,14 @@ public class BiometricServiceTest { invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FACE, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, 0 /* vendorCode */); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FACE, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */); waitForIdle(); @@ -748,7 +747,7 @@ public class BiometricServiceTest { // Dialog is hidden immediately verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(); // Auth session is over - assertNull(mBiometricService.mCurrentAuthSession); + assertNull(mBiometricService.mAuthSession); } @Test @@ -757,61 +756,61 @@ public class BiometricServiceTest { // For errors that show in SystemUI, BiometricService stays in STATE_ERROR_PENDING_SYSUI // until SystemUI notifies us that the dialog is dismissed at which point the current // session is done. - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FINGERPRINT, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */); waitForIdle(); // Sends error to SystemUI and does not notify client yet - assertEquals(STATE_ERROR_PENDING_SYSUI, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_ERROR_PENDING_SYSUI, mBiometricService.mAuthSession.getState()); verify(mBiometricService.mStatusBarService).onBiometricError( - eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS), eq(0 /* vendorCode */)); verify(mBiometricService.mStatusBarService, never()).hideAuthenticationDialog(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); // SystemUI animation completed, client is notified, auth session is over - mBiometricService.mSysuiReceiver.onDialogDismissed( + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( BiometricPrompt.DISMISSED_REASON_ERROR, null /* credentialAttestation */); waitForIdle(); verify(mReceiver1).onError( - eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS), eq(0 /* vendorCode */)); - assertNull(mBiometricService.mCurrentAuthSession); + assertNull(mBiometricService.mAuthSession); } @Test public void testErrorFromHal_whilePreparingAuthentication_credentialAllowed() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); waitForIdle(); - assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState()); - mBiometricService.mBiometricSensorReceiver.onError( + assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState()); + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FINGERPRINT, - getCookieForPendingSession(mBiometricService.mCurrentAuthSession), + getCookieForPendingSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, 0 /* vendorCode */); waitForIdle(); // We should be showing device credential now - assertNotNull(mBiometricService.mCurrentAuthSession); + assertNotNull(mBiometricService.mAuthSession); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); assertEquals(Authenticators.DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators()); + mBiometricService.mAuthSession.mPromptInfo.getAuthenticators()); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( - eq(mBiometricService.mCurrentAuthSession.mPromptInfo), + eq(mBiometricService.mAuthSession.mPromptInfo), any(IBiometricSysuiReceiver.class), AdditionalMatchers.aryEq(new int[0]) /* sensorIds */, eq(true) /* credentialAllowed */, @@ -826,23 +825,23 @@ public class BiometricServiceTest { @Test public void testErrorFromHal_whilePreparingAuthentication_credentialNotAllowed() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); waitForIdle(); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FINGERPRINT, - getCookieForPendingSession(mBiometricService.mCurrentAuthSession), + getCookieForPendingSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, 0 /* vendorCode */); waitForIdle(); // Error is sent to client - verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT), + verify(mReceiver1).onError(eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT), eq(0) /* vendorCode */); - assertNull(mBiometricService.mCurrentAuthSession); + assertNull(mBiometricService.mAuthSession); } @Test @@ -861,7 +860,7 @@ public class BiometricServiceTest { private void testBiometricAuth_whenLockout(@LockoutTracker.LockoutMode int lockoutMode, int biometricPromptError) throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(lockoutMode); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, @@ -869,16 +868,15 @@ public class BiometricServiceTest { waitForIdle(); // Modality and error are sent - verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT), + verify(mReceiver1).onError(eq(TYPE_FINGERPRINT), eq(biometricPromptError), eq(0) /* vendorCode */); } @Test public void testBiometricOrCredentialAuth_whenBiometricLockout_showsCredential() throws Exception { - when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) - .thenReturn(true); - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(LockoutTracker.LOCKOUT_PERMANENT); invokeAuthenticate(mBiometricService.mImpl, mReceiver1, @@ -887,13 +885,13 @@ public class BiometricServiceTest { waitForIdle(); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); - assertNotNull(mBiometricService.mCurrentAuthSession); + assertNotNull(mBiometricService.mAuthSession); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); assertEquals(Authenticators.DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.mPromptInfo.getAuthenticators()); + mBiometricService.mAuthSession.mPromptInfo.getAuthenticators()); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( - eq(mBiometricService.mCurrentAuthSession.mPromptInfo), + eq(mBiometricService.mAuthSession.mPromptInfo), any(IBiometricSysuiReceiver.class), AdditionalMatchers.aryEq(new int[0]) /* sensorIds */, eq(true) /* credentialAllowed */, @@ -959,73 +957,73 @@ public class BiometricServiceTest { @Test public void testErrorFromHal_whileShowingDeviceCredential_doesntNotifySystemUI() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); - mBiometricService.mSysuiReceiver.onDeviceCredentialPressed(); + mBiometricService.mAuthSession.mSysuiReceiver.onDeviceCredentialPressed(); waitForIdle(); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FINGERPRINT, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */); waitForIdle(); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); verify(mReceiver1, never()).onError(anyInt(), anyInt(), anyInt()); } @Test public void testLockout_whileAuthenticating_credentialAllowed() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, Authenticators.DEVICE_CREDENTIAL | Authenticators.BIOMETRIC_WEAK); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FINGERPRINT, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_LOCKOUT, 0 /* vendorCode */); waitForIdle(); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); verify(mBiometricService.mStatusBarService).onBiometricError( - eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_LOCKOUT), eq(0 /* vendorCode */)); } @Test public void testLockout_whenAuthenticating_credentialNotAllowed() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FINGERPRINT, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS, 0 /* vendorCode */); waitForIdle(); assertEquals(STATE_ERROR_PENDING_SYSUI, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); verify(mBiometricService.mStatusBarService).onBiometricError( - eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_UNABLE_TO_PROCESS), eq(0 /* vendorCode */)); } @@ -1033,20 +1031,20 @@ public class BiometricServiceTest { @Test public void testDismissedReasonUserCancel_whileAuthenticating_cancelsHalAuthentication() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mSysuiReceiver.onDialogDismissed( + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */); waitForIdle(); verify(mReceiver1).onError( - eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED), eq(0 /* vendorCode */)); verify(mBiometricService.mSensors.get(0).impl).cancelAuthenticationFromService( any(), any(), anyLong()); - assertNull(mBiometricService.mCurrentAuthSession); + assertNull(mBiometricService.mAuthSession); } @Test @@ -1055,12 +1053,12 @@ public class BiometricServiceTest { invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FACE, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, 0 /* vendorCode */); - mBiometricService.mSysuiReceiver.onDialogDismissed( + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( BiometricPrompt.DISMISSED_REASON_NEGATIVE, null /* credentialAttestation */); waitForIdle(); @@ -1069,18 +1067,17 @@ public class BiometricServiceTest { } @Test - public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws - Exception { + public void testDismissedReasonUserCancel_whilePaused_invokesHalCancel() throws Exception { setupAuthForOnly(BiometricAuthenticator.TYPE_FACE, Authenticators.BIOMETRIC_STRONG); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FACE, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, 0 /* vendorCode */); - mBiometricService.mSysuiReceiver.onDialogDismissed( + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */); waitForIdle(); @@ -1094,10 +1091,10 @@ public class BiometricServiceTest { invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, true /* requireConfirmation */, null /* authenticators */); - mBiometricService.mBiometricSensorReceiver.onAuthenticationSucceeded( + mBiometricService.mAuthSession.mSensorReceiver.onAuthenticationSucceeded( SENSOR_ID_FACE, new byte[69] /* HAT */); - mBiometricService.mSysuiReceiver.onDialogDismissed( + mBiometricService.mAuthSession.mSysuiReceiver.onDialogDismissed( BiometricPrompt.DISMISSED_REASON_USER_CANCEL, null /* credentialAttestation */); waitForIdle(); @@ -1108,19 +1105,19 @@ public class BiometricServiceTest { eq(BiometricConstants.BIOMETRIC_ERROR_USER_CANCELED), eq(0 /* vendorCode */)); verify(mBiometricService.mKeyStore, never()).addAuthToken(any(byte[].class)); - assertNull(mBiometricService.mCurrentAuthSession); + assertNull(mBiometricService.mAuthSession); } @Test public void testAcquire_whenAuthenticating_sentToSystemUI() throws Exception { when(mContext.getResources().getString(anyInt())).thenReturn("test string"); - final int modality = BiometricAuthenticator.TYPE_FINGERPRINT; + final int modality = TYPE_FINGERPRINT; setupAuthForOnly(modality, Authenticators.BIOMETRIC_STRONG); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mBiometricSensorReceiver.onAcquired( + mBiometricService.mAuthSession.mSensorReceiver.onAcquired( SENSOR_ID_FINGERPRINT, FingerprintManager.FINGERPRINT_ACQUIRED_IMAGER_DIRTY, 0 /* vendorCode */); @@ -1130,29 +1127,29 @@ public class BiometricServiceTest { // string is retrieved for now, but it's also very unlikely to break anyway. verify(mBiometricService.mStatusBarService) .onBiometricHelp(eq(modality), anyString()); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); } @Test public void testCancel_whenAuthenticating() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, null /* authenticators */); - mBiometricService.mImpl.cancelAuthentication(mBiometricService.mCurrentAuthSession.mToken, + mBiometricService.mImpl.cancelAuthentication(mBiometricService.mAuthSession.mToken, TEST_PACKAGE_NAME, TEST_REQUEST_ID); waitForIdle(); // Pretend that the HAL has responded to cancel with ERROR_CANCELED - mBiometricService.mBiometricSensorReceiver.onError( + mBiometricService.mAuthSession.mSensorReceiver.onError( SENSOR_ID_FINGERPRINT, - getCookieForCurrentSession(mBiometricService.mCurrentAuthSession), + getCookieForCurrentSession(mBiometricService.mAuthSession), BiometricConstants.BIOMETRIC_ERROR_CANCELED, 0 /* vendorCode */); waitForIdle(); // Hides system dialog and invokes the onError callback - verify(mReceiver1).onError(eq(BiometricAuthenticator.TYPE_FINGERPRINT), + verify(mReceiver1).onError(eq(TYPE_FINGERPRINT), eq(BiometricConstants.BIOMETRIC_ERROR_CANCELED), eq(0 /* vendorCode */)); verify(mBiometricService.mStatusBarService).hideAuthenticationDialog(); @@ -1161,7 +1158,7 @@ public class BiometricServiceTest { @Test public void testCanAuthenticate_whenDeviceHasRequestedBiometricStrength() throws Exception { // When only biometric is requested, and sensor is strong enough - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); assertEquals(BiometricManager.BIOMETRIC_SUCCESS, invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG)); @@ -1170,7 +1167,7 @@ public class BiometricServiceTest { @Test public void testCanAuthenticate_whenDeviceDoesNotHaveRequestedBiometricStrength() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_WEAK); // When only biometric is requested, and sensor is not strong enough when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) @@ -1208,9 +1205,8 @@ public class BiometricServiceTest { @Test public void testCanAuthenticate_whenNoBiometricsEnrolled() throws Exception { // With credential set up, test the following. - when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) - .thenReturn(true); - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, false /* enrolled */); // When only biometric is requested @@ -1277,7 +1273,7 @@ public class BiometricServiceTest { private void testCanAuthenticate_whenLockedOut(@LockoutTracker.LockoutMode int lockoutMode) throws Exception { // When only biometric is requested, and sensor is strong enough - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt())) .thenReturn(lockoutMode); @@ -1311,7 +1307,7 @@ public class BiometricServiceTest { for (int i = 0; i < testCases.length; i++) { final BiometricSensor sensor = new BiometricSensor(mContext, 0 /* id */, - BiometricAuthenticator.TYPE_FINGERPRINT, + TYPE_FINGERPRINT, testCases[i][0], mock(IBiometricAuthenticator.class)) { @Override @@ -1341,7 +1337,7 @@ public class BiometricServiceTest { .thenReturn(true); when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); mBiometricService.mImpl.registerAuthenticator(0 /* testId */, - BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, mFingerprintAuthenticator); verify(mBiometricService.mBiometricStrengthController).updateStrengths(); @@ -1360,7 +1356,7 @@ public class BiometricServiceTest { .thenReturn(true); when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); mBiometricService.mImpl.registerAuthenticator(testId /* id */, - BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, + TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG, mFingerprintAuthenticator); // Downgrade the authenticator @@ -1378,7 +1374,7 @@ public class BiometricServiceTest { false /* requireConfirmation */, authenticators); waitForIdle(); verify(mReceiver1).onError( - eq(BiometricAuthenticator.TYPE_FINGERPRINT), + eq(TYPE_FINGERPRINT), eq(BiometricPrompt.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED), eq(0) /* vendorCode */); @@ -1392,7 +1388,7 @@ public class BiometricServiceTest { authenticators); waitForIdle(); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( - eq(mBiometricService.mCurrentAuthSession.mPromptInfo), + eq(mBiometricService.mAuthSession.mPromptInfo), any(IBiometricSysuiReceiver.class), AdditionalMatchers.aryEq(new int[] {testId}), eq(false) /* credentialAllowed */, @@ -1414,9 +1410,9 @@ public class BiometricServiceTest { false /* requireConfirmation */, authenticators); waitForIdle(); - assertTrue(Utils.isCredentialRequested(mBiometricService.mCurrentAuthSession.mPromptInfo)); + assertTrue(Utils.isCredentialRequested(mBiometricService.mAuthSession.mPromptInfo)); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( - eq(mBiometricService.mCurrentAuthSession.mPromptInfo), + eq(mBiometricService.mAuthSession.mPromptInfo), any(IBiometricSysuiReceiver.class), AdditionalMatchers.aryEq(new int[0]) /* sensorIds */, eq(true) /* credentialAllowed */, @@ -1442,7 +1438,7 @@ public class BiometricServiceTest { false /* requireConfirmation */, authenticators); waitForIdle(); verify(mBiometricService.mStatusBarService).showAuthenticationDialog( - eq(mBiometricService.mCurrentAuthSession.mPromptInfo), + eq(mBiometricService.mAuthSession.mPromptInfo), any(IBiometricSysuiReceiver.class), AdditionalMatchers.aryEq(new int[] {testId}) /* sensorIds */, eq(false) /* credentialAllowed */, @@ -1495,29 +1491,29 @@ public class BiometricServiceTest { @Test public void testWorkAuthentication_fingerprintWorksIfNotDisabledByDevicePolicyManager() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(~DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1, Authenticators.BIOMETRIC_STRONG); waitForIdle(); - assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState()); startPendingAuthSession(mBiometricService); waitForIdle(); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); } @Test public void testAuthentication_normalAppIgnoresDevicePolicy() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); invokeAuthenticateAndStart(mBiometricService.mImpl, mReceiver1, false /* requireConfirmation */, Authenticators.BIOMETRIC_STRONG); waitForIdle(); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); } @Test @@ -1530,18 +1526,17 @@ public class BiometricServiceTest { invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver1, Authenticators.BIOMETRIC_STRONG); waitForIdle(); - assertEquals(STATE_AUTH_CALLED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_CALLED, mBiometricService.mAuthSession.getState()); startPendingAuthSession(mBiometricService); waitForIdle(); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); } @Test public void testWorkAuthentication_fingerprintFailsIfDisabledByDevicePolicyManager() throws Exception { - setupAuthForOnly(BiometricAuthenticator.TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); - when(mTrustManager.isDeviceSecure(anyInt(), anyInt())) - .thenReturn(true); + setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG); + when(mTrustManager.isDeviceSecure(anyInt(), anyInt())).thenReturn(true); when(mDevicePolicyManager .getKeyguardDisabledFeatures(any() /* admin */, anyInt() /* userHandle */)) .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT); @@ -1555,9 +1550,9 @@ public class BiometricServiceTest { invokeAuthenticateForWorkApp(mBiometricService.mImpl, mReceiver2, Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL); waitForIdle(); - assertNotNull(mBiometricService.mCurrentAuthSession); + assertNotNull(mBiometricService.mAuthSession); assertEquals(STATE_SHOWING_DEVICE_CREDENTIAL, - mBiometricService.mCurrentAuthSession.getState()); + mBiometricService.mAuthSession.getState()); verify(mReceiver2, never()).onError(anyInt(), anyInt(), anyInt()); } @@ -1580,7 +1575,7 @@ public class BiometricServiceTest { when(mBiometricService.mSettingObserver.getEnabledForApps(anyInt())).thenReturn(true); - if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) { + if ((modality & TYPE_FINGERPRINT) != 0) { when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())) .thenReturn(enrolled); when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); @@ -1614,7 +1609,7 @@ public class BiometricServiceTest { final int modality = modalities[i]; final int strength = strengths[i]; - if ((modality & BiometricAuthenticator.TYPE_FINGERPRINT) != 0) { + if ((modality & TYPE_FINGERPRINT) != 0) { when(mFingerprintAuthenticator.hasEnrolledTemplates(anyInt(), any())) .thenReturn(true); when(mFingerprintAuthenticator.isHardwareDetected(any())).thenReturn(true); @@ -1654,8 +1649,9 @@ public class BiometricServiceTest { startPendingAuthSession(mBiometricService); waitForIdle(); - assertNotNull(mBiometricService.mCurrentAuthSession); - assertEquals(STATE_AUTH_STARTED, mBiometricService.mCurrentAuthSession.getState()); + assertNotNull(mBiometricService.mAuthSession); + assertEquals(TEST_REQUEST_ID, mBiometricService.mAuthSession.getRequestId()); + assertEquals(STATE_AUTH_STARTED, mBiometricService.mAuthSession.getState()); return requestId; } @@ -1663,14 +1659,14 @@ public class BiometricServiceTest { private static void startPendingAuthSession(BiometricService service) throws Exception { // Get the cookie so we can pretend the hardware is ready to authenticate // Currently we only support single modality per auth - final PreAuthInfo preAuthInfo = service.mCurrentAuthSession.mPreAuthInfo; + final PreAuthInfo preAuthInfo = service.mAuthSession.mPreAuthInfo; assertEquals(preAuthInfo.eligibleSensors.size(), 1); assertEquals(preAuthInfo.numSensorsWaitingForCookie(), 1); final int cookie = preAuthInfo.eligibleSensors.get(0).getCookie(); assertNotEquals(cookie, 0); - service.mImpl.onReadyForAuthentication(cookie); + service.mImpl.onReadyForAuthentication(TEST_REQUEST_ID, cookie); } private static long invokeAuthenticate(IBiometricService.Stub service, diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java index bfb0be760f850..f40b31a0bc0d1 100644 --- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java +++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/CoexCoordinatorTest.java @@ -29,12 +29,8 @@ 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 static org.mockito.Mockito.withSettings; -import android.content.Context; import android.hardware.biometrics.BiometricConstants; -import android.os.Handler; -import android.os.Looper; import android.platform.test.annotations.Presubmit; import androidx.test.InstrumentationRegistry; @@ -43,9 +39,11 @@ import androidx.test.filters.SmallTest; import com.android.server.biometrics.sensors.fingerprint.Udfps; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import java.util.LinkedList; @@ -53,39 +51,38 @@ import java.util.LinkedList; @SmallTest public class CoexCoordinatorTest { - private static final String TAG = "CoexCoordinatorTest"; + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); - private CoexCoordinator mCoexCoordinator; - private Handler mHandler; - - @Mock - private Context mContext; @Mock private CoexCoordinator.Callback mCallback; @Mock private CoexCoordinator.ErrorCallback mErrorCallback; + @Mock + private AuthenticationClient mFaceClient; + @Mock + private AuthenticationClient mFingerprintClient; + @Mock(extraInterfaces = {Udfps.class}) + private AuthenticationClient mUdfpsClient; + + private CoexCoordinator mCoexCoordinator; @Before public void setUp() { - MockitoAnnotations.initMocks(this); - - mHandler = new Handler(Looper.getMainLooper()); - mCoexCoordinator = CoexCoordinator.getInstance(); mCoexCoordinator.setAdvancedLogicEnabled(true); mCoexCoordinator.setFaceHapticDisabledWhenNonBypass(true); + mCoexCoordinator.reset(); } @Test public void testBiometricPrompt_authSuccess() { - mCoexCoordinator.reset(); + when(mFaceClient.isBiometricPrompt()).thenReturn(true); - AuthenticationClient client = mock(AuthenticationClient.class); - when(client.isBiometricPrompt()).thenReturn(true); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback); + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, + mFaceClient, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); verify(mCallback).handleLifecycleAfterAuth(); @@ -93,15 +90,12 @@ public class CoexCoordinatorTest { @Test public void testBiometricPrompt_authReject_whenNotLockedOut() { - mCoexCoordinator.reset(); + when(mFaceClient.isBiometricPrompt()).thenReturn(true); - AuthenticationClient client = mock(AuthenticationClient.class); - when(client.isBiometricPrompt()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, - client, LockoutTracker.LOCKOUT_NONE, mCallback); + mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); verify(mCallback).handleLifecycleAfterAuth(); @@ -109,30 +103,97 @@ public class CoexCoordinatorTest { @Test public void testBiometricPrompt_authReject_whenLockedOut() { - mCoexCoordinator.reset(); + when(mFaceClient.isBiometricPrompt()).thenReturn(true); - AuthenticationClient client = mock(AuthenticationClient.class); - when(client.isBiometricPrompt()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, - client, LockoutTracker.LOCKOUT_TIMED, mCallback); + mFaceClient, LockoutTracker.LOCKOUT_TIMED, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback, never()).sendAuthenticationResult(anyBoolean()); verify(mCallback).handleLifecycleAfterAuth(); } + @Test + public void testBiometricPrompt_coex_success() { + testBiometricPrompt_coex_success(false /* twice */); + } + + @Test + public void testBiometricPrompt_coex_successWithoutDouble() { + testBiometricPrompt_coex_success(true /* twice */); + } + + private void testBiometricPrompt_coex_success(boolean twice) { + initFaceAndFingerprintForBiometricPrompt(); + when(mFaceClient.wasAuthSuccessful()).thenReturn(true); + when(mUdfpsClient.wasAuthSuccessful()).thenReturn(twice, true); + + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); + + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, + mFaceClient, mCallback); + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, + mUdfpsClient, mCallback); + + if (twice) { + verify(mCallback, never()).sendHapticFeedback(); + } else { + verify(mCallback).sendHapticFeedback(); + } + } + + @Test + public void testBiometricPrompt_coex_reject() { + initFaceAndFingerprintForBiometricPrompt(); + + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); + + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, + mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback); + + verify(mCallback, never()).sendHapticFeedback(); + + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, + mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback); + + verify(mCallback).sendHapticFeedback(); + } + + @Test + public void testBiometricPrompt_coex_errorNoHaptics() { + initFaceAndFingerprintForBiometricPrompt(); + + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); + + mCoexCoordinator.onAuthenticationError(mFaceClient, + BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback); + mCoexCoordinator.onAuthenticationError(mUdfpsClient, + BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback); + + verify(mErrorCallback, never()).sendHapticFeedback(); + } + + private void initFaceAndFingerprintForBiometricPrompt() { + when(mFaceClient.isKeyguard()).thenReturn(false); + when(mFaceClient.isBiometricPrompt()).thenReturn(true); + when(mFaceClient.wasAuthAttempted()).thenReturn(true); + when(mUdfpsClient.isKeyguard()).thenReturn(false); + when(mUdfpsClient.isBiometricPrompt()).thenReturn(true); + when(mUdfpsClient.wasAuthAttempted()).thenReturn(true); + } + @Test public void testKeyguard_faceAuthOnly_success() { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); - AuthenticationClient client = mock(AuthenticationClient.class); - when(client.isKeyguard()).thenReturn(true); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, client, mCallback); + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, + mFaceClient, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); verify(mCallback).handleLifecycleAfterAuth(); @@ -140,21 +201,16 @@ public class CoexCoordinatorTest { @Test public void testKeyguard_faceAuth_udfpsNotTouching_faceSuccess() { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(true); + when(mUdfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false); - AuthenticationClient udfpsClient = mock(AuthenticationClient.class, - withSettings().extraInterfaces(Udfps.class)); - when(udfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient, - mCallback); + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, + mFaceClient, mCallback); // Haptics tested in #testKeyguard_bypass_haptics. Let's leave this commented out (instead // of removed) to keep this context. // verify(mCallback).sendHapticFeedback(); @@ -192,25 +248,19 @@ public class CoexCoordinatorTest { private void testKeyguard_bypass_haptics(boolean bypassEnabled, boolean faceAccepted, boolean shouldReceiveHaptics) { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); + when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled); + when(mUdfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(true); - when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled); - - AuthenticationClient udfpsClient = mock(AuthenticationClient.class, - withSettings().extraInterfaces(Udfps.class)); - when(udfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); if (faceAccepted) { - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient, + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient, mCallback); } else { - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient, + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback); } @@ -244,24 +294,18 @@ public class CoexCoordinatorTest { private void testKeyguard_faceAuth_udfpsTouching_faceSuccess(boolean thenUdfpsAccepted, long udfpsRejectedAfterMs) { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); + when(mUdfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true); + when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(true); - - AuthenticationClient udfpsClient = mock(AuthenticationClient.class, - withSettings().extraInterfaces(Udfps.class)); - when(udfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true); - when (udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); // For easier reading final CoexCoordinator.Callback faceCallback = mCallback; - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, faceClient, + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mFaceClient, faceCallback); verify(faceCallback, never()).sendHapticFeedback(); verify(faceCallback, never()).sendAuthenticationResult(anyBoolean()); @@ -272,9 +316,9 @@ public class CoexCoordinatorTest { // Reset the mock CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class); assertEquals(1, mCoexCoordinator.mSuccessfulAuths.size()); - assertEquals(faceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient); + assertEquals(mFaceClient, mCoexCoordinator.mSuccessfulAuths.get(0).mAuthenticationClient); if (thenUdfpsAccepted) { - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient, + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient, udfpsCallback); verify(udfpsCallback).sendHapticFeedback(); verify(udfpsCallback).sendAuthenticationResult(true /* addAuthTokenIfStrong */); @@ -284,7 +328,7 @@ public class CoexCoordinatorTest { assertTrue(mCoexCoordinator.mSuccessfulAuths.isEmpty()); } else { - mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, udfpsClient, + mCoexCoordinator.onAuthenticationRejected(udfpsRejectedAfterMs, mUdfpsClient, LockoutTracker.LOCKOUT_NONE, udfpsCallback); if (udfpsRejectedAfterMs <= CoexCoordinator.SUCCESSFUL_AUTH_VALID_DURATION_MS) { verify(udfpsCallback, never()).sendHapticFeedback(); @@ -310,56 +354,44 @@ public class CoexCoordinatorTest { @Test public void testKeyguard_udfpsAuthSuccess_whileFaceScanning() { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); + when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(mUdfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(true); - when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - AuthenticationClient udfpsClient = mock(AuthenticationClient.class, - withSettings().extraInterfaces(Udfps.class)); - when(udfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, udfpsClient, + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, mUdfpsClient, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(true)); - verify(faceClient).cancel(); + verify(mFaceClient).cancel(); verify(mCallback).handleLifecycleAfterAuth(); } @Test public void testKeyguard_faceRejectedWhenUdfpsTouching_thenUdfpsRejected() { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); + when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(mUdfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(true); - when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - AuthenticationClient udfpsClient = mock(AuthenticationClient.class, - withSettings().extraInterfaces(Udfps.class)); - when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(udfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient, + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback); verify(mCallback, never()).sendHapticFeedback(); verify(mCallback).handleLifecycleAfterAuth(); // BiometricScheduler removes the face authentication client after rejection - mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, faceClient); + mCoexCoordinator.removeAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); // Then UDFPS rejected CoexCoordinator.Callback udfpsCallback = mock(CoexCoordinator.Callback.class); - mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, udfpsClient, + mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mUdfpsClient, LockoutTracker.LOCKOUT_NONE, udfpsCallback); verify(udfpsCallback).sendHapticFeedback(); verify(udfpsCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); @@ -368,26 +400,20 @@ public class CoexCoordinatorTest { @Test public void testKeyguard_udfpsRejected_thenFaceRejected_noKeyguardBypass() { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); + when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case + when(mUdfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(mUdfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(true); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(true); - when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(faceClient.isKeyguardBypassEnabled()).thenReturn(false); // TODO: also test "true" case + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - AuthenticationClient udfpsClient = mock(AuthenticationClient.class, - withSettings().extraInterfaces(Udfps.class)); - when(udfpsClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(udfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) udfpsClient).isPointerDown()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, udfpsClient, - LockoutTracker.LOCKOUT_NONE, mCallback); + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, + mUdfpsClient, LockoutTracker.LOCKOUT_NONE, mCallback); // Auth was attempted - when(udfpsClient.getState()) + when(mUdfpsClient.getState()) .thenReturn(AuthenticationClient.STATE_STARTED_PAUSED_ATTEMPTED); verify(mCallback, never()).sendHapticFeedback(); verify(mCallback).handleLifecycleAfterAuth(); @@ -395,7 +421,7 @@ public class CoexCoordinatorTest { // Then face rejected. Note that scheduler leaves UDFPS in the CoexCoordinator since // unlike face, its lifecycle becomes "paused" instead of "finished". CoexCoordinator.Callback faceCallback = mock(CoexCoordinator.Callback.class); - mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, faceClient, + mCoexCoordinator.onAuthenticationRejected(1 /* currentTimeMillis */, mFaceClient, LockoutTracker.LOCKOUT_NONE, faceCallback); verify(faceCallback).sendHapticFeedback(); verify(faceCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); @@ -404,20 +430,16 @@ public class CoexCoordinatorTest { @Test public void testKeyguard_capacitiveAccepted_whenFaceScanning() { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); + when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(mFingerprintClient.isKeyguard()).thenReturn(true); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(true); - when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient); - AuthenticationClient fpClient = mock(AuthenticationClient.class); - when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(fpClient.isKeyguard()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient); - - mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, fpClient, mCallback); + mCoexCoordinator.onAuthenticationSucceeded(0 /* currentTimeMillis */, + mFingerprintClient, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(true) /* addAuthTokenIfStrong */); verify(mCallback).handleLifecycleAfterAuth(); @@ -425,21 +447,16 @@ public class CoexCoordinatorTest { @Test public void testKeyguard_capacitiveRejected_whenFaceScanning() { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); + when(mFaceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(mFingerprintClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + when(mFingerprintClient.isKeyguard()).thenReturn(true); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(true); - when(faceClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, mFingerprintClient); - AuthenticationClient fpClient = mock(AuthenticationClient.class); - when(fpClient.getState()).thenReturn(AuthenticationClient.STATE_STARTED); - when(fpClient.isKeyguard()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FP_OTHER, fpClient); - - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, fpClient, - LockoutTracker.LOCKOUT_NONE, mCallback); + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, + mFingerprintClient, LockoutTracker.LOCKOUT_NONE, mCallback); verify(mCallback).sendHapticFeedback(); verify(mCallback).sendAuthenticationResult(eq(false) /* addAuthTokenIfStrong */); verify(mCallback).handleLifecycleAfterAuth(); @@ -447,14 +464,11 @@ public class CoexCoordinatorTest { @Test public void testNonKeyguard_rejectAndNotLockedOut() { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(false); + when(mFaceClient.isBiometricPrompt()).thenReturn(true); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(false); - when(faceClient.isBiometricPrompt()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient, + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient, LockoutTracker.LOCKOUT_NONE, mCallback); verify(mCallback).sendHapticFeedback(); @@ -464,14 +478,11 @@ public class CoexCoordinatorTest { @Test public void testNonKeyguard_rejectLockedOut() { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(false); + when(mFaceClient.isBiometricPrompt()).thenReturn(true); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(false); - when(faceClient.isBiometricPrompt()).thenReturn(true); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, faceClient, + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.onAuthenticationRejected(0 /* currentTimeMillis */, mFaceClient, LockoutTracker.LOCKOUT_TIMED, mCallback); verify(mCallback).sendHapticFeedback(); @@ -496,16 +507,13 @@ public class CoexCoordinatorTest { @Test public void testBiometricPrompt_FaceError() { - mCoexCoordinator.reset(); + when(mFaceClient.isBiometricPrompt()).thenReturn(true); + when(mFaceClient.wasAuthAttempted()).thenReturn(true); - AuthenticationClient client = mock(AuthenticationClient.class); - when(client.isBiometricPrompt()).thenReturn(true); - when(client.wasAuthAttempted()).thenReturn(true); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); - - mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, - mErrorCallback); + mCoexCoordinator.onAuthenticationError(mFaceClient, + BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback); verify(mErrorCallback).sendHapticFeedback(); } @@ -520,18 +528,15 @@ public class CoexCoordinatorTest { } private void testKeyguard_faceAuthOnly(boolean bypassEnabled) { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); + when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled); + when(mFaceClient.wasAuthAttempted()).thenReturn(true); + when(mFaceClient.wasUserDetected()).thenReturn(true); - AuthenticationClient client = mock(AuthenticationClient.class); - when(client.isKeyguard()).thenReturn(true); - when(client.isKeyguardBypassEnabled()).thenReturn(bypassEnabled); - when(client.wasAuthAttempted()).thenReturn(true); - when(client.wasUserDetected()).thenReturn(true); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, client); - - mCoexCoordinator.onAuthenticationError(client, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, - mErrorCallback); + mCoexCoordinator.onAuthenticationError(mFaceClient, + BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback); verify(mErrorCallback).sendHapticFeedback(); } @@ -546,23 +551,17 @@ public class CoexCoordinatorTest { } private void testKeyguard_coex_faceError(boolean bypassEnabled) { - mCoexCoordinator.reset(); + when(mFaceClient.isKeyguard()).thenReturn(true); + when(mFaceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled); + when(mFaceClient.wasAuthAttempted()).thenReturn(true); + when(mFaceClient.wasUserDetected()).thenReturn(true); + when(mUdfpsClient.isKeyguard()).thenReturn(true); + when(((Udfps) mUdfpsClient).isPointerDown()).thenReturn(false); - AuthenticationClient faceClient = mock(AuthenticationClient.class); - when(faceClient.isKeyguard()).thenReturn(true); - when(faceClient.isKeyguardBypassEnabled()).thenReturn(bypassEnabled); - when(faceClient.wasAuthAttempted()).thenReturn(true); - when(faceClient.wasUserDetected()).thenReturn(true); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, mFaceClient); + mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, mUdfpsClient); - AuthenticationClient udfpsClient = mock(AuthenticationClient.class, - withSettings().extraInterfaces(Udfps.class)); - when(udfpsClient.isKeyguard()).thenReturn(true); - when(((Udfps) udfpsClient).isPointerDown()).thenReturn(false); - - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_FACE, faceClient); - mCoexCoordinator.addAuthenticationClient(SENSOR_TYPE_UDFPS, udfpsClient); - - mCoexCoordinator.onAuthenticationError(faceClient, + mCoexCoordinator.onAuthenticationError(mFaceClient, BiometricConstants.BIOMETRIC_ERROR_TIMEOUT, mErrorCallback); if (bypassEnabled) {