Update face strong auth strongAuth message

If bypass is enabled & UDFPS fingerprint is enrolled,
if the user cannot user face auth for device entry,
immediately (when face auth would normally trigger)
inform the user that face unlock is unavailable
and that they can use fingerprint to unlock instead.

On devices with face bypass & a non-UDFPS
enrolled, faceDetect will run and, if a face is detected,
the user will be sent to the bouncer. This CUJ doesn't work for
UDFPS because on the bouncer, UDFPS cannot be used to authenticate.

Test: manual
Test: atest KeyguardIndicationControllerTest KeyguardUpdateMonitorTest
Fixes: 231029398
Change-Id: Ibbe30a4708a92750ba54fb3f7e164f1ab72e7a3a
This commit is contained in:
Beverly
2023-02-02 20:01:13 +00:00
committed by Beverly Tai
parent a858b620bd
commit e8da39df1b
4 changed files with 114 additions and 10 deletions

View File

@@ -260,6 +260,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@VisibleForTesting
public static final int BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED = -1;
public static final int BIOMETRIC_HELP_FACE_NOT_RECOGNIZED = -2;
public static final int BIOMETRIC_HELP_FACE_NOT_AVAILABLE = -3;
/**
* If no cancel signal has been received after this amount of time, set the biometric running
@@ -2948,9 +2949,26 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
// This would need to be updated for multi-sensor devices
final boolean supportsFaceDetection = !mFaceSensorProperties.isEmpty()
&& mFaceSensorProperties.get(0).supportsFaceDetection;
if (!isUnlockingWithBiometricAllowed(FACE) && supportsFaceDetection) {
mLogger.v("startListeningForFace - detect");
mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
if (!isUnlockingWithBiometricAllowed(FACE)) {
final boolean udfpsFingerprintAuthRunning = isUdfpsSupported()
&& isFingerprintDetectionRunning();
if (supportsFaceDetection && !udfpsFingerprintAuthRunning) {
// Run face detection. (If a face is detected, show the bouncer.)
mLogger.v("startListeningForFace - detect");
mFaceManager.detectFace(mFaceCancelSignal, mFaceDetectionCallback, userId);
} else {
// Don't run face detection. Instead, inform the user
// face auth is unavailable and how to proceed.
// (ie: "Use fingerprint instead" or "Swipe up to open")
mLogger.v("Ignoring \"startListeningForFace - detect\". "
+ "Informing user face isn't available.");
mFaceAuthenticationCallback.onAuthenticationHelp(
BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
mContext.getResources().getString(
R.string.keyguard_face_unlock_unavailable)
);
return;
}
} else {
mLogger.v("startListeningForFace - authenticate");
final boolean isBypassEnabled = mKeyguardBypassController != null

View File

@@ -25,6 +25,7 @@ import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
@@ -1080,18 +1081,23 @@ public class KeyguardIndicationController {
}
}
final boolean faceAuthUnavailable = biometricSourceType == FACE
&& msgId == BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
// TODO(b/141025588): refactor to reduce repetition of code/comments
// 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 not required), so it's ok to
// pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
// check of whether non-strong biometric is allowed
if (!mKeyguardUpdateMonitor
.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) {
.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
&& !faceAuthUnavailable) {
return;
}
final boolean faceAuthSoftError = biometricSourceType == FACE
&& msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
&& msgId != BIOMETRIC_HELP_FACE_NOT_RECOGNIZED
&& msgId != BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
final boolean faceAuthFailed = biometricSourceType == FACE
&& msgId == BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; // ran through matcher & failed
final boolean fpAuthFailed = biometricSourceType == FINGERPRINT
@@ -1134,6 +1140,13 @@ public class KeyguardIndicationController {
getTrustGrantedIndication(),
mContext.getString(R.string.keyguard_unlock)
);
} else if (faceAuthUnavailable) {
showBiometricMessage(
helpString,
isUnlockWithFingerprintPossible
? mContext.getString(R.string.keyguard_suggest_fingerprint)
: mContext.getString(R.string.keyguard_unlock)
);
} else {
showBiometricMessage(helpString);
}

View File

@@ -27,6 +27,7 @@ import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOM
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.keyguard.FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
@@ -240,7 +241,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
@Mock
private FingerprintInteractiveToAuthProvider mInteractiveToAuthProvider;
private List<FingerprintSensorPropertiesInternal> mFingerprintSensorProperties;
private final int mCurrentUserId = 100;
private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
@@ -280,9 +281,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mFaceSensorProperties.get(anyInt())).thenReturn(
createFaceSensorProperties(/* supportsFaceDetection = */ false));
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(List.of(
mFingerprintSensorProperties = List.of(
new FingerprintSensorPropertiesInternal(1 /* sensorId */,
FingerprintSensorProperties.STRENGTH_STRONG,
1 /* maxEnrollmentsPerUser */,
@@ -291,7 +290,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
"1.01" /* firmwareVersion */,
"00000001" /* serialNumber */, "" /* softwareVersion */)),
FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
false /* resetLockoutRequiresHAT */)));
false /* resetLockoutRequiresHAT */));
when(mFingerprintManager.isHardwareDetected()).thenReturn(true);
when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(true);
when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(
mFingerprintSensorProperties);
when(mUserManager.isUserUnlocked(anyInt())).thenReturn(true);
when(mUserManager.isPrimaryUser()).thenReturn(true);
when(mStrongAuthTracker.getStub()).thenReturn(mock(IStrongAuthTracker.Stub.class));
@@ -769,6 +772,36 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
anyBoolean());
}
@Test
public void nofaceDetect_whenStrongAuthRequiredAndBypassUdfpsSupportedAndFpRunning() {
// GIVEN mocked keyguardUpdateMonitorCallback
KeyguardUpdateMonitorCallback keyguardUpdateMonitorCallback =
mock(KeyguardUpdateMonitorCallback.class);
mKeyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback);
// GIVEN bypass is enabled, face detection is supported
lockscreenBypassIsAllowed();
supportsFaceDetection();
keyguardIsVisible();
// GIVEN udfps is supported and strong auth required for weak biometrics (face) only
givenUdfpsSupported();
strongAuthRequiredForWeakBiometricOnly(); // this allows fingerprint to run but not face
// WHEN the device wakes up
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
mTestableLooper.processAllMessages();
// THEN face detect and authenticate are NOT triggered
verify(mFaceManager, never()).detectFace(any(), any(), anyInt());
verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
anyBoolean());
// THEN biometric help message sent to callback
verify(keyguardUpdateMonitorCallback).onBiometricHelp(
eq(BIOMETRIC_HELP_FACE_NOT_AVAILABLE), anyString(), eq(BiometricSourceType.FACE));
}
@Test
public void faceDetect_whenStrongAuthRequiredAndBypass() {
// GIVEN bypass is enabled, face detection is supported and strong auth is required
@@ -776,6 +809,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
supportsFaceDetection();
strongAuthRequiredEncrypted();
keyguardIsVisible();
// fingerprint is NOT running, UDFPS is NOT supported
// WHEN the device wakes up
mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
@@ -2466,6 +2500,11 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(false);
}
private void strongAuthRequiredForWeakBiometricOnly() {
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(true))).thenReturn(true);
when(mStrongAuthTracker.isUnlockingWithBiometricAllowed(eq(false))).thenReturn(false);
}
private void strongAuthNotRequired() {
when(mStrongAuthTracker.getStrongAuthForUser(KeyguardUpdateMonitor.getCurrentUser()))
.thenReturn(0);
@@ -2520,6 +2559,12 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
}
private void givenUdfpsSupported() {
Assert.assertFalse(mFingerprintSensorProperties.isEmpty());
when(mAuthController.getUdfpsProps()).thenReturn(mFingerprintSensorProperties);
Assert.assertTrue(mKeyguardUpdateMonitor.isUdfpsSupported());
}
private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) {
BroadcastReceiver.PendingResult pendingResult =
new BroadcastReceiver.PendingResult(Activity.RESULT_OK,

View File

@@ -23,6 +23,7 @@ import static android.hardware.biometrics.BiometricFaceConstants.FACE_ACQUIRED_T
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_LOCKOUT_PERMANENT;
import static android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_AVAILABLE;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_HELP_FINGERPRINT_NOT_RECOGNIZED;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT;
@@ -619,6 +620,33 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
mContext.getString(R.string.keyguard_suggest_fingerprint));
}
@Test
public void onBiometricHelp_coEx_faceUnavailable() {
createController();
// GIVEN unlocking with fingerprint is possible
when(mKeyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(anyInt()))
.thenReturn(true);
String message = "A message";
mController.setVisible(true);
// WHEN there's a face unavailable message
mController.getKeyguardCallback().onBiometricHelp(
BIOMETRIC_HELP_FACE_NOT_AVAILABLE,
message,
BiometricSourceType.FACE);
// THEN show sequential messages such as: 'face unlock unavailable' and
// 'try fingerprint instead'
verifyIndicationMessage(
INDICATION_TYPE_BIOMETRIC_MESSAGE,
message);
verifyIndicationMessage(
INDICATION_TYPE_BIOMETRIC_MESSAGE_FOLLOW_UP,
mContext.getString(R.string.keyguard_suggest_fingerprint));
}
@Test
public void onBiometricHelp_coEx_fpFailure_faceAlreadyUnlocked() {
createController();