[DO NOT MERGE] Bouncer - Intercept touch events when not visible

There is a moment, when swiping on the lockscreen, that the bouncer
showing state is set to true but it isn't quite visible. In this
state, it is possible to interact with it.

Only allow interaction if the bouncer is at least 90% visible.

Also, refactor bouncer repository to an interface and add a
FakeKeyguardBouncerRepository so it can be tested with coroutines.

Test: atest PrimaryBouncerInteractorTest
PrimaryBouncerInteractorWithCoroutinesTest
Fixes: 260820843

Change-Id: I78cdf14521cc8cd02b6f2ab06e7ca518c92718c0
This commit is contained in:
Matt Pietal
2022-12-22 17:40:12 +00:00
parent d2fe4b9e81
commit 96cdb8a262
14 changed files with 421 additions and 82 deletions

View File

@@ -19,6 +19,7 @@ package com.android.keyguard;
import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;
/**
@@ -33,7 +34,7 @@ import android.widget.FrameLayout;
public class KeyguardHostView extends FrameLayout {
protected ViewMediatorCallback mViewMediatorCallback;
private boolean mIsInteractable;
public KeyguardHostView(Context context) {
this(context, null);
@@ -54,4 +55,24 @@ public class KeyguardHostView extends FrameLayout {
public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
mViewMediatorCallback = viewMediatorCallback;
}
/** Set true if the view can be interacted with */
public void setInteractable(boolean isInteractable) {
mIsInteractable = isInteractable;
}
/**
* Make sure to disallow touches while transitioning the bouncer, otherwise
* it can remain interactable even when barely visible.
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return !mIsInteractable;
}
/** True to consume any events that are sent to it */
@Override
public boolean onTouchEvent(MotionEvent ev) {
return true;
}
}

View File

@@ -527,4 +527,9 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
mKeyguardSecurityContainerController.updateKeyguardPosition(x);
}
}
/** Set true if the view can be interacted with */
public void setInteractable(boolean isInteractable) {
mView.setInteractable(isInteractable);
}
}

View File

