Merge "Add SideFps AlternateBouncer" into tm-qpr-dev am: 2818aa4132 am: aa362779d3

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20506839

Change-Id: Ia8299ddbd2c771507a67bbd6d62a035bffa81fce
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Beverly Tai
2023-01-26 17:57:36 +00:00
committed by Automerger Merge Worker
9 changed files with 174 additions and 25 deletions

View File

@@ -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
}

View File

@@ -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"

View File

@@ -32,4 +32,9 @@ interface KeyguardRepositoryModule {
fun lightRevealScrimRepository(impl: LightRevealScrimRepositoryImpl): LightRevealScrimRepository
@Binds fun biometricRepository(impl: BiometricRepositoryImpl): BiometricRepository
@Binds
fun deviceEntryFingerprintAuthRepository(
impl: DeviceEntryFingerprintAuthRepositoryImpl
): DeviceEntryFingerprintAuthRepository
}

View File

@@ -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)

View File

@@ -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
}
/**

View File

@@ -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)

View File

@@ -55,7 +55,11 @@ class DeviceEntryFingerprintAuthRepositoryTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
testScope = TestScope()
underTest = DeviceEntryFingerprintAuthRepositoryImpl(keyguardUpdateMonitor)
underTest =
DeviceEntryFingerprintAuthRepositoryImpl(
keyguardUpdateMonitor,
testScope.backgroundScope,
)
}
@After

View File

@@ -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() {

View File

@@ -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
}
}