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

Commit ee4daa24 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "[PiP2 on Desktop] Drag-corner-to-resize PiP." into main

parents 02dec96a 96b67726
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -81,8 +81,8 @@ public class PipDesktopState {
        return false;
    }

    /** Returns whether PiP is exiting while we're in a Desktop Mode session. */
    private boolean isPipExitingToDesktopMode() {
    /** Returns whether PiP is active in a display that is in active Desktop Mode session. */
    public boolean isPipInDesktopMode() {
        // Early return if PiP in Desktop Windowing is not supported.
        if (!isDesktopWindowingPipEnabled()) {
            return false;
@@ -137,7 +137,7 @@ public class PipDesktopState {
        // 1) If the display windowing mode is freeform, set windowing mode to UNDEFINED so it will
        //    resolve the windowing mode to the display's windowing mode.
        // 2) If the display windowing mode is not FREEFORM, set windowing mode to FREEFORM.
        if (isPipExitingToDesktopMode()) {
        if (isPipInDesktopMode()) {
            if (isDisplayInFreeform()) {
                return WINDOWING_MODE_UNDEFINED;
            } else {
+3 −2
Original line number Diff line number Diff line
@@ -174,6 +174,7 @@ public abstract class Pip2Module {
            @NonNull PipScheduler pipScheduler,
            @NonNull SizeSpecSource sizeSpecSource,
            @NonNull PipDisplayLayoutState pipDisplayLayoutState,
            PipDesktopState pipDesktopState,
            DisplayController displayController,
            PipMotionHelper pipMotionHelper,
            FloatingContentCoordinator floatingContentCoordinator,
@@ -182,8 +183,8 @@ public abstract class Pip2Module {
            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
        return new PipTouchHandler(context, shellInit, shellCommandHandler, menuPhoneController,
                pipBoundsAlgorithm, pipBoundsState, pipTransitionState, pipScheduler,
                sizeSpecSource, pipDisplayLayoutState, displayController, pipMotionHelper,
                floatingContentCoordinator, pipUiEventLogger, mainExecutor,
                sizeSpecSource, pipDisplayLayoutState, pipDesktopState, displayController,
                pipMotionHelper, floatingContentCoordinator, pipUiEventLogger, mainExecutor,
                pipPerfHintControllerOptional);
    }

+218 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.wm.shell.pip2.phone;

import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_BOTTOM;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
import static com.android.wm.shell.pip2.phone.PipMenuView.ANIM_TYPE_NONE;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
import android.view.MotionEvent;

import com.android.internal.policy.TaskResizingAlgorithm;
import com.android.wm.shell.R;
import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
import com.android.wm.shell.common.pip.PipBoundsState;

import java.util.function.Function;

/** Helper for handling drag-corner-to-resize gestures. */
public class PipDragToResizeHandler {
    private final Context mContext;
    private final PipResizeGestureHandler mPipResizeGestureHandler;
    private final PipBoundsState mPipBoundsState;
    private final PhonePipMenuController mPhonePipMenuController;
    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
    private final PipScheduler mPipScheduler;

    private final Region mTmpRegion = new Region();
    private final Rect mDragCornerSize = new Rect();
    private final Rect mTmpTopLeftCorner = new Rect();
    private final Rect mTmpTopRightCorner = new Rect();
    private final Rect mTmpBottomLeftCorner = new Rect();
    private final Rect mTmpBottomRightCorner = new Rect();
    private final Rect mDisplayBounds = new Rect();
    private final Function<Rect, Rect> mMovementBoundsSupplier;
    private int mDelta;

    public PipDragToResizeHandler(Context context, PipResizeGestureHandler pipResizeGestureHandler,
            PipBoundsState pipBoundsState,
            PhonePipMenuController phonePipMenuController, PipBoundsAlgorithm pipBoundsAlgorithm,
            PipScheduler pipScheduler, Function<Rect, Rect> movementBoundsSupplier) {
        mContext = context;
        mPipResizeGestureHandler = pipResizeGestureHandler;
        mPipBoundsState = pipBoundsState;
        mPhonePipMenuController = phonePipMenuController;
        mPipBoundsAlgorithm = pipBoundsAlgorithm;
        mPipScheduler = pipScheduler;
        mMovementBoundsSupplier = movementBoundsSupplier;
    }

    /** Invoked by {@link PipResizeGestureHandler#reloadResources}. */
    void reloadResources() {
        final Resources res = mContext.getResources();
        mDelta = res.getDimensionPixelSize(R.dimen.pip_resize_edge_size);
    }

    /** Invoked by {@link PipResizeGestureHandler#onInputEvent} if drag-corner-to-resize is
     * enabled. */
    void onDragCornerResize(MotionEvent ev, Rect lastResizeBounds, PointF downPoint,
            Rect downBounds, Point minSize, Point maxSize, float touchSlop) {
        int action = ev.getActionMasked();
        float x = ev.getX();
        float y = ev.getY();
        if (action == MotionEvent.ACTION_DOWN) {
            lastResizeBounds.setEmpty();
            final boolean allowGesture = isWithinDragResizeRegion((int) x, (int) y);
            mPipResizeGestureHandler.setAllowGesture(allowGesture);
            if (allowGesture) {
                setCtrlType((int) x, (int) y);
                downPoint.set(x, y);
                downBounds.set(mPipBoundsState.getBounds());
            }
        } else if (mPipResizeGestureHandler.getAllowGesture()) {
            switch (action) {
                case MotionEvent.ACTION_POINTER_DOWN:
                    // We do not support multi touch for resizing via drag
                    mPipResizeGestureHandler.setAllowGesture(false);
                    break;
                case MotionEvent.ACTION_MOVE:
                    final boolean thresholdCrossed = mPipResizeGestureHandler.getThresholdCrossed();
                    // Capture inputs
                    if (!mPipResizeGestureHandler.getThresholdCrossed()
                            && Math.hypot(x - downPoint.x, y - downPoint.y) > touchSlop) {
                        mPipResizeGestureHandler.setThresholdCrossed(true);
                        // Reset the down to begin resizing from this point
                        downPoint.set(x, y);
                        mPipResizeGestureHandler.pilferPointers();
                    }
                    if (mPipResizeGestureHandler.getThresholdCrossed()) {
                        if (mPhonePipMenuController.isMenuVisible()) {
                            mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE,
                                    false /* resize */);
                        }
                        final Rect currentPipBounds = mPipBoundsState.getBounds();
                        lastResizeBounds.set(TaskResizingAlgorithm.resizeDrag(x, y,
                                downPoint.x, downPoint.y, currentPipBounds,
                                mPipResizeGestureHandler.getCtrlType(), minSize.x,
                                minSize.y, maxSize, true,
                                downBounds.width() > downBounds.height()));
                        mPipBoundsAlgorithm.transformBoundsToAspectRatio(lastResizeBounds,
                                mPipBoundsState.getAspectRatio(), false /* useCurrentMinEdgeSize */,
                                true /* useCurrentSize */);
                        mPipScheduler.scheduleUserResizePip(lastResizeBounds);
                        mPipBoundsState.setHasUserResizedPip(true);
                    }
                    break;
                case MotionEvent.ACTION_UP:
                case MotionEvent.ACTION_CANCEL:
                    mPipResizeGestureHandler.finishResize();
                    break;
            }
        }
    }

    /**
     * Check whether the current x,y coordinate is within the region in which drag-resize should
     * start.
     * This consists of 4 small squares on the 4 corners of the PIP window, a quarter of which
     * overlaps with the PIP window while the rest goes outside of the PIP window.
     * _ _           _ _
     * |_|_|_________|_|_|
     * |_|_|         |_|_|
     * |     PIP     |
     * |   WINDOW    |
     * _|_           _|_
     * |_|_|_________|_|_|
     * |_|_|         |_|_|
     */
    boolean isWithinDragResizeRegion(int x, int y) {
        final Rect currentPipBounds = mPipBoundsState.getBounds();
        if (currentPipBounds == null) {
            return false;
        }
        resetDragCorners();
        mTmpTopLeftCorner.offset(currentPipBounds.left - mDelta / 2,
                currentPipBounds.top - mDelta / 2);
        mTmpTopRightCorner.offset(currentPipBounds.right - mDelta / 2,
                currentPipBounds.top - mDelta / 2);
        mTmpBottomLeftCorner.offset(currentPipBounds.left - mDelta / 2,
                currentPipBounds.bottom - mDelta / 2);
        mTmpBottomRightCorner.offset(currentPipBounds.right - mDelta / 2,
                currentPipBounds.bottom - mDelta / 2);

        mTmpRegion.setEmpty();
        mTmpRegion.op(mTmpTopLeftCorner, Region.Op.UNION);
        mTmpRegion.op(mTmpTopRightCorner, Region.Op.UNION);
        mTmpRegion.op(mTmpBottomLeftCorner, Region.Op.UNION);
        mTmpRegion.op(mTmpBottomRightCorner, Region.Op.UNION);

        return mTmpRegion.contains(x, y);
    }

    private void resetDragCorners() {
        mDragCornerSize.set(0, 0, mDelta, mDelta);
        mTmpTopLeftCorner.set(mDragCornerSize);
        mTmpTopRightCorner.set(mDragCornerSize);
        mTmpBottomLeftCorner.set(mDragCornerSize);
        mTmpBottomRightCorner.set(mDragCornerSize);
    }

    private void setCtrlType(int x, int y) {
        final Rect currentPipBounds = mPipBoundsState.getBounds();
        int ctrlType = mPipResizeGestureHandler.getCtrlType();

        Rect movementBounds = mMovementBoundsSupplier.apply(currentPipBounds);

        mDisplayBounds.set(movementBounds.left,
                movementBounds.top,
                movementBounds.right + currentPipBounds.width(),
                movementBounds.bottom + currentPipBounds.height());

        if (mTmpTopLeftCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
                && currentPipBounds.left != mDisplayBounds.left) {
            ctrlType |= CTRL_LEFT;
            ctrlType |= CTRL_TOP;
        }
        if (mTmpTopRightCorner.contains(x, y) && currentPipBounds.top != mDisplayBounds.top
                && currentPipBounds.right != mDisplayBounds.right) {
            ctrlType |= CTRL_RIGHT;
            ctrlType |= CTRL_TOP;
        }
        if (mTmpBottomRightCorner.contains(x, y)
                && currentPipBounds.bottom != mDisplayBounds.bottom
                && currentPipBounds.right != mDisplayBounds.right) {
            ctrlType |= CTRL_RIGHT;
            ctrlType |= CTRL_BOTTOM;
        }
        if (mTmpBottomLeftCorner.contains(x, y)
                && currentPipBounds.bottom != mDisplayBounds.bottom
                && currentPipBounds.left != mDisplayBounds.left) {
            ctrlType |= CTRL_LEFT;
            ctrlType |= CTRL_BOTTOM;
        }

        mPipResizeGestureHandler.setCtrlType(ctrlType);
    }

}
+129 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.wm.shell.pip2.phone;

import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.view.MotionEvent;

import com.android.wm.shell.common.pip.PipBoundsState;
import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm;

/** Helper for handling pinch-to-resize gestures. */
public class PipPinchToResizeHandler {
    private final PipResizeGestureHandler mPipResizeGestureHandler;
    private final PipBoundsState mPipBoundsState;
    private final PhonePipMenuController mPhonePipMenuController;
    private final PipScheduler mPipScheduler;
    private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;

    private int mFirstIndex = -1;
    private int mSecondIndex = -1;

    public PipPinchToResizeHandler(PipResizeGestureHandler pipResizeGestureHandler,
            PipBoundsState pipBoundsState, PhonePipMenuController phonePipMenuController,
            PipScheduler pipScheduler) {
        mPipResizeGestureHandler = pipResizeGestureHandler;
        mPipBoundsState = pipBoundsState;
        mPhonePipMenuController = phonePipMenuController;
        mPipScheduler = pipScheduler;

        mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
    }

    /** Invoked by {@link PipResizeGestureHandler#onInputEvent} if pinch-to-resize is enabled. */
    void onPinchResize(MotionEvent ev, PointF downPoint, PointF downSecondPoint, Rect downBounds,
            PointF lastPoint, PointF lastSecondPoint, Rect lastResizeBounds, float touchSlop,
            Point minSize, Point maxSize) {
        int action = ev.getActionMasked();

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
            mFirstIndex = -1;
            mSecondIndex = -1;
            mPipResizeGestureHandler.setAllowGesture(false);
            mPipResizeGestureHandler.finishResize();
        }

        if (ev.getPointerCount() != 2) {
            return;
        }

        final Rect pipBounds = mPipBoundsState.getBounds();
        if (action == MotionEvent.ACTION_POINTER_DOWN) {
            if (mFirstIndex == -1 && mSecondIndex == -1
                    && pipBounds.contains((int) ev.getRawX(0), (int) ev.getRawY(0))
                    && pipBounds.contains((int) ev.getRawX(1), (int) ev.getRawY(1))) {
                mPipResizeGestureHandler.setAllowGesture(true);
                mFirstIndex = 0;
                mSecondIndex = 1;
                downPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex));
                downSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex));
                downBounds.set(pipBounds);

                lastPoint.set(downPoint);
                lastSecondPoint.set(lastSecondPoint);
                lastResizeBounds.set(downBounds);

                // start the high perf session as the second pointer gets detected
                mPipResizeGestureHandler.startHighPerfSession();
            }
        }

        if (action == MotionEvent.ACTION_MOVE) {
            if (mFirstIndex == -1 || mSecondIndex == -1) {
                return;
            }

            float x0 = ev.getRawX(mFirstIndex);
            float y0 = ev.getRawY(mFirstIndex);
            float x1 = ev.getRawX(mSecondIndex);
            float y1 = ev.getRawY(mSecondIndex);
            lastPoint.set(x0, y0);
            lastSecondPoint.set(x1, y1);

            // Capture inputs
            if (!mPipResizeGestureHandler.getThresholdCrossed()
                    && (distanceBetween(downSecondPoint, lastSecondPoint) > touchSlop
                    || distanceBetween(downPoint, lastPoint) > touchSlop)) {
                mPipResizeGestureHandler.pilferPointers();
                mPipResizeGestureHandler.setThresholdCrossed(true);
                // Reset the down to begin resizing from this point
                downPoint.set(lastPoint);
                downSecondPoint.set(lastSecondPoint);

                if (mPhonePipMenuController.isMenuVisible()) {
                    mPhonePipMenuController.hideMenu();
                }
            }

            if (mPipResizeGestureHandler.getThresholdCrossed()) {
                final float angle = mPinchResizingAlgorithm.calculateBoundsAndAngle(downPoint,
                        downSecondPoint, lastPoint, lastSecondPoint, minSize, maxSize,
                        downBounds, lastResizeBounds);

                mPipResizeGestureHandler.setAngle(angle);
                mPipScheduler.scheduleUserResizePip(lastResizeBounds, angle);
                mPipBoundsState.setHasUserResizedPip(true);
            }
        }
    }

    private float distanceBetween(PointF p1, PointF p2) {
        return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y);
    }

}
+94 −102

File changed.

Preview size limit exceeded, changes collapsed.

Loading