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

Commit c007c87c authored by Naomi Musgrave's avatar Naomi Musgrave
Browse files

Enable edge resizing for touch input.

Support different corner edge drag regions, based on the input source.

Bug: 323383067
Test: manual drag from all 4 sides
Flag: com.android.window.flags.enable_windowing_edge_drag_resize DISABLED
Change-Id: Ic161ec765687ed79ac27c4d68c54c21eea8bb8ec
parent a0146dc3
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -513,8 +513,20 @@
    <!-- The size of the icon shown in the resize veil. -->
    <dimen name="desktop_mode_resize_veil_icon_size">96dp</dimen>

    <!-- The with of the border around the app task for edge resizing, when
         enable_windowing_edge_drag_resize is enabled. -->
    <dimen name="desktop_mode_edge_handle">12dp</dimen>

    <!-- The original width of the border around the app task for edge resizing, when
         enable_windowing_edge_drag_resize is disabled. -->
    <dimen name="freeform_resize_handle">15dp</dimen>

    <!-- The size of the corner region for drag resizing with touch, when a larger touch region is
         appropriate. Applied when enable_windowing_edge_drag_resize is enabled. -->
    <dimen name="desktop_mode_corner_resize_large">48dp</dimen>

    <!-- The original size of the corner region for darg resizing, when
         enable_windowing_edge_drag_resize is disabled. -->
    <dimen name="freeform_resize_corner">44dp</dimen>

    <!-- The width of the area at the sides of the screen where a freeform task will transition to
+8 −6
Original line number Diff line number Diff line
@@ -16,12 +16,17 @@

package com.android.wm.shell.windowdecor;

import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;

import android.annotation.NonNull;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration;
import android.app.WindowConfiguration.WindowingMode;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
@@ -234,13 +239,10 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
                .getScaledTouchSlop();
        mDragDetector.setTouchSlop(touchSlop);

        final int resizeHandle = mResult.mRootView.getResources()
                .getDimensionPixelSize(R.dimen.freeform_resize_handle);
        final int resizeCorner = mResult.mRootView.getResources()
                .getDimensionPixelSize(R.dimen.freeform_resize_corner);
        final Resources res = mResult.mRootView.getResources();
        mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
                        new Size(mResult.mWidth, mResult.mHeight), resizeHandle, resizeCorner),
                touchSlop);
                new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
                getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop);
    }

    /**
+7 −7
Original line number Diff line number Diff line
@@ -24,6 +24,9 @@ import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_UP;

import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;

import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -288,17 +291,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
                .getScaledTouchSlop();
        mDragDetector.setTouchSlop(touchSlop);

        final int resizeHandle = mResult.mRootView.getResources()
                .getDimensionPixelSize(R.dimen.freeform_resize_handle);
        final int resizeCorner = mResult.mRootView.getResources()
                .getDimensionPixelSize(R.dimen.freeform_resize_corner);

        // If either task geometry or position have changed, update this task's
        // exclusion region listener
        final Resources res = mResult.mRootView.getResources();
        if (mDragResizeListener.setGeometry(
                new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
                        new Size(mResult.mWidth, mResult.mHeight), resizeHandle, resizeCorner),
                touchSlop) || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
                        new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(res),
                        getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
                || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
            updateExclusionRegion();
        }

+3 −0
Original line number Diff line number Diff line
@@ -23,6 +23,9 @@ import android.graphics.Rect;
 * Callback called when receiving drag-resize or drag-move related input events.
 */
