diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt index 6f594d5eb0e25..c799e91ad36be 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt @@ -54,12 +54,18 @@ import com.android.internal.annotations.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.R import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.recents.OverviewProxyService import com.android.systemui.util.concurrency.DelayableExecutor import java.io.PrintWriter import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch private const val TAG = "SideFpsController" @@ -79,6 +85,9 @@ constructor( displayManager: DisplayManager, @Main private val mainExecutor: DelayableExecutor, @Main private val handler: Handler, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + @Application private val scope: CoroutineScope, + private val featureFlags: FeatureFlags, dumpManager: DumpManager ) : Dumpable { val requests: HashSet = HashSet() @@ -168,9 +177,26 @@ constructor( } ) overviewProxyService.addCallback(overviewProxyListener) + listenForAlternateBouncerVisibility() + dumpManager.registerDumpable(this) } + private fun listenForAlternateBouncerVisibility() { + alternateBouncerInteractor.setAlternateBouncerUIAvailable(true) + if (featureFlags.isEnabled(Flags.MODERN_ALTERNATE_BOUNCER)) { + scope.launch { + alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> + if (isVisible) { + show(SideFpsUiRequestSource.ALTERNATE_BOUNCER) + } else { + hide(SideFpsUiRequestSource.ALTERNATE_BOUNCER) + } + } + } + } + } + /** Shows the side fps overlay if not already shown. */ fun show(request: SideFpsUiRequestSource) { requests.add(request) @@ -423,4 +449,5 @@ enum class SideFpsUiRequestSource { AUTO_SHOW, /** Pin, pattern or password bouncer */ PRIMARY_BOUNCER, + ALTERNATE_BOUNCER } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt index 08edbc6d01633..b3a9cf58310a7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt @@ -22,14 +22,18 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.stateIn /** Encapsulates state about device entry fingerprint auth mechanism. */ interface DeviceEntryFingerprintAuthRepository { /** Whether the device entry fingerprint auth is locked out. */ - val isLockedOut: Flow + val isLockedOut: StateFlow } /** @@ -44,29 +48,34 @@ class DeviceEntryFingerprintAuthRepositoryImpl @Inject constructor( val keyguardUpdateMonitor: KeyguardUpdateMonitor, + @Application scope: CoroutineScope, ) : DeviceEntryFingerprintAuthRepository { - override val isLockedOut: Flow = conflatedCallbackFlow { - val sendLockoutUpdate = - fun() { - trySendWithFailureLogging( - keyguardUpdateMonitor.isFingerprintLockedOut, - TAG, - "onLockedOutStateChanged" - ) - } - val callback = - object : KeyguardUpdateMonitorCallback() { - override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) { - if (biometricSourceType == BiometricSourceType.FINGERPRINT) { - sendLockoutUpdate() + override val isLockedOut: StateFlow = + conflatedCallbackFlow { + val sendLockoutUpdate = + fun() { + trySendWithFailureLogging( + keyguardUpdateMonitor.isFingerprintLockedOut, + TAG, + "onLockedOutStateChanged" + ) } - } + val callback = + object : KeyguardUpdateMonitorCallback() { + override fun onLockedOutStateChanged( + biometricSourceType: BiometricSourceType? + ) { + if (biometricSourceType == BiometricSourceType.FINGERPRINT) { + sendLockoutUpdate() + } + } + } + keyguardUpdateMonitor.registerCallback(callback) + sendLockoutUpdate() + awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } - keyguardUpdateMonitor.registerCallback(callback) - sendLockoutUpdate() - awaitClose { keyguardUpdateMonitor.removeCallback(callback) } - } + .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false) companion object { const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index 4639597a9b8c0..cc99eb72da16d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -32,4 +32,9 @@ interface KeyguardRepositoryModule { fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository + + @Binds + fun deviceEntryFingerprintAuthRepository( + impl: DeviceEntryFingerprintAuthRepositoryImpl + ): DeviceEntryFingerprintAuthRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt index 28c0b288147bf..6020ef8017c86 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.BiometricRepository +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.LegacyAlternateBouncer import com.android.systemui.util.time.SystemClock @@ -34,6 +35,7 @@ class AlternateBouncerInteractor constructor( private val bouncerRepository: KeyguardBouncerRepository, private val biometricRepository: BiometricRepository, + private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, private val systemClock: SystemClock, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, featureFlags: FeatureFlags, @@ -99,7 +101,8 @@ constructor( bouncerRepository.isAlternateBouncerUIAvailable.value && biometricRepository.isFingerprintEnrolled.value && biometricRepository.isStrongBiometricAllowed.value && - biometricRepository.isFingerprintEnabledByDevicePolicy.value + biometricRepository.isFingerprintEnabledByDevicePolicy.value && + !deviceEntryFingerprintAuthRepository.isLockedOut.value } else { legacyAlternateBouncer != null && keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(true) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index b92c5d039d457..fd931b0a794ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -51,14 +51,24 @@ import android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG import android.view.WindowMetrics import androidx.test.filters.SmallTest import com.airbnb.lottie.LottieAnimationView +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.ViewMediatorCallback import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestableContext import com.android.systemui.dump.DumpManager +import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.Flags.MODERN_ALTERNATE_BOUNCER +import com.android.systemui.keyguard.data.repository.FakeBiometricRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository +import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.recents.OverviewProxyService import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.TestCoroutineScope import org.junit.Before import org.junit.Rule import org.junit.Test @@ -101,6 +111,9 @@ class SideFpsControllerTest : SysuiTestCase() { @Captor lateinit var overlayCaptor: ArgumentCaptor @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor + private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository + private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor + private val featureFlags = FakeFeatureFlags() private val executor = FakeExecutor(FakeSystemClock()) private lateinit var overlayController: ISidefpsController private lateinit var sideFpsController: SideFpsController @@ -121,6 +134,24 @@ class SideFpsControllerTest : SysuiTestCase() { @Before fun setup() { + featureFlags.set(MODERN_ALTERNATE_BOUNCER, true) + keyguardBouncerRepository = + KeyguardBouncerRepository( + mock(ViewMediatorCallback::class.java), + FakeSystemClock(), + TestCoroutineScope(), + mock(TableLogBuffer::class.java), + ) + alternateBouncerInteractor = + AlternateBouncerInteractor( + keyguardBouncerRepository, + FakeBiometricRepository(), + FakeDeviceEntryFingerprintAuthRepository(), + FakeSystemClock(), + mock(KeyguardUpdateMonitor::class.java), + featureFlags, + ) + context.addMockSystemService(DisplayManager::class.java, displayManager) context.addMockSystemService(WindowManager::class.java, windowManager) @@ -217,7 +248,10 @@ class SideFpsControllerTest : SysuiTestCase() { displayManager, executor, handler, - dumpManager + alternateBouncerInteractor, + TestCoroutineScope(), + featureFlags, + dumpManager, ) overlayController = @@ -507,6 +541,26 @@ class SideFpsControllerTest : SysuiTestCase() { private fun verifySfpsIndicatorVisibilityOnTaskbarUpdate(sfpsViewVisible: Boolean) { sideFpsController.overlayOffsets = sensorLocation + } + + fun alternateBouncerVisibility_showAndHideSideFpsUI() = testWithDisplay { + // WHEN alternate bouncer is visible + keyguardBouncerRepository.setAlternateVisible(true) + executor.runAllReady() + + // THEN side fps shows UI + verify(windowManager).addView(any(), any()) + verify(windowManager, never()).removeView(any()) + + // WHEN alternate bouncer is no longer visible + keyguardBouncerRepository.setAlternateVisible(false) + executor.runAllReady() + + // THEN side fps UI is hidden + verify(windowManager).removeView(any()) + } + + private fun hidesWithTaskbar(visible: Boolean) { overlayController.show(SENSOR_ID, REASON_UNKNOWN) executor.runAllReady() @@ -515,7 +569,7 @@ class SideFpsControllerTest : SysuiTestCase() { verify(windowManager).addView(any(), any()) verify(windowManager, never()).removeView(any()) - verify(sideFpsView).visibility = if (sfpsViewVisible) View.VISIBLE else View.GONE + verify(sideFpsView).visibility = if (visible) View.VISIBLE else View.GONE } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index 9060922266c05..81a6bc2b25b57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -27,6 +27,7 @@ import com.android.systemui.flags.FeatureFlags import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.data.BouncerView import com.android.systemui.keyguard.data.repository.BiometricRepository +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor @@ -91,6 +92,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle AlternateBouncerInteractor( keyguardBouncerRepository, mock(BiometricRepository::class.java), + mock(DeviceEntryFingerprintAuthRepository::class.java), mock(SystemClock::class.java), mock(KeyguardUpdateMonitor::class.java), mock(FeatureFlags::class.java) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt index c4ae2db93569c..9203f05602b6b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt @@ -55,7 +55,11 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) testScope = TestScope() - underTest = DeviceEntryFingerprintAuthRepositoryImpl(keyguardUpdateMonitor) + underTest = + DeviceEntryFingerprintAuthRepositoryImpl( + keyguardUpdateMonitor, + testScope.backgroundScope, + ) } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt index 1da7241e58bd8..68fff262214a7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt @@ -23,6 +23,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.data.repository.FakeBiometricRepository +import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.util.time.FakeSystemClock @@ -46,6 +47,8 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { private lateinit var underTest: AlternateBouncerInteractor private lateinit var bouncerRepository: KeyguardBouncerRepository private lateinit var biometricRepository: FakeBiometricRepository + private lateinit var deviceEntryFingerprintAuthRepository: + FakeDeviceEntryFingerprintAuthRepository @Mock private lateinit var systemClock: SystemClock @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var bouncerLogger: TableLogBuffer @@ -62,11 +65,13 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { bouncerLogger, ) biometricRepository = FakeBiometricRepository() + deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository() featureFlags = FakeFeatureFlags().apply { this.set(Flags.MODERN_ALTERNATE_BOUNCER, true) } underTest = AlternateBouncerInteractor( bouncerRepository, biometricRepository, + deviceEntryFingerprintAuthRepository, systemClock, keyguardUpdateMonitor, featureFlags, @@ -111,6 +116,14 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } + @Test + fun canShowAlternateBouncerForFingerprint_fingerprintLockedOut() { + givenCanShowAlternateBouncer() + deviceEntryFingerprintAuthRepository.setLockedOut(true) + + assertFalse(underTest.canShowAlternateBouncerForFingerprint()) + } + @Test fun show_whenCanShow() { givenCanShowAlternateBouncer() @@ -148,6 +161,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { biometricRepository.setFingerprintEnrolled(true) biometricRepository.setStrongBiometricAllowed(true) biometricRepository.setFingerprintEnabledByDevicePolicy(true) + deviceEntryFingerprintAuthRepository.setLockedOut(false) } private fun givenCannotShowAlternateBouncer() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt new file mode 100644 index 0000000000000..5641832b6ae23 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.repository + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeDeviceEntryFingerprintAuthRepository : DeviceEntryFingerprintAuthRepository { + private val _isLockedOut = MutableStateFlow(false) + override val isLockedOut: StateFlow = _isLockedOut.asStateFlow() + + fun setLockedOut(lockedOut: Boolean) { + _isLockedOut.value = lockedOut + } +}