Add SideFps AlternateBouncer
Only enabled if MODERN_ALTERNATE_BOUNCER is enabled. Bug: 237362467 Test: manual, normal cases 1. enable MODERN_ALTERNATE_BOUNCER flag 2. enroll side FPS 3. tap on a notification with an intent on LS 4. Observe: SideFPS bouncer, can auth from here 5. Repeat step 3 6. tap anywhere on screen 7. Observe: primary bouncer shows and side fps UI is still showing Test: manually check alternate bouncer doesn't show if fp is locked out Test: atest SideFpsControllerTest Test: atest DeviceEntryFingerprintAuthRepositoryTest Test: atest AlternateBouncerInteractorTest Change-Id: I5f02e0f67799df059a35dce34b8a12833ce5fac5
This commit is contained in:
@@ -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<SideFpsUiRequestSource> = 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
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -44,29 +48,34 @@ class DeviceEntryFingerprintAuthRepositoryImpl
|
||||
@Inject
|
||||
constructor(
|
||||
val keyguardUpdateMonitor: KeyguardUpdateMonitor,
|
||||
@Application scope: CoroutineScope,
|
||||
) : DeviceEntryFingerprintAuthRepository {
|
||||
|
||||
override val isLockedOut: Flow<Boolean> = 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<Boolean> =
|
||||
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"
|
||||
|
||||
@@ -32,4 +32,9 @@ interface KeyguardRepositoryModule {
|
||||
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
|
||||
|
||||
@Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository
|
||||
|
||||
@Binds
|
||||
fun deviceEntryFingerprintAuthRepository(
|
||||
impl: DeviceEntryFingerprintAuthRepositoryImpl
|
||||
): DeviceEntryFingerprintAuthRepository
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -55,7 +55,11 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
testScope = TestScope()
|
||||
|
||||
underTest = DeviceEntryFingerprintAuthRepositoryImpl(keyguardUpdateMonitor)
|
||||
underTest =
|
||||
DeviceEntryFingerprintAuthRepositoryImpl(
|
||||
keyguardUpdateMonitor,
|
||||
testScope.backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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<Boolean>(false)
|
||||
override val isLockedOut: StateFlow<Boolean> = _isLockedOut.asStateFlow()
|
||||
|
||||
fun setLockedOut(lockedOut: Boolean) {
|
||||
_isLockedOut.value = lockedOut
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user