public interface DragPositioningCallback {
    /**
     * Indicates the direction of resizing. May be combined together to indicate a diagonal drag.
     */
    @IntDef(flag = true, value = {
            CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM
    })
+206 −88
Original line number Diff line number Diff line
@@ -18,35 +18,38 @@ package com.android.wm.shell.windowdecor;

import static android.view.InputDevice.SOURCE_TOUCHSCREEN;

import static com.android.window.flags.Flags.enableWindowingEdgeDragResize;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;

import android.annotation.NonNull;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.Size;
import android.view.MotionEvent;

import com.android.wm.shell.R;

import java.util.Objects;

/**
 * Geometry for an input region for a particular window.
 * Geometry for a drag resize region for a particular window.
 */
final class DragResizeWindowGeometry {
    private final int mTaskCornerRadius;
    private final Size mTaskSize;
    // The size of the handle applied to the edges of the window, for the user to drag resize.
    private final int mResizeHandleThickness;
    // The size of the rectangle applied to the corners of the window, for the user to drag resize.
    private final int mCornerSize;
    // The bounds for the corner drag region, which can resize the task in two directions.
    private final @NonNull Rect mLeftTopCornerBounds;
    private final @NonNull Rect mRightTopCornerBounds;
    private final @NonNull Rect mLeftBottomCornerBounds;
    private final @NonNull Rect mRightBottomCornerBounds;
    // The task corners to permit drag resizing with a course input, such as touch.

    private final @NonNull TaskCorners mLargeTaskCorners;
    // The task corners to permit drag resizing with a fine input, such as stylus or cursor.
    private final @NonNull TaskCorners mFineTaskCorners;
    // The bounds for each edge drag region, which can resize the task in one direction.
    private final @NonNull Rect mTopEdgeBounds;
    private final @NonNull Rect mLeftEdgeBounds;
@@ -54,34 +57,13 @@ final class DragResizeWindowGeometry {
    private final @NonNull Rect mBottomEdgeBounds;

    DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
            int resizeHandleThickness, int cornerSize) {
            int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
        mTaskCornerRadius = taskCornerRadius;
        mTaskSize = taskSize;
        mResizeHandleThickness = resizeHandleThickness;
        mCornerSize = cornerSize;

        // Save touch areas in each corner.
        final int cornerRadius = mCornerSize / 2;
        mLeftTopCornerBounds = new Rect(
                -cornerRadius,
                -cornerRadius,
                cornerRadius,
                cornerRadius);
        mRightTopCornerBounds = new Rect(
                mTaskSize.getWidth() - cornerRadius,
                -cornerRadius,
                mTaskSize.getWidth() + cornerRadius,
                cornerRadius);
        mLeftBottomCornerBounds = new Rect(
                -cornerRadius,
                mTaskSize.getHeight() - cornerRadius,
                cornerRadius,
                mTaskSize.getHeight() + cornerRadius);
        mRightBottomCornerBounds = new Rect(
                mTaskSize.getWidth() - cornerRadius,
                mTaskSize.getHeight() - cornerRadius,
                mTaskSize.getWidth() + cornerRadius,
                mTaskSize.getHeight() + cornerRadius);
        mLargeTaskCorners = new TaskCorners(mTaskSize, largeCornerSize);
        mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);

        // Save touch areas for each edge.
        mTopEdgeBounds = new Rect(
@@ -106,6 +88,31 @@ final class DragResizeWindowGeometry {
                mTaskSize.getHeight() + mResizeHandleThickness);
    }

    /**
     * Returns the resource value to use for the resize handle on the edge of the window.
     */
    static int getResizeEdgeHandleSize(@NonNull Resources res) {
        return enableWindowingEdgeDragResize()
                ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
                : res.getDimensionPixelSize(R.dimen.freeform_resize_handle);
    }

    /**
     * Returns the resource value to use for course input, such as touch, that benefits from a large
     * square on each of the window's corners.
     */
    static int getLargeResizeCornerSize(@NonNull Resources res) {
        return res.getDimensionPixelSize(R.dimen.desktop_mode_corner_resize_large);
    }

    /**
     * Returns the resource value to use for fine input, such as stylus, that can use a smaller
     * square on each of the window's corners.
     */
    static int getFineResizeCornerSize(@NonNull Resources res) {
        return res.getDimensionPixelSize(R.dimen.freeform_resize_corner);
    }

