summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Bryce Lee <brycelee@google.com> 2024-07-15 14:28:04 -0700
committer Bryce Lee <brycelee@google.com> 2024-07-17 23:50:37 -0700
commit6376fc1c46e0f247d6e74eaf564445d12e68f7f0 (patch)
tree6c636153ffc1844bd93078f5d39fb7928e3135fa
parent59f4204a7be15575bf45e0a87d4b03f89ec45c07 (diff)
Fix dragging down notification shade handling over Glanceable Hub.
This changelist addresses a number of issues with notification shade touch handling in glanceable hub: - Properly handle cancel event to ShadeTouchHandler to end the touch session. not popping the touch session leads to a stale active session. - Recreating the touch input session on the last popped session. This resets any pilfering logic that was tied to the original session. - Only allow interactivity once the shade is expanded if the user is still interacting at full expansion. - Begin tracking touches on move as well as done in the GlancealbeHubContainerController, as the original stream might be canceled but replaced with another source. Test: atest GlanceableHubContainerControllerTest#lifecycle_doesNotResumeOnUserInteractivityOnceExpanded Test: atest TouchMonitorTest#testLastSessionPop_createsNewInputSession Test: atest ShadeTouchHandlerTest#testCancelMotionEvent_popsTouchSession Flag: EXEMPT bugfix Fixes: 353342159 Change-Id: Ide3f903cd44b1b4854adb3b32782e19946cecb20
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt42
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt83
6 files changed, 162 insertions, 12 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
index 5cf4f16aed62..7fd9ce20ab92 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt
@@ -146,6 +146,14 @@ class ShadeTouchHandlerTest : SysuiTestCase() {
verify(mShadeViewController, never()).handleExternalTouch(any())
}
+ @Test
+ fun testCancelMotionEvent_popsTouchSession() {
+ swipe(Direction.DOWN)
+ val event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0)
+ mInputListenerCaptor.lastValue.onInputEvent(event)
+ verify(mTouchSession).pop()
+ }
+
/**
* Simulates a swipe in the given direction and returns true if the touch was intercepted by the
* touch handler's gesture listener.
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
index f1fb45c9b16e..baca9594dd2f 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java
@@ -79,7 +79,8 @@ public class ShadeTouchHandler implements TouchHandler {
if (mCapture != null && mCapture) {
sendTouchEvent((MotionEvent) ev);
}
- if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP) {
+ if (((MotionEvent) ev).getAction() == MotionEvent.ACTION_UP
+ || ((MotionEvent) ev).getAction() == MotionEvent.ACTION_CANCEL) {
session.pop();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
index 4035e957a2db..efa55e90081e 100644
--- a/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java
@@ -128,8 +128,13 @@ public class TouchMonitor {
completer.set(predecessor);
}
- if (mActiveTouchSessions.isEmpty() && mStopMonitoringPending) {
- stopMonitoring(false);
+ if (mActiveTouchSessions.isEmpty()) {
+ if (mStopMonitoringPending) {
+ stopMonitoring(false);
+ } else {
+ // restart monitoring to reset any destructive state on the input session
+ startMonitoring();
+ }
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index d870fe69463d..d090aea4cee5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -56,13 +56,12 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
-import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.collectFlow
import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
/**
@@ -165,7 +164,14 @@ constructor(
*
* Based on [ShadeInteractor.isAnyFullyExpanded] and [ShadeInteractor.isUserInteracting].
*/
- private var shadeShowing = false
+ private var shadeShowingAndConsumingTouches = 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.
+ */
+ private var userNotInteractiveAtShadeFullyExpanded = false
/**
* True if the device is dreaming, in which case we shouldn't do anything for top/bottom swipes
@@ -317,9 +323,25 @@ constructor(
)
collectFlow(
containerView,
- allOf(shadeInteractor.isAnyFullyExpanded, not(shadeInteractor.isUserInteracting)),
- {
- shadeShowing = it
+ combine(
+ shadeInteractor.isAnyFullyExpanded,
+ shadeInteractor.isUserInteracting,
+ shadeInteractor.isShadeFullyCollapsed,
+ ::Triple
+ ),
+ { (isFullyExpanded, isUserInteracting, isShadeFullyCollapsed) ->
+ val expandedAndNotInteractive = isFullyExpanded && !isUserInteracting
+
+ // If we ever are fully expanded and not interacting, capture this state as we
+ // should not handle touches until we fully collapse again
+ userNotInteractiveAtShadeFullyExpanded =
+ !isShadeFullyCollapsed &&
+ (userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive)
+
+ // If the shade reaches full expansion without interaction, then we should allow it
+ // to consume touches rather than handling it here until it disappears.
+ shadeShowingAndConsumingTouches =
+ userNotInteractiveAtShadeFullyExpanded || expandedAndNotInteractive
updateTouchHandlingState()
}
)
@@ -337,7 +359,8 @@ constructor(
* Also clears gesture exclusion zones when the hub is occluded or gone.
*/
private fun updateTouchHandlingState() {
- val shouldInterceptGestures = hubShowing && !(shadeShowing || anyBouncerShowing)
+ val shouldInterceptGestures =
+ hubShowing && !(shadeShowingAndConsumingTouches || anyBouncerShowing)
if (shouldInterceptGestures) {
lifecycleRegistry.currentState = Lifecycle.State.RESUMED
} else {
@@ -395,11 +418,12 @@ constructor(
private fun handleTouchEventOnCommunalView(view: View, ev: MotionEvent): Boolean {
val isDown = ev.actionMasked == MotionEvent.ACTION_DOWN
val isUp = ev.actionMasked == MotionEvent.ACTION_UP
+ val isMove = ev.actionMasked == MotionEvent.ACTION_MOVE
val isCancel = ev.actionMasked == MotionEvent.ACTION_CANCEL
- val hubOccluded = anyBouncerShowing || shadeShowing
+ val hubOccluded = anyBouncerShowing || shadeShowingAndConsumingTouches
- if (isDown && !hubOccluded) {
+ if ((isDown || isMove) && !hubOccluded) {
isTrackingHubTouch = true
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
index 639b53bfb95b..5600b87280ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java
@@ -710,6 +710,35 @@ public class TouchMonitorTest extends SysuiTestCase {
environment.verifyLifecycleObserversUnregistered();
}
+ @Test
+ public void testLastSessionPop_createsNewInputSession() {
+ final TouchHandler touchHandler = createTouchHandler();
+
+ final TouchHandler.TouchSession.Callback callback =
+ Mockito.mock(TouchHandler.TouchSession.Callback.class);
+
+ final Environment environment = new Environment(Stream.of(touchHandler)
+ .collect(Collectors.toCollection(HashSet::new)), mKosmos);
+
+ final InputEvent initialEvent = Mockito.mock(InputEvent.class);
+ environment.publishInputEvent(initialEvent);
+
+ final TouchHandler.TouchSession session = captureSession(touchHandler);
+ session.registerCallback(callback);
+
+ // Clear invocations on input session and factory.
+ clearInvocations(environment.mInputFactory);
+ clearInvocations(environment.mInputSession);
+
+ // Pop only active touch session.
+ session.pop();
+ environment.executeAll();
+
+ // Verify that input session disposed and new session requested from factory.
+ verify(environment.mInputSession).dispose();
+ verify(environment.mInputFactory).create(any(), any(), any(), anyBoolean());
+ }
+
private GestureDetector.OnGestureListener registerGestureListener(TouchHandler handler) {
final GestureDetector.OnGestureListener gestureListener = Mockito.mock(
GestureDetector.OnGestureListener.class);
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 169511f827ef..86c9ab789429 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -312,6 +312,59 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
}
@Test
+ fun lifecycle_doesNotResumeOnUserInteractivityOnceExpanded() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Shade shows up.
+ shadeTestUtil.setShadeExpansion(1.0f)
+ testableLooper.processAllMessages()
+ underTest.onTouchEvent(DOWN_EVENT)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+
+ // Shade starts collapsing.
+ shadeTestUtil.setShadeExpansion(.5f)
+ testableLooper.processAllMessages()
+ underTest.onTouchEvent(DOWN_EVENT)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+
+ // Shade fully collpase, and then expand should with touch interaction should now
+ // be resumed.
+ shadeTestUtil.setShadeExpansion(0f)
+ testableLooper.processAllMessages()
+ shadeTestUtil.setShadeExpansion(.5f)
+ testableLooper.processAllMessages()
+ underTest.onTouchEvent(DOWN_EVENT)
+ testableLooper.processAllMessages()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+ }
+
+ @Test
+ fun touchHandling_moveEventProcessedAfterCancel() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is open.
+ goToScene(CommunalScenes.Communal)
+
+ // Shade shows up.
+ shadeTestUtil.setQsExpansion(0.5f)
+ testableLooper.processAllMessages()
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(CANCEL_EVENT)).isTrue()
+ assertThat(underTest.onTouchEvent(UP_EVENT)).isFalse()
+ assertThat(underTest.onTouchEvent(MOVE_EVENT)).isTrue()
+ }
+ }
+
+ @Test
fun editMode_communalAvailable() =
with(kosmos) {
testScope.runTest {
@@ -488,5 +541,35 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() {
CONTAINER_HEIGHT.toFloat() / 2,
0
)
+
+ private val CANCEL_EVENT =
+ MotionEvent.obtain(
+ 0L,
+ 0L,
+ MotionEvent.ACTION_CANCEL,
+ CONTAINER_WIDTH.toFloat() / 2,
+ CONTAINER_HEIGHT.toFloat() / 2,
+ 0
+ )
+
+ private val MOVE_EVENT =
+ MotionEvent.obtain(
+ 0L,
+ 0L,
+ MotionEvent.ACTION_MOVE,
+ CONTAINER_WIDTH.toFloat() / 2,
+ CONTAINER_HEIGHT.toFloat() / 2,
+ 0
+ )
+
+ private val UP_EVENT =
+ MotionEvent.obtain(
+ 0L,
+ 0L,
+ MotionEvent.ACTION_UP,
+ CONTAINER_WIDTH.toFloat() / 2,
+ CONTAINER_HEIGHT.toFloat() / 2,
+ 0
+ )
}
}