Merge "Move biometric errorMsg supression to its own helper class" into udc-qpr-dev

This commit is contained in:
Beverly Tai
2023-06-23 12:57:24 +00:00
committed by Android (Google) Code Review
4 changed files with 265 additions and 51 deletions

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2023 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.keyguard.util
import android.hardware.biometrics.BiometricFaceConstants
import android.hardware.biometrics.BiometricFingerprintConstants
import android.hardware.biometrics.BiometricSourceType
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
@SysUISingleton
class IndicationHelper
@Inject
constructor(
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
) {
fun shouldSuppressErrorMsg(biometricSource: BiometricSourceType, msgId: Int): Boolean {
return when (biometricSource) {
BiometricSourceType.FINGERPRINT ->
(isPrimaryAuthRequired() && !isFingerprintLockoutErrorMsg(msgId)) ||
msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED ||
msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED ||
msgId == BiometricFingerprintConstants.BIOMETRIC_ERROR_POWER_PRESSED
BiometricSourceType.FACE ->
(isPrimaryAuthRequired() && !isFaceLockoutErrorMsg(msgId)) ||
msgId == BiometricFaceConstants.FACE_ERROR_CANCELED ||
msgId == BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS
else -> false
}
}
private fun isFingerprintLockoutErrorMsg(msgId: Int): Boolean {
return msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT ||
msgId == BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
}
fun isFaceLockoutErrorMsg(msgId: Int): Boolean {
return msgId == BiometricFaceConstants.FACE_ERROR_LOCKOUT ||
msgId == BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
}
private fun isPrimaryAuthRequired(): Boolean {
// Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
// as long as primary auth, i.e. PIN/pattern/password, is required), so it's ok to
// pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
// check of whether non-strong biometric is allowed since strong biometrics can still be
// used.
return !keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
}
}

View File

