summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt37
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt77
2 files changed, 109 insertions, 5 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 22f62fcfe172..181c3df92222 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -182,6 +182,16 @@ constructor(
private var shadeShowingAndConsumingTouches = false
/**
+ * True anytime the shade is processing user touches, regardless of expansion state.
+ *
+ * Based on [ShadeInteractor.isUserInteracting].
+ */
+ private var shadeConsumingTouches = false
+
+ /** True if the keyguard transition state is finished on [KeyguardState.LOCKSCREEN]. */
+ private var onLockscreen = false
+
+ /**
* True if the shade ever fully expands and the user isn't interacting with it (aka finger on
* screen dragging). In this case, the shade should handle all touch events until it has fully
* collapsed.
@@ -338,6 +348,11 @@ constructor(
)
collectFlow(
containerView,
+ keyguardTransitionInteractor.isFinishedIn(KeyguardState.LOCKSCREEN),
+ { onLockscreen = it }
+ )
+ collectFlow(
+ containerView,
communalInteractor.isCommunalVisible,
{
hubShowing = it
@@ -369,6 +384,7 @@ constructor(
::Triple
),
{ (isFullyExpanded, isUserInteracting, isShadeFullyCollapsed) ->
+ shadeConsumingTouches = isUserInteracting
val expandedAndNotInteractive = isFullyExpanded && !isUserInteracting
// If we ever are fully expanded and not interacting, capture this state as we
@@ -497,10 +513,25 @@ constructor(
return true
}
try {
+ // On the lock screen, our touch handlers are not active and we rely on the NSWVC's
+ // touch handling for gestures on blank areas, which can go up to show the bouncer or
+ // down to show the notification shade. We see the touches first and they are not
+ // consumed and cancelled like on the dream or hub so we have to gracefully ignore them
+ // if the shade or bouncer are handling them. This issue only applies to touches on the
+ // keyguard itself, once the bouncer or shade are fully open, our logic stops us from
+ // taking touches.
+ val touchTaken = onLockscreen && (shadeConsumingTouches || anyBouncerShowing)
+
+ // Only dispatch touches to communal if not already handled or the touch is ending,
+ // meaning the event is an up or cancel. This is necessary as the hub always receives at
+ // least the initial down even if the shade or bouncer end up handling the touch.
+ val dispatchToCommunal = !touchTaken || !isTrackingHubTouch
var handled = false
- communalContainerWrapper?.dispatchTouchEvent(ev) {
- if (it) {
- handled = true
+ if (dispatchToCommunal) {
+ communalContainerWrapper?.dispatchTouchEvent(ev) {
+ if (it) {
+ handled = true
+ }
}
}
return handled || hubShowing
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index cb5c7399b769..b67e111af13d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -61,6 +61,7 @@ import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.controller.keyguardMediaController
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.statusbar.lockscreen.lockscreenSmartspaceController
import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
@@ -727,7 +728,9 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
// Touch event is sent to the container view.
assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
- verify(containerView).onTouchEvent(any())
+ verify(containerView).onTouchEvent(DOWN_EVENT)
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isTrue()
+ verify(containerView).onTouchEvent(UP_EVENT)
}
}
@@ -774,13 +777,83 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
}
+ @Test
+ fun onTouchEvent_shadeInteracting_movesNotDispatched() =
+ with(kosmos) {
+ testScope.runTest {
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
+ whenever(
+ notificationStackScrollLayoutController.isBelowLastNotification(
+ any(),
+ any()
+ )
+ )
+ .thenReturn(true)
+
+ // Touches not consumed by default but are received by containerView.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView).onTouchEvent(DOWN_EVENT)
+
+ // User is interacting with shade on lockscreen.
+ fakeShadeRepository.setLegacyLockscreenShadeTracking(true)
+ testableLooper.processAllMessages()
+
+ // A move event is ignored while the user is already interacting.
+ assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(MOVE_EVENT)
+
+ // An up event is still delivered.
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+ verify(containerView).onTouchEvent(UP_EVENT)
+ }
+ }
+
+ @Test
+ fun onTouchEvent_bouncerInteracting_movesNotDispatched() =
+ with(kosmos) {
+ testScope.runTest {
+ // On lockscreen.
+ goToScene(CommunalScenes.Blank)
+ whenever(
+ notificationStackScrollLayoutController.isBelowLastNotification(
+ any(),
+ any()
+ )
+ )
+ .thenReturn(true)
+
+ // Touches not consumed by default but are received by containerView.
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ verify(containerView).onTouchEvent(DOWN_EVENT)
+
+ // User is interacting with bouncer on lockscreen.
+ fakeKeyguardBouncerRepository.setPrimaryShow(true)
+ testableLooper.processAllMessages()
+
+ // A move event is ignored while the user is already interacting.
+ assertThat(underTest.onTouchEvent(MOVE_EVENT)).isFalse()
+ verify(containerView, never()).onTouchEvent(MOVE_EVENT)
+
+ // An up event is still delivered.
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+ verify(containerView).onTouchEvent(UP_EVENT)
+ }
+ }
+
private fun initAndAttachContainerView() {
val mockInsets =
mock<WindowInsets> {
on { getInsets(WindowInsets.Type.systemGestures()) } doReturn FAKE_INSETS
}
- containerView = spy(View(context)) { on { rootWindowInsets } doReturn mockInsets }
+ containerView =
+ spy(View(context)) {
+ on { rootWindowInsets } doReturn mockInsets
+ // Return true to handle touch events or else further events in the gesture will not
+ // be received as we are using real View objects.
+ onGeneric { onTouchEvent(any()) } doReturn true
+ }
parentView = FrameLayout(context)