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

Commit 43f5ce26 authored by Ikram Gabiyev's avatar Ikram Gabiyev
Browse files

Implement pinch resizing in PiP2

Use transitions and PipTransitionState to allow
pinch-to-resize gestures in PiP2.

At the moment, the animator for when both pointers
are release is not set up so after pinching is complete
and pointers are release a transition is played w/o
any animation - just a jumpcut instead.

Bug: 340883807
Test: enter PiP2 and pinch-to-resize
Change-Id: Ic2a11444506663c96a2c8ac052092e84ea292889
parent 99c3b4e8
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -133,6 +133,7 @@ public abstract class Pip2Module {
            PipBoundsAlgorithm pipBoundsAlgorithm,
            @NonNull PipBoundsState pipBoundsState,
            @NonNull PipTransitionState pipTransitionState,
            @NonNull PipScheduler pipScheduler,
            @NonNull SizeSpecSource sizeSpecSource,
            PipMotionHelper pipMotionHelper,
            FloatingContentCoordinator floatingContentCoordinator,
@@ -140,7 +141,7 @@ public abstract class Pip2Module {
            @ShellMainThread ShellExecutor mainExecutor,
            Optional<PipPerfHintController> pipPerfHintControllerOptional) {
        return new PipTouchHandler(context, shellInit, menuPhoneController, pipBoundsAlgorithm,
                pipBoundsState, pipTransitionState, sizeSpecSource, pipMotionHelper,
                pipBoundsState, pipTransitionState, pipScheduler, sizeSpecSource, pipMotionHelper,
                floatingContentCoordinator, pipUiEventLogger, mainExecutor,
                pipPerfHintControllerOptional);
    }
+5 −3
Original line number Diff line number Diff line
@@ -58,7 +58,8 @@ import java.util.function.Consumer;
 * A helper to animate and manipulate the PiP.
 */
public class PipMotionHelper implements PipAppOpsListener.Callback,
        FloatingContentCoordinator.FloatingContent {
        FloatingContentCoordinator.FloatingContent,
        PipTransitionState.PipTransitionStateChangedListener {
    private static final String TAG = "PipMotionHelper";
    private static final String FLING_BOUNDS_CHANGE = "fling_bounds_change";
    private static final boolean DEBUG = false;
@@ -181,7 +182,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
            }
        };
        mPipTransitionState = pipTransitionState;
        mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
        mPipTransitionState.addPipTransitionStateChangedListener(this);
    }

    void init() {
@@ -687,7 +688,8 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
        // setAnimatingToBounds(toBounds);
    }

    private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
    @Override
    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
            @PipTransitionState.TransitionState int newState,
            @Nullable Bundle extra) {
        switch (newState) {
+100 −67
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.Looper;
import android.view.BatchedInputEventReceiver;
import android.view.Choreographer;
@@ -32,6 +33,7 @@ import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputMonitor;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.ViewConfiguration;

import androidx.annotation.VisibleForTesting;
@@ -51,16 +53,20 @@ import java.util.function.Consumer;
 * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to
 * trigger dynamic resize.
 */
public class PipResizeGestureHandler {
public class PipResizeGestureHandler implements
        PipTransitionState.PipTransitionStateChangedListener {

    private static final String TAG = "PipResizeGestureHandler";
    private static final int PINCH_RESIZE_SNAP_DURATION = 250;
    private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f;
    private static final String RESIZE_BOUNDS_CHANGE = "resize_bounds_change";

    private final Context mContext;
    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
    private final PipBoundsState mPipBoundsState;
    private final PipTouchState mPipTouchState;
    private final PipScheduler mPipScheduler;
    private final PipTransitionState mPipTransitionState;
    private final PhonePipMenuController mPhonePipMenuController;
    private final PipUiEventLogger mPipUiEventLogger;
    private final PipPinchResizingAlgorithm mPinchResizingAlgorithm;
@@ -88,6 +94,7 @@ public class PipResizeGestureHandler {
    private boolean mIsSysUiStateValid;
    private boolean mThresholdCrossed;
    private boolean mOngoingPinchToResize = false;
    private boolean mWaitingForBoundsChangeTransition = false;
    private float mAngle = 0;
    int mFirstIndex = -1;
    int mSecondIndex = -1;
@@ -104,11 +111,17 @@ public class PipResizeGestureHandler {
    private int mCtrlType;
    private int mOhmOffset;

    public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
            PipBoundsState pipBoundsState, PipTouchState pipTouchState,
    public PipResizeGestureHandler(Context context,
            PipBoundsAlgorithm pipBoundsAlgorithm,
            PipBoundsState pipBoundsState,
            PipTouchState pipTouchState,
            PipScheduler pipScheduler,
            PipTransitionState pipTransitionState,
            Runnable updateMovementBoundsRunnable,
            PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
            ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) {
            PipUiEventLogger pipUiEventLogger,
            PhonePipMenuController menuActivityController,
            ShellExecutor mainExecutor,
            @Nullable PipPerfHintController pipPerfHintController) {
        mContext = context;
        mDisplayId = context.getDisplayId();
        mMainExecutor = mainExecutor;
@@ -116,6 +129,11 @@ public class PipResizeGestureHandler {
        mPipBoundsAlgorithm = pipBoundsAlgorithm;
        mPipBoundsState = pipBoundsState;
        mPipTouchState = pipTouchState;
        mPipScheduler = pipScheduler;

        mPipTransitionState = pipTransitionState;
        mPipTransitionState.addPipTransitionStateChangedListener(this);

        mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable;
        mPhonePipMenuController = menuActivityController;
        mPipUiEventLogger = pipUiEventLogger;
@@ -125,6 +143,7 @@ public class PipResizeGestureHandler {
            mUserResizeBounds.set(rect);
            // mMotionHelper.synchronizePinnedStackBounds();
            mUpdateMovementBoundsRunnable.run();
            mPipBoundsState.setBounds(rect);
            resetState();
        };
    }
@@ -202,7 +221,7 @@ public class PipResizeGestureHandler {
    @VisibleForTesting
    void onInputEvent(InputEvent ev) {
        if (!mEnablePinchResize) {
            // No need to handle anything if neither form of resizing is enabled.
            // No need to handle anything if resizing isn't enabled.
            return;
        }

@@ -227,7 +246,7 @@ public class PipResizeGestureHandler {
                }
            }

            if (mEnablePinchResize && mOngoingPinchToResize) {
            if (mOngoingPinchToResize) {
                onPinchResize(mv);
            }
        }
@@ -249,7 +268,6 @@ public class PipResizeGestureHandler {
    }

    boolean willStartResizeGesture(MotionEvent ev) {
        if (isInValidSysUiState()) {
        if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
            if (mEnablePinchResize && ev.getPointerCount() == 2) {
                onPinchResize(ev);
@@ -257,7 +275,6 @@ public class PipResizeGestureHandler {
                return mAllowGesture;
            }
        }
        }
        return false;
    }

@@ -284,7 +301,6 @@ public class PipResizeGestureHandler {
            mSecondIndex = -1;
            mAllowGesture = false;
            finishResize();
            cleanUpHighPerfSessionMaybe();
        }

        if (ev.getPointerCount() != 2) {
@@ -347,10 +363,7 @@ public class PipResizeGestureHandler {
                        mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize,
                        mDownBounds, mLastResizeBounds);

                /*
                mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds,
                        mAngle, null);
                 */
                mPipScheduler.scheduleUserResizePip(mLastResizeBounds, mAngle);
                mPipBoundsState.setHasUserResizedPip(true);
            }
        }
@@ -399,11 +412,14 @@ public class PipResizeGestureHandler {
    }

    private void finishResize() {
        if (!mLastResizeBounds.isEmpty()) {
            // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped
            // position correctly. Drag-resize does not need to move, so just finalize resize.
            if (mOngoingPinchToResize) {
        if (mLastResizeBounds.isEmpty()) {
            resetState();
        }
        if (!mOngoingPinchToResize) {
            return;
        }
        final Rect startBounds = new Rect(mLastResizeBounds);

        // If user resize is pretty close to max size, just auto resize to max.
        if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x
                || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) {
@@ -427,29 +443,12 @@ public class PipResizeGestureHandler {
                mLastResizeBounds, movementBounds);
        mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);

                // disable any touch events beyond resizing too
                mPipTouchState.setAllowInputEvents(false);
        // Update the transition state to schedule a resize transition.
        Bundle extra = new Bundle();
        extra.putBoolean(RESIZE_BOUNDS_CHANGE, true);
        mPipTransitionState.setState(PipTransitionState.SCHEDULED_BOUNDS_CHANGE, extra);

                /*
                mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
                        PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
                            // enable touch events
                            mPipTouchState.setAllowInputEvents(true);
                        });
                 */
            } else {
                /*
                mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
                        TRANSITION_DIRECTION_USER_RESIZE,
                        mUpdateResizeBoundsCallback);
                 */
            }
            final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f;
            mPipUiEventLogger.log(
                    PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
        } else {
            resetState();
        }
        mPipUiEventLogger.log(PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE);
    }

    private void resetState() {
@@ -509,6 +508,40 @@ public class PipResizeGestureHandler {
        rect.set(l, t, r, b);
    }

    @Override
    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
            @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
        switch (newState) {
            case PipTransitionState.SCHEDULED_BOUNDS_CHANGE:
                if (!extra.getBoolean(RESIZE_BOUNDS_CHANGE)) break;
                mWaitingForBoundsChangeTransition = true;
                mPipScheduler.scheduleAnimateResizePip(mLastResizeBounds);
                break;
            case PipTransitionState.CHANGING_PIP_BOUNDS:
                if (!mWaitingForBoundsChangeTransition) break;

                // If bounds change transition was scheduled from this class, handle leash updates.
                mWaitingForBoundsChangeTransition = false;

                SurfaceControl.Transaction startTx = extra.getParcelable(
                        PipTransition.PIP_START_TX, SurfaceControl.Transaction.class);
                Rect destinationBounds = extra.getParcelable(
                        PipTransition.PIP_DESTINATION_BOUNDS, Rect.class);
                startTx.setPosition(mPipTransitionState.mPinnedTaskLeash,
                        destinationBounds.left, destinationBounds.top);
                startTx.apply();

                // All motion operations have actually finished, so make bounds cache updates.
                cleanUpHighPerfSessionMaybe();

                // Setting state to CHANGED_PIP_BOUNDS applies finishTx and notifies Core.
                mPipTransitionState.setState(PipTransitionState.CHANGED_PIP_BOUNDS);

                mUpdateResizeBoundsCallback.accept(destinationBounds);
                break;
        }
    }

    /**
     * Dumps the {@link PipResizeGestureHandler} state.
     */
+21 −1
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
@@ -165,6 +166,16 @@ public class PipScheduler {
     * {@link WindowContainerTransaction}.
     */
    public void scheduleUserResizePip(Rect toBounds) {
        scheduleUserResizePip(toBounds, 0f /* degrees */);
    }

    /**
     * Directly perform a scaled matrix transformation on the leash. This will not perform any
     * {@link WindowContainerTransaction}.
     *
     * @param degrees the angle to rotate the bounds to.
     */
    public void scheduleUserResizePip(Rect toBounds, float degrees) {
        if (toBounds.isEmpty()) {
            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: Attempted to user resize PIP to empty bounds, aborting.", TAG);
@@ -172,7 +183,16 @@ public class PipScheduler {
        }
        SurfaceControl leash = mPipTransitionState.mPinnedTaskLeash;
        final SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
        tx.setPosition(leash, toBounds.left, toBounds.top);

        Matrix transformTensor = new Matrix();
        final float[] mMatrixTmp = new float[9];
        final float scale = (float) toBounds.width() / mPipBoundsState.getBounds().width();

        transformTensor.setScale(scale, scale);
        transformTensor.postTranslate(toBounds.left, toBounds.top);
        transformTensor.postRotate(degrees, toBounds.centerX(), toBounds.centerY());

        tx.setMatrix(leash, transformTensor, mMatrixTmp);
        tx.apply();
    }
}
+10 −6
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@ import java.util.Optional;
 * Manages all the touch handling for PIP on the Phone, including moving, dismissing and expanding
 * the PIP.
 */
public class PipTouchHandler {
public class PipTouchHandler implements PipTransitionState.PipTransitionStateChangedListener {

    private static final String TAG = "PipTouchHandler";
    private static final float DEFAULT_STASH_VELOCITY_THRESHOLD = 18000.f;
@@ -84,6 +84,7 @@ public class PipTouchHandler {
    private final PipBoundsAlgorithm mPipBoundsAlgorithm;
    @NonNull private final PipBoundsState mPipBoundsState;
    @NonNull private final PipTransitionState mPipTransitionState;
    @NonNull private final PipScheduler mPipScheduler;
    @NonNull private final SizeSpecSource mSizeSpecSource;
    private final PipUiEventLogger mPipUiEventLogger;
    private final PipDismissTargetHandler mPipDismissTargetHandler;
@@ -173,6 +174,7 @@ public class PipTouchHandler {
            PipBoundsAlgorithm pipBoundsAlgorithm,
            @NonNull PipBoundsState pipBoundsState,
            @NonNull PipTransitionState pipTransitionState,
            @NonNull PipScheduler pipScheduler,
            @NonNull SizeSpecSource sizeSpecSource,
            PipMotionHelper pipMotionHelper,
            FloatingContentCoordinator floatingContentCoordinator,
@@ -188,6 +190,7 @@ public class PipTouchHandler {

        mPipTransitionState = pipTransitionState;
        mPipTransitionState.addPipTransitionStateChangedListener(this::onPipTransitionStateChanged);
        mPipScheduler = pipScheduler;
        mSizeSpecSource = sizeSpecSource;
        mMenuController = menuController;
        mPipUiEventLogger = pipUiEventLogger;
@@ -213,10 +216,10 @@ public class PipTouchHandler {
                },
                menuController::hideMenu,
                mainExecutor);
        mPipResizeGestureHandler =
                new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
                        mTouchState, this::updateMovementBounds, pipUiEventLogger,
                        menuController, mainExecutor, mPipPerfHintController);
        mPipResizeGestureHandler = new PipResizeGestureHandler(context, pipBoundsAlgorithm,
                pipBoundsState, mTouchState, mPipScheduler, mPipTransitionState,
                this::updateMovementBounds, pipUiEventLogger, menuController, mainExecutor,
                mPipPerfHintController);
        mPipBoundsState.addOnAspectRatioChangedCallback(this::updateMinMaxSize);

        if (PipUtils.isPip2ExperimentEnabled()) {
@@ -1075,7 +1078,8 @@ public class PipTouchHandler {
        mPipResizeGestureHandler.setOhmOffset(offset);
    }

    private void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
    @Override
    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
            @PipTransitionState.TransitionState int newState,
            @Nullable Bundle extra) {
        switch (newState) {