@@ -56,7 +56,6 @@ import android.content.res.Resources;
import android.graphics.Color;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
import android.os.Handler;
import android.os.Looper;
@@ -86,6 +85,8 @@ import com.android.settingslib.fuelgauge.BatteryStatus;
import com.android.systemui.R;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
@@ -95,8 +96,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.keyguard.KeyguardIndication;
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.util.IndicationHelper;
import com.android.systemui.log.LogLevel;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -227,7 +227,8 @@ public class KeyguardIndicationController {
// triggered while the device is asleep
private final AlarmTimeout mHideTransientMessageHandler;
private final AlarmTimeout mHideBiometricMessageHandler;
private FeatureFlags mFeatureFlags;
private final FeatureFlags mFeatureFlags;
private final IndicationHelper mIndicationHelper;
/**
* Creates a new KeyguardIndicationController and registers callbacks.
@@ -259,7 +260,8 @@ public class KeyguardIndicationController {
AlarmManager alarmManager,
UserTracker userTracker,
BouncerMessageInteractor bouncerMessageInteractor,
FeatureFlags flags
FeatureFlags flags,
IndicationHelper indicationHelper
) {
mContext = context;
mBroadcastDispatcher = broadcastDispatcher;
@@ -286,6 +288,7 @@ public class KeyguardIndicationController {
mUserTracker = userTracker;
mBouncerMessageInteractor = bouncerMessageInteractor;
mFeatureFlags = flags;
mIndicationHelper = indicationHelper;
mFaceAcquiredMessageDeferral = faceHelpMessageDeferral;
mCoExFaceAcquisitionMsgIdsToShow = new HashSet<>();
@@ -1249,13 +1252,13 @@ public class KeyguardIndicationController {
private void onFaceAuthError(int msgId, String errString) {
CharSequence deferredFaceMessage = mFaceAcquiredMessageDeferral.getDeferredMessage();
mFaceAcquiredMessageDeferral.reset();
if (shouldSuppressFaceError(msgId)) {
mKeyguardLogger.logBiometricMessage("suppressingFaceError", msgId, errString);
if (mIndicationHelper.shouldSuppressErrorMsg(FACE, msgId)) {
mKeyguardLogger.logBiometricMessage("KIC suppressingFaceError", msgId, errString);
return;
}
if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
handleFaceAuthTimeoutError(deferredFaceMessage);
} else if (isLockoutError(msgId)) {
} else if (mIndicationHelper.isFaceLockoutErrorMsg(msgId)) {
handleFaceLockoutError(errString);
} else {
showErrorMessageNowOrLater(errString, null);
@@ -1263,8 +1266,8 @@ public class KeyguardIndicationController {
}
private void onFingerprintAuthError(int msgId, String errString) {
if (shouldSuppressFingerprintError(msgId)) {
mKeyguardLogger.logBiometricMessage("suppressingFingerprintError",
if (mIndicationHelper.shouldSuppressErrorMsg(FINGERPRINT, msgId)) {
mKeyguardLogger.logBiometricMessage("KIC suppressingFingerprintError",
msgId,
errString);
} else {
@@ -1272,19 +1275,6 @@ public class KeyguardIndicationController {
}
}
private boolean shouldSuppressFingerprintError(int msgId) {
return ((isPrimaryAuthRequired() && !isLockoutError(msgId))
|| msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED
|| msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED
|| msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED);
}
private boolean shouldSuppressFaceError(int msgId) {
return ((isPrimaryAuthRequired() && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
|| msgId == FaceManager.FACE_ERROR_CANCELED
|| msgId == FaceManager.FACE_ERROR_UNABLE_TO_PROCESS);
}
@Override
public void onTrustChanged(int userId) {
if (!isCurrentUser(userId)) return;
@@ -1408,11 +1398,6 @@ public class KeyguardIndicationController {
return mContext.getString(followupMsgId);
}
private static boolean isLockoutError(int msgId) {
return msgId == FaceManager.FACE_ERROR_LOCKOUT_PERMANENT
|| msgId == FaceManager.FACE_ERROR_LOCKOUT;
}
private void handleFaceAuthTimeoutError(@Nullable CharSequence deferredFaceMessage) {
mKeyguardLogger.logBiometricMessage("deferred message after face auth timeout",
null, String.valueOf(deferredFaceMessage));

View File

@@ -0,0 +1,173 @@
/*
* Copyright (C) 2023 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.keyguard.util
import android.hardware.biometrics.BiometricFaceConstants.BIOMETRIC_ERROR_POWER_PRESSED
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_CANCELED
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_UNABLE_TO_PROCESS
import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_VENDOR
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_CANCELED
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_TIMEOUT
import android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED
import android.hardware.biometrics.BiometricSourceType
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
@SmallTest
@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class IndicationHelperTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule = MockitoJUnit.rule()
@Mock lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private lateinit var underTest: IndicationHelper
@Before
fun setup() {
underTest =
IndicationHelper(
keyguardUpdateMonitor,
)
}
@Test
fun suppressErrorMsg_faceErrorCancelled() {
givenPrimaryAuthNotRequired()
assertTrue(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_CANCELED))
}
@Test
fun suppressErrorMsg_faceErrorUnableToProcess() {
givenPrimaryAuthNotRequired()
assertTrue(
underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_UNABLE_TO_PROCESS)
)
}
@Test
fun suppressErrorMsg_facePrimaryAuthRequired() {
givenPrimaryAuthRequired()
assertTrue(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_TIMEOUT))
}
@Test
fun doNotSuppressErrorMsg_facePrimaryAuthRequired_faceLockout() {
givenPrimaryAuthRequired()
assertFalse(underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_LOCKOUT))
assertFalse(
underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FACE_ERROR_LOCKOUT_PERMANENT)
)
}
@Test
fun suppressErrorMsg_fingerprintErrorCancelled() {
givenPrimaryAuthNotRequired()
assertTrue(
underTest.shouldSuppressErrorMsg(
BiometricSourceType.FINGERPRINT,
FINGERPRINT_ERROR_CANCELED
)
)
}
@Test
fun suppressErrorMsg_fingerprintErrorUserCancelled() {
givenPrimaryAuthNotRequired()
assertTrue(
underTest.shouldSuppressErrorMsg(
BiometricSourceType.FINGERPRINT,
FINGERPRINT_ERROR_USER_CANCELED
)
)
}
@Test
fun suppressErrorMsg_fingerprintErrorPowerPressed() {
givenPrimaryAuthNotRequired()
assertTrue(
underTest.shouldSuppressErrorMsg(
BiometricSourceType.FINGERPRINT,
BIOMETRIC_ERROR_POWER_PRESSED
)
)
}
@Test
fun suppressErrorMsg_fingerprintPrimaryAuthRequired() {
givenPrimaryAuthRequired()
assertTrue(
underTest.shouldSuppressErrorMsg(BiometricSourceType.FACE, FINGERPRINT_ERROR_TIMEOUT)
)
}
@Test
fun doNotSuppressErrorMsg_fingerprintPrimaryAuthRequired_fingerprintLockout() {
givenPrimaryAuthRequired()
assertFalse(
underTest.shouldSuppressErrorMsg(
BiometricSourceType.FINGERPRINT,
FINGERPRINT_ERROR_LOCKOUT
)
)
assertFalse(
underTest.shouldSuppressErrorMsg(
BiometricSourceType.FACE,
FINGERPRINT_ERROR_LOCKOUT_PERMANENT
)
)
}
@Test
fun isFaceLockoutErrorMsgId() {
givenPrimaryAuthRequired()
assertTrue(underTest.isFaceLockoutErrorMsg(FACE_ERROR_LOCKOUT))
assertTrue(underTest.isFaceLockoutErrorMsg(FACE_ERROR_LOCKOUT_PERMANENT))
assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_TIMEOUT))
assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_CANCELED))
assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_UNABLE_TO_PROCESS))
assertFalse(underTest.isFaceLockoutErrorMsg(FACE_ERROR_VENDOR))
}
private fun givenPrimaryAuthNotRequired() {
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
.thenReturn(true)
}
private fun givenPrimaryAuthRequired() {
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean()))
.thenReturn(false)
}
}

View File

@@ -72,6 +72,7 @@ import android.content.Intent;
import android.content.pm.UserInfo;
import android.graphics.Color;
import android.hardware.biometrics.BiometricFaceConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricSourceType;
import android.hardware.fingerprint.FingerprintManager;
import android.os.BatteryManager;
@@ -98,14 +99,15 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
import com.android.systemui.biometrics.FaceHelpMessageDeferral;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dock.DockManager;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.keyguard.KeyguardIndication;
import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.util.IndicationHelper;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserTracker;
@@ -213,6 +215,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
private StatusBarStateController.StateListener mStatusBarStateListener;
private ScreenLifecycle.Observer mScreenObserver;
private BroadcastReceiver mBroadcastReceiver;
private IndicationHelper mIndicationHelper;
private FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private TestableLooper mTestableLooper;
private final int mCurrentUserId = 1;
@@ -262,13 +265,14 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
when(mDevicePolicyManager.getDeviceOwnerType(DEVICE_OWNER_COMPONENT))
.thenReturn(DEVICE_OWNER_TYPE_DEFAULT);
when(mDevicePolicyResourcesManager.getString(anyString(), any()))
.thenReturn(mDisclosureGeneric);
when(mDevicePolicyResourcesManager.getString(anyString(), any(), anyString()))
.thenReturn(mDisclosureWithOrganization);
when(mUserTracker.getUserId()).thenReturn(mCurrentUserId);
mIndicationHelper = new IndicationHelper(mKeyguardUpdateMonitor);
mWakeLock = new WakeLockFake();
mWakeLockBuilder = new WakeLockFake.Builder(mContext);
mWakeLockBuilder.setWakeLock(mWakeLock);
@@ -304,7 +308,8 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mAlarmManager,
mUserTracker,
mock(BouncerMessageInteractor.class),
flags
flags,
mIndicationHelper
);
mController.init();
mController.setIndicationArea(mIndicationArea);
@@ -805,35 +810,21 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
}
@Test
public void transientIndication_visibleWhenDozing_ignoresFingerprintCancellation() {
public void transientIndication_visibleWhenDozing_ignoresFingerprintErrorMsg() {
createController();
mController.setVisible(true);
reset(mRotateTextViewController);
// WHEN a fingerprint error user cancelled message is received
mController.getKeyguardCallback().onBiometricError(
FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED, "foo",
BiometricSourceType.FINGERPRINT);
mController.getKeyguardCallback().onBiometricError(
FingerprintManager.FINGERPRINT_ERROR_CANCELED, "bar",
BiometricFingerprintConstants.FINGERPRINT_ERROR_USER_CANCELED, "foo",
BiometricSourceType.FINGERPRINT);
// THEN no message is shown
verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
verifyNoMessage(INDICATION_TYPE_TRANSIENT);
}
@Test
public void transientIndication_visibleWhenDozing_ignoresPowerPressed() {
createController();
mController.setVisible(true);
reset(mRotateTextViewController);
mController.getKeyguardCallback().onBiometricError(
FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED, "foo",
BiometricSourceType.FINGERPRINT);
verifyNoMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE);
}
@Test
public void transientIndication_swipeUpToRetry() {
createController();