diff options
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(); |