diff options
3 files changed, 155 insertions, 91 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java index 67e25713987f..4e0f86c8b146 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java @@ -109,9 +109,8 @@ public abstract class DreamOverlayModule { /** */ @Provides @DreamOverlayComponent.DreamOverlayScope - public static TouchInsetManager providesTouchInsetManager(@Main Executor executor, - DreamOverlayContainerView view) { - return new TouchInsetManager(executor, view); + public static TouchInsetManager providesTouchInsetManager(@Main Executor executor) { + return new TouchInsetManager(executor); } /** */ diff --git a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java index 3d07491283c5..8450c1e6e9d0 100644 --- a/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java +++ b/packages/SystemUI/src/com/android/systemui/touch/TouchInsetManager.java @@ -18,8 +18,9 @@ package com.android.systemui.touch; import android.graphics.Rect; import android.graphics.Region; +import android.util.Log; +import android.view.AttachedSurfaceControl; import android.view.View; -import android.view.ViewRootImpl; import androidx.concurrent.futures.CallbackToFutureAdapter; @@ -34,25 +35,37 @@ import java.util.concurrent.Executor; * is useful for passing through touch events for all but select areas. */ public class TouchInsetManager { + private static final String TAG = "TouchInsetManager"; /** * {@link TouchInsetSession} provides an individualized session with the * {@link TouchInsetManager}, linking any action to the client. */ public static class TouchInsetSession { private final TouchInsetManager mManager; - private final HashSet<View> mTrackedViews; private final Executor mExecutor; private final View.OnLayoutChangeListener mOnLayoutChangeListener = (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) - -> updateTouchRegion(); + -> updateTouchRegions(); + + private final View.OnAttachStateChangeListener mAttachListener = + new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + updateTouchRegions(); + } + + @Override + public void onViewDetachedFromWindow(View v) { + updateTouchRegions(); + } + }; /** * Default constructor * @param manager The parent {@link TouchInsetManager} which will be affected by actions on * this session. - * @param rootView The parent of views that will be tracked. * @param executor An executor for marshalling operations. */ TouchInsetSession(TouchInsetManager manager, Executor executor) { @@ -68,8 +81,9 @@ public class TouchInsetManager { public void addViewToTracking(View view) { mExecutor.execute(() -> { mTrackedViews.add(view); + view.addOnAttachStateChangeListener(mAttachListener); view.addOnLayoutChangeListener(mOnLayoutChangeListener); - updateTouchRegion(); + updateTouchRegions(); }); } @@ -81,22 +95,30 @@ public class TouchInsetManager { mExecutor.execute(() -> { mTrackedViews.remove(view); view.removeOnLayoutChangeListener(mOnLayoutChangeListener); - updateTouchRegion(); + view.removeOnAttachStateChangeListener(mAttachListener); + updateTouchRegions(); }); } - private void updateTouchRegion() { - final Region cumulativeRegion = Region.obtain(); - - mTrackedViews.stream().forEach(view -> { - final Rect boundaries = new Rect(); - view.getBoundsOnScreen(boundaries); - cumulativeRegion.op(boundaries, Region.Op.UNION); + private void updateTouchRegions() { + mExecutor.execute(() -> { + final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>(); + mTrackedViews.stream().forEach(view -> { + if (!view.isAttachedToWindow()) { + return; + } + + final AttachedSurfaceControl surface = view.getRootSurfaceControl(); + + if (!affectedSurfaces.containsKey(surface)) { + affectedSurfaces.put(surface, Region.obtain()); + } + final Rect boundaries = new Rect(); + view.getBoundsOnScreen(boundaries); + affectedSurfaces.get(surface).op(boundaries, Region.Op.UNION); + }); + mManager.setTouchRegions(this, affectedSurfaces); }); - - mManager.setTouchRegion(this, cumulativeRegion); - - cumulativeRegion.recycle(); } /** @@ -110,32 +132,17 @@ public class TouchInsetManager { } } - private final HashMap<TouchInsetSession, Region> mDefinedRegions = new HashMap<>(); + private final HashMap<TouchInsetSession, HashMap<AttachedSurfaceControl, Region>> + mSessionRegions = new HashMap<>(); + private final HashMap<AttachedSurfaceControl, Region> mLastAffectedSurfaces = new HashMap(); private final Executor mExecutor; - private final View mRootView; - - private final View.OnAttachStateChangeListener mAttachListener = - new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - updateTouchInset(); - } - - @Override - public void onViewDetachedFromWindow(View v) { - } - }; /** * Default constructor. * @param executor An {@link Executor} to marshal all operations on. - * @param rootView The root {@link View} for all views in sessions. */ - public TouchInsetManager(Executor executor, View rootView) { + public TouchInsetManager(Executor executor) { mExecutor = executor; - mRootView = rootView; - mRootView.addOnAttachStateChangeListener(mAttachListener); - } /** @@ -151,47 +158,68 @@ public class TouchInsetManager { public ListenableFuture<Boolean> checkWithinTouchRegion(int x, int y) { return CallbackToFutureAdapter.getFuture(completer -> { mExecutor.execute(() -> completer.set( - mDefinedRegions.values().stream().anyMatch(region -> region.contains(x, y)))); + mLastAffectedSurfaces.values().stream().anyMatch( + region -> region.contains(x, y)))); return "DreamOverlayTouchMonitor::checkWithinTouchRegion"; }); } - private void updateTouchInset() { - final ViewRootImpl viewRootImpl = mRootView.getViewRootImpl(); - - if (viewRootImpl == null) { - return; - } + private void updateTouchInsets() { + // Get affected + final HashMap<AttachedSurfaceControl, Region> affectedSurfaces = new HashMap<>(); + mSessionRegions.values().stream().forEach(regionMapping -> { + regionMapping.entrySet().stream().forEach(entry -> { + final AttachedSurfaceControl surface = entry.getKey(); + if (!affectedSurfaces.containsKey(surface)) { + affectedSurfaces.put(surface, Region.obtain()); + } - final Region aggregateRegion = Region.obtain(); + affectedSurfaces.get(surface).op(entry.getValue(), Region.Op.UNION); + }); + }); - for (Region region : mDefinedRegions.values()) { - aggregateRegion.op(region, Region.Op.UNION); - } + affectedSurfaces.entrySet().stream().forEach(entry -> { + entry.getKey().setTouchableRegion(entry.getValue()); + }); - viewRootImpl.setTouchableRegion(aggregateRegion); + mLastAffectedSurfaces.entrySet().forEach(entry -> { + final AttachedSurfaceControl surface = entry.getKey(); + if (!affectedSurfaces.containsKey(surface)) { + surface.setTouchableRegion(null); + } + entry.getValue().recycle(); + }); - aggregateRegion.recycle(); + mLastAffectedSurfaces.clear(); + mLastAffectedSurfaces.putAll(affectedSurfaces); } - protected void setTouchRegion(TouchInsetSession session, Region region) { - final Region introducedRegion = Region.obtain(region); + protected void setTouchRegions(TouchInsetSession session, + HashMap<AttachedSurfaceControl, Region> regions) { mExecutor.execute(() -> { - mDefinedRegions.put(session, introducedRegion); - updateTouchInset(); + recycleRegions(session); + mSessionRegions.put(session, regions); + updateTouchInsets(); }); } - private void clearRegion(TouchInsetSession session) { - mExecutor.execute(() -> { - final Region storedRegion = mDefinedRegions.remove(session); + private void recycleRegions(TouchInsetSession session) { + if (!mSessionRegions.containsKey(session)) { + Log.w(TAG, "Removing a session with no regions:" + session); + return; + } - if (storedRegion != null) { - storedRegion.recycle(); - } + for (Region region : mSessionRegions.get(session).values()) { + region.recycle(); + } + } - updateTouchInset(); + private void clearRegion(TouchInsetSession session) { + mExecutor.execute(() -> { + recycleRegions(session); + mSessionRegions.remove(session); + updateTouchInsets(); }); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java index 14b9bfb1393f..a7072225baa7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java @@ -26,8 +26,8 @@ import static org.mockito.Mockito.when; import android.graphics.Rect; import android.graphics.Region; import android.testing.AndroidTestingRunner; +import android.view.AttachedSurfaceControl; import android.view.View; -import android.view.ViewRootImpl; import androidx.test.filters.SmallTest; @@ -47,41 +47,78 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) public class TouchInsetManagerTest extends SysuiTestCase { @Mock - private View mRootView; - - @Mock - private ViewRootImpl mRootViewImpl; + private AttachedSurfaceControl mAttachedSurfaceControl; private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); @Before public void setup() { MockitoAnnotations.initMocks(this); - when(mRootView.getViewRootImpl()).thenReturn(mRootViewImpl); } @Test - public void testRootViewOnAttachedHandling() { + public void testViewOnAttachedHandling() { // Create inset manager - final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor, - mRootView); + final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor); final ArgumentCaptor<View.OnAttachStateChangeListener> listener = ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); + final View view = createView(new Rect(0, 0, 0, 0)); + when(view.isAttachedToWindow()).thenReturn(false); + + + // Create session + final TouchInsetManager.TouchInsetSession session = insetManager.createSession(); + session.addViewToTracking(view); + mFakeExecutor.runAllReady(); // Ensure manager has registered to listen to attached state of root view. - verify(mRootView).addOnAttachStateChangeListener(listener.capture()); + verify(view).addOnAttachStateChangeListener(listener.capture()); + + clearInvocations(mAttachedSurfaceControl); + when(view.isAttachedToWindow()).thenReturn(true); // Trigger attachment and verify touchable region is set. - listener.getValue().onViewAttachedToWindow(mRootView); - verify(mRootViewImpl).setTouchableRegion(any()); + listener.getValue().onViewAttachedToWindow(view); + + mFakeExecutor.runAllReady(); + + verify(mAttachedSurfaceControl).setTouchableRegion(any()); + } + + @Test + public void testViewOnDetachedHandling() { + // Create inset manager + final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor); + + final ArgumentCaptor<View.OnAttachStateChangeListener> listener = + ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); + final View view = createView(new Rect(0, 0, 0, 0)); + when(view.isAttachedToWindow()).thenReturn(true); + + // Create session + final TouchInsetManager.TouchInsetSession session = insetManager.createSession(); + session.addViewToTracking(view); + + mFakeExecutor.runAllReady(); + // Ensure manager has registered to listen to attached state of root view. + verify(view).addOnAttachStateChangeListener(listener.capture()); + + clearInvocations(mAttachedSurfaceControl); + when(view.isAttachedToWindow()).thenReturn(false); + + // Trigger detachment and verify touchable region is set. + listener.getValue().onViewDetachedFromWindow(view); + + mFakeExecutor.runAllReady(); + + verify(mAttachedSurfaceControl).setTouchableRegion(any()); } @Test public void testInsetRegionPropagation() { // Create inset manager - final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor, - mRootView); + final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor); // Create session final TouchInsetManager.TouchInsetSession session = insetManager.createSession(); @@ -95,14 +132,13 @@ public class TouchInsetManagerTest extends SysuiTestCase { // Check to see if view was properly accounted for. final Region expectedRegion = Region.obtain(); expectedRegion.op(rect, Region.Op.UNION); - verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion)); + verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion)); } @Test public void testMultipleRegions() { // Create inset manager - final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor, - mRootView); + final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor); // Create session final TouchInsetManager.TouchInsetSession session = insetManager.createSession(); @@ -112,7 +148,7 @@ public class TouchInsetManagerTest extends SysuiTestCase { session.addViewToTracking(createView(firstBounds)); mFakeExecutor.runAllReady(); - clearInvocations(mRootViewImpl); + clearInvocations(mAttachedSurfaceControl); // Create second session final TouchInsetManager.TouchInsetSession secondSession = insetManager.createSession(); @@ -128,27 +164,26 @@ public class TouchInsetManagerTest extends SysuiTestCase { final Region expectedRegion = Region.obtain(); expectedRegion.op(firstBounds, Region.Op.UNION); expectedRegion.op(secondBounds, Region.Op.UNION); - verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion)); + verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion)); } - clearInvocations(mRootViewImpl); + clearInvocations(mAttachedSurfaceControl); // clear first session, ensure second session is still reflected. session.clear(); mFakeExecutor.runAllReady(); { final Region expectedRegion = Region.obtain(); - expectedRegion.op(firstBounds, Region.Op.UNION); - verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion)); + expectedRegion.op(secondBounds, Region.Op.UNION); + verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion)); } } @Test public void testMultipleViews() { // Create inset manager - final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor, - mRootView); + final TouchInsetManager insetManager = new TouchInsetManager(mFakeExecutor); // Create session final TouchInsetManager.TouchInsetSession session = insetManager.createSession(); @@ -159,7 +194,7 @@ public class TouchInsetManagerTest extends SysuiTestCase { // only capture second invocation. mFakeExecutor.runAllReady(); - clearInvocations(mRootViewImpl); + clearInvocations(mAttachedSurfaceControl); // Add a second view to the session final Rect secondViewBounds = new Rect(4, 4, 9, 10); @@ -173,20 +208,20 @@ public class TouchInsetManagerTest extends SysuiTestCase { final Region expectedRegion = Region.obtain(); expectedRegion.op(firstViewBounds, Region.Op.UNION); expectedRegion.op(secondViewBounds, Region.Op.UNION); - verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion)); + verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion)); } // Remove second view. session.removeViewFromTracking(secondView); - clearInvocations(mRootViewImpl); + clearInvocations(mAttachedSurfaceControl); mFakeExecutor.runAllReady(); // Ensure first view still reflected in touch region. { final Region expectedRegion = Region.obtain(); expectedRegion.op(firstViewBounds, Region.Op.UNION); - verify(mRootViewImpl).setTouchableRegion(eq(expectedRegion)); + verify(mAttachedSurfaceControl).setTouchableRegion(eq(expectedRegion)); } } @@ -197,6 +232,8 @@ public class TouchInsetManagerTest extends SysuiTestCase { ((Rect) invocation.getArgument(0)).set(rect); return null; }).when(view).getBoundsOnScreen(any()); + when(view.isAttachedToWindow()).thenReturn(true); + when(view.getRootSurfaceControl()).thenReturn(mAttachedSurfaceControl); return view; } |