Loading packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -835,3 +835,13 @@ flag { description: "Enforce BaseUserRestriction for DISALLOW_CONFIG_BRIGHTNESS." bug: "329205638" } flag { name: "ambient_touch_monitor_listen_to_display_changes" namespace: "systemui" description: "listen to display changes and cache window metrics" bug: "330906135" metadata { purpose: PURPOSE_BUGFIX } } packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java +23 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.ambient.touch; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static com.android.systemui.shared.Flags.bouncerAreaExclusion; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.graphics.Rect; import android.graphics.Region; Loading @@ -37,7 +38,9 @@ import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import com.android.systemui.Flags; import com.android.systemui.ambient.touch.dagger.InputSessionComponent; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; Loading Loading @@ -69,6 +72,9 @@ public class TouchMonitor { public String TAG = "DreamOverlayTouchMonitor"; private final Executor mMainExecutor; private final Executor mBackgroundExecutor; private final ConfigurationInteractor mConfigurationInteractor; private final Lifecycle mLifecycle; private Rect mExclusionRect = null; Loading @@ -82,6 +88,8 @@ public class TouchMonitor { } }; private Consumer<Rect> mMaxBoundsConsumer = rect -> mMaxBounds = rect; /** * Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures. Loading Loading @@ -262,6 +270,7 @@ public class TouchMonitor { */ private void startMonitoring() { stopMonitoring(true); if (bouncerAreaExclusion()) { mBackgroundExecutor.execute(() -> { try { Loading Loading @@ -340,8 +349,13 @@ public class TouchMonitor { if (!handler.isEnabled()) { continue; } final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(), final Rect maxBounds = Flags.ambientTouchMonitorListenToDisplayChanges() ? mMaxBounds : mDisplayHelper.getMaxBounds(ev.getDisplayId(), TYPE_APPLICATION_OVERLAY); final Region initiationRegion = Region.obtain(); Rect exclusionRect = null; if (bouncerAreaExclusion()) { Loading Loading @@ -478,6 +492,8 @@ public class TouchMonitor { private final int mDisplayId; private final IWindowManager mWindowManagerService; private Rect mMaxBounds; /** * Designated constructor for {@link TouchMonitor} Loading @@ -500,6 +516,7 @@ public class TouchMonitor { Lifecycle lifecycle, InputSessionComponent.Factory inputSessionFactory, DisplayHelper displayHelper, ConfigurationInteractor configurationInteractor, Set<TouchHandler> handlers, IWindowManager windowManagerService, @DisplayId int displayId) { Loading @@ -511,6 +528,7 @@ public class TouchMonitor { mLifecycle = lifecycle; mDisplayHelper = displayHelper; mWindowManagerService = windowManagerService; mConfigurationInteractor = configurationInteractor; } /** Loading @@ -518,6 +536,9 @@ public class TouchMonitor { */ public void init() { mLifecycle.addObserver(mLifecycleObserver); if (Flags.ambientTouchMonitorListenToDisplayChanges()) { collectFlow(mLifecycle, mConfigurationInteractor.getMaxBounds(), mMaxBoundsConsumer); } } private void isolate(Set<TouchSessionImpl> sessions) { Loading packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt +6 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,12 @@ class ConfigurationInteractor @Inject constructor(private val repository: Config } } /** Returns the unadjusted screen size. */ val maxBounds: Flow<Rect> = repository.configurationValues .map { Rect(it.windowConfiguration.maxBounds) } .distinctUntilChanged() /** * Returns screen size adjusted to rotation, so returned screen sizes are stable across all * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on Loading packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java +98 −49 Original line number Diff line number Diff line Loading @@ -24,26 +24,35 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.testing.TestableLooper; import android.view.GestureDetector; import android.view.IWindowManager; import android.view.InputEvent; import android.view.MotionEvent; import android.view.WindowManager; import android.view.WindowMetrics; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import androidx.test.filters.SmallTest; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.ambient.touch.dagger.InputSessionComponent; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.display.DisplayHelper; Loading @@ -67,31 +76,58 @@ import java.util.stream.Stream; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class TouchMonitorTest extends SysuiTestCase { private KosmosJavaAdapter mKosmos; @Before public void setup() { MockitoAnnotations.initMocks(this); mKosmos = new KosmosJavaAdapter(this); } private static class SimpleLifecycleOwner implements LifecycleOwner { LifecycleRegistry mLifecycle = new LifecycleRegistry(this); @NonNull @Override public Lifecycle getLifecycle() { return mLifecycle; } public void setState(Lifecycle.State state) { mLifecycle.setCurrentState(state); } } private static class Environment { private final InputSessionComponent.Factory mInputFactory; private final InputSession mInputSession; private final Lifecycle mLifecycle; private final LifecycleOwner mLifecycleOwner; private final SimpleLifecycleOwner mLifecycleOwner; private final LifecycleRegistry mLifecycleRegistry; private final TouchMonitor mMonitor; private final DefaultLifecycleObserver mLifecycleObserver; private final InputChannelCompat.InputEventListener mEventListener; private final GestureDetector.OnGestureListener mGestureListener; private final DisplayHelper mDisplayHelper; private final DisplayManager mDisplayManager; private final WindowManager mWindowManager; private final WindowMetrics mWindowMetrics; private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); private final Rect mDisplayBounds = Mockito.mock(Rect.class); private final IWindowManager mIWindowManager; Environment(Set<TouchHandler> handlers) { mLifecycle = Mockito.mock(Lifecycle.class); mLifecycleOwner = Mockito.mock(LifecycleOwner.class); private final KosmosJavaAdapter mKosmos; Environment(Set<TouchHandler> handlers, KosmosJavaAdapter kosmos) { mLifecycleOwner = new SimpleLifecycleOwner(); mLifecycleRegistry = spy(new LifecycleRegistry(mLifecycleOwner)); mIWindowManager = Mockito.mock(IWindowManager.class); mDisplayManager = Mockito.mock(DisplayManager.class); mWindowManager = Mockito.mock(WindowManager.class); mKosmos = kosmos; mInputFactory = Mockito.mock(InputSessionComponent.Factory.class); final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class); Loading @@ -104,18 +140,16 @@ public class TouchMonitorTest extends SysuiTestCase { mDisplayHelper = Mockito.mock(DisplayHelper.class); when(mDisplayHelper.getMaxBounds(anyInt(), anyInt())) .thenReturn(mDisplayBounds); mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor, mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0); mMonitor.init(); final ArgumentCaptor<LifecycleObserver> lifecycleObserverCaptor = ArgumentCaptor.forClass(LifecycleObserver.class); verify(mLifecycle).addObserver(lifecycleObserverCaptor.capture()); assertThat(lifecycleObserverCaptor.getValue() instanceof DefaultLifecycleObserver) .isTrue(); mLifecycleObserver = (DefaultLifecycleObserver) lifecycleObserverCaptor.getValue(); mWindowMetrics = Mockito.mock(WindowMetrics.class); when(mWindowMetrics.getBounds()).thenReturn(mDisplayBounds); when(mWindowManager.getMaximumWindowMetrics()).thenReturn(mWindowMetrics); mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor, mLifecycleRegistry, mInputFactory, mDisplayHelper, mKosmos.getConfigurationInteractor(), handlers, mIWindowManager, 0); mMonitor.init(); updateLifecycle(observer -> observer.first.onResume(observer.second)); updateLifecycle(Lifecycle.State.RESUMED); // Capture creation request. final ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor = Loading Loading @@ -145,8 +179,8 @@ public class TouchMonitorTest extends SysuiTestCase { listenerConsumer.accept(mGestureListener); } void updateLifecycle(Consumer<Pair<DefaultLifecycleObserver, LifecycleOwner>> consumer) { consumer.accept(Pair.create(mLifecycleObserver, mLifecycleOwner)); void updateLifecycle(Lifecycle.State state) { mLifecycleRegistry.setCurrentState(state); } void verifyInputSessionDispose() { Loading @@ -156,10 +190,33 @@ public class TouchMonitorTest extends SysuiTestCase { } @Test @EnableFlags(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES) public void testConfigurationListenerUpdatesBounds() { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new)), mKosmos); ArgumentCaptor<DisplayManager.DisplayListener> listenerCaptor = ArgumentCaptor.forClass(DisplayManager.DisplayListener.class); final Rect testRect = new Rect(0, 0, 2, 2); final Configuration configuration = new Configuration(); configuration.windowConfiguration.setMaxBounds(testRect); mKosmos.getConfigurationRepository().onConfigurationChange(configuration); final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); when(initialEvent.getX()).thenReturn(0.0f); when(initialEvent.getY()).thenReturn(0.0f); environment.publishInputEvent(initialEvent); // Verify display bounds passed into TouchHandler#getTouchInitiationRegion verify(touchHandler).getTouchInitiationRegion(eq(testRect), any(), any()); } @Test @DisableFlags(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES) public void testReportedDisplayBounds() { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); when(initialEvent.getX()).thenReturn(0.0f); Loading Loading @@ -190,7 +247,7 @@ public class TouchMonitorTest extends SysuiTestCase { }).when(touchHandler).getTouchInitiationRegion(any(), any(), any()); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); // Ensure touch outside specified region is not delivered. final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); Loading Loading @@ -219,7 +276,7 @@ public class TouchMonitorTest extends SysuiTestCase { }).when(touchHandler).getTouchInitiationRegion(any(), any(), any()); final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); // Ensure touch outside specified region is delivered to unzoned touch handler. final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); Loading Loading @@ -261,7 +318,7 @@ public class TouchMonitorTest extends SysuiTestCase { when(touchHandler.isEnabled()).thenReturn(false); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); when(initialEvent.getX()).thenReturn(5.0f); when(initialEvent.getY()).thenReturn(5.0f); Loading @@ -277,7 +334,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -297,7 +354,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -321,7 +378,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -340,7 +397,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -365,7 +422,7 @@ public class TouchMonitorTest extends SysuiTestCase { when(touchHandler2.isEnabled()).thenReturn(true); final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -389,7 +446,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading Loading @@ -435,7 +492,7 @@ public class TouchMonitorTest extends SysuiTestCase { Mockito.mock(TouchHandler.TouchSession.Callback.class); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -453,11 +510,9 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); environment.updateLifecycle(observerOwnerPair -> { observerOwnerPair.first.onPause(observerOwnerPair.second); }); environment.updateLifecycle(Lifecycle.State.STARTED); environment.verifyInputSessionDispose(); } Loading @@ -467,7 +522,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -486,9 +541,7 @@ public class TouchMonitorTest extends SysuiTestCase { verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture()); environment.updateLifecycle(observerOwnerPair -> { observerOwnerPair.first.onPause(observerOwnerPair.second); }); environment.updateLifecycle(Lifecycle.State.STARTED); verify(environment.mInputSession, never()).dispose(); Loading @@ -505,7 +558,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -524,9 +577,7 @@ public class TouchMonitorTest extends SysuiTestCase { verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture()); environment.updateLifecycle(observerOwnerPair -> { observerOwnerPair.first.onDestroy(observerOwnerPair.second); }); environment.updateLifecycle(Lifecycle.State.DESTROYED); // Check to make sure the input session is now disposed. environment.verifyInputSessionDispose(); Loading @@ -538,7 +589,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler1 = createTouchHandler(); final TouchHandler touchHandler2 = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading Loading @@ -574,7 +625,7 @@ public class TouchMonitorTest extends SysuiTestCase { Mockito.mock(TouchHandler.TouchSession.Callback.class); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -584,9 +635,7 @@ public class TouchMonitorTest extends SysuiTestCase { environment.executeAll(); environment.updateLifecycle(observerOwnerPair -> { observerOwnerPair.first.onDestroy(observerOwnerPair.second); }); environment.updateLifecycle(Lifecycle.State.DESTROYED); environment.executeAll(); Loading packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt +45 −3 Original line number Diff line number Diff line Loading @@ -91,7 +91,7 @@ class ConfigurationInteractorTest : SysuiTestCase() { @Test fun maxBoundsChange_emitsMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) val values by collectValues(underTest.maxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() Loading @@ -109,7 +109,7 @@ class ConfigurationInteractorTest : SysuiTestCase() { @Test fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) val values by collectValues(underTest.maxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() Loading @@ -121,6 +121,48 @@ class ConfigurationInteractorTest : SysuiTestCase() { @Test fun firstMaxBoundsChange_emitsMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.maxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)) } @Test fun maxBoundsChange_emitsNaturalMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3) runCurrent() assertThat(values) .containsExactly( Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT), Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3), ) .inOrder() } @Test fun maxBoundsSameOnConfigChange_doesNotEmitNaturalMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)) } @Test fun firstMaxBoundsChange_emitsNaturalMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) Loading @@ -131,7 +173,7 @@ class ConfigurationInteractorTest : SysuiTestCase() { } @Test fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() = fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewNaturalMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) Loading Loading
packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -835,3 +835,13 @@ flag { description: "Enforce BaseUserRestriction for DISALLOW_CONFIG_BRIGHTNESS." bug: "329205638" } flag { name: "ambient_touch_monitor_listen_to_display_changes" namespace: "systemui" description: "listen to display changes and cache window metrics" bug: "330906135" metadata { purpose: PURPOSE_BUGFIX } }
packages/SystemUI/src/com/android/systemui/ambient/touch/TouchMonitor.java +23 −2 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.ambient.touch; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static com.android.systemui.shared.Flags.bouncerAreaExclusion; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.graphics.Rect; import android.graphics.Region; Loading @@ -37,7 +38,9 @@ import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import com.android.systemui.Flags; import com.android.systemui.ambient.touch.dagger.InputSessionComponent; import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; Loading Loading @@ -69,6 +72,9 @@ public class TouchMonitor { public String TAG = "DreamOverlayTouchMonitor"; private final Executor mMainExecutor; private final Executor mBackgroundExecutor; private final ConfigurationInteractor mConfigurationInteractor; private final Lifecycle mLifecycle; private Rect mExclusionRect = null; Loading @@ -82,6 +88,8 @@ public class TouchMonitor { } }; private Consumer<Rect> mMaxBoundsConsumer = rect -> mMaxBounds = rect; /** * Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures. Loading Loading @@ -262,6 +270,7 @@ public class TouchMonitor { */ private void startMonitoring() { stopMonitoring(true); if (bouncerAreaExclusion()) { mBackgroundExecutor.execute(() -> { try { Loading Loading @@ -340,8 +349,13 @@ public class TouchMonitor { if (!handler.isEnabled()) { continue; } final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(), final Rect maxBounds = Flags.ambientTouchMonitorListenToDisplayChanges() ? mMaxBounds : mDisplayHelper.getMaxBounds(ev.getDisplayId(), TYPE_APPLICATION_OVERLAY); final Region initiationRegion = Region.obtain(); Rect exclusionRect = null; if (bouncerAreaExclusion()) { Loading Loading @@ -478,6 +492,8 @@ public class TouchMonitor { private final int mDisplayId; private final IWindowManager mWindowManagerService; private Rect mMaxBounds; /** * Designated constructor for {@link TouchMonitor} Loading @@ -500,6 +516,7 @@ public class TouchMonitor { Lifecycle lifecycle, InputSessionComponent.Factory inputSessionFactory, DisplayHelper displayHelper, ConfigurationInteractor configurationInteractor, Set<TouchHandler> handlers, IWindowManager windowManagerService, @DisplayId int displayId) { Loading @@ -511,6 +528,7 @@ public class TouchMonitor { mLifecycle = lifecycle; mDisplayHelper = displayHelper; mWindowManagerService = windowManagerService; mConfigurationInteractor = configurationInteractor; } /** Loading @@ -518,6 +536,9 @@ public class TouchMonitor { */ public void init() { mLifecycle.addObserver(mLifecycleObserver); if (Flags.ambientTouchMonitorListenToDisplayChanges()) { collectFlow(mLifecycle, mConfigurationInteractor.getMaxBounds(), mMaxBoundsConsumer); } } private void isolate(Set<TouchSessionImpl> sessions) { Loading
packages/SystemUI/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractor.kt +6 −0 Original line number Diff line number Diff line Loading @@ -48,6 +48,12 @@ class ConfigurationInteractor @Inject constructor(private val repository: Config } } /** Returns the unadjusted screen size. */ val maxBounds: Flow<Rect> = repository.configurationValues .map { Rect(it.windowConfiguration.maxBounds) } .distinctUntilChanged() /** * Returns screen size adjusted to rotation, so returned screen sizes are stable across all * rotations, could be useful if you need to react to screen resize (e.g. fold/unfold on Loading
packages/SystemUI/tests/src/com/android/systemui/ambient/touch/TouchMonitorTest.java +98 −49 Original line number Diff line number Diff line Loading @@ -24,26 +24,35 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManager; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.testing.TestableLooper; import android.view.GestureDetector; import android.view.IWindowManager; import android.view.InputEvent; import android.view.MotionEvent; import android.view.WindowManager; import android.view.WindowMetrics; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.annotation.NonNull; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import androidx.test.filters.SmallTest; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.ambient.touch.dagger.InputSessionComponent; import com.android.systemui.kosmos.KosmosJavaAdapter; import com.android.systemui.shared.system.InputChannelCompat; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.display.DisplayHelper; Loading @@ -67,31 +76,58 @@ import java.util.stream.Stream; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class TouchMonitorTest extends SysuiTestCase { private KosmosJavaAdapter mKosmos; @Before public void setup() { MockitoAnnotations.initMocks(this); mKosmos = new KosmosJavaAdapter(this); } private static class SimpleLifecycleOwner implements LifecycleOwner { LifecycleRegistry mLifecycle = new LifecycleRegistry(this); @NonNull @Override public Lifecycle getLifecycle() { return mLifecycle; } public void setState(Lifecycle.State state) { mLifecycle.setCurrentState(state); } } private static class Environment { private final InputSessionComponent.Factory mInputFactory; private final InputSession mInputSession; private final Lifecycle mLifecycle; private final LifecycleOwner mLifecycleOwner; private final SimpleLifecycleOwner mLifecycleOwner; private final LifecycleRegistry mLifecycleRegistry; private final TouchMonitor mMonitor; private final DefaultLifecycleObserver mLifecycleObserver; private final InputChannelCompat.InputEventListener mEventListener; private final GestureDetector.OnGestureListener mGestureListener; private final DisplayHelper mDisplayHelper; private final DisplayManager mDisplayManager; private final WindowManager mWindowManager; private final WindowMetrics mWindowMetrics; private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); private final Rect mDisplayBounds = Mockito.mock(Rect.class); private final IWindowManager mIWindowManager; Environment(Set<TouchHandler> handlers) { mLifecycle = Mockito.mock(Lifecycle.class); mLifecycleOwner = Mockito.mock(LifecycleOwner.class); private final KosmosJavaAdapter mKosmos; Environment(Set<TouchHandler> handlers, KosmosJavaAdapter kosmos) { mLifecycleOwner = new SimpleLifecycleOwner(); mLifecycleRegistry = spy(new LifecycleRegistry(mLifecycleOwner)); mIWindowManager = Mockito.mock(IWindowManager.class); mDisplayManager = Mockito.mock(DisplayManager.class); mWindowManager = Mockito.mock(WindowManager.class); mKosmos = kosmos; mInputFactory = Mockito.mock(InputSessionComponent.Factory.class); final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class); Loading @@ -104,18 +140,16 @@ public class TouchMonitorTest extends SysuiTestCase { mDisplayHelper = Mockito.mock(DisplayHelper.class); when(mDisplayHelper.getMaxBounds(anyInt(), anyInt())) .thenReturn(mDisplayBounds); mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor, mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0); mMonitor.init(); final ArgumentCaptor<LifecycleObserver> lifecycleObserverCaptor = ArgumentCaptor.forClass(LifecycleObserver.class); verify(mLifecycle).addObserver(lifecycleObserverCaptor.capture()); assertThat(lifecycleObserverCaptor.getValue() instanceof DefaultLifecycleObserver) .isTrue(); mLifecycleObserver = (DefaultLifecycleObserver) lifecycleObserverCaptor.getValue(); mWindowMetrics = Mockito.mock(WindowMetrics.class); when(mWindowMetrics.getBounds()).thenReturn(mDisplayBounds); when(mWindowManager.getMaximumWindowMetrics()).thenReturn(mWindowMetrics); mMonitor = new TouchMonitor(mExecutor, mBackgroundExecutor, mLifecycleRegistry, mInputFactory, mDisplayHelper, mKosmos.getConfigurationInteractor(), handlers, mIWindowManager, 0); mMonitor.init(); updateLifecycle(observer -> observer.first.onResume(observer.second)); updateLifecycle(Lifecycle.State.RESUMED); // Capture creation request. final ArgumentCaptor<InputChannelCompat.InputEventListener> inputEventListenerCaptor = Loading Loading @@ -145,8 +179,8 @@ public class TouchMonitorTest extends SysuiTestCase { listenerConsumer.accept(mGestureListener); } void updateLifecycle(Consumer<Pair<DefaultLifecycleObserver, LifecycleOwner>> consumer) { consumer.accept(Pair.create(mLifecycleObserver, mLifecycleOwner)); void updateLifecycle(Lifecycle.State state) { mLifecycleRegistry.setCurrentState(state); } void verifyInputSessionDispose() { Loading @@ -156,10 +190,33 @@ public class TouchMonitorTest extends SysuiTestCase { } @Test @EnableFlags(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES) public void testConfigurationListenerUpdatesBounds() { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new)), mKosmos); ArgumentCaptor<DisplayManager.DisplayListener> listenerCaptor = ArgumentCaptor.forClass(DisplayManager.DisplayListener.class); final Rect testRect = new Rect(0, 0, 2, 2); final Configuration configuration = new Configuration(); configuration.windowConfiguration.setMaxBounds(testRect); mKosmos.getConfigurationRepository().onConfigurationChange(configuration); final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); when(initialEvent.getX()).thenReturn(0.0f); when(initialEvent.getY()).thenReturn(0.0f); environment.publishInputEvent(initialEvent); // Verify display bounds passed into TouchHandler#getTouchInitiationRegion verify(touchHandler).getTouchInitiationRegion(eq(testRect), any(), any()); } @Test @DisableFlags(Flags.FLAG_AMBIENT_TOUCH_MONITOR_LISTEN_TO_DISPLAY_CHANGES) public void testReportedDisplayBounds() { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); when(initialEvent.getX()).thenReturn(0.0f); Loading Loading @@ -190,7 +247,7 @@ public class TouchMonitorTest extends SysuiTestCase { }).when(touchHandler).getTouchInitiationRegion(any(), any(), any()); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); // Ensure touch outside specified region is not delivered. final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); Loading Loading @@ -219,7 +276,7 @@ public class TouchMonitorTest extends SysuiTestCase { }).when(touchHandler).getTouchInitiationRegion(any(), any(), any()); final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); // Ensure touch outside specified region is delivered to unzoned touch handler. final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); Loading Loading @@ -261,7 +318,7 @@ public class TouchMonitorTest extends SysuiTestCase { when(touchHandler.isEnabled()).thenReturn(false); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final MotionEvent initialEvent = Mockito.mock(MotionEvent.class); when(initialEvent.getX()).thenReturn(5.0f); when(initialEvent.getY()).thenReturn(5.0f); Loading @@ -277,7 +334,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -297,7 +354,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -321,7 +378,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -340,7 +397,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -365,7 +422,7 @@ public class TouchMonitorTest extends SysuiTestCase { when(touchHandler2.isEnabled()).thenReturn(true); final Environment environment = new Environment(Stream.of(touchHandler, touchHandler2) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -389,7 +446,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading Loading @@ -435,7 +492,7 @@ public class TouchMonitorTest extends SysuiTestCase { Mockito.mock(TouchHandler.TouchSession.Callback.class); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -453,11 +510,9 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); environment.updateLifecycle(observerOwnerPair -> { observerOwnerPair.first.onPause(observerOwnerPair.second); }); environment.updateLifecycle(Lifecycle.State.STARTED); environment.verifyInputSessionDispose(); } Loading @@ -467,7 +522,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -486,9 +541,7 @@ public class TouchMonitorTest extends SysuiTestCase { verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture()); environment.updateLifecycle(observerOwnerPair -> { observerOwnerPair.first.onPause(observerOwnerPair.second); }); environment.updateLifecycle(Lifecycle.State.STARTED); verify(environment.mInputSession, never()).dispose(); Loading @@ -505,7 +558,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -524,9 +577,7 @@ public class TouchMonitorTest extends SysuiTestCase { verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture()); environment.updateLifecycle(observerOwnerPair -> { observerOwnerPair.first.onDestroy(observerOwnerPair.second); }); environment.updateLifecycle(Lifecycle.State.DESTROYED); // Check to make sure the input session is now disposed. environment.verifyInputSessionDispose(); Loading @@ -538,7 +589,7 @@ public class TouchMonitorTest extends SysuiTestCase { final TouchHandler touchHandler1 = createTouchHandler(); final TouchHandler touchHandler2 = createTouchHandler(); final Environment environment = new Environment(Stream.of(touchHandler1, touchHandler2) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading Loading @@ -574,7 +625,7 @@ public class TouchMonitorTest extends SysuiTestCase { Mockito.mock(TouchHandler.TouchSession.Callback.class); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); .collect(Collectors.toCollection(HashSet::new)), mKosmos); final InputEvent initialEvent = Mockito.mock(InputEvent.class); environment.publishInputEvent(initialEvent); Loading @@ -584,9 +635,7 @@ public class TouchMonitorTest extends SysuiTestCase { environment.executeAll(); environment.updateLifecycle(observerOwnerPair -> { observerOwnerPair.first.onDestroy(observerOwnerPair.second); }); environment.updateLifecycle(Lifecycle.State.DESTROYED); environment.executeAll(); Loading
packages/SystemUI/tests/src/com/android/systemui/common/ui/domain/interactor/ConfigurationInteractorTest.kt +45 −3 Original line number Diff line number Diff line Loading @@ -91,7 +91,7 @@ class ConfigurationInteractorTest : SysuiTestCase() { @Test fun maxBoundsChange_emitsMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) val values by collectValues(underTest.maxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() Loading @@ -109,7 +109,7 @@ class ConfigurationInteractorTest : SysuiTestCase() { @Test fun maxBoundsSameOnConfigChange_doesNotEmitMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) val values by collectValues(underTest.maxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() Loading @@ -121,6 +121,48 @@ class ConfigurationInteractorTest : SysuiTestCase() { @Test fun firstMaxBoundsChange_emitsMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.maxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)) } @Test fun maxBoundsChange_emitsNaturalMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() updateDisplay(width = DISPLAY_WIDTH * 2, height = DISPLAY_HEIGHT * 3) runCurrent() assertThat(values) .containsExactly( Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT), Rect(0, 0, DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 3), ) .inOrder() } @Test fun maxBoundsSameOnConfigChange_doesNotEmitNaturalMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() updateDisplay(width = DISPLAY_WIDTH, height = DISPLAY_HEIGHT) runCurrent() assertThat(values).containsExactly(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT)) } @Test fun firstMaxBoundsChange_emitsNaturalMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) Loading @@ -131,7 +173,7 @@ class ConfigurationInteractorTest : SysuiTestCase() { } @Test fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewMaxBoundsChange() = fun displayRotatedButMaxBoundsTheSame_doesNotEmitNewNaturalMaxBoundsChange() = testScope.runTest { val values by collectValues(underTest.naturalMaxBounds) Loading