Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bf3bc1b4 authored by Adrian Roos's avatar Adrian Roos Committed by Tiger Huang
Browse files

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
parent 8fe7767d
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -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;
+26 −0
Original line number Diff line number Diff line
@@ -292,6 +292,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
     *
@@ -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 =
+105 −8
Original line number Diff line number Diff line
@@ -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);

            // 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);
        }, false /* topToBottom */);
            }
            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;
+22 −0
Original line number Diff line number Diff line
@@ -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());
    }

+16 −0
Original line number Diff line number Diff line
@@ -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);
        }
    }
}
Loading