@@ -41,31 +41,96 @@ import kotlinx.coroutines.flow.map
*
* Make sure to add newly added flows to the logger.
*/
interface KeyguardBouncerRepository {
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
val primaryBouncerVisible: StateFlow<Boolean>
val primaryBouncerShow: StateFlow<KeyguardBouncerModel?>
val primaryBouncerShowingSoon: StateFlow<Boolean>
val primaryBouncerHide: StateFlow<Boolean>
val primaryBouncerStartingToHide: StateFlow<Boolean>
val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
val primaryBouncerScrimmed: StateFlow<Boolean>
/**
* Set how much of the notification panel is showing on the screen.
* ```
* 0f = panel fully hidden = bouncer fully showing
* 1f = panel fully showing = bouncer fully hidden
* ```
*/
val panelExpansionAmount: StateFlow<Float>
val keyguardPosition: StateFlow<Float>
val onScreenTurnedOff: StateFlow<Boolean>
val isBackButtonEnabled: StateFlow<Boolean?>
/** Determines if user is already unlocked */
val keyguardAuthenticated: StateFlow<Boolean?>
val showMessage: StateFlow<BouncerShowMessageModel?>
val resourceUpdateRequests: StateFlow<Boolean>
val bouncerPromptReason: Int
val bouncerErrorMessage: CharSequence?
val isAlternateBouncerVisible: StateFlow<Boolean>
val isAlternateBouncerUIAvailable: StateFlow<Boolean>
var lastAlternateBouncerVisibleTime: Long
fun setPrimaryScrimmed(isScrimmed: Boolean)
fun setPrimaryVisible(isVisible: Boolean)
fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?)
fun setPrimaryShowingSoon(showingSoon: Boolean)
fun setPrimaryHide(hide: Boolean)
fun setPrimaryStartingToHide(startingToHide: Boolean)
fun setPrimaryStartDisappearAnimation(runnable: Runnable?)
fun setPanelExpansion(panelExpansion: Float)
fun setKeyguardPosition(keyguardPosition: Float)
fun setResourceUpdateRequests(willUpdateResources: Boolean)
fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean)
fun setAlternateVisible(isVisible: Boolean)
fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
}
@SysUISingleton
class KeyguardBouncerRepository
class KeyguardBouncerRepositoryImpl
@Inject
constructor(
private val viewMediatorCallback: ViewMediatorCallback,
private val clock: SystemClock,
@Application private val applicationScope: CoroutineScope,
@BouncerLog private val buffer: TableLogBuffer,
) {
) : KeyguardBouncerRepository {
/** Values associated with the PrimaryBouncer (pin/pattern/password) input. */
private val _primaryBouncerVisible = MutableStateFlow(false)
val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
private val _primaryBouncerShowingSoon = MutableStateFlow(false)
val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
private val _primaryBouncerHide = MutableStateFlow(false)
val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
val primaryBouncerStartingDisappearAnimation = _primaryBouncerDisappearAnimation.asStateFlow()
override val primaryBouncerStartingDisappearAnimation =
_primaryBouncerDisappearAnimation.asStateFlow()
/** Determines if we want to instantaneously show the primary bouncer instead of translating. */
private val _primaryBouncerScrimmed = MutableStateFlow(false)
val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
/**
* Set how much of the notification panel is showing on the screen.
* ```
@@ -74,23 +139,23 @@ constructor(
* ```
*/
private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
val keyguardPosition = _keyguardPosition.asStateFlow()
override val keyguardPosition = _keyguardPosition.asStateFlow()
private val _onScreenTurnedOff = MutableStateFlow(false)
val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
/** Determines if user is already unlocked */
val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
val showMessage = _showMessage.asStateFlow()
override val showMessage = _showMessage.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
val bouncerPromptReason: Int
override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
override val bouncerPromptReason: Int
get() = viewMediatorCallback.bouncerPromptReason
val bouncerErrorMessage: CharSequence?
override val bouncerErrorMessage: CharSequence?
get() = viewMediatorCallback.consumeCustomMessage()
init {
@@ -99,21 +164,21 @@ constructor(
/** Values associated with the AlternateBouncer */
private val _isAlternateBouncerVisible = MutableStateFlow(false)
val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE
private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
override val isAlternateBouncerUIAvailable: StateFlow<Boolean> =
_isAlternateBouncerUIAvailable.asStateFlow()
fun setPrimaryScrimmed(isScrimmed: Boolean) {
override fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
}
fun setPrimaryVisible(isVisible: Boolean) {
override fun setPrimaryVisible(isVisible: Boolean) {
_primaryBouncerVisible.value = isVisible
}
fun setAlternateVisible(isVisible: Boolean) {
override fun setAlternateVisible(isVisible: Boolean) {
if (isVisible && !_isAlternateBouncerVisible.value) {
lastAlternateBouncerVisibleTime = clock.uptimeMillis()
} else if (!isVisible) {
@@ -122,55 +187,55 @@ constructor(
_isAlternateBouncerVisible.value = isVisible
}
fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
_isAlternateBouncerUIAvailable.value = isAvailable
}
fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
_primaryBouncerShow.value = keyguardBouncerModel
}
fun setPrimaryShowingSoon(showingSoon: Boolean) {
override fun setPrimaryShowingSoon(showingSoon: Boolean) {
_primaryBouncerShowingSoon.value = showingSoon
}
fun setPrimaryHide(hide: Boolean) {
override fun setPrimaryHide(hide: Boolean) {
_primaryBouncerHide.value = hide
}
fun setPrimaryStartingToHide(startingToHide: Boolean) {
override fun setPrimaryStartingToHide(startingToHide: Boolean) {
_primaryBouncerStartingToHide.value = startingToHide
}
fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
_primaryBouncerDisappearAnimation.value = runnable
}
fun setPanelExpansion(panelExpansion: Float) {
override fun setPanelExpansion(panelExpansion: Float) {
_panelExpansionAmount.value = panelExpansion
}
fun setKeyguardPosition(keyguardPosition: Float) {
override fun setKeyguardPosition(keyguardPosition: Float) {
_keyguardPosition.value = keyguardPosition
}
fun setResourceUpdateRequests(willUpdateResources: Boolean) {
override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
_resourceUpdateRequests.value = willUpdateResources
}
fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
_showMessage.value = bouncerShowMessageModel
}
fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
_keyguardAuthenticated.value = keyguardAuthenticated
}
fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
_isBackButtonEnabled.value = isBackButtonEnabled
}
fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
_onScreenTurnedOff.value = onScreenTurnedOff
}

View File

@@ -37,4 +37,7 @@ interface KeyguardRepositoryModule {
fun deviceEntryFingerprintAuthRepository(
impl: DeviceEntryFingerprintAuthRepositoryImpl
): DeviceEntryFingerprintAuthRepository
@Binds
fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository
}

View File

@@ -113,6 +113,8 @@ constructor(
0f
}
}
/** Allow for interaction when just about fully visible */
val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
// TODO(b/243685699): Move isScrimmed logic to data layer.
// TODO(b/243695312): Encapsulate all of the show logic for the bouncer.

View File

@@ -160,6 +160,12 @@ object KeyguardBouncerViewBinder {
}
}
launch {
viewModel.isInteractable.collect { isInteractable ->
hostViewController.setInteractable(isInteractable)
}
}
launch {
viewModel.isBouncerVisible
.filter { !it }

View File

@@ -41,6 +41,9 @@ constructor(
/** Observe on bouncer visibility. */
val isBouncerVisible: Flow<Boolean> = interactor.isVisible
/** Can the user interact with the view? */
val isInteractable: Flow<Boolean> = interactor.isInteractable
/** Observe whether bouncer is showing. */
val show: Flow<KeyguardBouncerModel> = interactor.show

View File

@@ -52,7 +52,6 @@ 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
@@ -61,9 +60,8 @@ 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.data.repository.FakeKeyguardBouncerRepository
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
@@ -111,7 +109,7 @@ class SideFpsControllerTest : SysuiTestCase() {
@Captor lateinit var overlayCaptor: ArgumentCaptor<View>
@Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
private val featureFlags = FakeFeatureFlags()
private val executor = FakeExecutor(FakeSystemClock())
@@ -135,13 +133,7 @@ class SideFpsControllerTest : SysuiTestCase() {
@Before
fun setup() {
featureFlags.set(MODERN_ALTERNATE_BOUNCER, true)
keyguardBouncerRepository =
KeyguardBouncerRepository(
mock(ViewMediatorCallback::class.java),
FakeSystemClock(),
TestCoroutineScope(),
mock(TableLogBuffer::class.java),
)
keyguardBouncerRepository = FakeKeyguardBouncerRepository()
alternateBouncerInteractor =
AlternateBouncerInteractor(
keyguardBouncerRepository,

View File

@@ -29,6 +29,7 @@ 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.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor
import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor
@@ -65,7 +66,7 @@ class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControlle
allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
MockitoAnnotations.initMocks(this)
keyguardBouncerRepository =
KeyguardBouncerRepository(
KeyguardBouncerRepositoryImpl(
mock(com.android.keyguard.ViewMediatorCallback::class.java),
FakeSystemClock(),
TestCoroutineScope(),

View File

@@ -45,7 +45,7 @@ class KeyguardBouncerRepositoryTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
val testCoroutineScope = TestCoroutineScope()
underTest =
KeyguardBouncerRepository(
KeyguardBouncerRepositoryImpl(
viewMediatorCallback,
systemClock,
testCoroutineScope,

View File

@@ -25,6 +25,7 @@ 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.keyguard.data.repository.KeyguardBouncerRepositoryImpl
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.util.time.FakeSystemClock
import com.android.systemui.util.time.SystemClock
@@ -58,7 +59,7 @@ class AlternateBouncerInteractorTest : SysuiTestCase() {
fun setup() {
MockitoAnnotations.initMocks(this)
bouncerRepository =
KeyguardBouncerRepository(
KeyguardBouncerRepositoryImpl(
mock(ViewMediatorCallback::class.java),
FakeSystemClock(),
TestCoroutineScope(),

View File

@@ -68,13 +68,13 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Mock private lateinit var keyguardBypassController: KeyguardBypassController
@Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
private val mainHandler = FakeHandler(Looper.getMainLooper())
private lateinit var mPrimaryBouncerInteractor: PrimaryBouncerInteractor
private lateinit var underTest: PrimaryBouncerInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
DejankUtils.setImmediate(true)
mPrimaryBouncerInteractor =
underTest =
PrimaryBouncerInteractor(
repository,
bouncerView,
@@ -94,7 +94,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testShow_isScrimmed() {
mPrimaryBouncerInteractor.show(true)
underTest.show(true)
verify(repository).setOnScreenTurnedOff(false)
verify(repository).setKeyguardAuthenticated(null)
verify(repository).setPrimaryHide(false)
@@ -124,7 +124,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testHide() {
mPrimaryBouncerInteractor.hide()
underTest.hide()
verify(falsingCollector).onBouncerHidden()
verify(keyguardStateController).notifyBouncerShowing(false)
verify(repository).setPrimaryShowingSoon(false)
@@ -137,7 +137,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testExpansion() {
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
mPrimaryBouncerInteractor.setPanelExpansion(0.6f)
underTest.setPanelExpansion(0.6f)
verify(repository).setPanelExpansion(0.6f)
verify(mPrimaryBouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
}
@@ -146,7 +146,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
fun testExpansion_fullyShown() {
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
underTest.setPanelExpansion(EXPANSION_VISIBLE)
verify(falsingCollector).onBouncerShown()
verify(mPrimaryBouncerCallbackInteractor).dispatchFullyShown()
}
@@ -155,7 +155,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
fun testExpansion_fullyHidden() {
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
mPrimaryBouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
underTest.setPanelExpansion(EXPANSION_HIDDEN)
verify(repository).setPrimaryVisible(false)
verify(repository).setPrimaryShow(null)
verify(repository).setPrimaryHide(true)
@@ -167,7 +167,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testExpansion_startingToHide() {
`when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
mPrimaryBouncerInteractor.setPanelExpansion(0.1f)
underTest.setPanelExpansion(0.1f)
verify(repository).setPrimaryStartingToHide(true)
verify(mPrimaryBouncerCallbackInteractor).dispatchStartingToHide()
}
@@ -175,7 +175,7 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
@Test
fun testShowMessage() {
val argCaptor = ArgumentCaptor.forClass(BouncerShowMessageModel::class.java)
mPrimaryBouncerInteractor.showMessage("abc", null)
underTest.showMessage("abc", null)
verify(repository).setShowMessage(argCaptor.capture())
assertThat(argCaptor.value.message).isEqualTo("abc")
}
@@ -184,62 +184,62 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
fun testDismissAction() {
val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
val cancelAction = mock(Runnable::class.java)
mPrimaryBouncerInteractor.setDismissAction(onDismissAction, cancelAction)
underTest.setDismissAction(onDismissAction, cancelAction)
verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
}
@Test
fun testUpdateResources() {
mPrimaryBouncerInteractor.updateResources()
underTest.updateResources()
verify(repository).setResourceUpdateRequests(true)
}
@Test
fun testNotifyKeyguardAuthenticated() {
mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(true)
underTest.notifyKeyguardAuthenticated(true)
verify(repository).setKeyguardAuthenticated(true)
}
@Test
fun testNotifyShowedMessage() {
mPrimaryBouncerInteractor.onMessageShown()
underTest.onMessageShown()
verify(repository).setShowMessage(null)
}
@Test
fun testOnScreenTurnedOff() {
mPrimaryBouncerInteractor.onScreenTurnedOff()
underTest.onScreenTurnedOff()
verify(repository).setOnScreenTurnedOff(true)
}
@Test
fun testSetKeyguardPosition() {
mPrimaryBouncerInteractor.setKeyguardPosition(0f)
underTest.setKeyguardPosition(0f)
verify(repository).setKeyguardPosition(0f)
}
@Test
fun testNotifyKeyguardAuthenticatedHandled() {
mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedHandled()
underTest.notifyKeyguardAuthenticatedHandled()
verify(repository).setKeyguardAuthenticated(null)
}
@Test
fun testNotifyUpdatedResources() {
mPrimaryBouncerInteractor.notifyUpdatedResources()
underTest.notifyUpdatedResources()
verify(repository).setResourceUpdateRequests(false)
}
@Test
fun testSetBackButtonEnabled() {
mPrimaryBouncerInteractor.setBackButtonEnabled(true)
underTest.setBackButtonEnabled(true)
verify(repository).setIsBackButtonEnabled(true)
}
@Test
fun testStartDisappearAnimation() {
val runnable = mock(Runnable::class.java)
mPrimaryBouncerInteractor.startDisappearAnimation(runnable)
underTest.startDisappearAnimation(runnable)
verify(repository).setPrimaryStartDisappearAnimation(any(Runnable::class.java))
}
@@ -248,42 +248,42 @@ class PrimaryBouncerInteractorTest : SysuiTestCase() {
`when`(repository.primaryBouncerVisible.value).thenReturn(true)
`when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isTrue()
assertThat(underTest.isFullyShowing()).isTrue()
`when`(repository.primaryBouncerVisible.value).thenReturn(false)
assertThat(mPrimaryBouncerInteractor.isFullyShowing()).isFalse()
assertThat(underTest.isFullyShowing()).isFalse()
}
@Test
fun testIsScrimmed() {
`when`(repository.primaryBouncerScrimmed.value).thenReturn(true)
assertThat(mPrimaryBouncerInteractor.isScrimmed()).isTrue()
assertThat(underTest.isScrimmed()).isTrue()
`when`(repository.primaryBouncerScrimmed.value).thenReturn(false)
assertThat(mPrimaryBouncerInteractor.isScrimmed()).isFalse()
assertThat(underTest.isScrimmed()).isFalse()
}
@Test
fun testIsInTransit() {
`when`(repository.primaryBouncerShowingSoon.value).thenReturn(true)
assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
assertThat(underTest.isInTransit()).isTrue()
`when`(repository.primaryBouncerShowingSoon.value).thenReturn(false)
assertThat(mPrimaryBouncerInteractor.isInTransit()).isFalse()
assertThat(underTest.isInTransit()).isFalse()
`when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
assertThat(mPrimaryBouncerInteractor.isInTransit()).isTrue()
assertThat(underTest.isInTransit()).isTrue()
}
@Test
fun testIsAnimatingAway() {
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(Runnable {})
assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isTrue()
assertThat(underTest.isAnimatingAway()).isTrue()
`when`(repository.primaryBouncerStartingDisappearAnimation.value).thenReturn(null)
assertThat(mPrimaryBouncerInteractor.isAnimatingAway()).isFalse()
assertThat(underTest.isAnimatingAway()).isFalse()
}
@Test
fun testWillDismissWithAction() {
`when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isTrue()
assertThat(underTest.willDismissWithAction()).isTrue()
`when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
assertThat(mPrimaryBouncerInteractor.willDismissWithAction()).isFalse()
assertThat(underTest.willDismissWithAction()).isFalse()
}
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2022 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.domain.interactor
import android.os.Looper
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.coroutines.collectLastValue
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.FakeKeyguardBouncerRepository
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.utils.os.FakeHandler
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(JUnit4::class)
class PrimaryBouncerInteractorWithCoroutinesTest : SysuiTestCase() {
private lateinit var repository: FakeKeyguardBouncerRepository
@Mock private lateinit var bouncerView: BouncerView
@Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
@Mock private lateinit var keyguardStateController: KeyguardStateController
@Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
@Mock private lateinit var primaryBouncerCallbackInteractor: 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 underTest: PrimaryBouncerInteractor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
repository = FakeKeyguardBouncerRepository()
underTest =
PrimaryBouncerInteractor(
repository,
bouncerView,
mainHandler,
keyguardStateController,
keyguardSecurityModel,
primaryBouncerCallbackInteractor,
falsingCollector,
dismissCallbackRegistry,
keyguardBypassController,
keyguardUpdateMonitor,
)
}
@Test
fun notInteractableWhenExpansionIsBelow90Percent() = runTest {
val isInteractable = collectLastValue(underTest.isInteractable)
repository.setPrimaryVisible(true)
repository.setPanelExpansion(0.15f)
assertThat(isInteractable()).isFalse()
}
@Test
fun notInteractableWhenExpansionAbove90PercentButNotVisible() = runTest {
val isInteractable = collectLastValue(underTest.isInteractable)
repository.setPrimaryVisible(false)
repository.setPanelExpansion(0.05f)
assertThat(isInteractable()).isFalse()
}
@Test
fun isInteractableWhenExpansionAbove90PercentAndVisible() = runTest {
var isInteractable = collectLastValue(underTest.isInteractable)
repository.setPrimaryVisible(true)
repository.setPanelExpansion(0.09f)
assertThat(isInteractable()).isTrue()
repository.setPanelExpansion(0.12f)
assertThat(isInteractable()).isFalse()
repository.setPanelExpansion(0f)
assertThat(isInteractable()).isTrue()
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2022 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 com.android.systemui.keyguard.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
/** Fake implementation of [KeyguardRepository] */
class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
private val _primaryBouncerVisible = MutableStateFlow(false)
override val primaryBouncerVisible = _primaryBouncerVisible.asStateFlow()
private val _primaryBouncerShow = MutableStateFlow<KeyguardBouncerModel?>(null)
override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
private val _primaryBouncerShowingSoon = MutableStateFlow(false)
override val primaryBouncerShowingSoon = _primaryBouncerShowingSoon.asStateFlow()
private val _primaryBouncerHide = MutableStateFlow(false)
override val primaryBouncerHide = _primaryBouncerHide.asStateFlow()
private val _primaryBouncerStartingToHide = MutableStateFlow(false)
override val primaryBouncerStartingToHide = _primaryBouncerStartingToHide.asStateFlow()
private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
override val primaryBouncerStartingDisappearAnimation =
_primaryBouncerDisappearAnimation.asStateFlow()
private val _primaryBouncerScrimmed = MutableStateFlow(false)
override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
private val _panelExpansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
override val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
private val _keyguardPosition = MutableStateFlow(0f)
override val keyguardPosition = _keyguardPosition.asStateFlow()
private val _onScreenTurnedOff = MutableStateFlow(false)
override val onScreenTurnedOff = _onScreenTurnedOff.asStateFlow()
private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
override val showMessage = _showMessage.asStateFlow()
private val _resourceUpdateRequests = MutableStateFlow(false)
override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
override val bouncerPromptReason = 0
override val bouncerErrorMessage: CharSequence? = null
private val _isAlternateBouncerVisible = MutableStateFlow(false)
override val isAlternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow()
override var lastAlternateBouncerVisibleTime: Long = 0L
private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false)
override val isAlternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow()
override fun setPrimaryScrimmed(isScrimmed: Boolean) {
_primaryBouncerScrimmed.value = isScrimmed
}
override fun setPrimaryVisible(isVisible: Boolean) {
_primaryBouncerVisible.value = isVisible
}
override fun setAlternateVisible(isVisible: Boolean) {
_isAlternateBouncerVisible.value = isVisible
}
override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) {
_isAlternateBouncerUIAvailable.value = isAvailable
}
override fun setPrimaryShow(keyguardBouncerModel: KeyguardBouncerModel?) {
_primaryBouncerShow.value = keyguardBouncerModel
}
override fun setPrimaryShowingSoon(showingSoon: Boolean) {
_primaryBouncerShowingSoon.value = showingSoon
}
override fun setPrimaryHide(hide: Boolean) {
_primaryBouncerHide.value = hide
}
override fun setPrimaryStartingToHide(startingToHide: Boolean) {
_primaryBouncerStartingToHide.value = startingToHide
}
override fun setPrimaryStartDisappearAnimation(runnable: Runnable?) {
_primaryBouncerDisappearAnimation.value = runnable
}
override fun setPanelExpansion(panelExpansion: Float) {
_panelExpansionAmount.value = panelExpansion
}
override fun setKeyguardPosition(keyguardPosition: Float) {
_keyguardPosition.value = keyguardPosition
}
override fun setResourceUpdateRequests(willUpdateResources: Boolean) {
_resourceUpdateRequests.value = willUpdateResources
}
override fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?) {
_showMessage.value = bouncerShowMessageModel
}
override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
_keyguardAuthenticated.value = keyguardAuthenticated
}
override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
_isBackButtonEnabled.value = isBackButtonEnabled
}
override fun setOnScreenTurnedOff(onScreenTurnedOff: Boolean) {
_onScreenTurnedOff.value = onScreenTurnedOff
}
}