Merge "Fix dragging down notification shade handling over Glanceable Hub." into main
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 5cf4f16..7fd9ce2 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 @@
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 f1fb45c..baca959 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 @@
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 4035e95..efa55e9 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 @@
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 d870fe6..d090aea 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.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 @@
*
* 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 @@
)
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 @@
* 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 @@
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 639b53b..5600b87 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 @@
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 169511f..86c9ab7 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 @@
}
@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 @@
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
+ )
}
}