    /**
     * Returns the size of the task this geometry is calculated for.
     */
@@ -116,7 +123,7 @@ final class DragResizeWindowGeometry {

    /**
     * Returns the union of all regions that can be touched for drag resizing; the corners window
     * edges.
     * and window edges.
     */
    void union(@NonNull Region region) {
        // Apply the edge resize regions.
@@ -125,11 +132,14 @@ final class DragResizeWindowGeometry {
        region.union(mRightEdgeBounds);
        region.union(mBottomEdgeBounds);

        // Apply the corners as well.
        region.union(mLeftTopCornerBounds);
        region.union(mRightTopCornerBounds);
        region.union(mLeftBottomCornerBounds);
        region.union(mRightBottomCornerBounds);
        if (enableWindowingEdgeDragResize()) {
            // Apply the corners as well for the larger corners, to ensure we capture all possible
            // touches.
            mLargeTaskCorners.union(region);
        } else {
            // Only apply fine corners for the legacy approach.
            mFineTaskCorners.union(region);
        }
    }

    /**
@@ -143,27 +153,39 @@ final class DragResizeWindowGeometry {
     * Returns if this MotionEvent should be handled, based on its source and position.
     */
    boolean shouldHandleEvent(@NonNull MotionEvent e, boolean isTouch, @NonNull Point offset) {
        boolean result;
        final float x = e.getX(0) + offset.x;
        final float y = e.getY(0) + offset.y;
        if (isTouch) {
            result = isInCornerBounds(x, y);
        } else {
            result = isInResizeHandleBounds(x, y);

        if (enableWindowingEdgeDragResize()) {
            // First check if touch falls within a corner.
            // Large corner bounds are used for course input like touch, otherwise fine bounds.
            boolean result = isTouch
                    ? isInCornerBounds(mLargeTaskCorners, x, y)
                    : isInCornerBounds(mFineTaskCorners, x, y);
            // Check if touch falls within the edge resize handle, since edge resizing can apply
            // for any input source.
            if (!result) {
                result = isInEdgeResizeBounds(x, y);
            }
            return result;
        } else {
            // Legacy uses only fine corners for touch, and edges only for non-touch input.
            return isTouch
                    ? isInCornerBounds(mFineTaskCorners, x, y)
                    : isInEdgeResizeBounds(x, y);
        }
    }

    private boolean isTouchEvent(@NonNull MotionEvent e) {
        return (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
    }

    private boolean isInCornerBounds(float xf, float yf) {
        return calculateCornersCtrlType(xf, yf) != 0;
    private boolean isInCornerBounds(TaskCorners corners, float xf, float yf) {
        return corners.calculateCornersCtrlType(xf, yf) != 0;
    }

    private boolean isInResizeHandleBounds(float x, float y) {
        return calculateResizeHandlesCtrlType(x, y) != 0;
    private boolean isInEdgeResizeBounds(float x, float y) {
        return calculateEdgeResizeCtrlType(x, y) != 0;
    }

    /**
@@ -172,36 +194,31 @@ final class DragResizeWindowGeometry {
     */
    @DragPositioningCallback.CtrlType
    int calculateCtrlType(boolean isTouch, float x, float y) {
        if (isTouch) {
            return calculateCornersCtrlType(x, y);
        }
        return calculateResizeHandlesCtrlType(x, y);
    }

    @DragPositioningCallback.CtrlType
    private int calculateCornersCtrlType(float x, float y) {
        int xi = (int) x;
        int yi = (int) y;
        if (mLeftTopCornerBounds.contains(xi, yi)) {
            return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
        }
        if (mLeftBottomCornerBounds.contains(xi, yi)) {
            return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
        }
        if (mRightTopCornerBounds.contains(xi, yi)) {
            return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
        if (enableWindowingEdgeDragResize()) {
            // First check if touch falls within a corner.
            // Large corner bounds are used for course input like touch, otherwise fine bounds.
            int ctrlType = isTouch
                    ? mLargeTaskCorners.calculateCornersCtrlType(x, y)
                    : mFineTaskCorners.calculateCornersCtrlType(x, y);
            // Check if touch falls within the edge resize handle, since edge resizing can apply
            // for any input source.
            if (ctrlType == CTRL_TYPE_UNDEFINED) {
                ctrlType = calculateEdgeResizeCtrlType(x, y);
            }
        if (mRightBottomCornerBounds.contains(xi, yi)) {
            return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
            return ctrlType;
        } else {
            // Legacy uses only fine corners for touch, and edges only for non-touch input.
            return isTouch
                    ? mFineTaskCorners.calculateCornersCtrlType(x, y)
                    : calculateEdgeResizeCtrlType(x, y);
        }
        return 0;
    }

    @DragPositioningCallback.CtrlType
    private int calculateResizeHandlesCtrlType(float x, float y) {
        int ctrlType = 0;
    private int calculateEdgeResizeCtrlType(float x, float y) {
        int ctrlType = CTRL_TYPE_UNDEFINED;
        // mTaskCornerRadius is only used in comparing with corner regions. Comparisons with
        // sides will use the bounds specified and not go into task bounds.
        // sides will use the bounds specified in setGeometry and not go into task bounds.
        if (x < mTaskCornerRadius) {
            ctrlType |= CTRL_TYPE_LEFT;
        }
@@ -214,19 +231,20 @@ final class DragResizeWindowGeometry {
        if (y > mTaskSize.getHeight() - mTaskCornerRadius) {
            ctrlType |= CTRL_TYPE_BOTTOM;
        }
        // Check distances from the center if it's in one of four corners.
        // If the touch is within one of the four corners, check if it is within the bounds of the
        // // handle.
        if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
                && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
            return checkDistanceFromCenter(ctrlType, x, y);
        }
        // Otherwise, we should make sure we don't resize tasks inside task bounds.
        return (x < 0 || y < 0 || x >= mTaskSize.getWidth() || y >= mTaskSize.getHeight())
                ? ctrlType : 0;
                ? ctrlType : CTRL_TYPE_UNDEFINED;
    }

    /**
     * If corner input is not within appropriate distance of corner radius, do not use it.
     * If input is not on a corner or is within valid distance, return ctrlType.
     * Return {@code ctrlType} if the corner input is outside the (potentially rounded) corner of
     * the task, and within the thickness of the resize handle. Otherwise, return 0.
     */
    @DragPositioningCallback.CtrlType
    private int checkDistanceFromCenter(@DragPositioningCallback.CtrlType int ctrlType, float x,
@@ -238,7 +256,7 @@ final class DragResizeWindowGeometry {
                && distanceFromCenter >= mTaskCornerRadius) {
            return ctrlType;
        }
        return 0;
        return CTRL_TYPE_UNDEFINED;
    }

    /**
@@ -286,11 +304,8 @@ final class DragResizeWindowGeometry {
        return this.mTaskCornerRadius == other.mTaskCornerRadius
                && this.mTaskSize.equals(other.mTaskSize)
                && this.mResizeHandleThickness == other.mResizeHandleThickness
                && this.mCornerSize == other.mCornerSize
                && this.mLeftTopCornerBounds.equals(other.mLeftTopCornerBounds)
                && this.mRightTopCornerBounds.equals(other.mRightTopCornerBounds)
                && this.mLeftBottomCornerBounds.equals(other.mLeftBottomCornerBounds)
                && this.mRightBottomCornerBounds.equals(other.mRightBottomCornerBounds)
                && this.mFineTaskCorners.equals(other.mFineTaskCorners)
                && this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
                && this.mTopEdgeBounds.equals(other.mTopEdgeBounds)
                && this.mLeftEdgeBounds.equals(other.mLeftEdgeBounds)
                && this.mRightEdgeBounds.equals(other.mRightEdgeBounds)
@@ -303,14 +318,117 @@ final class DragResizeWindowGeometry {
                mTaskCornerRadius,
                mTaskSize,
                mResizeHandleThickness,
                mCornerSize,
                mLeftTopCornerBounds,
                mRightTopCornerBounds,
                mLeftBottomCornerBounds,
                mRightBottomCornerBounds,
                mFineTaskCorners,
                mLargeTaskCorners,
                mTopEdgeBounds,
                mLeftEdgeBounds,
                mRightEdgeBounds,
                mBottomEdgeBounds);
    }

    /**
     * Representation of the drag resize regions at the corner of the window.
     */
    private static class TaskCorners {
        // The size of the square applied to the corners of the window, for the user to drag
        // resize.
        private final int mCornerSize;
        // The square for each corner.
        private final @NonNull Rect mLeftTopCornerBounds;
        private final @NonNull Rect mRightTopCornerBounds;
        private final @NonNull Rect mLeftBottomCornerBounds;
        private final @NonNull Rect mRightBottomCornerBounds;

        TaskCorners(@NonNull Size taskSize, int cornerSize) {
            mCornerSize = cornerSize;
            final int cornerRadius = cornerSize / 2;
            mLeftTopCornerBounds = new Rect(
                    -cornerRadius,
                    -cornerRadius,
                    cornerRadius,
                    cornerRadius);

            mRightTopCornerBounds = new Rect(
                    taskSize.getWidth() - cornerRadius,
                    -cornerRadius,
                    taskSize.getWidth() + cornerRadius,
                    cornerRadius);

            mLeftBottomCornerBounds = new Rect(
                    -cornerRadius,
                    taskSize.getHeight() - cornerRadius,
                    cornerRadius,
                    taskSize.getHeight() + cornerRadius);

            mRightBottomCornerBounds = new Rect(
                    taskSize.getWidth() - cornerRadius,
                    taskSize.getHeight() - cornerRadius,
                    taskSize.getWidth() + cornerRadius,
                    taskSize.getHeight() + cornerRadius);
        }

        /**
         * Updates the region to include all four corners.
         */
        void union(Region region) {
            region.union(mLeftTopCornerBounds);
            region.union(mRightTopCornerBounds);
            region.union(mLeftBottomCornerBounds);
            region.union(mRightBottomCornerBounds);
        }

        /**
         * Returns the control type based on the position of the {@code MotionEvent}'s coordinates.
         */
        @DragPositioningCallback.CtrlType
        int calculateCornersCtrlType(float x, float y) {
            int xi = (int) x;
            int yi = (int) y;
            if (mLeftTopCornerBounds.contains(xi, yi)) {
                return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
            }
            if (mLeftBottomCornerBounds.contains(xi, yi)) {
                return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
            }
            if (mRightTopCornerBounds.contains(xi, yi)) {
                return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
            }
            if (mRightBottomCornerBounds.contains(xi, yi)) {
                return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
            }
            return 0;
        }

        @Override
        public String toString() {
            return "TaskCorners of size " + mCornerSize + " for the"
                    + " top left " + mLeftTopCornerBounds
                    + " top right " + mRightTopCornerBounds
                    + " bottom left " + mLeftBottomCornerBounds
                    + " bottom right " + mRightBottomCornerBounds;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) return false;
            if (this == obj) return true;
            if (!(obj instanceof TaskCorners other)) return false;

            return this.mCornerSize == other.mCornerSize
                    && this.mLeftTopCornerBounds.equals(other.mLeftTopCornerBounds)
                    && this.mRightTopCornerBounds.equals(other.mRightTopCornerBounds)
                    && this.mLeftBottomCornerBounds.equals(other.mLeftBottomCornerBounds)
                    && this.mRightBottomCornerBounds.equals(other.mRightBottomCornerBounds);
        }

        @Override
        public int hashCode() {
            return Objects.hash(
                    mCornerSize,
                    mLeftTopCornerBounds,
                    mRightTopCornerBounds,
                    mLeftBottomCornerBounds,
                    mRightBottomCornerBounds);
        }
    }
}
Loading