diff options
| author | 2019-06-18 16:13:53 +0200 | |
|---|---|---|
| committer | 2019-06-28 18:13:12 +0800 | |
| commit | bf3bc1b48364bb93789e75191fc3a9508018976c (patch) | |
| tree | 9df786701387568b7c10ac549991adc82f9d864b | |
| parent | 8fe7767d0d480f066e5af8ae5d892d1ccfe106aa (diff) | |
GestureNav: Limit exclusion rects
Add limits for system gesture exclusion on the left and right edges.
This prevents non-immersive-sticky apps from totally disabling the
back gesture.
Bug: 135522625
Test: atest SystemGestureExclusionRectsTest
Change-Id: Ib26161663a6aababe803d3c70044f4017bdbe675
Exempt-From-Owner-Approval: Already +2ed on another branch
| -rw-r--r-- | api/test-current.txt | 5 | ||||
| -rw-r--r-- | core/java/android/provider/DeviceConfig.java | 26 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/DisplayContent.java | 113 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/WindowManagerService.java | 22 | ||||
| -rw-r--r-- | services/core/java/com/android/server/wm/utils/RegionUtils.java | 16 | ||||
| -rw-r--r-- | services/tests/wmtests/AndroidManifest.xml | 1 |
6 files changed, 175 insertions, 8 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index bf2730abcb99..fe73fecb6b86 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -2301,6 +2301,7 @@ package android.provider { field public static final String NAMESPACE_PRIVACY = "privacy"; field public static final String NAMESPACE_ROLLBACK = "rollback"; field public static final String NAMESPACE_ROLLBACK_BOOT = "rollback_boot"; + field public static final String NAMESPACE_WINDOW_MANAGER = "android:window_manager"; } public static interface DeviceConfig.OnPropertiesChangedListener { @@ -2317,6 +2318,10 @@ package android.provider { method @Nullable public String getString(@NonNull String, @Nullable String); } + public static interface DeviceConfig.WindowManager { + field public static final String KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP = "system_gesture_exclusion_limit_dp"; + } + public final class MediaStore { method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static void deleteContributedMedia(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; method @RequiresPermission(android.Manifest.permission.CLEAR_APP_USER_DATA) public static long getContributedMediaSize(android.content.Context, String, android.os.UserHandle) throws java.io.IOException; diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 01b6758f8b15..48faf114d2d7 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -293,6 +293,15 @@ public final class DeviceConfig { public static final String NAMESPACE_SETTINGS_UI = "settings_ui"; /** + * Namespace for window manager related features. The names to access the properties in this + * namespace should be defined in {@link WindowManager}. + * + * @hide + */ + @TestApi + public static final String NAMESPACE_WINDOW_MANAGER = "android:window_manager"; + + /** * List of namespaces which can be read without READ_DEVICE_CONFIG permission * * @hide @@ -309,6 +318,23 @@ public final class DeviceConfig { @TestApi public static final String NAMESPACE_PRIVACY = "privacy"; + /** + * Interface for accessing keys belonging to {@link #NAMESPACE_WINDOW_MANAGER}. + * @hide + */ + @TestApi + public interface WindowManager { + + /** + * Key for accessing the system gesture exclusion limit (an integer in dp). + * + * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER + * @hide + */ + @TestApi + String KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP = "system_gesture_exclusion_limit_dp"; + } + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners = diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 57ed92d8b794..72a7fcfe1e29 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -30,16 +30,21 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.util.DisplayMetrics.DENSITY_DEFAULT; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_PRIVATE; import static android.view.Display.FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS; import static android.view.Display.INVALID_DISPLAY; import static android.view.InsetsState.TYPE_IME; +import static android.view.InsetsState.TYPE_LEFT_GESTURES; +import static android.view.InsetsState.TYPE_RIGHT_GESTURES; import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; import static android.view.View.GONE; +import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; +import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; import static android.view.WindowManager.DOCKED_BOTTOM; import static android.view.WindowManager.DOCKED_INVALID; import static android.view.WindowManager.DOCKED_TOP; @@ -135,6 +140,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.forEachRect; import static com.android.server.wm.utils.RegionUtils.rectListToRegion; import android.animation.AnimationHandler; @@ -323,6 +329,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo private final RemoteCallbackList<ISystemGestureExclusionListener> mSystemGestureExclusionListeners = new RemoteCallbackList<>(); private final Region mSystemGestureExclusion = new Region(); + private int mSystemGestureExclusionLimit; /** * For default display it contains real metrics, empty for others. @@ -893,6 +900,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mWallpaperController = new WallpaperController(mWmService, this); display.getDisplayInfo(mDisplayInfo); display.getMetrics(mDisplayMetrics); + mSystemGestureExclusionLimit = mWmService.mSystemGestureExclusionLimitDp + * mDisplayMetrics.densityDpi / DENSITY_DEFAULT; isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation)); @@ -1548,8 +1557,8 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo longSize = height; } - final int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / mBaseDisplayDensity; - final int longSizeDp = longSize * DisplayMetrics.DENSITY_DEFAULT / mBaseDisplayDensity; + final int shortSizeDp = shortSize * DENSITY_DEFAULT / mBaseDisplayDensity; + final int longSizeDp = longSize * DENSITY_DEFAULT / mBaseDisplayDensity; mDisplayPolicy.updateConfigurationAndScreenSizeDependentBehaviors(); mDisplayRotation.configure(width, height, shortSizeDp, longSizeDp); @@ -2199,6 +2208,18 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo onDisplayChanged(this); } + @Override + void onDisplayChanged(DisplayContent dc) { + super.onDisplayChanged(dc); + updateSystemGestureExclusionLimit(); + } + + void updateSystemGestureExclusionLimit() { + mSystemGestureExclusionLimit = mWmService.mSystemGestureExclusionLimitDp + * mDisplayMetrics.densityDpi / DENSITY_DEFAULT; + updateSystemGestureExclusion(); + } + void initializeDisplayBaseInfo() { final DisplayManagerInternal displayManagerInternal = mWmService.mDisplayManagerInternal; if (displayManagerInternal != null) { @@ -5130,24 +5151,35 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo @VisibleForTesting Region calculateSystemGestureExclusion() { + final Region unhandled = Region.obtain(); + unhandled.set(0, 0, mDisplayFrames.mDisplayWidth, mDisplayFrames.mDisplayHeight); + + final Rect leftEdge = mInsetsStateController.getSourceProvider(TYPE_LEFT_GESTURES) + .getSource().getFrame(); + final Rect rightEdge = mInsetsStateController.getSourceProvider(TYPE_RIGHT_GESTURES) + .getSource().getFrame(); + final Region global = Region.obtain(); final Region touchableRegion = Region.obtain(); final Region local = Region.obtain(); + final int[] remainingLeftRight = + {mSystemGestureExclusionLimit, mSystemGestureExclusionLimit}; // 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) { + || (w.getAttrs().flags & FLAG_NOT_TOUCHABLE) != 0 + || unhandled.isEmpty()) { 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. + // Get the touchable region of the window, and intersect with where the screen is still + // touchable, i.e. touchable regions on top are not covering it yet. w.getTouchableRegion(touchableRegion); - global.op(touchableRegion, Op.DIFFERENCE); + touchableRegion.op(unhandled, Op.INTERSECT); rectListToRegion(w.getSystemGestureExclusion(), local); @@ -5159,13 +5191,78 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo // A window can only exclude system gestures where it is actually touchable local.op(touchableRegion, Op.INTERSECT); - global.op(local, Op.UNION); - }, false /* topToBottom */); + // Apply restriction if necessary. + if (needsGestureExclusionRestrictions(w, mLastDispatchedSystemUiVisibility)) { + + // Processes the region along the left edge. + remainingLeftRight[0] = addToGlobalAndConsumeLimit(local, global, leftEdge, + remainingLeftRight[0]); + + // Processes the region along the right edge. + remainingLeftRight[1] = addToGlobalAndConsumeLimit(local, global, rightEdge, + remainingLeftRight[1]); + + // Adds the middle (unrestricted area) + final Region middle = Region.obtain(local); + middle.op(leftEdge, Op.DIFFERENCE); + middle.op(rightEdge, Op.DIFFERENCE); + global.op(middle, Op.UNION); + middle.recycle(); + } else { + global.op(local, Op.UNION); + } + unhandled.op(touchableRegion, Op.DIFFERENCE); + }, true /* topToBottom */); local.recycle(); touchableRegion.recycle(); + unhandled.recycle(); return global; } + /** + * @return Whether gesture exclusion area should be restricted from the window depending on the + * current SystemUI visibility flags. + */ + private static boolean needsGestureExclusionRestrictions(WindowState win, int sysUiVisibility) { + final int type = win.mAttrs.type; + final int stickyHideNavFlags = + SYSTEM_UI_FLAG_HIDE_NAVIGATION | SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + final boolean stickyHideNav = + (sysUiVisibility & stickyHideNavFlags) == stickyHideNavFlags; + return !stickyHideNav && type != TYPE_INPUT_METHOD && type != TYPE_STATUS_BAR + && win.getActivityType() != ACTIVITY_TYPE_HOME; + } + + /** + * Adds a local gesture exclusion area to the global area while applying a limit per edge. + * + * @param local The gesture exclusion area to add. + * @param global The destination. + * @param edge Only processes the part in that region. + * @param limit How much limit in pixels we have. + * @return How much of the limit are remaining. + */ + private static int addToGlobalAndConsumeLimit(Region local, Region global, Rect edge, + int limit) { + final Region r = Region.obtain(local); + r.op(edge, Op.INTERSECT); + + final int[] remaining = {limit}; + forEachRect(r, rect -> { + if (remaining[0] <= 0) { + return; + } + final int height = rect.height(); + if (height > remaining[0]) { + rect.bottom = rect.top + remaining[0]; + } + remaining[0] -= height; + global.op(rect, Op.UNION); + }); + r.recycle(); + return remaining[0]; + } + void registerSystemGestureExclusionListener(ISystemGestureExclusionListener listener) { mSystemGestureExclusionListeners.register(listener); final boolean changed; diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6750ece1861e..54f712cc6cef 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -32,6 +32,7 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.Process.SYSTEM_UID; import static android.os.Process.myPid; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; +import static android.provider.DeviceConfig.WindowManager.KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -155,6 +156,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.Looper; @@ -176,6 +178,7 @@ import android.os.SystemService; import android.os.Trace; import android.os.UserHandle; import android.os.WorkSource; +import android.provider.DeviceConfig; import android.provider.Settings; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; @@ -382,6 +385,8 @@ public class WindowManagerService extends IWindowManager.Stub private static final int ANIMATION_COMPLETED_TIMEOUT_MS = 5000; + private static final int MIN_GESTURE_EXCLUSION_LIMIT_DP = 200; + final WindowTracing mWindowTracing; final private KeyguardDisableHandler mKeyguardDisableHandler; @@ -840,6 +845,8 @@ public class WindowManagerService extends IWindowManager.Stub final ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<>(); boolean mWindowsChanged = false; + int mSystemGestureExclusionLimitDp; + public interface WindowChangeListener { public void windowsChanged(); public void focusChanged(); @@ -1134,6 +1141,21 @@ public class WindowManagerService extends IWindowManager.Stub this, mInputManager, mActivityTaskManager, mH.getLooper()); mDragDropController = new DragDropController(this, mH.getLooper()); + mSystemGestureExclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP, + DeviceConfig.getInt(DeviceConfig.NAMESPACE_WINDOW_MANAGER, + KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0)); + DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER, + new HandlerExecutor(mH), properties -> { + synchronized (mGlobalLock) { + final int exclusionLimitDp = Math.max(MIN_GESTURE_EXCLUSION_LIMIT_DP, + properties.getInt(KEY_SYSTEM_GESTURE_EXCLUSION_LIMIT_DP, 0)); + if (mSystemGestureExclusionLimitDp != exclusionLimitDp) { + mSystemGestureExclusionLimitDp = exclusionLimitDp; + mRoot.forAllDisplays(DisplayContent::updateSystemGestureExclusionLimit); + } + } + }); + LocalServices.addService(WindowManagerInternal.class, new LocalService()); } diff --git a/services/core/java/com/android/server/wm/utils/RegionUtils.java b/services/core/java/com/android/server/wm/utils/RegionUtils.java index 1458440f7b81..8cd6f8826083 100644 --- a/services/core/java/com/android/server/wm/utils/RegionUtils.java +++ b/services/core/java/com/android/server/wm/utils/RegionUtils.java @@ -18,8 +18,10 @@ package com.android.server.wm.utils; import android.graphics.Rect; import android.graphics.Region; +import android.graphics.RegionIterator; import java.util.List; +import java.util.function.Consumer; /** * Utility methods to handle Regions. @@ -42,4 +44,18 @@ public class RegionUtils { outRegion.union(rects.get(i)); } } + + /** + * Applies actions on each rect contained within a {@code Region}. + * + * @param region the given region. + * @param rectConsumer the action holder. + */ + public static void forEachRect(Region region, Consumer<Rect> rectConsumer) { + final RegionIterator it = new RegionIterator(region); + final Rect rect = new Rect(); + while (it.next(rect)) { + rectConsumer.accept(rect); + } + } } diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index 4c27a3ce0bf3..8c4544249b5c 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -36,6 +36,7 @@ <uses-permission android:name="android.permission.READ_FRAME_BUFFER" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.REORDER_TASKS" /> + <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" /> <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) --> <application android:debuggable="true" |