From a4ffa0ac9abe7ca63e4545f8cd8eefd01586d502 Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Tue, 17 Aug 2021 19:38:46 -0700 Subject: [PATCH] Exclude CommunalSurfaceView tap region from keyguard. This changelist excludes the fullscreen tap region (minus header and footer margin) when communal is visible. This allows touches to pass through the notification shade window to the CommunalSurfaceView underneath. Note that this currently has no effect on the CommunalSurfaceView as it is on top of the z-order. However, subsequent changes will move the CommunalSurfaceView into the correct z-order. Test: CommunalSurfaceViewControllerTest#testTapExclusion Bug: 197026983 Change-Id: Ib80bdbb788e20756c18646833c23f844273af82c --- .../communal/CommunalHostViewController.java | 15 +++- .../communal/service/CommunalSourceImpl.java | 30 +++++-- .../CommunalSurfaceViewController.java | 67 +++++++++++++- .../NotificationShadeWindowController.java | 4 + ...NotificationShadeWindowControllerImpl.java | 17 ++++ .../CommunalHostViewControllerTest.java | 4 + .../CommunalSurfaceViewControllerTest.java | 90 +++++++++++++++++-- 7 files changed, 212 insertions(+), 15 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java b/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java index 1680e42691633..07f765b010276 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalHostViewController.java @@ -27,6 +27,7 @@ import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardVisibilityHelper; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -121,6 +122,11 @@ public class CommunalHostViewController extends ViewController setState(STATE_DOZING, isDozing); } + + @Override + public void onStateChanged(int newState) { + updateCommunalViewOccluded(); + } }; @Inject @@ -195,6 +201,8 @@ public class CommunalHostViewController extends ViewController if (existingState != mState) { showSource(); } + + updateCommunalViewOccluded(); } private String describeState(@State int stateFlag) { @@ -308,7 +316,12 @@ public class CommunalHostViewController extends ViewController } private void updateCommunalViewOccluded() { + final boolean bouncerShowing = (mState & STATE_BOUNCER_SHOWING) == STATE_BOUNCER_SHOWING; + final int statusBarState = mStatusBarStateController.getState(); + final boolean shadeExpanded = statusBarState == StatusBarState.SHADE + || statusBarState == StatusBarState.SHADE_LOCKED; + mCommunalStateController.setCommunalViewOccluded( - mQsExpansion > 0.0f || mShadeExpansion > 0.0f); + bouncerShowing || shadeExpanded || mQsExpansion > 0.0f || mShadeExpansion > 0.0f); } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java index df368c337ce43..d8bf2dc73c3dd 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java +++ b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSourceImpl.java @@ -17,6 +17,7 @@ package com.android.systemui.communal.service; import android.content.Context; +import android.content.res.Resources; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -30,6 +31,7 @@ import com.android.systemui.communal.CommunalStateController; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.shared.communal.ICommunalSource; import com.android.systemui.shared.communal.ICommunalSurfaceCallback; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.google.android.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; @@ -47,23 +49,32 @@ import javax.inject.Inject; */ public class CommunalSourceImpl implements CommunalSource { private static final String TAG = "CommunalSourceImpl"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final ICommunalSource mSourceProxy; + private final Resources mResources; private final Executor mMainExecutor; + private final NotificationShadeWindowController mNotificationShadeWindowController; private final CommunalStateController mCommunalStateController; static class Factory { private final Executor mExecutor; + private final Resources mResources; private final CommunalStateController mCommunalStateController; + private final NotificationShadeWindowController mNotificationShadeWindowController; @Inject - Factory(@Main Executor executor, CommunalStateController communalStateController) { + Factory(@Main Executor executor, @Main Resources resources, + NotificationShadeWindowController notificationShadeWindowController, + CommunalStateController communalStateController) { mExecutor = executor; + mResources = resources; + mNotificationShadeWindowController = notificationShadeWindowController; mCommunalStateController = communalStateController; } public CommunalSource create(ICommunalSource source) { - return new CommunalSourceImpl(mExecutor, mCommunalStateController, source); + return new CommunalSourceImpl(mExecutor, mResources, mCommunalStateController, + mNotificationShadeWindowController, source); } } @@ -75,10 +86,14 @@ public class CommunalSourceImpl implements CommunalSource { // A list of {@link Callback} that have registered to receive updates. private final ArrayList> mCallbacks = Lists.newArrayList(); - public CommunalSourceImpl(Executor mainExecutor, - CommunalStateController communalStateController, ICommunalSource sourceProxy) { + public CommunalSourceImpl(Executor mainExecutor, Resources resources, + CommunalStateController communalStateController, + NotificationShadeWindowController notificationShadeWindowController, + ICommunalSource sourceProxy) { mMainExecutor = mainExecutor; mCommunalStateController = communalStateController; + mNotificationShadeWindowController = notificationShadeWindowController; + mResources = resources; mSourceProxy = sourceProxy; try { @@ -120,8 +135,9 @@ public class CommunalSourceImpl implements CommunalSource { CallbackToFutureAdapter.getFuture(completer -> { final SurfaceView view = new SurfaceView(context); completer.set(new CommunalViewResult(view, - new CommunalSurfaceViewController(view, mMainExecutor, - mCommunalStateController, this))); + new CommunalSurfaceViewController(view, mResources, mMainExecutor, + mCommunalStateController, mNotificationShadeWindowController, + this))); return "CommunalSourceImpl::requestCommunalSurface::getCommunalSurface"; }); diff --git a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java index 8a67744f0325b..89239cfd13491 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java +++ b/packages/SystemUI/src/com/android/systemui/communal/service/CommunalSurfaceViewController.java @@ -17,14 +17,21 @@ package com.android.systemui.communal.service; import android.annotation.IntDef; +import android.content.res.Resources; +import android.graphics.Region; import android.util.Log; +import android.view.IWindow; import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; +import android.view.View; import androidx.annotation.NonNull; +import com.android.systemui.R; import com.android.systemui.communal.CommunalStateController; +import com.android.systemui.statusbar.NotificationShadeWindowController; +import com.android.systemui.util.Utils; import com.android.systemui.util.ViewController; import com.google.common.util.concurrent.ListenableFuture; @@ -40,7 +47,10 @@ public class CommunalSurfaceViewController extends ViewController { private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Executor mMainExecutor; private final CommunalStateController mCommunalStateController; + private final NotificationShadeWindowController mNotificationShadeWindowController; private final CommunalSourceImpl mSource; + private final Resources mResources; + private final Region mSurfaceViewTouchableRegion; @IntDef({STATE_SURFACE_CREATED, STATE_SURFACE_VIEW_ATTACHED}) private @interface State {} @@ -73,18 +83,51 @@ public class CommunalSurfaceViewController extends ViewController { } }; - protected CommunalSurfaceViewController(SurfaceView view, Executor executor, - CommunalStateController communalStateController, CommunalSourceImpl source) { + private final View.OnLayoutChangeListener mOnLayoutChangeListener = + new View.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, + int oldTop, int oldRight, int oldBottom) { + // The margin for the status bar and keyguard indication are excluded from the tap + // exclusion to preserve vertical swipes in this region. + final int topMargin = mResources.getDimensionPixelSize( + Utils.shouldUseSplitNotificationShade(mResources) + ? R.dimen.split_shade_header_height + : R.dimen.notification_panel_margin_top); + final int bottomMargin = mResources.getDimensionPixelSize( + R.dimen.keyguard_indication_bottom_padding); + + mSurfaceViewTouchableRegion.set(left, top + topMargin, right, bottom - bottomMargin); + updateTouchExclusion(); + } + }; + + private CommunalStateController.Callback mCommunalStateCallback = + new CommunalStateController.Callback() { + @Override + public void onCommunalViewOccludedChanged() { + updateTouchExclusion(); + } + }; + + protected CommunalSurfaceViewController(SurfaceView view, Resources resources, + Executor executor, CommunalStateController communalStateController, + NotificationShadeWindowController notificationShadeWindowController, + CommunalSourceImpl source) { super(view); mCommunalStateController = communalStateController; mSource = source; + mResources = resources; mMainExecutor = executor; + mNotificationShadeWindowController = notificationShadeWindowController; + mSurfaceViewTouchableRegion = new Region(); } @Override public void init() { super.init(); mView.getHolder().addCallback(mSurfaceHolderCallback); + mView.addOnLayoutChangeListener(mOnLayoutChangeListener); } private void setState(@State int state, boolean enabled) { @@ -106,6 +149,24 @@ public class CommunalSurfaceViewController extends ViewController { mCurrentState = newState; showSurface(newState == STATE_CAN_SHOW_SURFACE); + + updateTouchExclusion(); + } + + private void updateTouchExclusion() { + final IWindow window = IWindow.Stub.asInterface(mView.getWindowToken()); + final boolean excludeTouches = (mCurrentState & STATE_SURFACE_VIEW_ATTACHED) != 0 + && !mCommunalStateController.getCommunalViewOccluded(); + if (excludeTouches) { + mNotificationShadeWindowController.setTouchExclusionRegion(mSurfaceViewTouchableRegion); + } else { + final Region emptyRegion = Region.obtain(); + mNotificationShadeWindowController.setTouchExclusionRegion(emptyRegion); + emptyRegion.recycle(); + } + // TODO(b/197036940): This is no longer necessary once the surface view is not on top of the + // z-order. + mView.setZOrderOnTop(excludeTouches); } private void showSurface(boolean show) { @@ -163,10 +224,12 @@ public class CommunalSurfaceViewController extends ViewController { @Override protected void onViewAttached() { setState(STATE_SURFACE_VIEW_ATTACHED, true); + mCommunalStateController.addCallback(mCommunalStateCallback); } @Override protected void onViewDetached() { + mCommunalStateController.removeCallback(mCommunalStateCallback); setState(STATE_SURFACE_VIEW_ATTACHED, false); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index 6ea79af8b9ad9..4adf2bc836c46 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -16,6 +16,7 @@ package com.android.systemui.statusbar; +import android.graphics.Region; import android.view.ViewGroup; import androidx.annotation.Nullable; @@ -158,6 +159,9 @@ public interface NotificationShadeWindowController extends RemoteInputController /** Sets the state of whether the notification shade is touchable or not. */ default void setNotTouchable(boolean notTouchable) {} + /** Sets the region where touch is excluded from the parent window. */ + default void setTouchExclusionRegion(Region region) {} + /** Sets a {@link OtherwisedCollapsedListener}. */ default void setStateListener(OtherwisedCollapsedListener listener) {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 030a8951943d3..e1cb9f68ecd28 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -27,16 +27,20 @@ import android.app.IActivityManager; import android.content.Context; import android.content.pm.ActivityInfo; import android.graphics.PixelFormat; +import android.graphics.Region; import android.os.Binder; import android.os.RemoteException; import android.os.Trace; import android.util.Log; import android.view.Display; import android.view.Gravity; +import android.view.IWindow; +import android.view.IWindowSession; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; +import android.view.WindowManagerGlobal; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.Dumpable; @@ -506,6 +510,19 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } } + @Override + public void setTouchExclusionRegion(Region region) { + try { + final IWindowSession session = WindowManagerGlobal.getWindowSession(); + session.updateTapExcludeRegion( + IWindow.Stub.asInterface(getNotificationShadeView().getWindowToken()), + region); + } catch (RemoteException e) { + Log.e(TAG, "could not update the tap exclusion region:" + e); + } + } + + @Override public void setKeyguardShowing(boolean showing) { mCurrentState.mKeyguardShowing = showing; diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java index 9c6c04407bbeb..b7128325a6407 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/CommunalHostViewControllerTest.java @@ -29,6 +29,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.statusbar.StatusBarStateController; +import com.android.systemui.statusbar.StatusBarState; import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; import com.android.systemui.statusbar.policy.KeyguardStateController; @@ -82,12 +83,14 @@ public class CommunalHostViewControllerTest extends SysuiTestCase { when(mKeyguardStateController.isShowing()).thenReturn(true); when(mCommunalView.isAttachedToWindow()).thenReturn(true); + when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); mController = new CommunalHostViewController(mFakeExecutor, mCommunalStateController, mKeyguardUpdateMonitor, mKeyguardStateController, mDozeParameters, mUnlockedScreenOffAnimationController, mStatusBarStateController, mCommunalView); mController.init(); mFakeExecutor.runAllReady(); + Mockito.clearInvocations(mCommunalView); } @@ -170,6 +173,7 @@ public class CommunalHostViewControllerTest extends SysuiTestCase { public void testReportOcclusion() { // Ensure CommunalHostViewController reports view occluded when either the QS or Shade is // expanded. + clearInvocations(mCommunalStateController); mController.updateShadeExpansion(0); verify(mCommunalStateController).setCommunalViewOccluded(false); clearInvocations(mCommunalStateController); diff --git a/packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java index a31e54350d7e9..6ca1357bef20c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/communal/service/CommunalSurfaceViewControllerTest.java @@ -19,21 +19,27 @@ package com.android.systemui.communal.service; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.content.res.Resources; +import android.graphics.Region; import android.os.IBinder; import android.view.Display; import android.view.SurfaceControlViewHost; import android.view.SurfaceHolder; import android.view.SurfaceView; +import android.view.View; import androidx.test.filters.SmallTest; +import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.communal.CommunalStateController; +import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.time.FakeSystemClock; @@ -50,6 +56,9 @@ public class CommunalSurfaceViewControllerTest extends SysuiTestCase { private static final int MEASURED_HEIGHT = 200; private static final int MEASURED_WIDTH = 500; private static final int DISPLAY_ID = 3; + private static final int SPLIT_NOTIFICATION_STATUS_BAR_HEIGHT = 23; + private static final int NOTIFICATION_PANEL_MARGIN_TOP = 20; + private static final int KEYGUARD_INDICATION_BOTTOM_PADDING = 15; @Mock private Display mDisplay; @@ -72,6 +81,15 @@ public class CommunalSurfaceViewControllerTest extends SysuiTestCase { @Mock private CommunalStateController mCommunalStateController; + @Mock + private Resources mResources; + + @Mock + private NotificationShadeWindowController mNotificationShadeWindowController; + + @Mock + private IBinder mWindowToken; + private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock()); private SurfaceHolder.Callback mCallback; @@ -80,24 +98,40 @@ public class CommunalSurfaceViewControllerTest extends SysuiTestCase { private SettableFuture mPackageFuture; + private View.OnLayoutChangeListener mLayoutChangeListener; + @Before public void setup() { MockitoAnnotations.initMocks(this); - final ArgumentCaptor callbackCapture = - ArgumentCaptor.forClass(SurfaceHolder.Callback.class); when(mSurfaceView.getHolder()).thenReturn(mSurfaceHolder); when(mSurfaceView.getDisplay()).thenReturn(mDisplay); when(mDisplay.getDisplayId()).thenReturn(DISPLAY_ID); when(mSurfaceView.getHostToken()).thenReturn(mHostToken); + when(mSurfaceView.getWindowToken()).thenReturn(mWindowToken); when(mSurfaceView.getMeasuredWidth()).thenReturn(MEASURED_WIDTH); when(mSurfaceView.getMeasuredHeight()).thenReturn(MEASURED_HEIGHT); when(mSurfaceView.isAttachedToWindow()).thenReturn(false); - mController = new CommunalSurfaceViewController(mSurfaceView, mFakeExecutor, - mCommunalStateController, mCommunalSource); + when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(true); + when(mResources.getDimensionPixelSize(R.dimen.split_shade_header_height)) + .thenReturn(SPLIT_NOTIFICATION_STATUS_BAR_HEIGHT); + when(mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top)) + .thenReturn(NOTIFICATION_PANEL_MARGIN_TOP); + when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding)) + .thenReturn(KEYGUARD_INDICATION_BOTTOM_PADDING); + mController = new CommunalSurfaceViewController(mSurfaceView, mResources, mFakeExecutor, + mCommunalStateController, mNotificationShadeWindowController, mCommunalSource); mController.init(); + + final ArgumentCaptor callbackCapture = + ArgumentCaptor.forClass(SurfaceHolder.Callback.class); verify(mSurfaceHolder).addCallback(callbackCapture.capture()); mCallback = callbackCapture.getValue(); + final ArgumentCaptor listenerCapture = + ArgumentCaptor.forClass(View.OnLayoutChangeListener.class); + verify(mSurfaceView).addOnLayoutChangeListener(listenerCapture.capture()); + mLayoutChangeListener = listenerCapture.getValue(); + mPackageFuture = SettableFuture.create(); when(mCommunalSource.requestCommunalSurface(any(), anyInt(), anyInt(), anyInt())) @@ -131,7 +165,6 @@ public class CommunalSurfaceViewControllerTest extends SysuiTestCase { // Make sure SurfaceView is set. verify(mSurfaceView).setChildSurfacePackage(mSurfacePackage); - verify(mSurfaceView).setZOrderOnTop(true); verify(mSurfaceView).setWillNotDraw(false); } @@ -188,4 +221,51 @@ public class CommunalSurfaceViewControllerTest extends SysuiTestCase { assertTrue(mPackageFuture.isCancelled()); verify(mSurfaceView).setWillNotDraw(true); } + + @Test + public void testTapExclusion() { + final int left = 0; + final int top = 0; + final int right = 200; + final int bottom = 100; + final Region splitNotificationExclusionRegion = new Region( + left, + top + SPLIT_NOTIFICATION_STATUS_BAR_HEIGHT, + right, + bottom - KEYGUARD_INDICATION_BOTTOM_PADDING); + + final Region notificationExclusionRegion = new Region( + left, + top + NOTIFICATION_PANEL_MARGIN_TOP, + right, + bottom - KEYGUARD_INDICATION_BOTTOM_PADDING); + + // There should be no exclusion when communal isn't present. + mLayoutChangeListener.onLayoutChange(mSurfaceView, left, top, right, bottom, 0, 0, 0, 0); + verify(mNotificationShadeWindowController) + .setTouchExclusionRegion(eq(new Region())); + + + // Attach view + mController.onViewAttached(); + clearInvocations(mNotificationShadeWindowController); + // Verify tap exclusion area matches proper dimensions. + mLayoutChangeListener.onLayoutChange(mSurfaceView, left, top, right, bottom, 0, 0, 0, 0); + verify(mNotificationShadeWindowController) + .setTouchExclusionRegion(eq(splitNotificationExclusionRegion)); + + // Switch to normal notification margin, verify padding changes. + clearInvocations(mNotificationShadeWindowController); + when(mResources.getBoolean(R.bool.config_use_split_notification_shade)).thenReturn(false); + mLayoutChangeListener.onLayoutChange(mSurfaceView, left, top, right, bottom, 0, 0, 0, 0); + verify(mNotificationShadeWindowController) + .setTouchExclusionRegion(eq(notificationExclusionRegion)); + + // Occlude, verify no exclude region. + clearInvocations(mNotificationShadeWindowController); + when(mCommunalStateController.getCommunalViewOccluded()).thenReturn(true); + mLayoutChangeListener.onLayoutChange(mSurfaceView, left, top, right, bottom, 0, 0, 0, 0); + verify(mNotificationShadeWindowController) + .setTouchExclusionRegion(eq(new Region())); + } }