summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardFaceAuthModule.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt196
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt200
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java4
14 files changed, 368 insertions, 179 deletions
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 87a775866faf..db38d3414915 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -75,6 +75,7 @@ import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -115,6 +116,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final SessionTracker mSessionTracker;
private final Optional<SideFpsController> mSideFpsController;
private final FalsingA11yDelegate mFalsingA11yDelegate;
+ private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private int mTranslationY;
// Whether the volume keys should be handled by keyguard. If true, then
// they will be handled here for specific media types such as music, otherwise
@@ -300,6 +302,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
@Override
public void onSwipeUp() {
if (!mUpdateMonitor.isFaceDetectionRunning()) {
+ mKeyguardFaceAuthInteractor.onSwipeUpOnBouncer();
boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
mKeyguardSecurityCallback.userActivity();
@@ -389,7 +392,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
FalsingA11yDelegate falsingA11yDelegate,
TelephonyManager telephonyManager,
ViewMediatorCallback viewMediatorCallback,
- AudioManager audioManager
+ AudioManager audioManager,
+ KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
) {
super(view);
mLockPatternUtils = lockPatternUtils;
@@ -414,6 +418,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mTelephonyManager = telephonyManager;
mViewMediatorCallback = viewMediatorCallback;
mAudioManager = audioManager;
+ mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index aabdafb88558..7a237591a212 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -83,6 +83,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
@@ -151,6 +152,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull private final DumpManager mDumpManager;
@NonNull private final SystemUIDialogManager mDialogManager;
@NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ @NonNull private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
@NonNull private final VibratorHelper mVibrator;
@NonNull private final FeatureFlags mFeatureFlags;
@NonNull private final FalsingManager mFalsingManager;
@@ -818,7 +820,8 @@ public class UdfpsController implements DozeReceiver, Dumpable {
@NonNull AlternateBouncerInteractor alternateBouncerInteractor,
@NonNull SecureSettings secureSettings,
@NonNull InputManager inputManager,
- @NonNull UdfpsUtils udfpsUtils) {
+ @NonNull UdfpsUtils udfpsUtils,
+ @NonNull KeyguardFaceAuthInteractor keyguardFaceAuthInteractor) {
mContext = context;
mExecution = execution;
mVibrator = vibrator;
@@ -882,6 +885,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
}
return Unit.INSTANCE;
});
+ mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
final UdfpsOverlayController mUdfpsOverlayController = new UdfpsOverlayController();
mFingerprintManager.setUdfpsOverlayController(mUdfpsOverlayController);
@@ -1141,6 +1145,7 @@ public class UdfpsController implements DozeReceiver, Dumpable {
if (!mOnFingerDown) {
playStartHaptic();
+ mKeyguardFaceAuthInteractor.onUdfpsSensorTouched();
if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
new file mode 100644
index 000000000000..a44df7e11fa1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardFaceAuthNotSupportedModule.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.dagger
+
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.domain.interactor.NoopKeyguardFaceAuthInteractor
+import dagger.Binds
+import dagger.Module
+
+/**
+ * Module that provides bindings for face auth classes that are injected into SysUI components that
+ * are used across different SysUI variants, where face auth is not supported.
+ *
+ * Some variants that do not support face authentication can install this module to provide a no-op
+ * implementation of the interactor.
+ */
+@Module
+interface KeyguardFaceAuthNotSupportedModule {
+ @Binds
+ fun keyguardFaceAuthInteractor(impl: NoopKeyguardFaceAuthInteractor): KeyguardFaceAuthInteractor
+}
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 1a72375457dd..ef8b40191149 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
@@ -20,6 +20,7 @@ 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.keyguard.domain.interactor.SystemUIKeyguardFaceAuthInteractor
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.TableLogBufferFactory
import dagger.Binds
@@ -37,8 +38,13 @@ interface KeyguardFaceAuthModule {
@Binds
@IntoMap
- @ClassKey(KeyguardFaceAuthInteractor::class)
- fun bind(impl: KeyguardFaceAuthInteractor): CoreStartable
+ @ClassKey(SystemUIKeyguardFaceAuthInteractor::class)
+ fun bind(impl: SystemUIKeyguardFaceAuthInteractor): CoreStartable
+
+ @Binds
+ fun keyguardFaceAuthInteractor(
+ impl: SystemUIKeyguardFaceAuthInteractor
+ ): KeyguardFaceAuthInteractor
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
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
index 7a21be187dcf..06ae11fe810c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt
@@ -16,181 +16,49 @@
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
+ * Interactor that exposes API to get the face authentication status and handle any events that can
+ * cause face authentication to run.
*/
-@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 {
+interface KeyguardFaceAuthInteractor {
- private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf()
+ /** Current authentication status */
+ val authenticationStatus: Flow<AuthenticationStatus>
- 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)
+ /** Current detection status */
+ val detectionStatus: Flow<DetectionStatus>
- alternateBouncerInteractor.isVisible
- .whenItFlipsToTrue()
- .onEach {
- faceAuthenticationLogger.alternateBouncerVisibilityChanged()
- runFaceAuth(
- FaceAuthUiEvent.FACE_AUTH_TRIGGERED_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN,
- fallbackToDetect = false
- )
- }
- .launchIn(applicationScope)
+ /** Can face auth be run right now */
+ fun canFaceAuthRun(): Boolean
- 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)
- }
+ /** Whether face auth is currently running or not. */
+ fun isRunning(): Boolean
- 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"
- )
- }
- }
+ /** Whether face auth is in lock out state. */
+ fun isLockedOut(): Boolean
- fun onSwipeUpOnBouncer() {
- runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
- }
+ /**
+ * Register listener for use from code that cannot use [authenticationStatus] or
+ * [detectionStatus]
+ */
+ fun registerListener(listener: FaceAuthenticationListener)
- fun onNotificationPanelClicked() {
- runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
- }
+ /** Unregister previously registered listener */
+ fun unregisterListener(listener: FaceAuthenticationListener)
- fun onQsExpansionStared() {
- runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)
- }
+ /** Whether the face auth interactor is enabled or not. */
+ fun isEnabled(): Boolean
- 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"
- }
+ fun onUdfpsSensorTouched()
+ fun onAssistantTriggeredOnLockScreen()
+ fun onDeviceLifted()
+ fun onQsExpansionStared()
+ fun onNotificationPanelClicked()
+ fun onSwipeUpOnBouncer()
}
/**
@@ -208,11 +76,3 @@ interface FaceAuthenticationListener {
/** 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/NoopKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
new file mode 100644
index 000000000000..cad40aac00d3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/NoopKeyguardFaceAuthInteractor.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.shared.model.AuthenticationStatus
+import com.android.systemui.keyguard.shared.model.DetectionStatus
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.emptyFlow
+
+/**
+ * Implementation of the interactor that noops all face auth operations.
+ *
+ * This is required for SystemUI variants that do not support face authentication but still inject
+ * other SysUI components that depend on [KeyguardFaceAuthInteractor]
+ */
+@SysUISingleton
+class NoopKeyguardFaceAuthInteractor @Inject constructor() : KeyguardFaceAuthInteractor {
+ override val authenticationStatus: Flow<AuthenticationStatus>
+ get() = emptyFlow()
+ override val detectionStatus: Flow<DetectionStatus>
+ get() = emptyFlow()
+
+ override fun canFaceAuthRun(): Boolean = false
+
+ override fun isRunning(): Boolean = false
+
+ override fun isLockedOut(): Boolean = false
+
+ override fun isEnabled() = false
+
+ override fun registerListener(listener: FaceAuthenticationListener) {}
+
+ override fun unregisterListener(listener: FaceAuthenticationListener) {}
+
+ override fun onUdfpsSensorTouched() {}
+
+ override fun onAssistantTriggeredOnLockScreen() {}
+
+ override fun onDeviceLifted() {}
+
+ override fun onQsExpansionStared() {}
+
+ override fun onNotificationPanelClicked() {}
+
+ override fun onSwipeUpOnBouncer() {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
new file mode 100644
index 000000000000..20ebb711c42d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.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
+ * SystemUI Keyguard.
+ */
+@SysUISingleton
+class SystemUIKeyguardFaceAuthInteractor
+@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, KeyguardFaceAuthInteractor {
+
+ 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)
+ }
+
+ override fun onSwipeUpOnBouncer() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_SWIPE_UP_ON_BOUNCER, false)
+ }
+
+ override fun onNotificationPanelClicked() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_NOTIFICATION_PANEL_CLICKED, true)
+ }
+
+ override fun onQsExpansionStared() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_QS_EXPANDED, true)
+ }
+
+ override fun onDeviceLifted() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_PICK_UP_GESTURE_TRIGGERED, true)
+ }
+
+ override fun onAssistantTriggeredOnLockScreen() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_UPDATED_ASSISTANT_VISIBILITY_CHANGED, true)
+ }
+
+ override fun onUdfpsSensorTouched() {
+ runFaceAuth(FaceAuthUiEvent.FACE_AUTH_TRIGGERED_UDFPS_POINTER_DOWN, false)
+ }
+
+ override fun registerListener(listener: FaceAuthenticationListener) {
+ listeners.add(listener)
+ }
+
+ override fun unregisterListener(listener: FaceAuthenticationListener) {
+ listeners.remove(listener)
+ }
+
+ override fun isLockedOut(): Boolean = repository.isLockedOut.value
+
+ override fun isRunning(): Boolean = repository.isAuthRunning.value
+
+ override fun canFaceAuthRun(): Boolean = repository.canRunFaceAuth.value
+
+ override fun isEnabled(): Boolean {
+ return featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)
+ }
+
+ /** Provide the status of face authentication */
+ override val authenticationStatus = repository.authenticationStatus
+
+ /** Provide the status of face detection */
+ override val detectionStatus = repository.detectionStatus
+
+ 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"
+ )
+ }
+ }
+
+ 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"
+ }
+}
+
+// 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/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index 7cb1cbe77539..ef14d1cb7f63 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -64,6 +64,7 @@ import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -132,6 +133,7 @@ public class QuickSettingsController {
private final FalsingCollector mFalsingCollector;
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final ShadeLogger mShadeLog;
+ private final KeyguardFaceAuthInteractor mKeyguardFaceAuthInteractor;
private final FeatureFlags mFeatureFlags;
private final InteractionJankMonitor mInteractionJankMonitor;
private final FalsingManager mFalsingManager;
@@ -318,7 +320,8 @@ public class QuickSettingsController {
MetricsLogger metricsLogger,
FeatureFlags featureFlags,
InteractionJankMonitor interactionJankMonitor,
- ShadeLogger shadeLog
+ ShadeLogger shadeLog,
+ KeyguardFaceAuthInteractor keyguardFaceAuthInteractor
) {
mPanelViewControllerLazy = panelViewControllerLazy;
mPanelView = panelView;
@@ -357,6 +360,7 @@ public class QuickSettingsController {
mLockscreenGestureLogger = lockscreenGestureLogger;
mMetricsLogger = metricsLogger;
mShadeLog = shadeLog;
+ mKeyguardFaceAuthInteractor = keyguardFaceAuthInteractor;
mFeatureFlags = featureFlags;
mInteractionJankMonitor = interactionJankMonitor;
@@ -937,6 +941,7 @@ public class QuickSettingsController {
// When expanding QS, let's authenticate the user if possible,
// this will speed up notification actions.
if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+ mKeyguardFaceAuthInteractor.onQsExpansionStared();
mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 8ee2c6f2c399..74ab47ff27a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -29,6 +29,7 @@ import com.android.systemui.CoreStartable
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.util.Assert
import com.android.systemui.util.sensors.AsyncSensorManager
@@ -46,6 +47,7 @@ class KeyguardLiftController @Inject constructor(
private val statusBarStateController: StatusBarStateController,
private val asyncSensorManager: AsyncSensorManager,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+ private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
private val dumpManager: DumpManager
) : Dumpable, CoreStartable {
@@ -72,6 +74,7 @@ class KeyguardLiftController @Inject constructor(
// Not listening anymore since trigger events unregister themselves
isListening = false
updateListeningState()
+ keyguardFaceAuthInteractor.onDeviceLifted()
keyguardUpdateMonitor.requestFaceAuth(
FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index d760189bcfd6..3e4fd891a668 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -67,6 +67,7 @@ import com.android.systemui.classifier.FalsingA11yDelegate;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
@@ -208,7 +209,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mConfigurationController, mFalsingCollector, mFalsingManager,
mUserSwitcherController, mFeatureFlags, mGlobalSettings,
mSessionTracker, Optional.of(mSideFpsController), mFalsingA11yDelegate,
- mTelephonyManager, mViewMediatorCallback, mAudioManager);
+ mTelephonyManager, mViewMediatorCallback, mAudioManager,
+ mock(KeyguardFaceAuthInteractor.class));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 64c028e6a095..eae95a54744b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -87,6 +87,7 @@ import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor;
import com.android.systemui.log.SessionTracker;
import com.android.systemui.plugins.FalsingManager;
@@ -311,7 +312,8 @@ public class UdfpsControllerTest extends SysuiTestCase {
mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
- mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils);
+ mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
+ mock(KeyguardFaceAuthInteractor.class));
verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
mOverlayController = mOverlayCaptor.getValue();
verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
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
index e0acf55ddac1..3d1d2f46a65e 100644
--- 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
@@ -62,7 +62,7 @@ import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
- private lateinit var underTest: KeyguardFaceAuthInteractor
+ private lateinit var underTest: SystemUIKeyguardFaceAuthInteractor
private lateinit var testScope: TestScope
private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
private lateinit var keyguardTransitionRepository: FakeKeyguardTransitionRepository
@@ -85,7 +85,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
keyguardTransitionInteractor = KeyguardTransitionInteractor(keyguardTransitionRepository)
underTest =
- KeyguardFaceAuthInteractor(
+ SystemUIKeyguardFaceAuthInteractor(
testScope.backgroundScope,
dispatcher,
faceAuthRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 5ca37716cbff..4bfd6a2e28f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -93,6 +93,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerReposito
import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository;
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
@@ -665,7 +666,8 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase {
mMetricsLogger,
mFeatureFlags,
mInteractionJankMonitor,
- mShadeLog
+ mShadeLog,
+ mock(KeyguardFaceAuthInteractor.class)
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
index d8ffe39e427d..908f7cbf4801 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerTest.java
@@ -62,6 +62,7 @@ import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
import com.android.systemui.media.controls.pipeline.MediaDataManager;
import com.android.systemui.media.controls.ui.MediaHierarchyManager;
import com.android.systemui.plugins.FalsingManager;
@@ -239,7 +240,8 @@ public class QuickSettingsControllerTest extends SysuiTestCase {
mMetricsLogger,
mFeatureFlags,
mInteractionJankMonitor,
- mShadeLogger
+ mShadeLogger,
+ mock(KeyguardFaceAuthInteractor.class)
);
mFragmentListener = mQsController.getQsFragmentListener();