diff options
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) |