Loading packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt +8 −0 Original line number Diff line number Diff line Loading @@ -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. Loading packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java +2 −1 Original line number Diff line number Diff line Loading @@ -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(); } } Loading packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java +7 −2 Original line number Diff line number Diff line Loading @@ -128,8 +128,13 @@ public class TouchMonitor { completer.set(predecessor); } if (mActiveTouchSessions.isEmpty() && mStopMonitoringPending) { if (mActiveTouchSessions.isEmpty()) { if (mStopMonitoringPending) { stopMonitoring(false); } else { // restart monitoring to reset any destructive state on the input session startMonitoring(); } } }); Loading packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +33 −9 Original line number Diff line number Diff line Loading @@ -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 /** Loading Loading @@ -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 Loading Loading @@ -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() } ) Loading @@ -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 { Loading Loading @@ -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 } Loading packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java +29 −0 Original line number Diff line number Diff line Loading @@ -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); Loading Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/ambient/touch/ShadeTouchHandlerTest.kt +8 −0 Original line number Diff line number Diff line Loading @@ -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. Loading
packages/SystemUI/src/com/android/systemui/ambient/touch/ShadeTouchHandler.java +2 −1 Original line number Diff line number Diff line Loading @@ -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(); } } Loading
packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java +7 −2 Original line number Diff line number Diff line Loading @@ -128,8 +128,13 @@ public class TouchMonitor { completer.set(predecessor); } if (mActiveTouchSessions.isEmpty() && mStopMonitoringPending) { if (mActiveTouchSessions.isEmpty()) { if (mStopMonitoringPending) { stopMonitoring(false); } else { // restart monitoring to reset any destructive state on the input session startMonitoring(); } } }); Loading
packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +33 −9 Original line number Diff line number Diff line Loading @@ -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 /** Loading Loading @@ -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 Loading Loading @@ -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() } ) Loading @@ -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 { Loading Loading @@ -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 } Loading
packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java +29 −0 Original line number Diff line number Diff line Loading @@ -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); Loading