Gestures: Assemble per-window exclusion zones

Adds a facility to listen for per-window exclusion zones and
assembles them into a per-display exclusion zone ready for
consumption by interested parties.

Bug: 126360272
Test: atest DisplayContentTests
Change-Id: Ic186f86ca551af98a5f77beb254d257c281a4160
This commit is contained in:
Adrian Roos
2019-02-07 20:45:11 +01:00
parent c00ee56966
commit 4ffc8977e2
13 changed files with 397 additions and 4 deletions

View File

@@ -383,6 +383,7 @@ java_defaults {
"core/java/android/view/IRecentsAnimationRunner.aidl",
"core/java/android/view/IRemoteAnimationFinishedCallback.aidl",
"core/java/android/view/IRotationWatcher.aidl",
"core/java/android/view/ISystemGestureExclusionListener.aidl",
"core/java/android/view/IWallpaperVisibilityListener.aidl",
"core/java/android/view/IWindow.aidl",
"core/java/android/view/IWindowFocusObserver.aidl",

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
import android.graphics.Region;
/**
* Listener for changes to the system gesture exclusion region
*
* {@hide}
*/
oneway interface ISystemGestureExclusionListener {
/**
* Called when the system gesture exclusion for the given display changed.
* @param displayId the display whose system gesture exclusion changed
* @param systemGestureExclusion a {@code Region} where the app would like priority over the
* system gestures, in display coordinates.
*/
void onSystemGestureExclusionChanged(int displayId, in Region systemGestureExclusion);
}

View File

@@ -40,6 +40,7 @@ import android.view.IOnKeyguardExitResult;
import android.view.IPinnedStackListener;
import android.view.RemoteAnimationAdapter;
import android.view.IRotationWatcher;
import android.view.ISystemGestureExclusionListener;
import android.view.IWallpaperVisibilityListener;
import android.view.IWindowSession;
import android.view.IWindowSessionCallback;
@@ -280,6 +281,18 @@ interface IWindowManager
void unregisterWallpaperVisibilityListener(IWallpaperVisibilityListener listener,
int displayId);
/**
* Registers a system gesture exclusion listener for a given display.
*/
void registerSystemGestureExclusionListener(ISystemGestureExclusionListener listener,
int displayId);
/**
* Unregisters a system gesture exclusion listener for a given display.
*/
void unregisterSystemGestureExclusionListener(ISystemGestureExclusionListener listener,
int displayId);
/**
* Used only for assist -- request a screenshot of the current application.
*/

View File

@@ -32,6 +32,8 @@ import android.view.InsetsState;
import android.view.Surface;
import android.view.SurfaceControl;
import java.util.List;
/**
* System private per-application interface to the window manager.
*
@@ -265,4 +267,10 @@ interface IWindowSession {
* that new state.
*/
void insetsModified(IWindow window, in InsetsState state);
/**
* Called when the system gesture exclusion has changed.
*/
void reportSystemGestureExclusionChanged(IWindow window, in List<Rect> exclusionRects);
}

View File

