Merge "Introduce bouncer delay behind flag" into udc-dev

This commit is contained in:
Beverly Tai
2023-05-03 13:36:10 +00:00
committed by Android (Google) Code Review
12 changed files with 170 additions and 58 deletions

View File

@@ -889,4 +889,8 @@
-->
<dimen name="shade_swipe_collapse_threshold">0.5</dimen>
<!-- [END] MULTI SHADE -->
<!-- Time (in ms) to delay the bouncer views from showing when passive auth may be used for
device entry. -->
<integer name="primary_bouncer_passive_auth_delay">250</integer>
</resources>

View File

@@ -2884,7 +2884,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
}
/**
* If the current state of the device allows for triggering active unlock. This does not
* include active unlock availability.
*/
public boolean canTriggerActiveUnlockBasedOnDeviceState() {
return shouldTriggerActiveUnlock(/* shouldLog */ false);
}
private boolean shouldTriggerActiveUnlock() {
return shouldTriggerActiveUnlock(/* shouldLog */ true);
}
private boolean shouldTriggerActiveUnlock(boolean shouldLog) {
// Triggers:
final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing
@@ -2914,19 +2926,21 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
&& !mKeyguardGoingAway
&& !mSecureCameraLaunched;
// Aggregate relevant fields for debug logging.
logListenerModelData(
new KeyguardActiveUnlockModel(
System.currentTimeMillis(),
user,
shouldTriggerActiveUnlock,
awakeKeyguard,
mAuthInterruptActive,
fpLockedOut,
primaryAuthRequired,
mSwitchingUser,
triggerActiveUnlockForAssistant,
userCanDismissLockScreen));
if (shouldLog) {
// Aggregate relevant fields for debug logging.
logListenerModelData(
new KeyguardActiveUnlockModel(
System.currentTimeMillis(),
user,
shouldTriggerActiveUnlock,
awakeKeyguard,
mAuthInterruptActive,
fpLockedOut,
primaryAuthRequired,
mSwitchingUser,
triggerActiveUnlockForAssistant,
userCanDismissLockScreen));
}
return shouldTriggerActiveUnlock;
}

View File

@@ -46,8 +46,6 @@ interface KeyguardFaceAuthModule {
impl: SystemUIKeyguardFaceAuthInteractor
): KeyguardFaceAuthInteractor
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
companion object {
@Provides
@SysUISingleton

View File

@@ -45,4 +45,6 @@ interface KeyguardRepositoryModule {
@Binds
fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
@Binds fun trustRepository(impl: TrustRepositoryImpl): TrustRepository
}

View File

@@ -28,7 +28,9 @@ import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
@@ -40,6 +42,9 @@ import kotlinx.coroutines.flow.shareIn
interface TrustRepository {
/** Flow representing whether the current user is trusted. */
val isCurrentUserTrusted: Flow<Boolean>
/** Flow representing whether active unlock is available for the current user. */
val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean>
}
@SysUISingleton
@@ -89,11 +94,13 @@ constructor(
}
.shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
override val isCurrentUserTrusted: Flow<Boolean>
get() =
combine(trust, userRepository.selectedUserInfo, ::Pair)
.map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
.distinctUntilChanged()
.onEach { logger.isCurrentUserTrusted(it) }
.onStart { emit(false) }
override val isCurrentUserTrusted: Flow<Boolean> =
combine(trust, userRepository.selectedUserInfo, ::Pair)
.map { latestTrustModelForUser[it.second.id]?.isTrusted ?: false }
.distinctUntilChanged()
.onEach { logger.isCurrentUserTrusted(it) }
.onStart { emit(false) }
// TODO: Implement based on TrustManager callback b/267322286
override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> = MutableStateFlow(true)
}

View File

