summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java196
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt102
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt218
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt292
-rw-r--r--packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt8
11 files changed, 876 insertions, 93 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 350c4ed084b9..33a822440866 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -154,6 +154,15 @@ import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.DumpsysTableLogger;
+import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
+import com.android.systemui.keyguard.shared.model.AcquiredAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.DetectionStatus;
+import com.android.systemui.keyguard.shared.model.ErrorAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.FailedAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.HelpAuthenticationStatus;
+import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus;
import com.android.systemui.keyguard.shared.model.SysUiFaceAuthenticateOptions;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.WeatherData;
@@ -372,6 +381,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final FingerprintManager mFpm;
@Nullable
private final FaceManager mFaceManager;
+ @Nullable
+ private KeyguardFaceAuthInteractor mFaceAuthInteractor;
private final LockPatternUtils mLockPatternUtils;
@VisibleForTesting
@DevicePostureInt
@@ -1165,8 +1176,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Trace.endSection();
}
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceAuthFailed() {
Assert.isMainThread();
+ String reason =
+ mKeyguardBypassController.canBypass() ? "bypass"
+ : mAlternateBouncerShowing ? "alternateBouncer"
+ : mPrimaryBouncerFullyShown ? "bouncer"
+ : "udfpsFpDown";
+ requestActiveUnlock(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+ "faceFailure-" + reason);
+
mLogger.d("onFaceAuthFailed");
mFaceCancelSignal = null;
setFaceRunningState(BIOMETRIC_STATE_STOPPED);
@@ -1180,6 +1204,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mContext.getString(R.string.kg_face_not_recognized));
}
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceAcquired(int acquireInfo) {
Assert.isMainThread();
mLogger.logFaceAcquired(acquireInfo);
@@ -1189,8 +1217,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
cb.onBiometricAcquired(FACE, acquireInfo);
}
}
+
+ if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
+ acquireInfo)) {
+ requestActiveUnlock(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+ "faceAcquireInfo-" + acquireInfo);
+ }
}
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceAuthenticated(int authUserId, boolean isStrongBiometric) {
Trace.beginSection("KeyGuardUpdateMonitor#handlerFaceAuthenticated");
try {
@@ -1203,7 +1242,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mLogger.logFaceAuthForWrongUser(authUserId);
return;
}
- if (isFaceDisabled(userId)) {
+ if (!isFaceAuthInteractorEnabled() && isFaceDisabled(userId)) {
mLogger.logFaceAuthDisabledForUser(userId);
return;
}
@@ -1215,6 +1254,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
Trace.endSection();
}
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceHelp(int msgId, String helpString) {
Assert.isMainThread();
mLogger.logFaceAuthHelpMsg(msgId, helpString);
@@ -1226,22 +1269,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- private final Runnable mRetryFaceAuthentication = new Runnable() {
- @Override
- public void run() {
- mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
- updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
- FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
- }
- };
-
- private void onFaceCancelNotReceived() {
- mLogger.e("Face cancellation not received, transitioning to STOPPED");
- mFaceRunningState = BIOMETRIC_STATE_STOPPED;
- KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
- FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
- }
-
+ /**
+ * @deprecated This is being migrated to use modern architecture, this method is visible purely
+ * for bridging the gap while the migration is active.
+ */
private void handleFaceError(int msgId, final String originalErrMsg) {
Assert.isMainThread();
String errString = originalErrMsg;
@@ -1299,6 +1330,28 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
if (lockedOutStateChanged) {
notifyLockedOutStateChanged(FACE);
}
+
+ if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(msgId)) {
+ requestActiveUnlock(
+ ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
+ "faceError-" + msgId);
+ }
+ }
+
+ private final Runnable mRetryFaceAuthentication = new Runnable() {
+ @Override
+ public void run() {
+ mLogger.logRetryingAfterFaceHwUnavailable(mHardwareFaceUnavailableRetryCount);
+ updateFaceListeningState(BIOMETRIC_ACTION_UPDATE,
+ FACE_AUTH_TRIGGERED_RETRY_AFTER_HW_UNAVAILABLE);
+ }
+ };
+
+ private void onFaceCancelNotReceived() {
+ mLogger.e("Face cancellation not received, transitioning to STOPPED");
+ mFaceRunningState = BIOMETRIC_STATE_STOPPED;
+ KeyguardUpdateMonitor.this.updateFaceListeningState(BIOMETRIC_ACTION_STOP,
+ FACE_AUTH_STOPPED_FACE_CANCEL_NOT_RECEIVED);
}
private void handleFaceLockoutReset(@LockoutMode int mode) {
@@ -1343,10 +1396,61 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
}
+ /**
+ * @deprecated This is being migrated to use modern architecture.
+ */
+ @Deprecated
public boolean isFaceDetectionRunning() {
+ if (isFaceAuthInteractorEnabled()) {
+ return getFaceAuthInteractor().isRunning();
+ }
return mFaceRunningState == BIOMETRIC_STATE_RUNNING;
}
+ private boolean isFaceAuthInteractorEnabled() {
+ return mFaceAuthInteractor != null && mFaceAuthInteractor.isEnabled();
+ }
+
+ private @Nullable KeyguardFaceAuthInteractor getFaceAuthInteractor() {
+ return mFaceAuthInteractor;
+ }
+
+ /**
+ * Set the face auth interactor that should be used for initiating face authentication.
+ */
+ public void setFaceAuthInteractor(@Nullable KeyguardFaceAuthInteractor faceAuthInteractor) {
+ mFaceAuthInteractor = faceAuthInteractor;
+ mFaceAuthInteractor.registerListener(mFaceAuthenticationListener);
+ }
+
+ private FaceAuthenticationListener mFaceAuthenticationListener =
+ new FaceAuthenticationListener() {
+ @Override
+ public void onAuthenticationStatusChanged(@NonNull AuthenticationStatus status) {
+ if (status instanceof AcquiredAuthenticationStatus) {
+ handleFaceAcquired(
+ ((AcquiredAuthenticationStatus) status).getAcquiredInfo());
+ } else if (status instanceof ErrorAuthenticationStatus) {
+ ErrorAuthenticationStatus error = (ErrorAuthenticationStatus) status;
+ handleFaceError(error.getMsgId(), error.getMsg());
+ } else if (status instanceof FailedAuthenticationStatus) {
+ handleFaceAuthFailed();
+ } else if (status instanceof HelpAuthenticationStatus) {
+ HelpAuthenticationStatus helpMsg = (HelpAuthenticationStatus) status;
+ handleFaceHelp(helpMsg.getMsgId(), helpMsg.getMsg());
+ } else if (status instanceof SuccessAuthenticationStatus) {
+ FaceManager.AuthenticationResult result =
+ ((SuccessAuthenticationStatus) status).getSuccessResult();
+ handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
+ }
+ }
+
+ @Override
+ public void onDetectionStatusChanged(@NonNull DetectionStatus status) {
+ handleFaceAuthenticated(status.getUserId(), status.isStrongBiometric());
+ }
+ };
+
private boolean isTrustDisabled() {
// Don't allow trust agent if device is secured with a SIM PIN. This is here
// mainly because there's no other way to prompt the user to enter their SIM PIN
@@ -1360,6 +1464,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| isSimPinSecure();
}
+ /**
+ * @deprecated This method is not needed anymore with the new face auth system.
+ */
+ @Deprecated
private boolean isFaceDisabled(int userId) {
// TODO(b/140035044)
return whitelistIpcs(() ->
@@ -1371,7 +1479,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
/**
* @return whether the current user has been authenticated with face. This may be true
* on the lockscreen if the user doesn't have bypass enabled.
+ *
+ * @deprecated This is being migrated to use modern architecture.
*/
+ @Deprecated
public boolean getIsFaceAuthenticated() {
boolean faceAuthenticated = false;
BiometricAuthenticated bioFaceAuthenticated = mUserFaceAuthenticated.get(getCurrentUser());
@@ -1619,6 +1730,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
void setAssistantVisible(boolean assistantVisible) {
mAssistantVisible = assistantVisible;
mLogger.logAssistantVisible(mAssistantVisible);
+ if (isFaceAuthInteractorEnabled()) {
+ mFaceAuthInteractor.onAssistantTriggeredOnLockScreen();
+ }
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE,
FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED);
if (mAssistantVisible) {
@@ -1832,54 +1946,27 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
@Override
public void onAuthenticationFailed() {
- String reason =
- mKeyguardBypassController.canBypass() ? "bypass"
- : mAlternateBouncerShowing ? "alternateBouncer"
- : mPrimaryBouncerFullyShown ? "bouncer"
- : "udfpsFpDown";
- requestActiveUnlock(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
- "faceFailure-" + reason);
-
handleFaceAuthFailed();
}
@Override
public void onAuthenticationSucceeded(FaceManager.AuthenticationResult result) {
- Trace.beginSection("KeyguardUpdateMonitor#onAuthenticationSucceeded");
handleFaceAuthenticated(result.getUserId(), result.isStrongBiometric());
- Trace.endSection();
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
- if (mFaceAcquiredInfoIgnoreList.contains(helpMsgId)) {
- return;
- }
handleFaceHelp(helpMsgId, helpString.toString());
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
handleFaceError(errMsgId, errString.toString());
-
- if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) {
- requestActiveUnlock(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
- "faceError-" + errMsgId);
- }
}
@Override
public void onAuthenticationAcquired(int acquireInfo) {
handleFaceAcquired(acquireInfo);
-
- if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo(
- acquireInfo)) {
- requestActiveUnlock(
- ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
- "faceAcquireInfo-" + acquireInfo);
- }
}
};
@@ -2628,7 +2715,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
* @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
* invoked.
* @return current face auth detection state, true if it is running.
+ * @deprecated This is being migrated to use modern architecture.
*/
+ @Deprecated
public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
mLogger.logFaceAuthRequested(reason);
updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
@@ -2643,6 +2732,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void updateFaceListeningState(int action, @NonNull FaceAuthUiEvent faceAuthUiEvent) {
+ if (isFaceAuthInteractorEnabled()) return;
// If this message exists, we should not authenticate again until this message is
// consumed by the handler
if (mHandler.hasMessages(MSG_BIOMETRIC_AUTHENTICATION_CONTINUE)) {
@@ -3154,6 +3244,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
public boolean isFaceLockedOut() {
+ if (isFaceAuthInteractorEnabled()) {
+ return getFaceAuthInteractor().isLockedOut();
+ }
return mFaceLockedOutPermanent;
}
@@ -3202,13 +3295,23 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return mIsUnlockWithFingerprintPossible.getOrDefault(userId, false);
}
+ /**
+ * @deprecated This is being migrated to use modern architecture.
+ */
+ @Deprecated
private boolean isUnlockWithFacePossible(int userId) {
+ if (isFaceAuthInteractorEnabled()) {
+ return getFaceAuthInteractor().canFaceAuthRun();
+ }
return isFaceAuthEnabledForUser(userId) && !isFaceDisabled(userId);
}
/**
* If face hardware is available, user has enrolled and enabled auth via setting.
+ *
+ * @deprecated This is being migrated to use modern architecture.
*/
+ @Deprecated
public boolean isFaceAuthEnabledForUser(int userId) {
// TODO (b/242022358), make this rely on onEnrollmentChanged event and update it only once.
updateFaceEnrolled(userId);
@@ -3232,6 +3335,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
private void stopListeningForFace(@NonNull FaceAuthUiEvent faceAuthUiEvent) {
+ if (isFaceAuthInteractorEnabled()) return;
mLogger.v("stopListeningForFace()");
mLogger.logStoppedListeningForFace(mFaceRunningState, faceAuthUiEvent.getReason());
mUiEventLogger.log(faceAuthUiEvent, getKeyguardSessionId());
@@ -4098,6 +4202,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mStatusBarStateController.removeCallback(mStatusBarStateControllerListener);
mTelephonyListenerManager.removeActiveDataSubscriptionIdListener(mPhoneStateListener);
mSubscriptionManager.removeOnSubscriptionsChangedListener(mSubscriptionListener);
+ if (isFaceAuthInteractorEnabled()) {
+ mFaceAuthInteractor.unregisterListener(mFaceAuthenticationListener);
+ }
if (mDeviceProvisionedObserver != null) {
mContext.getContentResolver().unregisterContentObserver(mDeviceProvisionedObserver);
@@ -4127,6 +4234,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
pw.println(" getUserHasTrust()=" + getUserHasTrust(getCurrentUser()));
pw.println(" getUserUnlockedWithBiometric()="
+ getUserUnlockedWithBiometric(getCurrentUser()));
+ pw.println(" isFaceAuthInteractorEnabled: " + isFaceAuthInteractorEnabled());
pw.println(" SIM States:");
for (SimData data : mSimDatas.values()) {
pw.println(" " + data.toString());
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index 0abce82527f9..c4fc8834df83 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -44,6 +44,8 @@ import com.android.systemui.keyguard.shared.model.SuccessAuthenticationStatus
import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.logDiffsForTable
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.user.data.repository.UserRepository
import java.io.PrintWriter
@@ -78,7 +80,7 @@ interface DeviceEntryFaceAuthRepository {
val isAuthenticated: Flow<Boolean>
/** Whether face auth can run at this point. */
- val canRunFaceAuth: Flow<Boolean>
+ val canRunFaceAuth: StateFlow<Boolean>
/** Provide the current status of face authentication. */
val authenticationStatus: Flow<AuthenticationStatus>
@@ -87,10 +89,10 @@ interface DeviceEntryFaceAuthRepository {
val detectionStatus: Flow<DetectionStatus>
/** Current state of whether face authentication is locked out or not. */
- val isLockedOut: Flow<Boolean>
+ val isLockedOut: StateFlow<Boolean>
/** Current state of whether face authentication is running. */
- val isAuthRunning: Flow<Boolean>
+ val isAuthRunning: StateFlow<Boolean>
/** Whether bypass is currently enabled */
val isBypassEnabled: Flow<Boolean>
@@ -129,6 +131,8 @@ constructor(
private val keyguardRepository: KeyguardRepository,
private val keyguardInteractor: KeyguardInteractor,
private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
+ @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
dumpManager: DumpManager,
) : DeviceEntryFaceAuthRepository, Dumpable {
private var authCancellationSignal: CancellationSignal? = null
@@ -224,17 +228,19 @@ constructor(
// Face detection can run only when lockscreen bypass is enabled
// & detection is supported & biometric unlock is not allowed.
listOf(
- canFaceAuthOrDetectRun(),
- logAndObserve(isBypassEnabled, "isBypassEnabled"),
+ canFaceAuthOrDetectRun(faceDetectLog),
+ logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
logAndObserve(
biometricSettingsRepository.isNonStrongBiometricAllowed.isFalse(),
- "nonStrongBiometricIsNotAllowed"
+ "nonStrongBiometricIsNotAllowed",
+ faceDetectLog
),
// We don't want to run face detect if it's not possible to authenticate with FP
// from the bouncer. UDFPS is the only fp sensor type that won't support this.
logAndObserve(
and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
- "udfpsAuthIsNotPossibleAnymore"
+ "udfpsAuthIsNotPossibleAnymore",
+ faceDetectLog
)
)
.reduce(::and)
@@ -246,6 +252,7 @@ constructor(
cancelDetection()
}
}
+ .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
.launchIn(applicationScope)
}
@@ -254,26 +261,34 @@ constructor(
it == BiometricType.UNDER_DISPLAY_FINGERPRINT
}
- private fun canFaceAuthOrDetectRun(): Flow<Boolean> {
+ private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
return listOf(
- logAndObserve(biometricSettingsRepository.isFaceEnrolled, "isFaceEnrolled"),
+ logAndObserve(
+ biometricSettingsRepository.isFaceEnrolled,
+ "isFaceEnrolled",
+ tableLogBuffer
+ ),
logAndObserve(
biometricSettingsRepository.isFaceAuthenticationEnabled,
- "isFaceAuthenticationEnabled"
+ "isFaceAuthenticationEnabled",
+ tableLogBuffer
),
logAndObserve(
userRepository.userSwitchingInProgress.isFalse(),
- "userSwitchingNotInProgress"
+ "userSwitchingNotInProgress",
+ tableLogBuffer
),
logAndObserve(
keyguardRepository.isKeyguardGoingAway.isFalse(),
- "keyguardNotGoingAway"
+ "keyguardNotGoingAway",
+ tableLogBuffer
),
logAndObserve(
keyguardRepository.wakefulness
.map { WakefulnessModel.isSleepingOrStartingToSleep(it) }
.isFalse(),
- "deviceNotSleepingOrNotStartingToSleep"
+ "deviceNotSleepingOrNotStartingToSleep",
+ tableLogBuffer
),
logAndObserve(
combine(
@@ -282,15 +297,18 @@ constructor(
) { a, b ->
!a || b
},
- "secureCameraNotActiveOrAltBouncerIsShowing"
+ "secureCameraNotActiveOrAltBouncerIsShowing",
+ tableLogBuffer
),
logAndObserve(
biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
- "isFaceAuthSupportedInCurrentPosture"
+ "isFaceAuthSupportedInCurrentPosture",
+ tableLogBuffer
),
logAndObserve(
biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
- "userHasNotLockedDownDevice"
+ "userHasNotLockedDownDevice",
+ tableLogBuffer
)
)
.reduce(::and)
@@ -299,20 +317,27 @@ constructor(
private fun observeFaceAuthGatingChecks() {
// Face auth can run only if all of the gating conditions are true.
listOf(
- canFaceAuthOrDetectRun(),
- logAndObserve(isLockedOut.isFalse(), "isNotLocked"),
+ canFaceAuthOrDetectRun(faceAuthLog),
+ logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
logAndObserve(
deviceEntryFingerprintAuthRepository.isLockedOut.isFalse(),
- "fpLockedOut"
+ "fpLockedOut",
+ faceAuthLog
+ ),
+ logAndObserve(
+ trustRepository.isCurrentUserTrusted.isFalse(),
+ "currentUserTrusted",
+ faceAuthLog
),
- logAndObserve(trustRepository.isCurrentUserTrusted.isFalse(), "currentUserTrusted"),
logAndObserve(
biometricSettingsRepository.isNonStrongBiometricAllowed,
- "nonStrongBiometricIsAllowed"
+ "nonStrongBiometricIsAllowed",
+ faceAuthLog
),
logAndObserve(
userRepository.selectedUserInfo.map { it.isPrimary },
- "userIsPrimaryUser"
+ "userIsPrimaryUser",
+ faceAuthLog
),
)
.reduce(::and)
@@ -326,6 +351,7 @@ constructor(
cancel()
}
}
+ .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
.launchIn(applicationScope)
}
@@ -340,7 +366,6 @@ constructor(
override fun onAuthenticationAcquired(acquireInfo: Int) {
_authenticationStatus.value = AcquiredAuthenticationStatus(acquireInfo)
- faceAuthLogger.authenticationAcquired(acquireInfo)
}
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
@@ -401,7 +426,7 @@ constructor(
override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
if (_isAuthRunning.value) {
- faceAuthLogger.ignoredFaceAuthTrigger(uiEvent)
+ faceAuthLogger.ignoredFaceAuthTrigger(uiEvent, "face auth is currently running")
return
}
@@ -438,7 +463,16 @@ constructor(
)
}
} else if (fallbackToDetection && canRunDetection.value) {
+ faceAuthLogger.ignoredFaceAuthTrigger(
+ uiEvent,
+ "face auth gating check is false, falling back to detection."
+ )
detect()
+ } else {
+ faceAuthLogger.ignoredFaceAuthTrigger(
+ uiEvent,
+ "face auth & detect gating check is false"
+ )
}
}
@@ -467,7 +501,7 @@ constructor(
private val currentUserId: Int
get() = userRepository.getSelectedUserInfo().id
- fun cancelDetection() {
+ private fun cancelDetection() {
detectCancellationSignal?.cancel()
detectCancellationSignal = null
}
@@ -491,10 +525,20 @@ constructor(
_isAuthRunning.value = false
}
- private fun logAndObserve(cond: Flow<Boolean>, loggingContext: String): Flow<Boolean> {
- return cond.distinctUntilChanged().onEach {
- faceAuthLogger.observedConditionChanged(it, loggingContext)
- }
+ private fun logAndObserve(
+ cond: Flow<Boolean>,
+ conditionName: String,
+ logBuffer: TableLogBuffer
+ ): Flow<Boolean> {
+ return cond
+ .distinctUntilChanged()
+ .logDiffsForTable(
+ logBuffer,
+ columnName = conditionName,
+ columnPrefix = "",
+ initialValue = false
+ )
+ .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
}
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt
new file mode 100644
index 000000000000..6c23032143c1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceAuthTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.data.repository
+
+import javax.inject.Qualifier
+
+/** Face auth logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceAuthTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
new file mode 100644
index 000000000000..342064f02ef2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/FaceDetectTableLog.kt
@@ -0,0 +1,25 @@
+/*
+ * 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.data.repository
+
+import javax.inject.Qualifier
+
+/** Face detect logs in table format. */
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class FaceDetectTableLog
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
index 3c66f2424c7b..1a72375457dd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt
@@ -17,8 +17,16 @@
package com.android.systemui.keyguard.data.repository
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
import dagger.Binds
import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
@Module
interface KeyguardFaceAuthModule {
@@ -27,5 +35,26 @@ interface KeyguardFaceAuthModule {
impl: DeviceEntryFaceAuthRepositoryImpl
): DeviceEntryFaceAuthRepository
+ @Binds
+ @IntoMap
+ @ClassKey(KeyguardFaceAuthInteractor::class)
+ fun bind(impl: KeyguardFaceAuthInteractor): CoreStartable
+
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @FaceAuthTableLog
+ fun provideFaceAuthTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("FaceAuthTableLog", 100)
+ }
+
+ @Provides
+ @SysUISingleton
+ @FaceDetectTableLog
+ fun provideFaceDetectTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("FaceDetectTableLog", 100)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
new file mode 100644
index 000000000000..7a21be187dcf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -0,0 +1,218 @@
+/*
+ * 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.domain.interactor
+
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.util.kotlin.pairwise
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+
+/**
+ * Encapsulates business logic related face authentication being triggered for device entry from
+ * keyguard
+ */
+@SysUISingleton
+class KeyguardFaceAuthInteractor
+@Inject
+constructor(
+ @Application private val applicationScope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
+ private val repository: DeviceEntryFaceAuthRepository,
+ private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+ private val alternateBouncerInteractor: AlternateBouncerInteractor,
+ private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+ private val featureFlags: FeatureFlags,
+ private val faceAuthenticationLogger: FaceAuthenticationLogger,
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) : CoreStartable {
+
+ private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
+
+ override fun start() {
+ if (!isEnabled()) {
+ return
+ }
+ // This is required because fingerprint state required for the face auth repository is
+ // backed by KeyguardUpdateMonitor. KeyguardUpdateMonitor constructor accesses the biometric
+ // state which makes lazy injection not an option.
+ keyguardUpdateMonitor.setFaceAuthInteractor(this)
+ observeFaceAuthStateUpdates()
+ faceAuthenticationLogger.interactorStarted()
+ primaryBouncerInteractor.isShowing
+ .whenItFlipsToTrue()
+ .onEach {
+ faceAuthenticationLogger.bouncerVisibilityChanged()
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN,
+ fallbackToDetect = true
+ )
+ }
+ .launchIn(applicationScope)
+
+ alternateBouncerInteractor.isVisible
+ .whenItFlipsToTrue()
+ .onEach {
+ faceAuthenticationLogger.alternateBouncerVisibilityChanged()
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+ fallbackToDetect = false
+ )
+ }
+ .launchIn(applicationScope)
+
+ merge(
+ keyguardTransitionInteractor.aodToLockscreenTransition,
+ keyguardTransitionInteractor.offToLockscreenTransition,
+ keyguardTransitionInteractor.dozingToLockscreenTransition
+ )
+ .filter { it.transitionState == TransitionState.STARTED }
+ .onEach {
+ faceAuthenticationLogger.lockscreenBecameVisible(it)
+ runFaceAuth(
+ FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED,
+ fallbackToDetect = true
+ )
+ }
+ .launchIn(applicationScope)
+ }
+
+ private fun runFaceAuth(uiEvent: FaceAuthUiEvent, fallbackToDetect: Boolean) {
+ if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+ applicationScope.launch {
+ faceAuthenticationLogger.authRequested(uiEvent)
+ repository.authenticate(uiEvent, fallbackToDetection = fallbackToDetect)
+ }
+ } else {
+ faceAuthenticationLogger.ignoredFaceAuthTrigger(
+ uiEvent,
+ ignoredReason = "Skipping face auth request because feature flag is false"
+ )
+ }
+ }
+
+ fun onSwipeUpOnBouncer() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
+ }
+
+ fun onNotificationPanelClicked() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+ }
+
+ fun onQsExpansionStared() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)
+ }
+
+ fun onDeviceLifted() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+ }
+
+ fun onAssistantTriggeredOnLockScreen() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+ }
+
+ fun onUdfpsSensorTouched() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)
+ }
+
+ fun registerListener(listener: FaceAuthenticationListener) {
+ listeners.add(listener)
+ }
+
+ fun unregisterListener(listener: FaceAuthenticationListener) {
+ listeners.remove(listener)
+ }
+
+ fun isLockedOut(): Boolean = repository.isLockedOut.value
+
+ fun isRunning(): Boolean = repository.isAuthRunning.value
+
+ fun canFaceAuthRun(): Boolean = repository.canRunFaceAuth.value
+
+ fun isEnabled(): Boolean {
+ return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)
+ }
+
+ /** Provide the status of face authentication */
+ val authenticationStatus = repository.authenticationStatus
+
+ /** Provide the status of face detection */
+ val detectionStatus = repository.detectionStatus
+
+ private fun observeFaceAuthStateUpdates() {
+ authenticationStatus
+ .onEach { authStatusUpdate ->
+ listeners.forEach { it.onAuthenticationStatusChanged(authStatusUpdate) }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
+ detectionStatus
+ .onEach { detectionStatusUpdate ->
+ listeners.forEach { it.onDetectionStatusChanged(detectionStatusUpdate) }
+ }
+ .flowOn(mainDispatcher)
+ .launchIn(applicationScope)
+ }
+
+ companion object {
+ const val TAG = "KeyguardFaceAuthInteractor"
+ }
+}
+
+/**
+ * Listener that can be registered with the [KeyguardFaceAuthInteractor] to receive updates about
+ * face authentication & detection updates.
+ *
+ * This is present to make it easier for use the new face auth API for code that cannot use
+ * [KeyguardFaceAuthInteractor.authenticationStatus] or [KeyguardFaceAuthInteractor.detectionStatus]
+ * flows.
+ */
+interface FaceAuthenticationListener {
+ /** Receive face authentication status updates */
+ fun onAuthenticationStatusChanged(status: AuthenticationStatus)
+
+ /** Receive status updates whenever face detection runs */
+ fun onDetectionStatusChanged(status: DetectionStatus)
+}
+
+// Extension method that filters a generic Boolean flow to one that emits
+// whenever there is flip from false -> true
+private fun Flow<Boolean>.whenItFlipsToTrue(): Flow<Boolean> {
+ return this.pairwise()
+ .filter { pair -> !pair.previousValue && pair.newValue }
+ .map { it.newValue }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index aabd212c1bd3..da0ada160518 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -78,6 +78,14 @@ constructor(
val primaryBouncerToGoneTransition: Flow<TransitionStep> =
repository.transition(PRIMARY_BOUNCER, GONE)
+ /** OFF->LOCKSCREEN transition information. */
+ val offToLockscreenTransition: Flow<TransitionStep> =
+ repository.transition(KeyguardState.OFF, LOCKSCREEN)
+
+ /** DOZING->LOCKSCREEN transition information. */
+ val dozingToLockscreenTransition: Flow<TransitionStep> =
+ repository.transition(KeyguardState.DOZING, LOCKSCREEN)
+
/**
* AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
* Lockscreen (0f).
diff --git a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
index f7355d5c11e2..7f6e4a903d76 100644
--- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt
@@ -4,6 +4,7 @@ import android.hardware.face.FaceManager
import android.hardware.face.FaceSensorPropertiesInternal
import com.android.keyguard.FaceAuthUiEvent
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.dagger.FaceAuthLog
import com.android.systemui.plugins.log.LogBuffer
import com.android.systemui.plugins.log.LogLevel.DEBUG
@@ -27,15 +28,15 @@ class FaceAuthenticationLogger
constructor(
@FaceAuthLog private val logBuffer: LogBuffer,
) {
- fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent) {
+ fun ignoredFaceAuthTrigger(uiEvent: FaceAuthUiEvent, ignoredReason: String) {
logBuffer.log(
TAG,
DEBUG,
- { str1 = uiEvent.reason },
{
- "Ignoring trigger because face auth is currently running. " +
- "Trigger reason: $str1"
- }
+ str1 = uiEvent.reason
+ str2 = ignoredReason
+ },
+ { "Ignoring trigger because $str2, Trigger reason: $str1" }
)
}
@@ -135,15 +136,6 @@ constructor(
logBuffer.log(TAG, DEBUG, "Face authentication failed")
}
- fun authenticationAcquired(acquireInfo: Int) {
- logBuffer.log(
- TAG,
- DEBUG,
- { int1 = acquireInfo },
- { "Face acquired during face authentication: acquireInfo: $int1 " }
- )
- }
-
fun authenticationError(
errorCode: Int,
errString: CharSequence?,
@@ -217,4 +209,34 @@ constructor(
fun cancellingFaceAuth() {
logBuffer.log(TAG, DEBUG, "cancelling face auth because a gating condition became false")
}
+
+ fun interactorStarted() {
+ logBuffer.log(TAG, DEBUG, "KeyguardFaceAuthInteractor started")
+ }
+
+ fun bouncerVisibilityChanged() {
+ logBuffer.log(TAG, DEBUG, "Triggering face auth because primary bouncer is visible")
+ }
+
+ fun alternateBouncerVisibilityChanged() {
+ logBuffer.log(TAG, DEBUG, "Triggering face auth because alternate bouncer is visible")
+ }
+
+ fun lockscreenBecameVisible(transitionStep: TransitionStep?) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = "$transitionStep" },
+ { "Triggering face auth because lockscreen became visible due to transition: $str1" }
+ )
+ }
+
+ fun authRequested(uiEvent: FaceAuthUiEvent) {
+ logBuffer.log(
+ TAG,
+ DEBUG,
+ { str1 = "$uiEvent" },
+ { "Requesting face auth for trigger: $str1" }
+ )
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 2489e043c7db..f21ea3dbed6d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -54,6 +54,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessModel
import com.android.systemui.keyguard.shared.model.WakefulnessState
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.log.SessionTracker
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.FakeKeyguardStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -61,8 +62,11 @@ import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.KotlinArgumentCaptor
import com.android.systemui.util.mockito.captureMany
import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.io.StringWriter
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.StandardTestDispatcher
@@ -88,8 +92,6 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
-import java.io.PrintWriter
-import java.io.StringWriter
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@@ -183,8 +185,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
private fun createDeviceEntryFaceAuthRepositoryImpl(
fmOverride: FaceManager? = faceManager,
bypassControllerOverride: KeyguardBypassController? = bypassController
- ) =
- DeviceEntryFaceAuthRepositoryImpl(
+ ): DeviceEntryFaceAuthRepositoryImpl {
+ val systemClock = FakeSystemClock()
+ val faceAuthBuffer = TableLogBuffer(10, "face auth", systemClock)
+ val faceDetectBuffer = TableLogBuffer(10, "face detect", systemClock)
+ return DeviceEntryFaceAuthRepositoryImpl(
mContext,
fmOverride,
fakeUserRepository,
@@ -200,8 +205,11 @@ class DeviceEntryFaceAuthRepositoryTest : SysuiTestCase() {
keyguardRepository,
keyguardInteractor,
alternateBouncerInteractor,
+ faceDetectBuffer,
+ faceAuthBuffer,
dumpManager,
)
+ }
@Test
fun faceAuthRunsAndProvidesAuthStatusUpdates() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
new file mode 100644
index 000000000000..e0acf55ddac1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -0,0 +1,292 @@
+/*
+ * 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.domain.interactor
+
+import android.os.Handler
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.FaceAuthUiEvent
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.log.FaceAuthenticationLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestCoroutineScheduler
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
+
+ private lateinit var underTest: KeyguardFaceAuthInteractor
+ private lateinit var testScope: TestScope
+ private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+ private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
+ private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+ private lateinit var faceAuthRepository: FakeDeviceEntryFaceAuthRepository
+
+ @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ val scheduler = TestCoroutineScheduler()
+ val dispatcher = StandardTestDispatcher(scheduler)
+ testScope = TestScope(dispatcher)
+ val featureFlags = FakeFeatureFlags()
+ featureFlags.set(Flags.FACE_AUTH_REFACTOR, true)
+ bouncerRepository = FakeKeyguardBouncerRepository()
+ faceAuthRepository = FakeDeviceEntryFaceAuthRepository()
+ keyguardTransitionRepository = FakeKeyguardTransitionRepository()
+ keyguardTransitionInteractor = KeyguardTransitionInteractor(keyguardTransitionRepository)
+
+ underTest =
+ KeyguardFaceAuthInteractor(
+ testScope.backgroundScope,
+ dispatcher,
+ faceAuthRepository,
+ PrimaryBouncerInteractor(
+ bouncerRepository,
+ mock(BouncerView::class.java),
+ mock(Handler::class.java),
+ mock(KeyguardStateController::class.java),
+ mock(KeyguardSecurityModel::class.java),
+ mock(PrimaryBouncerCallbackInteractor::class.java),
+ mock(FalsingCollector::class.java),
+ mock(DismissCallbackRegistry::class.java),
+ context,
+ keyguardUpdateMonitor,
+ mock(KeyguardBypassController::class.java),
+ ),
+ AlternateBouncerInteractor(
+ mock(StatusBarStateController::class.java),
+ mock(KeyguardStateController::class.java),
+ bouncerRepository,
+ mock(BiometricSettingsRepository::class.java),
+ FakeDeviceEntryFingerprintAuthRepository(),
+ FakeSystemClock(),
+ ),
+ keyguardTransitionInteractor,
+ featureFlags,
+ FaceAuthenticationLogger(logcatLogBuffer("faceAuthBuffer")),
+ keyguardUpdateMonitor,
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromOffState() =
+ testScope.runTest {
+ underTest.start()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.OFF,
+ KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED
+ )
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromAodState() =
+ testScope.runTest {
+ underTest.start()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.AOD,
+ KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED
+ )
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenLockscreenBecomesVisibleFromDozingState() =
+ testScope.runTest {
+ underTest.start()
+
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ KeyguardState.DOZING,
+ KeyguardState.LOCKSCREEN,
+ transitionState = TransitionState.STARTED
+ )
+ )
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_KEYGUARD_VISIBILITY_CHANGED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenPrimaryBouncerIsVisible() =
+ testScope.runTest {
+ underTest.start()
+
+ bouncerRepository.setPrimaryShow(false)
+ runCurrent()
+
+ bouncerRepository.setPrimaryShow(true)
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_PRIMARY_BOUNCER_SHOWN, true))
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenAlternateBouncerIsVisible() =
+ testScope.runTest {
+ underTest.start()
+
+ bouncerRepository.setAlternateVisible(false)
+ runCurrent()
+
+ bouncerRepository.setAlternateVisible(true)
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(
+ FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
+ false
+ )
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenUdfpsSensorTouched() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onUdfpsSensorTouched()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false))
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenOnAssistantTriggeredOnLockScreen() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onAssistantTriggeredOnLockScreen()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenDeviceLifted() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onDeviceLifted()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenQsExpansionStared() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onQsExpansionStared()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true))
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenNotificationPanelClicked() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onNotificationPanelClicked()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(
+ Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+ )
+ }
+
+ @Test
+ fun faceAuthIsRequestedWhenSwipeUpOnBouncer() =
+ testScope.runTest {
+ underTest.start()
+
+ underTest.onSwipeUpOnBouncer()
+
+ runCurrent()
+ assertThat(faceAuthRepository.runningAuthRequest.value)
+ .isEqualTo(Pair(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false))
+ }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
index c08ecd0e3b0c..738f09ddce3d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt
@@ -24,7 +24,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.filterNotNull
-import kotlinx.coroutines.flow.map
class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
@@ -46,14 +45,19 @@ class FakeDeviceEntryFaceAuthRepository : DeviceEntryFaceAuthRepository {
private val _runningAuthRequest = MutableStateFlow<Pair<FaceAuthUiEvent, Boolean>?>(null)
val runningAuthRequest: StateFlow<Pair<FaceAuthUiEvent, Boolean>?> =
_runningAuthRequest.asStateFlow()
- override val isAuthRunning = _runningAuthRequest.map { it != null }
+
+ private val _isAuthRunning = MutableStateFlow(false)
+ override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+
override val isBypassEnabled = MutableStateFlow(false)
override suspend fun authenticate(uiEvent: FaceAuthUiEvent, fallbackToDetection: Boolean) {
_runningAuthRequest.value = uiEvent to fallbackToDetection
+ _isAuthRunning.value = true
}
override fun cancel() {
+ _isAuthRunning.value = false
_runningAuthRequest.value = null
}
}