@@ -3990,7 +3990,11 @@ public final class ViewRootImpl implements ViewParent,
void systemGestureExclusionChanged() {
final List<Rect> rectsForWindowManager = mGestureExclusionTracker.computeChangedRects();
if (rectsForWindowManager != null) {
// TODO Send to WM
try {
mWindowSession.reportSystemGestureExclusionChanged(mWindow, rectsForWindowManager);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
mAttachInfo.mTreeObserver
.dispatchOnSystemGestureExclusionRectsChanged(rectsForWindowManager);
}

View File

@@ -20,12 +20,16 @@ import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.hardware.input.InputManager.InputDeviceListener;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.util.Log;
import android.util.Slog;
import android.view.ISystemGestureExclusionListener;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
@@ -34,6 +38,7 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowInsets;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
import java.util.ArrayList;
@@ -124,12 +129,16 @@ public class PointerLocationView extends View implements InputDeviceListener,
private int mActivePointerId;
private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>();
private final PointerCoords mTempCoords = new PointerCoords();
private final Region mSystemGestureExclusion = new Region();
private final Path mSystemGestureExclusionPath = new Path();
private final Paint mSystemGestureExclusionPaint;
private final VelocityTracker mVelocity;
private final VelocityTracker mAltVelocity;
private final FasterStringBuilder mText = new FasterStringBuilder();
private boolean mPrintCoords = true;
public PointerLocationView(Context c) {
@@ -168,7 +177,11 @@ public class PointerLocationView extends View implements InputDeviceListener,
mPathPaint.setARGB(255, 0, 96, 255);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(1);
mSystemGestureExclusionPaint = new Paint();
mSystemGestureExclusionPaint.setARGB(25, 255, 0, 0);
mSystemGestureExclusionPaint.setStyle(Paint.Style.FILL_AND_STROKE);
PointerState ps = new PointerState();
mPointers.add(ps);
mActivePointerId = 0;
@@ -236,6 +249,12 @@ public class PointerLocationView extends View implements InputDeviceListener,
final int NP = mPointers.size();
if (!mSystemGestureExclusion.isEmpty()) {
mSystemGestureExclusionPath.reset();
mSystemGestureExclusion.getBoundaryPath(mSystemGestureExclusionPath);
canvas.drawPath(mSystemGestureExclusionPath, mSystemGestureExclusionPaint);
}
// Labels
if (mActivePointerId >= 0) {
final PointerState ps = mPointers.get(mActivePointerId);
@@ -719,6 +738,12 @@ public class PointerLocationView extends View implements InputDeviceListener,
super.onAttachedToWindow();
mIm.registerInputDeviceListener(this, getHandler());
try {
WindowManagerGlobal.getWindowManagerService().registerSystemGestureExclusionListener(
mSystemGestureExclusionListener, mContext.getDisplayId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
logInputDevices();
}
@@ -727,6 +752,12 @@ public class PointerLocationView extends View implements InputDeviceListener,
super.onDetachedFromWindow();
mIm.unregisterInputDeviceListener(this);
try {
WindowManagerGlobal.getWindowManagerService().unregisterSystemGestureExclusionListener(
mSystemGestureExclusionListener, mContext.getDisplayId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@Override
@@ -876,4 +907,17 @@ public class PointerLocationView extends View implements InputDeviceListener,
return oldLength;
}
}
private ISystemGestureExclusionListener mSystemGestureExclusionListener =
new ISystemGestureExclusionListener.Stub() {
@Override
public void onSystemGestureExclusionChanged(int displayId, Region systemGestureExclusion) {
Region exclusion = Region.obtain(systemGestureExclusion);
getHandler().post(() -> {
mSystemGestureExclusion.set(exclusion);
exclusion.recycle();
invalidate();
});
}
};
}

View File

@@ -128,6 +128,7 @@ import static com.android.server.wm.WindowManagerService.logSurface;
import static com.android.server.wm.WindowState.RESIZE_HANDLE_WIDTH_IN_DP;
import static com.android.server.wm.WindowStateAnimator.DRAW_PENDING;
import static com.android.server.wm.WindowStateAnimator.READY_TO_SHOW;
import static com.android.server.wm.utils.RegionUtils.rectListToRegion;
import android.animation.AnimationHandler;
import android.annotation.CallSuper;
@@ -151,6 +152,7 @@ import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -163,6 +165,7 @@ import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.ISystemGestureExclusionListener;
import android.view.InputChannel;
import android.view.InputDevice;
import android.view.InputWindowHandle;
@@ -308,6 +311,10 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
private DisplayRotation mDisplayRotation;
DisplayFrames mDisplayFrames;
private final RemoteCallbackList<ISystemGestureExclusionListener>
mSystemGestureExclusionListeners = new RemoteCallbackList<>();
private final Region mSystemGestureExclusion = new Region();
/**
* For default display it contains real metrics, empty for others.
* @see WindowManagerService#createWatermarkInTransaction()
@@ -2788,6 +2795,14 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
pw.println();
mWallpaperController.dump(pw, " ");
pw.println();
pw.print("mSystemGestureExclusion=");
if (mSystemGestureExclusionListeners.getRegisteredCallbackCount() > 0) {
pw.println(mSystemGestureExclusion);
} else {
pw.println("<no lstnrs>");
}
pw.println();
pw.println(prefix + "Application tokens in top down Z order:");
for (int stackNdx = mTaskStackContainers.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
@@ -4913,6 +4928,100 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo
return mWindowingLayer;
}
/**
* Updates the display's system gesture exclusion.
*
* @return true, if the exclusion changed.
*/
boolean updateSystemGestureExclusion() {
if (mSystemGestureExclusionListeners.getRegisteredCallbackCount() == 0) {
// No one's interested anyways.
return false;
}
final Region systemGestureExclusion = calculateSystemGestureExclusion();
try {
if (mSystemGestureExclusion.equals(systemGestureExclusion)) {
return false;
}
mSystemGestureExclusion.set(systemGestureExclusion);
for (int i = mSystemGestureExclusionListeners.beginBroadcast() - 1; i >= 0; --i) {
try {
mSystemGestureExclusionListeners.getBroadcastItem(i)
.onSystemGestureExclusionChanged(mDisplayId, systemGestureExclusion);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to notify SystemGestureExclusionListener", e);
}
}
mSystemGestureExclusionListeners.finishBroadcast();
return true;
} finally {
systemGestureExclusion.recycle();
}
}
@VisibleForTesting
Region calculateSystemGestureExclusion() {
final Region global = Region.obtain();
final Region touchableRegion = Region.obtain();
final Region local = Region.obtain();
// Traverse all windows bottom up to assemble the gesture exclusion rects.
// For each window, we only take the rects that fall within its touchable region.
forAllWindows(w -> {
if (w.cantReceiveTouchInput() || !w.isVisible()
|| (w.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0) {
return;
}
final boolean modal =
(w.mAttrs.flags & (FLAG_NOT_TOUCH_MODAL | FLAG_NOT_FOCUSABLE)) == 0;
// Only keep the exclusion zones from the windows behind where the current window
// isn't touchable.
w.getTouchableRegion(touchableRegion);
global.op(touchableRegion, Op.DIFFERENCE);
rectListToRegion(w.getSystemGestureExclusion(), local);
// Transform to display coordinates
local.scale(w.mGlobalScale);
final Rect frame = w.getWindowFrames().mFrame;
local.translate(frame.left, frame.top);
// A window can only exclude system gestures where it is actually touchable
local.op(touchableRegion, Op.INTERSECT);
global.op(local, Op.UNION);
}, false /* topToBottom */);
local.recycle();
touchableRegion.recycle();
return global;
}
void registerSystemGestureExclusionListener(ISystemGestureExclusionListener listener) {
mSystemGestureExclusionListeners.register(listener);
final boolean changed;
if (mSystemGestureExclusionListeners.getRegisteredCallbackCount() == 1) {
changed = updateSystemGestureExclusion();
} else {
changed = false;
}
if (!changed) {
// If updateSystemGestureExclusion changed the exclusion, it will already have
// notified the listener. Otherwise, we'll do it here.
try {
listener.onSystemGestureExclusionChanged(mDisplayId, mSystemGestureExclusion);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to notify SystemGestureExclusionListener during register", e);
}
}
}
void unregisterSystemGestureExclusionListener(ISystemGestureExclusionListener listener) {
mSystemGestureExclusionListeners.unregister(listener);
}
/**
* Create a portal window handle for input. This window transports any touch to the display
* indicated by {@link InputWindowHandle#portalToDisplayId} if the touch hits this window.

View File

@@ -654,6 +654,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// Finally update all input windows now that the window changes have stabilized.
forAllDisplays(dc -> {
dc.getInputMonitor().updateInputWindowsLw(true /*force*/);
dc.updateSystemGestureExclusion();
});
mWmService.setHoldScreenLocked(mHoldScreen);

View File

@@ -57,6 +57,7 @@ import com.android.server.wm.WindowManagerService.H;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
@@ -314,6 +315,16 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
}
@Override
public void reportSystemGestureExclusionChanged(IWindow window, List<Rect> exclusionRects) {
long ident = Binder.clearCallingIdentity();
try {
mService.reportSystemGestureExclusionChanged(this, window, exclusionRects);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void actionOnWallpaper(IBinder window,
BiConsumer<WallpaperController, WindowState> action) {
final WindowState windowState = mService.windowForClientLocked(this, window, true);

View File

@@ -202,6 +202,7 @@ import android.view.IOnKeyguardExitResult;
import android.view.IPinnedStackListener;
import android.view.IRecentsAnimationRunner;
import android.view.IRotationWatcher;
import android.view.ISystemGestureExclusionListener;
import android.view.IWallpaperVisibilityListener;
import android.view.IWindow;
import android.view.IWindowId;
@@ -274,6 +275,7 @@ import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/** {@hide} */
public class WindowManagerService extends IWindowManager.Stub
@@ -3787,6 +3789,42 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
@Override
public void registerSystemGestureExclusionListener(ISystemGestureExclusionListener listener,
int displayId) {
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent == null) {
throw new IllegalArgumentException("Trying to register visibility event "
+ "for invalid display: " + displayId);
}
displayContent.registerSystemGestureExclusionListener(listener);
}
}
@Override
public void unregisterSystemGestureExclusionListener(ISystemGestureExclusionListener listener,
int displayId) {
synchronized (mGlobalLock) {
final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
if (displayContent == null) {
throw new IllegalArgumentException("Trying to register visibility event "
+ "for invalid display: " + displayId);
}
displayContent.unregisterSystemGestureExclusionListener(listener);
}
}
void reportSystemGestureExclusionChanged(Session session, IWindow window,
List<Rect> exclusionRects) {
synchronized (mGlobalLock) {
final WindowState win = windowForClientLocked(session, window, true);
if (win.setSystemGestureExclusion(exclusionRects)) {
win.getDisplayContent().updateSystemGestureExclusion();
}
}
}
@Override
public void registerDisplayFoldListener(IDisplayFoldListener listener) {
mPolicy.registerDisplayFoldListener(listener);

View File

@@ -205,6 +205,7 @@ import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
/** A window in the window manager. */
@@ -363,6 +364,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
*/
private final Rect mInsetFrame = new Rect();
/**
* List of rects where system gestures should be ignored.
*
* Coordinates are relative to the window's position.
*/
private final List<Rect> mExclusionRects = new ArrayList<>();
// If a window showing a wallpaper: the requested offset for the
// wallpaper; if a wallpaper window: the currently applied offset.
float mWallpaperX = -1;
@@ -612,6 +620,24 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
List<Rect> getSystemGestureExclusion() {
return mExclusionRects;
}
/**
* Sets the system gesture exclusion rects.
*
* @return {@code true} if anything changed
*/
boolean setSystemGestureExclusion(List<Rect> exclusionRects) {
if (mExclusionRects.equals(exclusionRects)) {
return false;
}
mExclusionRects.clear();
mExclusionRects.addAll(exclusionRects);
return true;
}
interface PowerManagerWrapper {
void wakeUp(long time, @WakeReason int reason, String details);

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.wm.utils;
import android.graphics.Rect;
import android.graphics.Region;
import java.util.List;
/**
* Utility methods to handle Regions.
*/
public class RegionUtils {
private RegionUtils() {}
/**
* Converts a list of rects into a {@code Region}.
*
* @param rects the list of rects to convert
* @param outRegion the Region to set to the list of rects
*/
public static void rectListToRegion(List<Rect> rects, Region outRegion) {
outRegion.setEmpty();
final int n = rects.size();
for (int i = 0; i < n; i++) {
outRegion.union(rects.get(i));
}
}
}

View File

@@ -25,6 +25,8 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT;
import static android.view.DisplayCutout.BOUNDS_POSITION_TOP;
import static android.view.DisplayCutout.fromBoundingRect;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
@@ -57,12 +59,15 @@ import android.annotation.SuppressLint;
import android.app.WindowConfiguration;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.graphics.Region;
import android.metrics.LogMaker;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.util.MutableBoolean;
import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.ISystemGestureExclusionListener;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.ViewRootImpl;
@@ -672,6 +677,60 @@ public class DisplayContentTests extends WindowTestsBase {
}
}
@Test
public void testUpdateSystemGestureExclusion() throws Exception {
final DisplayContent dc = createNewDisplay();
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40)));
dc.setLayoutNeeded();
dc.performLayout(true /* initial */, false /* updateImeWindows */);
win.setHasSurface(true);
dc.updateSystemGestureExclusion();
final MutableBoolean invoked = new MutableBoolean(false);
final ISystemGestureExclusionListener.Stub verifier =
new ISystemGestureExclusionListener.Stub() {
@Override
public void onSystemGestureExclusionChanged(int displayId, Region actual) {
Region expected = Region.obtain();
expected.set(10, 20, 30, 40);
assertEquals(expected, actual);
invoked.value = true;
}
};
try {
dc.registerSystemGestureExclusionListener(verifier);
} finally {
dc.unregisterSystemGestureExclusionListener(verifier);
}
assertTrue("SystemGestureExclusionListener was not invoked", invoked.value);
}
@Test
public void testCalculateSystemGestureExclusion() throws Exception {
final DisplayContent dc = createNewDisplay();
final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40)));
final WindowState win2 = createWindow(null, TYPE_APPLICATION, dc, "win2");
win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win2.setSystemGestureExclusion(Collections.singletonList(new Rect(20, 30, 40, 50)));
dc.setLayoutNeeded();
dc.performLayout(true /* initial */, false /* updateImeWindows */);
win.setHasSurface(true);
win2.setHasSurface(true);
final Region expected = Region.obtain();
expected.set(20, 30, 40, 50);
assertEquals(expected, dc.calculateSystemGestureExclusion());
}
@Test
public void testOrientationChangeLogging() {
MetricsLogger mockLogger = mock(MetricsLogger.class);