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
This commit is contained in:
Bryce Lee
2021-08-17 19:38:46 -07:00
parent 0320d8c7e7
commit a4ffa0ac9a
7 changed files with 212 additions and 15 deletions

View File

@@ -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<CommunalHostView>
setState(STATE_DOZING, isDozing);
}
@Override
public void onStateChanged(int newState) {
updateCommunalViewOccluded();
}
};
@Inject
@@ -195,6 +201,8 @@ public class CommunalHostViewController extends ViewController<CommunalHostView>
if (existingState != mState) {
showSource();
}
updateCommunalViewOccluded();
}
private String describeState(@State int stateFlag) {
@@ -308,7 +316,12 @@ public class CommunalHostViewController extends ViewController<CommunalHostView>
}
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);
}
}

View File

@@ -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<WeakReference<Callback>> 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";
});

View File

@@ -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<SurfaceView> {
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<SurfaceView> {
}
};
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<SurfaceView> {
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<SurfaceView> {
@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);
}
}

View File

@@ -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) {}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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<SurfaceControlViewHost.SurfacePackage> mPackageFuture;
private View.OnLayoutChangeListener mLayoutChangeListener;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
final ArgumentCaptor<SurfaceHolder.Callback> 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<SurfaceHolder.Callback> callbackCapture =
ArgumentCaptor.forClass(SurfaceHolder.Callback.class);
verify(mSurfaceHolder).addCallback(callbackCapture.capture());
mCallback = callbackCapture.getValue();
final ArgumentCaptor<View.OnLayoutChangeListener> 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()));
}
}