From aec54aadc385aed0ea5c86ac4f8e9081e5e8d765 Mon Sep 17 00:00:00 2001 From: burakov Date: Thu, 13 Jul 2023 14:48:00 +0000 Subject: [PATCH] [flexiglass] Implement lockscreen bypass. When bypass is enabled, the lock screen will be automatically dismissed once the authentication challenge is completed. For example, completing a biometric authentication challenge via face unlock or fingerprint sensor can automatically bypass the lock screen. It is deliberately not implemented as a Flow, since its state only needs to be checked on demand. Note: to enable this feature, the "Face auth modern arch" flag needs to be enabled via Flag Flipper. Fix: 290771600 Fix: 290404894 Test: new unit tests added Test: manually verified in system UI that the lockscreen is skipped when bypass is enabled in settings, and that it is not skipped when bypass is disabled. To reach this setting, go to Settings > Security & privacy > Device unlock > Face & Fingerprint unlock > Face Unlock > Skip lock screen. Change-Id: I393b9bcb1fed1299efaf8c426d269b4b83b36476 --- .../repository/AuthenticationRepository.kt | 20 +---------- .../interactor/AuthenticationInteractor.kt | 25 +++++++------- .../data/repository/KeyguardRepository.kt | 34 ++++++++++++------- .../SystemUiDefaultSceneContainerStartable.kt | 2 +- .../phone/KeyguardBypassController.kt | 14 ++------ .../AuthenticationInteractorTest.kt | 13 ------- .../repository/KeyguardRepositoryImplTest.kt | 17 ++++++++++ ...temUiDefaultSceneContainerStartableTest.kt | 4 +-- .../FakeAuthenticationRepository.kt | 7 ---- .../data/repository/FakeKeyguardRepository.kt | 9 +++++ .../android/systemui/scene/SceneTestUtils.kt | 1 + 11 files changed, 65 insertions(+), 81 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index 70b43713599bb..a9779663cc7c5 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -63,14 +63,6 @@ interface AuthenticationRepository { */ val isUnlocked: StateFlow - /** - * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically - * dismisses once the authentication challenge is completed. For example, completing a biometric - * authentication challenge via face unlock or fingerprint sensor can automatically bypass the - * lock screen. - */ - val isBypassEnabled: StateFlow - /** * Whether the auto confirm feature is enabled for the currently-selected user. * @@ -113,9 +105,6 @@ interface AuthenticationRepository { */ suspend fun isLockscreenEnabled(): Boolean - /** See [isBypassEnabled]. */ - fun setBypassEnabled(isBypassEnabled: Boolean) - /** Reports an authentication attempt. */ suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) @@ -157,7 +146,7 @@ constructor( private val lockPatternUtils: LockPatternUtils, ) : AuthenticationRepository { - override val isUnlocked: StateFlow = keyguardRepository.isKeyguardUnlocked + override val isUnlocked = keyguardRepository.isKeyguardUnlocked override suspend fun isLockscreenEnabled(): Boolean { return withContext(backgroundDispatcher) { @@ -166,9 +155,6 @@ constructor( } } - private val _isBypassEnabled = MutableStateFlow(false) - override val isBypassEnabled: StateFlow = _isBypassEnabled.asStateFlow() - override val isAutoConfirmEnabled: StateFlow = userRepository.selectedUserInfo .map { it.id } @@ -225,10 +211,6 @@ constructor( } } - override fun setBypassEnabled(isBypassEnabled: Boolean) { - _isBypassEnabled.value = isBypassEnabled - } - override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) { val selectedUserId = userRepository.selectedUserId withContext(backgroundDispatcher) { diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 3283e406ddb04..b482977bde679 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -24,6 +24,7 @@ import com.android.systemui.authentication.shared.model.AuthenticationThrottling import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -51,6 +52,7 @@ constructor( private val repository: AuthenticationRepository, @Background private val backgroundDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, + private val keyguardRepository: KeyguardRepository, private val clock: SystemClock, ) { /** @@ -76,14 +78,6 @@ constructor( initialValue = true, ) - /** - * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically - * dismisses once the authentication challenge is completed. For example, completing a biometric - * authentication challenge via face unlock or fingerprint sensor can automatically bypass the - * lock screen. - */ - val isBypassEnabled: StateFlow = repository.isBypassEnabled - /** The current authentication throttling state, only meaningful if [isThrottled] is `true`. */ val throttling: StateFlow = repository.throttling @@ -155,6 +149,16 @@ constructor( return !isUnlocked.value && getAuthenticationMethod().isSecure } + /** + * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically + * dismisses once the authentication challenge is completed. For example, completing a biometric + * authentication challenge via face unlock or fingerprint sensor can automatically bypass the + * lock screen. + */ + fun isBypassEnabled(): Boolean { + return keyguardRepository.isBypassEnabled() + } + /** * Attempts to authenticate the user and unlock the device. * @@ -218,11 +222,6 @@ constructor( return authenticationResult.isSuccessful } - /** See [isBypassEnabled]. */ - fun toggleBypassEnabled() { - repository.setBypassEnabled(!repository.isBypassEnabled.value) - } - /** Starts refreshing the throttling state every second. */ private suspend fun startThrottlingCountdown() { cancelCountdown() diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index d119920e1a422..42bee4a3bdcd2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -42,6 +42,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher @@ -171,6 +172,14 @@ interface KeyguardRepository { */ fun isKeyguardShowing(): Boolean + /** + * Whether lock screen bypass is enabled. When enabled, the lock screen will be automatically + * dismissed once the authentication challenge is completed. For example, completing a biometric + * authentication challenge via face unlock or fingerprint sensor can automatically bypass the + * lock screen. + */ + fun isBypassEnabled(): Boolean + /** Sets whether the bottom area UI should animate the transition out of doze state. */ fun setAnimateDozingTransitions(animate: Boolean) @@ -206,6 +215,7 @@ constructor( wakefulnessLifecycle: WakefulnessLifecycle, biometricUnlockController: BiometricUnlockController, private val keyguardStateController: KeyguardStateController, + private val keyguardBypassController: KeyguardBypassController, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val dozeTransitionListener: DozeTransitionListener, private val dozeParameters: DozeParameters, @@ -252,23 +262,17 @@ constructor( override val isAodAvailable: Flow = conflatedCallbackFlow { val callback = - object : DozeParameters.Callback { - override fun onAlwaysOnChange() { - trySendWithFailureLogging( - dozeParameters.getAlwaysOn(), - TAG, - "updated isAodAvailable" - ) - } + DozeParameters.Callback { + trySendWithFailureLogging( + dozeParameters.alwaysOn, + TAG, + "updated isAodAvailable" + ) } dozeParameters.addCallback(callback) // Adding the callback does not send an initial update. - trySendWithFailureLogging( - dozeParameters.getAlwaysOn(), - TAG, - "initial isAodAvailable" - ) + trySendWithFailureLogging(dozeParameters.alwaysOn, TAG, "initial isAodAvailable") awaitClose { dozeParameters.removeCallback(callback) } } @@ -464,6 +468,10 @@ constructor( return keyguardStateController.isShowing } + override fun isBypassEnabled(): Boolean { + return keyguardBypassController.bypassEnabled + } + override val statusBarState: Flow = conflatedCallbackFlow { val callback = object : StatusBarStateController.StateListener { diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt index 285ff74f8b2e2..b23c4ec9aa8bc 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartable.kt @@ -77,7 +77,7 @@ constructor( authenticationInteractor.isUnlocked .map { isUnlocked -> val currentSceneKey = sceneInteractor.currentScene(CONTAINER_NAME).value.key - val isBypassEnabled = authenticationInteractor.isBypassEnabled.value + val isBypassEnabled = authenticationInteractor.isBypassEnabled() when { isUnlocked -> when (currentSceneKey) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index ff1b31d8848f5..924aac4e70bef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -50,12 +50,6 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr @DevicePostureInt private var postureState: Int = DEVICE_POSTURE_UNKNOWN private var pendingUnlock: PendingUnlock? = null private val listeners = mutableListOf() - private val postureCallback = DevicePostureController.Callback { posture -> - if (postureState != posture) { - postureState = posture - notifyListeners() - } - } private val faceAuthEnabledChangedCallback = object : KeyguardStateController.Callback { override fun onFaceAuthEnabledChanged() = notifyListeners() } @@ -162,10 +156,8 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr val dismissByDefault = if (context.resources.getBoolean( com.android.internal.R.bool.config_faceAuthDismissesKeyguard)) 1 else 0 - tunerService.addTunable(object : TunerService.Tunable { - override fun onTuningChanged(key: String?, newValue: String?) { - bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0 - } + tunerService.addTunable({ key, _ -> + bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0 }, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD) lockscreenUserManager.addUserChangedListener( object : NotificationLockscreenUserManager.UserChangedListener { @@ -281,8 +273,6 @@ open class KeyguardBypassController : Dumpable, StackScrollAlgorithm.BypassContr } companion object { - const val BYPASS_FADE_DURATION = 67 - private const val FACE_UNLOCK_BYPASS_NO_OVERRIDE = 0 private const val FACE_UNLOCK_BYPASS_ALWAYS = 1 private const val FACE_UNLOCK_BYPASS_NEVER = 2 diff --git a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index c223c5af6079c..a6ad4b24b63dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -102,19 +102,6 @@ class AuthenticationInteractorTest : SysuiTestCase() { assertThat(isUnlocked).isFalse() } - @Test - fun toggleBypassEnabled() = - testScope.runTest { - val isBypassEnabled by collectLastValue(underTest.isBypassEnabled) - assertThat(isBypassEnabled).isFalse() - - underTest.toggleBypassEnabled() - assertThat(isBypassEnabled).isTrue() - - underTest.toggleBypassEnabled() - assertThat(isBypassEnabled).isFalse() - } - @Test fun isAuthenticationRequired_lockedAndSecured_true() = testScope.runTest { diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt index e9f0d561371c0..f541815d2711b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt @@ -41,6 +41,7 @@ import com.android.systemui.keyguard.shared.model.WakefulnessState import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.phone.DozeParameters +import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever @@ -73,6 +74,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var dozeTransitionListener: DozeTransitionListener @Mock private lateinit var authController: AuthController + @Mock private lateinit var keyguardBypassController: KeyguardBypassController @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var dreamOverlayCallbackController: DreamOverlayCallbackController @Mock private lateinit var dozeParameters: DozeParameters @@ -92,6 +94,7 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { wakefulnessLifecycle, biometricUnlockController, keyguardStateController, + keyguardBypassController, keyguardUpdateMonitor, dozeTransitionListener, dozeParameters, @@ -185,6 +188,20 @@ class KeyguardRepositoryImplTest : SysuiTestCase() { job.cancel() } + @Test + fun isBypassEnabled_disabledInController() { + whenever(keyguardBypassController.isBypassEnabled).thenReturn(false) + whenever(keyguardBypassController.bypassEnabled).thenReturn(false) + assertThat(underTest.isBypassEnabled()).isFalse() + } + + @Test + fun isBypassEnabled_enabledInController() { + whenever(keyguardBypassController.isBypassEnabled).thenReturn(true) + whenever(keyguardBypassController.bypassEnabled).thenReturn(true) + assertThat(underTest.isBypassEnabled()).isTrue() + } + @Test fun isAodAvailable() = runTest { val flow = underTest.isAodAvailable diff --git a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt index 3e9ddcb06389b..5638d708cf304 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/scene/domain/startable/SystemUiDefaultSceneContainerStartableTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.scene.shared.model.SceneContainerNames import com.android.systemui.scene.shared.model.SceneKey import com.android.systemui.scene.shared.model.SceneModel import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.runTest import org.junit.Before @@ -36,7 +35,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 -@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(JUnit4::class) class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { @@ -385,7 +383,7 @@ class SystemUiDefaultSceneContainerStartableTest : SysuiTestCase() { ) { featureFlags.set(Flags.SCENE_CONTAINER, isFeatureEnabled) authenticationRepository.setUnlocked(isDeviceUnlocked) - authenticationRepository.setBypassEnabled(isBypassEnabled) + keyguardRepository.setBypassEnabled(isBypassEnabled) initialSceneKey?.let { sceneInteractor.setCurrentScene(SceneContainerNames.SYSTEM_UI_DEFAULT, SceneModel(it)) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index 28892baec3c5c..c2e1ac70af80d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -31,9 +31,6 @@ class FakeAuthenticationRepository( private val currentTime: () -> Long, ) : AuthenticationRepository { - private val _isBypassEnabled = MutableStateFlow(false) - override val isBypassEnabled: StateFlow = _isBypassEnabled - private val _isAutoConfirmEnabled = MutableStateFlow(false) override val isAutoConfirmEnabled: StateFlow = _isAutoConfirmEnabled.asStateFlow() @@ -85,10 +82,6 @@ class FakeAuthenticationRepository( return (credentialOverride ?: DEFAULT_PIN).size } - override fun setBypassEnabled(isBypassEnabled: Boolean) { - _isBypassEnabled.value = isBypassEnabled - } - override suspend fun getFailedAuthenticationAttemptCount(): Int { return failedAttemptCount } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 63097401bc5a2..b9d098fe2851a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -114,6 +114,11 @@ class FakeKeyguardRepository : KeyguardRepository { return _isKeyguardShowing.value } + private var _isBypassEnabled = false + override fun isBypassEnabled(): Boolean { + return _isBypassEnabled + } + override fun setAnimateDozingTransitions(animate: Boolean) { _animateBottomAreaDozingTransitions.tryEmit(animate) } @@ -198,6 +203,10 @@ class FakeKeyguardRepository : KeyguardRepository { _isKeyguardUnlocked.value = isUnlocked } + fun setBypassEnabled(isEnabled: Boolean) { + _isBypassEnabled = isEnabled + } + override fun isUdfpsSupported(): Boolean { return _isUdfpsSupported.value } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt index 47e1daf4008c5..9317981304995 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt @@ -142,6 +142,7 @@ class SceneTestUtils( repository = repository, backgroundDispatcher = testDispatcher, userRepository = userRepository, + keyguardRepository = keyguardRepository, clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } } ) }