Loading packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +65 −24 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.res.R Loading @@ -52,6 +53,7 @@ constructor( private val communalViewModel: CommunalViewModel, private val dialogFactory: SystemUIDialogFactory, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardInteractor: KeyguardInteractor, private val shadeInteractor: ShadeInteractor, private val powerManager: PowerManager, @Communal private val dataSourceDelegator: SceneDataSourceDelegator, Loading Loading @@ -89,6 +91,9 @@ constructor( /** True if we are currently tracking a touch on the hub while it's open. */ private var isTrackingHubTouch = false /** True if we are tracking a top or bottom swipe gesture while the hub is open. */ private var isTrackingHubGesture = false /** * True if the hub UI is fully open, meaning it should receive touch input. * Loading @@ -111,6 +116,16 @@ constructor( */ private var shadeShowing = false /** * True if the device is dreaming, in which case we shouldn't do anything for top/bottom swipes * and just let the dream overlay's touch handling deal with them. * * Tracks [KeyguardInteractor.isDreaming]. * * TODO(b/328838259): figure out a proper solution for touch handling above the lock screen too */ private var isDreaming = false /** Returns a flow that tracks whether communal hub is available. */ fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable Loading Loading @@ -166,6 +181,7 @@ constructor( ) collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it }) collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it }) collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it }) communalContainerView = containerView Loading Loading @@ -194,61 +210,86 @@ constructor( } private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean { // If the hub is fully visible, send all touch events to it, other than top and bottom edge // swipes. return if (hubShowing) { handleHubOpenTouch(view, ev) } else { handleHubClosedTouch(view, ev) } } private fun handleHubOpenTouch(view: View, ev: MotionEvent): Boolean { val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN val isUp = ev.actionMasked == MotionEvent.ACTION_UP val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL // TODO(b/315207481): also account for opening animations of shade/bouncer and not just // fully showing state val hubOccluded = anyBouncerShowing || shadeShowing // If the hub is fully visible, send all touch events to it, other than top and bottom edge // swipes. if (hubShowing && isDown) { if (isDown && !hubOccluded) { // Only intercept down events if the hub isn't occluded by the bouncer or // notification shade. val y = ev.rawY val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth if (topSwipe || bottomSwipe) { // Don't intercept touches at the top/bottom edge so that swipes can open the // notification shade and bouncer. return false } if (!hubOccluded) { isTrackingHubGesture = true } else { isTrackingHubTouch = true dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true } } else if (isTrackingHubTouch) { } if (isTrackingHubTouch) { // Tracking a touch on the hub UI itself. if (isUp || isCancel) { isTrackingHubTouch = false } dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a gesture // Return true regardless of dispatch result as some touches at the start of a // gesture // may return false from dispatchTouchEvent. return true } else if (isTrackingHubGesture) { // Tracking a top or bottom swipe on the hub UI. if (isUp || isCancel) { isTrackingHubGesture = false } // If we're dreaming, intercept touches so the hub UI doesn't receive them, but // don't do anything so that the dream's touch handling takes care of opening // the bouncer or shade. // // If we're not dreaming, we don't intercept touches at the top/bottom edge so that // swipes can open the notification shade and bouncer. return isDreaming } return false } private fun handleHubClosedTouch(view: View, ev: MotionEvent): Boolean { val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN val isUp = ev.actionMasked == MotionEvent.ACTION_UP val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL val hubOccluded = anyBouncerShowing || shadeShowing if (rightEdgeSwipeRegionWidth == 0) { // If the edge region width has not been read yet for whatever reason, don't bother // intercepting touches to open the hub. return false } if (!isTrackingOpenGesture && isDown) { if (isDown && !hubOccluded) { val x = ev.rawX val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth if (inOpeningSwipeRegion && !hubOccluded) { if (inOpeningSwipeRegion) { isTrackingOpenGesture = true dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true } } else if (isTrackingOpenGesture) { } if (isTrackingOpenGesture) { if (isUp || isCancel) { isTrackingOpenGesture = false } Loading packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +173 −99 Original line number Diff line number Diff line Loading @@ -36,22 +36,30 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.shared.model.sceneDataSourceDelegator import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Assert.assertThrows import org.junit.Before Loading @@ -75,10 +83,11 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Mock private lateinit var communalViewModel: CommunalViewModel @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock private lateinit var shadeInteractor: ShadeInteractor @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var dialogFactory: SystemUIDialogFactory private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var shadeInteractor: ShadeInteractor private lateinit var keyguardInteractor: KeyguardInteractor private lateinit var parentView: FrameLayout private lateinit var containerView: View Loading @@ -88,15 +97,15 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { private lateinit var communalRepository: FakeCommunalRepository private lateinit var underTest: GlanceableHubContainerController private val bouncerShowingFlow = MutableStateFlow(false) private val shadeShowingFlow = MutableStateFlow(false) @Before fun setUp() { MockitoAnnotations.initMocks(this) communalInteractor = kosmos.communalInteractor communalRepository = kosmos.fakeCommunalRepository keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor keyguardInteractor = kosmos.keyguardInteractor shadeInteractor = kosmos.shadeInteractor underTest = GlanceableHubContainerController( Loading @@ -104,16 +113,13 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { communalViewModel, dialogFactory, keyguardTransitionInteractor, keyguardInteractor, shadeInteractor, powerManager, kosmos.sceneDataSourceDelegator, ) testableLooper = TestableLooper.get(this) whenever(keyguardTransitionInteractor.isFinishedInStateWhere(any())) .thenReturn(bouncerShowingFlow) whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(shadeShowingFlow) overrideResource(R.dimen.communal_right_edge_swipe_region_width, RIGHT_SWIPE_REGION_WIDTH) overrideResource(R.dimen.communal_top_edge_swipe_region_height, TOP_SWIPE_REGION_WIDTH) overrideResource( Loading @@ -138,13 +144,16 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test fun initView_calledTwice_throwsException() { fun initView_calledTwice_throwsException() = with(kosmos) { testScope.runTest { underTest = GlanceableHubContainerController( communalInteractor, communalViewModel, dialogFactory, keyguardTransitionInteractor, keyguardInteractor, shadeInteractor, powerManager, kosmos.sceneDataSourceDelegator, Loading @@ -156,30 +165,40 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // Second call throws. assertThrows(RuntimeException::class.java) { underTest.initView(context) } } } @Test fun onTouchEvent_communalClosed_doesNotIntercept() { fun onTouchEvent_communalClosed_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is closed. goToScene(CommunalScenes.Blank) assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() } } @Test fun onTouchEvent_openGesture_interceptsTouches() { fun onTouchEvent_openGesture_interceptsTouches() = with(kosmos) { testScope.runTest { // Communal is closed. goToScene(CommunalScenes.Blank) // Initial touch down is intercepted, and so are touches outside of the region, until an // Initial touch down is intercepted, and so are touches outside of the region, // until an // up event is received. assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue() assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue() assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue() assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse() } } @Test fun onTouchEvent_communalOpen_interceptsTouches() { fun onTouchEvent_communalOpen_interceptsTouches() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) Loading @@ -188,32 +207,77 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // User activity sent to PowerManager. verify(powerManager).userActivity(any(), any(), any()) } } @Test fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() { fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Touch event in the top swipe reqgion is not intercepted. // Touch event in the top swipe region is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse() } } @Test fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() { fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Touch event in the bottom swipe reqgion is not intercepted. // Touch event in the bottom swipe region is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse() } } @Test fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() { fun onTouchEvent_topSwipeWhenDreaming_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Device is dreaming. fakeKeyguardRepository.setDreaming(true) runCurrent() // Touch event in the top swipe region is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse() } } @Test fun onTouchEvent_bottomSwipeWhenDreaming_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Device is dreaming. fakeKeyguardRepository.setDreaming(true) runCurrent() // Touch event in the bottom swipe region is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse() } } @Test fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Bouncer is visible. bouncerShowingFlow.value = true fakeKeyguardTransitionRepository.sendTransitionSteps( KeyguardState.GLANCEABLE_HUB, KeyguardState.PRIMARY_BOUNCER, testScope ) testableLooper.processAllMessages() // Touch events are not intercepted. Loading @@ -221,21 +285,28 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // User activity is not sent to PowerManager. verify(powerManager, times(0)).userActivity(any(), any(), any()) } } @Test fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() { fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) shadeShowingFlow.value = true // Shade shows up. fakeShadeRepository.setQsExpansion(1.0f) testableLooper.processAllMessages() // Touch events are not intercepted. assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() } } @Test fun onTouchEvent_containerViewDisposed_doesNotIntercept() { fun onTouchEvent_containerViewDisposed_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) Loading @@ -248,6 +319,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // Touch events are not intercepted. assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() } } private fun initAndAttachContainerView() { containerView = View(context) Loading @@ -259,6 +331,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // Attach the view so that flows start collecting. ViewUtils.attachView(parentView) // Attaching is async so processAllMessages is required for view.repeatWhenAttached to run. testableLooper.processAllMessages() // Give the view a fixed size to simplify testing for edge swipes. val lp = Loading Loading
packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +65 −24 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.res.R Loading @@ -52,6 +53,7 @@ constructor( private val communalViewModel: CommunalViewModel, private val dialogFactory: SystemUIDialogFactory, private val keyguardTransitionInteractor: KeyguardTransitionInteractor, private val keyguardInteractor: KeyguardInteractor, private val shadeInteractor: ShadeInteractor, private val powerManager: PowerManager, @Communal private val dataSourceDelegator: SceneDataSourceDelegator, Loading Loading @@ -89,6 +91,9 @@ constructor( /** True if we are currently tracking a touch on the hub while it's open. */ private var isTrackingHubTouch = false /** True if we are tracking a top or bottom swipe gesture while the hub is open. */ private var isTrackingHubGesture = false /** * True if the hub UI is fully open, meaning it should receive touch input. * Loading @@ -111,6 +116,16 @@ constructor( */ private var shadeShowing = false /** * True if the device is dreaming, in which case we shouldn't do anything for top/bottom swipes * and just let the dream overlay's touch handling deal with them. * * Tracks [KeyguardInteractor.isDreaming]. * * TODO(b/328838259): figure out a proper solution for touch handling above the lock screen too */ private var isDreaming = false /** Returns a flow that tracks whether communal hub is available. */ fun communalAvailable(): Flow<Boolean> = communalInteractor.isCommunalAvailable Loading Loading @@ -166,6 +181,7 @@ constructor( ) collectFlow(containerView, communalInteractor.isCommunalShowing, { hubShowing = it }) collectFlow(containerView, shadeInteractor.isAnyFullyExpanded, { shadeShowing = it }) collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it }) communalContainerView = containerView Loading Loading @@ -194,61 +210,86 @@ constructor( } private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean { // If the hub is fully visible, send all touch events to it, other than top and bottom edge // swipes. return if (hubShowing) { handleHubOpenTouch(view, ev) } else { handleHubClosedTouch(view, ev) } } private fun handleHubOpenTouch(view: View, ev: MotionEvent): Boolean { val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN val isUp = ev.actionMasked == MotionEvent.ACTION_UP val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL // TODO(b/315207481): also account for opening animations of shade/bouncer and not just // fully showing state val hubOccluded = anyBouncerShowing || shadeShowing // If the hub is fully visible, send all touch events to it, other than top and bottom edge // swipes. if (hubShowing && isDown) { if (isDown && !hubOccluded) { // Only intercept down events if the hub isn't occluded by the bouncer or // notification shade. val y = ev.rawY val topSwipe: Boolean = y <= topEdgeSwipeRegionWidth val bottomSwipe = y >= view.height - bottomEdgeSwipeRegionWidth if (topSwipe || bottomSwipe) { // Don't intercept touches at the top/bottom edge so that swipes can open the // notification shade and bouncer. return false } if (!hubOccluded) { isTrackingHubGesture = true } else { isTrackingHubTouch = true dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true } } else if (isTrackingHubTouch) { } if (isTrackingHubTouch) { // Tracking a touch on the hub UI itself. if (isUp || isCancel) { isTrackingHubTouch = false } dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a gesture // Return true regardless of dispatch result as some touches at the start of a // gesture // may return false from dispatchTouchEvent. return true } else if (isTrackingHubGesture) { // Tracking a top or bottom swipe on the hub UI. if (isUp || isCancel) { isTrackingHubGesture = false } // If we're dreaming, intercept touches so the hub UI doesn't receive them, but // don't do anything so that the dream's touch handling takes care of opening // the bouncer or shade. // // If we're not dreaming, we don't intercept touches at the top/bottom edge so that // swipes can open the notification shade and bouncer. return isDreaming } return false } private fun handleHubClosedTouch(view: View, ev: MotionEvent): Boolean { val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN val isUp = ev.actionMasked == MotionEvent.ACTION_UP val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL val hubOccluded = anyBouncerShowing || shadeShowing if (rightEdgeSwipeRegionWidth == 0) { // If the edge region width has not been read yet for whatever reason, don't bother // intercepting touches to open the hub. return false } if (!isTrackingOpenGesture && isDown) { if (isDown && !hubOccluded) { val x = ev.rawX val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth if (inOpeningSwipeRegion && !hubOccluded) { if (inOpeningSwipeRegion) { isTrackingOpenGesture = true dispatchTouchEvent(view, ev) // Return true regardless of dispatch result as some touches at the start of a // gesture may return false from dispatchTouchEvent. return true } } else if (isTrackingOpenGesture) { } if (isTrackingOpenGesture) { if (isUp || isCancel) { isTrackingOpenGesture = false } Loading
packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +173 −99 Original line number Diff line number Diff line Loading @@ -36,22 +36,30 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.CommunalViewModel import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.shared.model.sceneDataSourceDelegator import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.phone.SystemUIDialogFactory import com.android.systemui.testKosmos import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Assert.assertThrows import org.junit.Before Loading @@ -75,10 +83,11 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Mock private lateinit var communalViewModel: CommunalViewModel @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock private lateinit var shadeInteractor: ShadeInteractor @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var dialogFactory: SystemUIDialogFactory private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var shadeInteractor: ShadeInteractor private lateinit var keyguardInteractor: KeyguardInteractor private lateinit var parentView: FrameLayout private lateinit var containerView: View Loading @@ -88,15 +97,15 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { private lateinit var communalRepository: FakeCommunalRepository private lateinit var underTest: GlanceableHubContainerController private val bouncerShowingFlow = MutableStateFlow(false) private val shadeShowingFlow = MutableStateFlow(false) @Before fun setUp() { MockitoAnnotations.initMocks(this) communalInteractor = kosmos.communalInteractor communalRepository = kosmos.fakeCommunalRepository keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor keyguardInteractor = kosmos.keyguardInteractor shadeInteractor = kosmos.shadeInteractor underTest = GlanceableHubContainerController( Loading @@ -104,16 +113,13 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { communalViewModel, dialogFactory, keyguardTransitionInteractor, keyguardInteractor, shadeInteractor, powerManager, kosmos.sceneDataSourceDelegator, ) testableLooper = TestableLooper.get(this) whenever(keyguardTransitionInteractor.isFinishedInStateWhere(any())) .thenReturn(bouncerShowingFlow) whenever(shadeInteractor.isAnyFullyExpanded).thenReturn(shadeShowingFlow) overrideResource(R.dimen.communal_right_edge_swipe_region_width, RIGHT_SWIPE_REGION_WIDTH) overrideResource(R.dimen.communal_top_edge_swipe_region_height, TOP_SWIPE_REGION_WIDTH) overrideResource( Loading @@ -138,13 +144,16 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } @Test fun initView_calledTwice_throwsException() { fun initView_calledTwice_throwsException() = with(kosmos) { testScope.runTest { underTest = GlanceableHubContainerController( communalInteractor, communalViewModel, dialogFactory, keyguardTransitionInteractor, keyguardInteractor, shadeInteractor, powerManager, kosmos.sceneDataSourceDelegator, Loading @@ -156,30 +165,40 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // Second call throws. assertThrows(RuntimeException::class.java) { underTest.initView(context) } } } @Test fun onTouchEvent_communalClosed_doesNotIntercept() { fun onTouchEvent_communalClosed_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is closed. goToScene(CommunalScenes.Blank) assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() } } @Test fun onTouchEvent_openGesture_interceptsTouches() { fun onTouchEvent_openGesture_interceptsTouches() = with(kosmos) { testScope.runTest { // Communal is closed. goToScene(CommunalScenes.Blank) // Initial touch down is intercepted, and so are touches outside of the region, until an // Initial touch down is intercepted, and so are touches outside of the region, // until an // up event is received. assertThat(underTest.onTouchEvent(DOWN_IN_RIGHT_SWIPE_REGION_EVENT)).isTrue() assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue() assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue() assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse() } } @Test fun onTouchEvent_communalOpen_interceptsTouches() { fun onTouchEvent_communalOpen_interceptsTouches() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) Loading @@ -188,32 +207,77 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // User activity sent to PowerManager. verify(powerManager).userActivity(any(), any(), any()) } } @Test fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() { fun onTouchEvent_topSwipeWhenCommunalOpen_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Touch event in the top swipe reqgion is not intercepted. // Touch event in the top swipe region is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse() } } @Test fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() { fun onTouchEvent_bottomSwipeWhenCommunalOpen_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Touch event in the bottom swipe reqgion is not intercepted. // Touch event in the bottom swipe region is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse() } } @Test fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() { fun onTouchEvent_topSwipeWhenDreaming_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Device is dreaming. fakeKeyguardRepository.setDreaming(true) runCurrent() // Touch event in the top swipe region is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_TOP_SWIPE_REGION_EVENT)).isFalse() } } @Test fun onTouchEvent_bottomSwipeWhenDreaming_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Device is dreaming. fakeKeyguardRepository.setDreaming(true) runCurrent() // Touch event in the bottom swipe region is not intercepted. assertThat(underTest.onTouchEvent(DOWN_IN_BOTTOM_SWIPE_REGION_EVENT)).isFalse() } } @Test fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) // Bouncer is visible. bouncerShowingFlow.value = true fakeKeyguardTransitionRepository.sendTransitionSteps( KeyguardState.GLANCEABLE_HUB, KeyguardState.PRIMARY_BOUNCER, testScope ) testableLooper.processAllMessages() // Touch events are not intercepted. Loading @@ -221,21 +285,28 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // User activity is not sent to PowerManager. verify(powerManager, times(0)).userActivity(any(), any(), any()) } } @Test fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() { fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) shadeShowingFlow.value = true // Shade shows up. fakeShadeRepository.setQsExpansion(1.0f) testableLooper.processAllMessages() // Touch events are not intercepted. assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() } } @Test fun onTouchEvent_containerViewDisposed_doesNotIntercept() { fun onTouchEvent_containerViewDisposed_doesNotIntercept() = with(kosmos) { testScope.runTest { // Communal is open. goToScene(CommunalScenes.Communal) Loading @@ -248,6 +319,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // Touch events are not intercepted. assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse() } } private fun initAndAttachContainerView() { containerView = View(context) Loading @@ -259,6 +331,8 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { // Attach the view so that flows start collecting. ViewUtils.attachView(parentView) // Attaching is async so processAllMessages is required for view.repeatWhenAttached to run. testableLooper.processAllMessages() // Give the view a fixed size to simplify testing for edge swipes. val lp = Loading