Allow in-progress touch sessions to continue outside resume.

Currently, touch sessions are terminated outside the resume lifecycle
state. This behavior causes issues when a touch behavior is directly
leading to another state, such as dragging down the notification shade.
This changelist allows in-progress touch sessions to continue until
the session is popped. Note that lifecycle states here are used
abstractly and do not correspond to Activity usage. Also, touch
sessions are still forcefully ended in the case of resetting the touch
monitor due to the destroy lifecycle state or from starting monitoring
again.

Test: atest DreamOverlayTouchMonitorTest#testPauseWithNoActiveSessions
      atest DreamOverlayTouchMonitorTest#testDeferredPauseWithActiveSessions
      atest DreamOverlayTouchMonitorTest#testDestroyWithActiveSessions
Bug: 267565290
Change-Id: Ibac6e94bcf6b8c4d751349edfbcb04b2fe53285a
This commit is contained in:
Bryce Lee
2023-03-16 20:55:04 -07:00
parent 9c52c83981
commit 5d0b6e3b19
2 changed files with 81 additions and 5 deletions

View File

@@ -101,6 +101,10 @@ public class DreamOverlayTouchMonitor {
completer.set(predecessor);
}
if (mActiveTouchSessions.isEmpty() && mStopMonitoringPending) {
stopMonitoring(false);
}
});
return "DreamOverlayTouchMonitor::pop";
@@ -214,7 +218,12 @@ public class DreamOverlayTouchMonitor {
@Override
public void onPause(@NonNull LifecycleOwner owner) {
stopMonitoring();
stopMonitoring(false);
}
@Override
public void onDestroy(LifecycleOwner owner) {
stopMonitoring(true);
}
};
@@ -222,7 +231,7 @@ public class DreamOverlayTouchMonitor {
* When invoked, instantiates a new {@link InputSession} to monitor touch events.
*/
private void startMonitoring() {
stopMonitoring();
stopMonitoring(true);
mCurrentInputSession = mInputSessionFactory.create(
"dreamOverlay",
mInputEventListener,
@@ -234,11 +243,16 @@ public class DreamOverlayTouchMonitor {
/**
* Destroys any active {@link InputSession}.
*/
private void stopMonitoring() {
private void stopMonitoring(boolean force) {
if (mCurrentInputSession == null) {
return;
}
if (!mActiveTouchSessions.isEmpty() && !force) {
mStopMonitoringPending = true;
return;
}
// When we stop monitoring touches, we must ensure that all active touch sessions and
// descendants informed of the removal so any cleanup for active tracking can proceed.
mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> {
@@ -250,6 +264,7 @@ public class DreamOverlayTouchMonitor {
mCurrentInputSession.dispose();
mCurrentInputSession = null;
mStopMonitoringPending = false;
}
@@ -257,6 +272,8 @@ public class DreamOverlayTouchMonitor {
private final Collection<DreamTouchHandler> mHandlers;
private final DisplayHelper mDisplayHelper;
private boolean mStopMonitoringPending;
private InputChannelCompat.InputEventListener mInputEventListener =
new InputChannelCompat.InputEventListener() {
@Override

View File

@@ -399,7 +399,21 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
}
@Test
public void testPause() {
public void testPauseWithNoActiveSessions() {
final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
environment.updateLifecycle(observerOwnerPair -> {
observerOwnerPair.first.onPause(observerOwnerPair.second);
});
environment.verifyInputSessionDispose();
}
@Test
public void testDeferredPauseWithActiveSessions() {
final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
final Environment environment = new Environment(Stream.of(touchHandler)
@@ -417,13 +431,58 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
environment.publishInputEvent(event);
verify(eventListener).onInputEvent(eq(event));
final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
environment.updateLifecycle(observerOwnerPair -> {
observerOwnerPair.first.onPause(observerOwnerPair.second);
});
verify(environment.mInputSession, never()).dispose();
// End session
touchSessionArgumentCaptor.getValue().pop();
environment.executeAll();
// Check to make sure the input session is now disposed.
environment.verifyInputSessionDispose();
}
@Test
public void testDestroyWithActiveSessions() {
final DreamTouchHandler touchHandler = Mockito.mock(DreamTouchHandler.class);
final Environment environment = new Environment(Stream.of(touchHandler)
.collect(Collectors.toCollection(HashSet::new)));
final InputEvent initialEvent = Mockito.mock(InputEvent.class);
environment.publishInputEvent(initialEvent);
// Ensure session started
final InputChannelCompat.InputEventListener eventListener =
registerInputEventListener(touchHandler);
// First event will be missed since we register after the execution loop,
final InputEvent event = Mockito.mock(InputEvent.class);
environment.publishInputEvent(event);
verify(eventListener).onInputEvent(eq(event));
final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor =
ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class);
verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture());
environment.updateLifecycle(observerOwnerPair -> {
observerOwnerPair.first.onDestroy(observerOwnerPair.second);
});
// Check to make sure the input session is now disposed.
environment.verifyInputSessionDispose();
}
@Test
public void testPilfering() {
final DreamTouchHandler touchHandler1 = Mockito.mock(DreamTouchHandler.class);
@@ -476,7 +535,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase {
environment.executeAll();
environment.updateLifecycle(observerOwnerPair -> {
observerOwnerPair.first.onPause(observerOwnerPair.second);
observerOwnerPair.first.onDestroy(observerOwnerPair.second);
});
environment.executeAll();