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

Commit 559081cf authored by Jeremy Sim's avatar Jeremy Sim Committed by Android (Google) Code Review
Browse files

Merge "Flexible 2-app split: Motion and Overview" into main

parents 2814d858 f861cea5
Loading
Loading
Loading
Loading
+27 −13
Original line number Diff line number Diff line
@@ -118,8 +118,16 @@ public class DividerSnapAlgorithm {
        mDisplayHeight = displayHeight;
        mIsLeftRightSplit = isLeftRightSplit;
        mInsets.set(insets);
        mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED :
                res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode);
        if (Flags.enableFlexibleTwoAppSplit()) {
            // In flexible split, we always use a fixed ratio (50%, 66%, or 90%) for splitting
            mSnapMode = SNAP_FIXED_RATIO;
        } else {
            // Set SNAP_MODE_MINIMIZED, SNAP_MODE_16_9, or SNAP_FIXED_RATIO depending on config
            mSnapMode = isMinimizedMode
                    ? SNAP_MODE_MINIMIZED
                    : res.getInteger(
                            com.android.internal.R.integer.config_dockedStackDividerSnapMode);
        }
        mFreeSnapMode = res.getBoolean(
                com.android.internal.R.bool.config_dockedStackDividerFreeSnapMode);
        mFixedRatio = res.getFraction(
@@ -129,8 +137,7 @@ public class DividerSnapAlgorithm {
        mCalculateRatiosBasedOnAvailableSpace = res.getBoolean(
                com.android.internal.R.bool.config_flexibleSplitRatios);
        // If this is a small screen or a foldable, use offscreen ratios
        mAllowOffscreenRatios =
                Flags.enableFlexibleTwoAppSplit() && SplitScreenUtils.allowOffscreenRatios(res);
        mAllowOffscreenRatios = SplitScreenUtils.allowOffscreenRatios(res);
        mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
                com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
        calculateTargets(isLeftRightSplit, dockSide);
@@ -299,9 +306,9 @@ public class DividerSnapAlgorithm {
    private void addNonDismissingTargets(boolean isLeftRightSplit, int topPosition,
            int bottomPosition, int dividerMax) {
        @PersistentSnapPosition int firstTarget =
                mAllowOffscreenRatios ? SNAP_TO_2_10_90 : SNAP_TO_2_33_66;
                areOffscreenRatiosSupported() ? SNAP_TO_2_10_90 : SNAP_TO_2_33_66;
        @PersistentSnapPosition int lastTarget =
                mAllowOffscreenRatios ? SNAP_TO_2_90_10 : SNAP_TO_2_66_33;
                areOffscreenRatiosSupported() ? SNAP_TO_2_90_10 : SNAP_TO_2_66_33;
        maybeAddTarget(topPosition, topPosition - getStartInset(), firstTarget);
        addMiddleTarget(isLeftRightSplit);
        maybeAddTarget(bottomPosition,
@@ -313,14 +320,21 @@ public class DividerSnapAlgorithm {
        int end = isLeftRightSplit
                ? mDisplayWidth - mInsets.right
                : mDisplayHeight - mInsets.bottom;
        int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;

        if (mAllowOffscreenRatios) {
            // TODO (b/349828130): This is a placeholder value, real measurements to come
            size = (int) (0.3f * (end - start)) - mDividerSize / 2;
        } else if (mCalculateRatiosBasedOnAvailableSpace) {
        int size;
        if (Flags.enableFlexibleTwoAppSplit()) {
            float ratio = areOffscreenRatiosSupported()
                    ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO
                    : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
            size = (int) (ratio * (end - start)) - mDividerSize / 2;
        } else {
            size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;

            if (mCalculateRatiosBasedOnAvailableSpace) {
                size = Math.max(size, mMinimalSizeResizableTask);
            }
        }

        int topPosition = start + size;
        int bottomPosition = end - size - mDividerSize;
        addNonDismissingTargets(isLeftRightSplit, topPosition, bottomPosition, dividerMax);
@@ -347,7 +361,7 @@ public class DividerSnapAlgorithm {
     * meets the minimal size requirement.
     */
    private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapPosition) {
        if (smallerSize >= mMinimalSizeResizableTask || mAllowOffscreenRatios) {
        if (smallerSize >= mMinimalSizeResizableTask || areOffscreenRatiosSupported()) {
            mTargets.add(new SnapTarget(position, snapPosition));
        }
    }
+1 −0
Original line number Diff line number Diff line
@@ -480,6 +480,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
                    mLastDraggingPosition,
                    position,
                    mSplitLayout.FLING_RESIZE_DURATION,
                    Interpolators.FAST_OUT_SLOW_IN,
                    () -> mSplitLayout.setDividerPosition(position, true /* applyLayoutChange */));
            mMoving = false;
        }
+37 −11
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLATOR;
import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
import static com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.wm.shell.shared.animation.Interpolators.LINEAR;
import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
@@ -77,7 +78,6 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.pip.PipUtils;
import com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.shared.annotations.ShellMainThread;
import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition;
@@ -100,6 +100,12 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
    public static final int FLING_RESIZE_DURATION = 250;
    private static final int FLING_ENTER_DURATION = 450;
    private static final int FLING_EXIT_DURATION = 450;
    private static final int FLING_OFFSCREEN_DURATION = 500;

    /** A split ratio used on larger screens, where we can fit both apps onscreen. */
    public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f;
    /** A split ratio used on smaller screens, where we place one app mostly offscreen. */
    public static final float OFFSCREEN_ASYMMETRIC_RATIO = 0.1f;

    // Here are some (arbitrarily decided) layer definitions used during animations to make sure the
    // layers stay in order. Note: This does not affect any other layer numbering systems because
@@ -604,25 +610,35 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
     * Sets new divider position and updates bounds correspondingly. Notifies listener if the new
     * target indicates dismissing split.
     */
    public void snapToTarget(int currentPosition, SnapTarget snapTarget) {
    public void snapToTarget(int currentPosition, SnapTarget snapTarget, int duration,
            Interpolator interpolator) {
        switch (snapTarget.snapPosition) {
            case SNAP_TO_START_AND_DISMISS:
                flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
                        () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
                                EXIT_REASON_DRAG_DIVIDER));
                break;
            case SNAP_TO_END_AND_DISMISS:
                flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
                        () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
                                EXIT_REASON_DRAG_DIVIDER));
                break;
            default:
                flingDividerPosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                flingDividerPosition(currentPosition, snapTarget.position, duration, interpolator,
                        () -> setDividerPosition(snapTarget.position, true /* applyLayoutChange */));
                break;
        }
    }

    /**
     * Same as {@link #snapToTarget(int, SnapTarget)}, with default animation duration and
     * interpolator.
     */
    public void snapToTarget(int currentPosition, SnapTarget snapTarget) {
        snapToTarget(currentPosition, snapTarget, FLING_RESIZE_DURATION,
                FAST_OUT_SLOW_IN);
    }

    void onStartDragging() {
        mInteractionJankMonitor.begin(getDividerLeash(), mContext, mHandler,
                CUJ_SPLIT_SCREEN_RESIZE);
@@ -674,14 +690,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
    public void flingDividerToDismiss(boolean toEnd, int reason) {
        final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
                : mDividerSnapAlgorithm.getDismissStartTarget().position;
        flingDividerPosition(getDividerPosition(), target, FLING_EXIT_DURATION,
        flingDividerPosition(getDividerPosition(), target, FLING_EXIT_DURATION, FAST_OUT_SLOW_IN,
                () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
    }

    /** Fling divider from current position to center position. */
    public void flingDividerToCenter(@Nullable Runnable finishCallback) {
        final int pos = mDividerSnapAlgorithm.getMiddleTarget().position;
        flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION,
        flingDividerPosition(getDividerPosition(), pos, FLING_ENTER_DURATION, FAST_OUT_SLOW_IN,
                () -> {
                    setDividerPosition(pos, true /* applyLayoutChange */);
                    if (finishCallback != null) {
@@ -699,14 +715,16 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
    public void flingDividerToOtherSide(@PersistentSnapPosition int currentSnapPosition) {
        switch (currentSnapPosition) {
            case SNAP_TO_2_10_90 ->
                    snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget());
                    snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getLastSplitTarget(),
                            FLING_OFFSCREEN_DURATION, EMPHASIZED);
            case SNAP_TO_2_90_10 ->
                    snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getFirstSplitTarget());
                    snapToTarget(mDividerPosition, mDividerSnapAlgorithm.getFirstSplitTarget(),
                            FLING_OFFSCREEN_DURATION, EMPHASIZED);
        }
    }

    @VisibleForTesting
    void flingDividerPosition(int from, int to, int duration,
    void flingDividerPosition(int from, int to, int duration, Interpolator interpolator,
            @Nullable Runnable flingFinishedCallback) {
        if (from == to) {
            if (flingFinishedCallback != null) {
@@ -724,7 +742,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        mDividerFlingAnimator = ValueAnimator
                .ofInt(from, to)
                .setDuration(duration);
        mDividerFlingAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
        mDividerFlingAnimator.setInterpolator(interpolator);

        // If the divider is being physically controlled by the user, we use a cool parallax effect
        // on the task windows. So if this "snap" animation is an extension of a user-controlled
@@ -1048,6 +1066,14 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        return (int) (minWidth / density);
    }

    public int getDisplayWidth() {
        return mRootBounds.width();
    }

    public int getDisplayHeight() {
        return mRootBounds.height();
    }

    /**
     * Shift configuration bounds to prevent client apps get configuration changed or relaunch. And
     * restore shifted configuration bounds if it's no longer shifted.
+15 −3
Original line number Diff line number Diff line
@@ -141,14 +141,26 @@ public class SplitScreenUtils {
        return config.smallestScreenWidthDp >= LARGE_SCREEN_MIN_EDGE_DP;
    }

    /**
     * Convenience function for {@link #isLargeScreen(Configuration)}.
     */
    public static boolean isLargeScreen(Resources res) {
        return isLargeScreen(res.getConfiguration());
    }

    /**
     * Returns whether the current device is a foldable
     */
    public static boolean isFoldable(Resources res) {
        return res.getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0;
    }

    /**
     * Returns whether we should allow split ratios to go offscreen or not. If the device is a phone
     * or a foldable (either screen), we allow it.
     */
    public static boolean allowOffscreenRatios(Resources res) {
        return !isLargeScreen(res.getConfiguration())
                ||
                res.getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0;
        return Flags.enableFlexibleTwoAppSplit() && (!isLargeScreen(res) || isFoldable(res));
    }

    /**
+15 −0
Original line number Diff line number Diff line
@@ -1625,6 +1625,21 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
                leftTopTaskId = mainStageTopTaskId;
                rightBottomTaskId = sideStageTopTaskId;
            }

            if (Flags.enableFlexibleTwoAppSplit()) {
                // Split screen can be laid out in such a way that some of the apps are offscreen.
                // For the purposes of passing SplitBounds up to launcher (for use in thumbnails
                // etc.), we crop the bounds down to the screen size.
                topLeftBounds.left =
                        Math.max(topLeftBounds.left, 0);
                topLeftBounds.top =
                        Math.max(topLeftBounds.top, 0);
                bottomRightBounds.right =
                        Math.min(bottomRightBounds.right, mSplitLayout.getDisplayWidth());
                bottomRightBounds.top =
                        Math.min(bottomRightBounds.top, mSplitLayout.getDisplayHeight());
            }

            SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
                    leftTopTaskId, rightBottomTaskId, mSplitLayout.calculateCurrentSnapPosition());
            if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
Loading