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())); + } }