Merge "Exclude CommunalSurfaceView tap region from keyguard."

This commit is contained in:
Bryce Lee
2021-08-30 21:30:56 +00:00
committed by Android (Google) Code Review
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()));
}
}