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