@@ -32,14 +32,16 @@ import com.android.systemui.R
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@@ -56,28 +58,21 @@ import javax.inject.Inject
class PrimaryBouncerInteractor
@Inject
constructor(
private val repository: KeyguardBouncerRepository,
private val primaryBouncerView: BouncerView,
@Main private val mainHandler: Handler,
private val keyguardStateController: KeyguardStateController,
private val keyguardSecurityModel: KeyguardSecurityModel,
private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
private val falsingCollector: FalsingCollector,
private val dismissCallbackRegistry: DismissCallbackRegistry,
private val context: Context,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
keyguardBypassController: KeyguardBypassController,
private val repository: KeyguardBouncerRepository,
private val primaryBouncerView: BouncerView,
@Main private val mainHandler: Handler,
private val keyguardStateController: KeyguardStateController,
private val keyguardSecurityModel: KeyguardSecurityModel,
private val primaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor,
private val falsingCollector: FalsingCollector,
private val dismissCallbackRegistry: DismissCallbackRegistry,
private val context: Context,
private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
private val trustRepository: TrustRepository,
private val featureFlags: FeatureFlags,
) {
/** Whether we want to wait for face auth. */
private val primaryBouncerFaceDelay =
keyguardStateController.isFaceAuthEnabled &&
!keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
KeyguardUpdateMonitor.getCurrentUser()
) &&
!needsFullscreenBouncer() &&
keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE) &&
!keyguardBypassController.bypassEnabled
private val passiveAuthBouncerDelay = context.resources.getInteger(
R.integer.primary_bouncer_passive_auth_delay).toLong()
/** Runnable to show the primary bouncer. */
val showRunnable = Runnable {
repository.setPrimaryShow(true)
@@ -160,8 +155,9 @@ constructor(
}
repository.setPrimaryShowingSoon(true)
if (primaryBouncerFaceDelay) {
mainHandler.postDelayed(showRunnable, 1200L)
if (usePrimaryBouncerPassiveAuthDelay()) {
Log.d(TAG, "delay bouncer, passive auth may succeed")
mainHandler.postDelayed(showRunnable, passiveAuthBouncerDelay)
} else {
DejankUtils.postAfterTraversal(showRunnable)
}
@@ -377,6 +373,17 @@ constructor(
return repository.primaryBouncerShow.value
}
/** Whether we want to wait to show the bouncer in case passive auth succeeds. */
private fun usePrimaryBouncerPassiveAuthDelay(): Boolean {
val canRunFaceAuth = keyguardStateController.isFaceAuthEnabled &&
keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE)
val canRunActiveUnlock = trustRepository.isCurrentUserActiveUnlockAvailable.value &&
keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()
return featureFlags.isEnabled(Flags.DELAY_BOUNCER) &&
!needsFullscreenBouncer() &&
(canRunFaceAuth || canRunActiveUnlock)
}
companion object {
private const val TAG = "PrimaryBouncerInteractor"
}

View File

@@ -21,22 +21,23 @@ import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.systemui.RoboPilotTest
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.RoboPilotTest
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
@@ -94,7 +95,8 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle
mock(DismissCallbackRegistry::class.java),
context,
mKeyguardUpdateMonitor,
mock(KeyguardBypassController::class.java),
mock(TrustRepository::class.java),
FakeFeatureFlags(),
)
mAlternateBouncerInteractor =
AlternateBouncerInteractor(

View File

@@ -35,12 +35,12 @@ import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepo
import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.log.FaceAuthenticationLogger
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
@@ -100,7 +100,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() {
mock(DismissCallbackRegistry::class.java),
context,
keyguardUpdateMonitor,
mock(KeyguardBypassController::class.java),
mock(TrustRepository::class.java),
FakeFeatureFlags(),
),
AlternateBouncerInteractor(
mock(StatusBarStateController::class.java),

View File

@@ -16,7 +16,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.Looper
import android.hardware.biometrics.BiometricSourceType
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.testing.TestableResources
@@ -28,15 +28,17 @@ import com.android.systemui.DejankUtils
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.BouncerViewDelegate
import com.android.systemui.keyguard.data.repository.FakeTrustRepository
import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
import com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
@@ -67,16 +69,23 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Mock private lateinit var mPrimaryBouncerCallbackInteractor: PrimaryBouncerCallbackInteractor
@Mock private lateinit var falsingCollector: FalsingCollector
@Mock private lateinit var dismissCallbackRegistry: DismissCallbackRegistry
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private val mainHandler = FakeHandler(Looper.getMainLooper())
private lateinit var mainHandler: FakeHandler
private lateinit var underTest: PrimaryBouncerInteractor
private lateinit var resources: TestableResources
private lateinit var trustRepository: FakeTrustRepository
private lateinit var featureFlags: FakeFeatureFlags
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
whenever(keyguardSecurityModel.getSecurityMode(anyInt()))
.thenReturn(KeyguardSecurityModel.SecurityMode.PIN)
DejankUtils.setImmediate(true)
mainHandler = FakeHandler(android.os.Looper.getMainLooper())
trustRepository = FakeTrustRepository()
featureFlags = FakeFeatureFlags().apply { set(Flags.DELAY_BOUNCER, false) }
underTest =
PrimaryBouncerInteractor(
repository,
@@ -89,7 +98,8 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
dismissCallbackRegistry,
context,
keyguardUpdateMonitor,
keyguardBypassController,
trustRepository,
featureFlags,
)
whenever(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
whenever(repository.primaryBouncerShow.value).thenReturn(false)
@@ -383,6 +393,55 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
verify(repository).setSideFpsShowing(false)
}
@Test
fun delayBouncerWhenFaceAuthPossible() {
mainHandler.setMode(FakeHandler.Mode.QUEUEING)
// GIVEN bouncer should be delayed due to face auth
featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
whenever(keyguardStateController.isFaceAuthEnabled).thenReturn(true)
whenever(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed(BiometricSourceType.FACE))
.thenReturn(true)
// WHEN bouncer show is requested
underTest.show(true)
// THEN primary show & primary showing soon aren't updated immediately
verify(repository, never()).setPrimaryShow(true)
verify(repository, never()).setPrimaryShowingSoon(false)
// WHEN all queued messages are dispatched
mainHandler.dispatchQueuedMessages()
// THEN primary show & primary showing soon are updated
verify(repository).setPrimaryShow(true)
verify(repository).setPrimaryShowingSoon(false)
}
@Test
fun delayBouncerWhenActiveUnlockPossible() {
mainHandler.setMode(FakeHandler.Mode.QUEUEING)
// GIVEN bouncer should be delayed due to active unlock
featureFlags.apply { set(Flags.DELAY_BOUNCER, true) }
trustRepository.setCurrentUserActiveUnlockAvailable(true)
whenever(keyguardUpdateMonitor.canTriggerActiveUnlockBasedOnDeviceState()).thenReturn(true)
// WHEN bouncer show is requested
underTest.show(true)
// THEN primary show & primary showing soon were scheduled to update
verify(repository, never()).setPrimaryShow(true)
verify(repository, never()).setPrimaryShowingSoon(false)
// WHEN all queued messages are dispatched
mainHandler.dispatchQueuedMessages()
// THEN primary show & primary showing soon are updated
verify(repository).setPrimaryShow(true)
verify(repository).setPrimaryShowingSoon(false)
}
private fun updateSideFpsVisibilityParameters(
isVisible: Boolean,
sfpsEnabled: Boolean,

View File

@@ -25,9 +25,11 @@ import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.utils.os.FakeHandler
@@ -37,6 +39,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@SmallTest
@@ -71,7 +74,8 @@ class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
dismissCallbackRegistry,
context,
keyguardUpdateMonitor,
keyguardBypassController,
Mockito.mock(TrustRepository::class.java),
FakeFeatureFlags(),
)
}

View File

@@ -24,9 +24,11 @@ import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.RoboPilotTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.flags.FakeFeatureFlags
import com.android.systemui.keyguard.DismissCallbackRegistry
import com.android.systemui.keyguard.data.BouncerView
import com.android.systemui.keyguard.data.repository.FakeKeyguardBouncerRepository
import com.android.systemui.keyguard.data.repository.TrustRepository
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
@@ -42,6 +44,7 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.MockitoAnnotations
@SmallTest
@@ -77,7 +80,8 @@ class KeyguardBouncerViewModelTest : SysuiTestCase() {
dismissCallbackRegistry,
context,
keyguardUpdateMonitor,
keyguardBypassController,
Mockito.mock(TrustRepository::class.java),
FakeFeatureFlags(),
)
underTest = KeyguardBouncerViewModel(bouncerView, bouncerInteractor)
}

View File

@@ -19,13 +19,23 @@ package com.android.systemui.keyguard.data.repository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeTrustRepository : TrustRepository {
private val _isCurrentUserTrusted = MutableStateFlow(false)
override val isCurrentUserTrusted: Flow<Boolean>
get() = _isCurrentUserTrusted
private val _isCurrentUserActiveUnlockAvailable = MutableStateFlow(false)
override val isCurrentUserActiveUnlockAvailable: StateFlow<Boolean> =
_isCurrentUserActiveUnlockAvailable.asStateFlow()
fun setCurrentUserTrusted(trust: Boolean) {
_isCurrentUserTrusted.value = trust
}
fun setCurrentUserActiveUnlockAvailable(available: Boolean) {
_isCurrentUserActiveUnlockAvailable.value = available
}
}