From 96cdb8a2628156363403826e06fc8d43fa127fef Mon Sep 17 00:00:00 2001 From: Matt Pietal Date: Thu, 22 Dec 2022 17:40:12 +0000 Subject: [PATCH] [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 --- .../android/keyguard/KeyguardHostView.java | 23 ++- .../keyguard/KeyguardHostViewController.java | 5 + .../repository/KeyguardBouncerRepository.kt | 139 +++++++++++++----- .../repository/KeyguardRepositoryModule.kt | 3 + .../interactor/PrimaryBouncerInteractor.kt | 2 + .../ui/binder/KeyguardBouncerViewBinder.kt | 6 + .../ui/viewmodel/KeyguardBouncerViewModel.kt | 3 + .../biometrics/SideFpsControllerTest.kt | 14 +- ...eyguardViewControllerWithCoroutinesTest.kt | 3 +- .../KeyguardBouncerRepositoryTest.kt | 2 +- .../AlternateBouncerInteractorTest.kt | 3 +- .../PrimaryBouncerInteractorTest.kt | 60 ++++---- ...maryBouncerInteractorWithCoroutinesTest.kt | 112 ++++++++++++++ .../FakeKeyguardBouncerRepository.kt | 128 ++++++++++++++++ 14 files changed, 421 insertions(+), 82 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt create mode 100644 packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java index 08e9cf60a65ac..2a389b6132e9f 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java @@ -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; + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java index ea84438bf4baa..61394035d731b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java @@ -527,4 +527,9 @@ public class KeyguardHostViewController extends ViewController mKeyguardSecurityContainerController.updateKeyguardPosition(x); } } + + /** Set true if the view can be interacted with */ + public void setInteractable(boolean isInteractable) { + mView.setInteractable(isInteractable); + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt index 3e171361a6f1f..4ac6ac8d9cce8 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt @@ -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 + val primaryBouncerShow: StateFlow + val primaryBouncerShowingSoon: StateFlow + val primaryBouncerHide: StateFlow + val primaryBouncerStartingToHide: StateFlow + val primaryBouncerStartingDisappearAnimation: StateFlow + /** Determines if we want to instantaneously show the primary bouncer instead of translating. */ + val primaryBouncerScrimmed: StateFlow + /** + * 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 + val keyguardPosition: StateFlow + val onScreenTurnedOff: StateFlow + val isBackButtonEnabled: StateFlow + /** Determines if user is already unlocked */ + val keyguardAuthenticated: StateFlow + val showMessage: StateFlow + val resourceUpdateRequests: StateFlow + val bouncerPromptReason: Int + val bouncerErrorMessage: CharSequence? + val isAlternateBouncerVisible: StateFlow + val isAlternateBouncerUIAvailable: StateFlow + 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(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(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(null) - val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow() + override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow() private val _keyguardAuthenticated = MutableStateFlow(null) /** Determines if user is already unlocked */ - val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow() + override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow() private val _showMessage = MutableStateFlow(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(false) - val isAlternateBouncerUIAvailable: StateFlow = + override val isAlternateBouncerUIAvailable: StateFlow = _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 } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt index cc99eb72da16d..2cbc2bb44189b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt @@ -37,4 +37,7 @@ interface KeyguardRepositoryModule { fun deviceEntryFingerprintAuthRepository( impl: DeviceEntryFingerprintAuthRepositoryImpl ): DeviceEntryFingerprintAuthRepository + + @Binds + fun keyguardBouncerRepository(impl: KeyguardBouncerRepositoryImpl): KeyguardBouncerRepository } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt index a92540d733b5f..96bf815b28d85 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt @@ -113,6 +113,8 @@ constructor( 0f } } + /** Allow for interaction when just about fully visible */ + val isInteractable: Flow = 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. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt index 5e46c5d1f67ac..5b4adee8cede9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt @@ -160,6 +160,12 @@ object KeyguardBouncerViewBinder { } } + launch { + viewModel.isInteractable.collect { isInteractable -> + hostViewController.setInteractable(isInteractable) + } + } + launch { viewModel.isBouncerVisible .filter { !it } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt index c6002d6db91ad..92a7fa046d15c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt @@ -41,6 +41,9 @@ constructor( /** Observe on bouncer visibility. */ val isBouncerVisible: Flow = interactor.isVisible + /** Can the user interact with the view? */ + val isInteractable: Flow = interactor.isInteractable + /** Observe whether bouncer is showing. */ val show: Flow = interactor.show diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt index fd931b0a794ba..b5c0f201dc3b9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt @@ -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 @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor - 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, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt index 81a6bc2b25b57..d2183b317bbe6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt @@ -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(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt index 969537d231119..444a2a7eada63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt @@ -45,7 +45,7 @@ class KeyguardBouncerRepositoryTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) val testCoroutineScope = TestCoroutineScope() underTest = - KeyguardBouncerRepository( + KeyguardBouncerRepositoryImpl( viewMediatorCallback, systemClock, testCoroutineScope, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt index 68fff262214a7..28974f8252c15 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractorTest.kt @@ -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(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt index 7f48ea19c91a3..c5e0252859443 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorTest.kt @@ -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() } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt new file mode 100644 index 0000000000000..ea7bc91cd2d5d --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractorWithCoroutinesTest.kt @@ -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() + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt new file mode 100644 index 0000000000000..d0383e996121a --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardBouncerRepository.kt @@ -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(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(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(null) + override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow() + private val _keyguardAuthenticated = MutableStateFlow(null) + override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow() + private val _showMessage = MutableStateFlow(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(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 + } +}