Loading packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +27 −0 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -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<SideFpsUiRequestSource> = HashSet() Loading Loading @@ -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) Loading Loading @@ -423,4 +449,5 @@ enum class SideFpsUiRequestSource { AUTO_SHOW, /** Pin, pattern or password bouncer */ PRIMARY_BOUNCER, ALTERNATE_BOUNCER } packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +30 −21 Original line number Diff line number Diff line Loading @@ -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<Boolean> val isLockedOut: StateFlow<Boolean> } /** Loading @@ -44,9 +48,11 @@ class DeviceEntryFingerprintAuthRepositoryImpl @Inject constructor( val keyguardUpdateMonitor: KeyguardUpdateMonitor, @Application scope: CoroutineScope, ) : DeviceEntryFingerprintAuthRepository { override val isLockedOut: Flow<Boolean> = conflatedCallbackFlow { override val isLockedOut: StateFlow<Boolean> = conflatedCallbackFlow { val sendLockoutUpdate = fun() { trySendWithFailureLogging( Loading @@ -57,7 +63,9 @@ constructor( } val callback = object : KeyguardUpdateMonitorCallback() { override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) { override fun onLockedOutStateChanged( biometricSourceType: BiometricSourceType? ) { if (biometricSourceType == BiometricSourceType.FINGERPRINT) { sendLockoutUpdate() } Loading @@ -67,6 +75,7 @@ constructor( sendLockoutUpdate() awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false) companion object { const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl" Loading packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +5 −0 Original line number Diff line number Diff line Loading @@ -32,4 +32,9 @@ interface KeyguardRepositoryModule { fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository @Binds fun deviceEntryFingerprintAuthRepository( impl: DeviceEntryFingerprintAuthRepositoryImpl ): DeviceEntryFingerprintAuthRepository } packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt +4 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading Loading @@ -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) Loading packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +56 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -101,6 +111,9 @@ class SideFpsControllerTest : SysuiTestCase() { @Captor lateinit var overlayCaptor: ArgumentCaptor<View> @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> 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 Loading @@ -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) Loading Loading @@ -217,7 +248,10 @@ class SideFpsControllerTest : SysuiTestCase() { displayManager, executor, handler, dumpManager alternateBouncerInteractor, TestCoroutineScope(), featureFlags, dumpManager, ) overlayController = Loading Loading @@ -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() Loading @@ -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 } /** Loading Loading
packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt +27 −0 Original line number Diff line number Diff line Loading @@ -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" Loading @@ -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<SideFpsUiRequestSource> = HashSet() Loading Loading @@ -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) Loading Loading @@ -423,4 +449,5 @@ enum class SideFpsUiRequestSource { AUTO_SHOW, /** Pin, pattern or password bouncer */ PRIMARY_BOUNCER, ALTERNATE_BOUNCER }
packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt +30 −21 Original line number Diff line number Diff line Loading @@ -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<Boolean> val isLockedOut: StateFlow<Boolean> } /** Loading @@ -44,9 +48,11 @@ class DeviceEntryFingerprintAuthRepositoryImpl @Inject constructor( val keyguardUpdateMonitor: KeyguardUpdateMonitor, @Application scope: CoroutineScope, ) : DeviceEntryFingerprintAuthRepository { override val isLockedOut: Flow<Boolean> = conflatedCallbackFlow { override val isLockedOut: StateFlow<Boolean> = conflatedCallbackFlow { val sendLockoutUpdate = fun() { trySendWithFailureLogging( Loading @@ -57,7 +63,9 @@ constructor( } val callback = object : KeyguardUpdateMonitorCallback() { override fun onLockedOutStateChanged(biometricSourceType: BiometricSourceType?) { override fun onLockedOutStateChanged( biometricSourceType: BiometricSourceType? ) { if (biometricSourceType == BiometricSourceType.FINGERPRINT) { sendLockoutUpdate() } Loading @@ -67,6 +75,7 @@ constructor( sendLockoutUpdate() awaitClose { keyguardUpdateMonitor.removeCallback(callback) } } .stateIn(scope, started = SharingStarted.Eagerly, initialValue = false) companion object { const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl" Loading
packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +5 −0 Original line number Diff line number Diff line Loading @@ -32,4 +32,9 @@ interface KeyguardRepositoryModule { fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository @Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository @Binds fun deviceEntryFingerprintAuthRepository( impl: DeviceEntryFingerprintAuthRepositoryImpl ): DeviceEntryFingerprintAuthRepository }
packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt +4 −1 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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, Loading Loading @@ -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) Loading
packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +56 −2 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -101,6 +111,9 @@ class SideFpsControllerTest : SysuiTestCase() { @Captor lateinit var overlayCaptor: ArgumentCaptor<View> @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams> 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 Loading @@ -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) Loading Loading @@ -217,7 +248,10 @@ class SideFpsControllerTest : SysuiTestCase() { displayManager, executor, handler, dumpManager alternateBouncerInteractor, TestCoroutineScope(), featureFlags, dumpManager, ) overlayController = Loading Loading @@ -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() Loading @@ -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 } /** Loading