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

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

Merge "Flexible Split: Reference class" into main

parents ec47a5c5 da1bb958
Loading
Loading
Loading
Loading
+19 −1
Original line number Diff line number Diff line
@@ -159,7 +159,8 @@ public class SplitScreenConstants {
     * {@link PersistentSnapPosition} + {@link #NOT_IN_SPLIT}.
     */
    @IntDef(value = {
            NOT_IN_SPLIT,
            NOT_IN_SPLIT, // user is not in split screen
            SNAP_TO_NONE, // in "free snap mode," where apps are fully resizable
            SNAP_TO_2_33_66,
            SNAP_TO_2_50_50,
            SNAP_TO_2_66_33,
@@ -171,6 +172,23 @@ public class SplitScreenConstants {
    })
    public @interface SplitScreenState {}

    /** Converts a {@link SplitScreenState} to a human-readable string. */
    public static String stateToString(@SplitScreenState int state) {
        return switch (state) {
            case NOT_IN_SPLIT -> "NOT_IN_SPLIT";
            case SNAP_TO_NONE -> "SNAP_TO_NONE";
            case SNAP_TO_2_33_66 -> "SNAP_TO_2_33_66";
            case SNAP_TO_2_50_50 -> "SNAP_TO_2_50_50";
            case SNAP_TO_2_66_33 -> "SNAP_TO_2_66_33";
            case SNAP_TO_2_90_10 -> "SNAP_TO_2_90_10";
            case SNAP_TO_2_10_90 -> "SNAP_TO_2_10_90";
            case SNAP_TO_3_33_33_33 -> "SNAP_TO_3_33_33_33";
            case SNAP_TO_3_45_45_10 -> "SNAP_TO_3_45_45_10";
            case SNAP_TO_3_10_45_45 -> "SNAP_TO_3_10_45_45";
            default -> "UNKNOWN";
        };
    }

    /**
     * Checks if the snapPosition in question is a {@link PersistentSnapPosition}.
     */
+2 −2
Original line number Diff line number Diff line
@@ -352,8 +352,8 @@ public class DividerSnapAlgorithm {
                ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom;

        float ratio = areOffscreenRatiosSupported()
                ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO
                : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
                ? SplitSpec.OFFSCREEN_ASYMMETRIC_RATIO
                : SplitSpec.ONSCREEN_ONLY_ASYMMETRIC_RATIO;
        int size = (int) (ratio * (end - start)) - mDividerSize / 2;

        int leftTopPosition = start + pinnedTaskbarShiftStart + size;
+24 −15
Original line number Diff line number Diff line
@@ -112,11 +112,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
    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. (During transitions, everything is reparented onto a transition root
    // and can be freely relayered.)
@@ -236,7 +231,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        updateDividerConfig(mContext);

        mRootBounds.set(configuration.windowConfiguration.getBounds());
        mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
        updateLayouts();
        mInteractionJankMonitor = InteractionJankMonitor.getInstance();
        resetDividerPosition();
        updateInvisibleRect();
@@ -490,7 +485,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        mIsLargeScreen = configuration.smallestScreenWidthDp >= 600;
        mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
                configuration);
        mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
        updateLayouts();
        updateDividerConfig(mContext);
        initDividerPosition(mTempRect, wasLeftRightSplit);
        updateInvisibleRect();
@@ -518,7 +513,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        mRootBounds.set(tmpRect);
        mIsLeftRightSplit = SplitScreenUtils.isLeftRightSplit(mAllowLeftRightSplitInPortrait,
                mIsLargeScreen, mRootBounds.width() >= mRootBounds.height());
        mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
        updateLayouts();
        initDividerPosition(mTempRect, wasLeftRightSplit);
    }

@@ -652,7 +647,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) {
            mPinnedTaskbarInsets = pinnedTaskbarInsets;
            // Refresh the DividerSnapAlgorithm.
            mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds);
            updateLayouts();
            // If the divider is no longer placed on a snap point, animate it to the nearest one.
            DividerSnapAlgorithm.SnapTarget snapTarget =
                    findSnapTarget(mDividerPosition, 0, false /* hardDismiss */);
@@ -824,8 +819,22 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
        return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
    }

    private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds) {
        final Rect insets = getDisplayStableInsets(context);
    /**
     * (Re)calculates the split screen logic for this particular display/orientation. Refreshes the
     * DividerSnapAlgorithm, which controls divider snap points, and populates a map in SplitState
     * with bounds for all valid split layouts.
     */
    private void updateLayouts() {
        // Update SplitState map

        if (Flags.enableFlexibleTwoAppSplit()) {
            mSplitState.populateLayouts(
                    mRootBounds, mDividerSize, mIsLeftRightSplit, mPinnedTaskbarInsets.toRect());
        }

        // Get new DividerSnapAlgorithm

        final Rect insets = getDisplayStableInsets(mContext);

        // Make split axis insets value same as the larger one to avoid bounds1 and bounds2
        // have difference for avoiding size-compat mode when switching unresizable apps in
@@ -835,10 +844,10 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
            insets.set(insets.left, largerInsets, insets.right, largerInsets);
        }

        return new DividerSnapAlgorithm(
                context.getResources(),
                rootBounds.width(),
                rootBounds.height(),
        mDividerSnapAlgorithm = new DividerSnapAlgorithm(
                mContext.getResources(),
                mRootBounds.width(),
                mRootBounds.height(),
                mDividerSize,
                mIsLeftRightSplit,
                insets,
+183 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.common.split;

import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_10_90;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_33_66;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_50_50;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_66_33;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_2_90_10;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_10_45_45;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_33_33_33;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_3_45_45_10;
import static com.android.wm.shell.shared.split.SplitScreenConstants.stateToString;

import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;

import com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * A reference class that stores the split layouts available in this device/orientation. Layouts are
 * available as lists of RectFs, where each RectF represents the bounds of an app.
 */
public class SplitSpec {
    private static final String TAG = "SplitSpec";
    private static final boolean DEBUG = true;

    /** 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;
    /** A 50-50 split ratio. */
    public static final float MIDDLE_RATIO = 0.5f;

    private final boolean mIsLeftRightSplit;
    /** The usable display area, considering insets that affect split bounds. */
    private final RectF mUsableArea;
    /** Half the divider size. */
    private final float mHalfDiv;

    /** A large map that stores all valid split layouts. */
    private final Map<Integer, List<RectF>> mLayouts = new HashMap<>();

    /** Constructor; initializes the layout map. */
    public SplitSpec(Rect displayBounds, int dividerSize, boolean isLeftRightSplit,
            Rect pinnedTaskbarInsets) {
        mIsLeftRightSplit = isLeftRightSplit;
        mUsableArea = new RectF(displayBounds);
        mUsableArea.left += pinnedTaskbarInsets.left;
        mUsableArea.top += pinnedTaskbarInsets.top;
        mUsableArea.right -= pinnedTaskbarInsets.right;
        mUsableArea.bottom -= pinnedTaskbarInsets.bottom;
        mHalfDiv = dividerSize / 2f;

        // The "start" position, considering insets.
        float s = isLeftRightSplit ? mUsableArea.left : mUsableArea.top;
        // The "end" position, considering insets.
        float e = isLeftRightSplit ? mUsableArea.right : mUsableArea.bottom;
        // The "length" of the usable display (width or height). Apps are arranged along this axis.
        float l = e - s;
        float divPos;
        float divPos2;

        // SNAP_TO_2_10_90
        divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO);
        createAppLayout(SNAP_TO_2_10_90, divPos);

        // SNAP_TO_2_33_66
        divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO);
        createAppLayout(SNAP_TO_2_33_66, divPos);

        // SNAP_TO_2_50_50
        divPos = s + (l * MIDDLE_RATIO);
        createAppLayout(SNAP_TO_2_50_50, divPos);

        // SNAP_TO_2_66_33
        divPos = s + (l * (1 - ONSCREEN_ONLY_ASYMMETRIC_RATIO));
        createAppLayout(SNAP_TO_2_66_33, divPos);

        // SNAP_TO_2_90_10
        divPos = s + (l * (1 - OFFSCREEN_ASYMMETRIC_RATIO));
        createAppLayout(SNAP_TO_2_90_10, divPos);

        // SNAP_TO_3_10_45_45
        divPos = s + (l * OFFSCREEN_ASYMMETRIC_RATIO);
        divPos2 = e - ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f);
        createAppLayout(SNAP_TO_3_10_45_45, divPos, divPos2);

        // SNAP_TO_3_33_33_33
        divPos = s + (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO);
        divPos2 = e - (l * ONSCREEN_ONLY_ASYMMETRIC_RATIO);
        createAppLayout(SNAP_TO_3_33_33_33, divPos, divPos2);

        // SNAP_TO_3_45_45_10
        divPos = s + ((l * (1 - OFFSCREEN_ASYMMETRIC_RATIO)) / 2f);
        divPos2 = e - (l * OFFSCREEN_ASYMMETRIC_RATIO);
        createAppLayout(SNAP_TO_3_45_45_10, divPos, divPos2);

        if (DEBUG) {
            dump();
        }
    }

    /**
     * Creates a two-app layout and enters it into the layout map.
     * @param divPos The position of the divider.
     */
    private void createAppLayout(@SplitScreenState int state, float divPos) {
        List<RectF> list = new ArrayList<>();
        RectF rect1 = new RectF(mUsableArea);
        RectF rect2 = new RectF(mUsableArea);
        if (mIsLeftRightSplit) {
            rect1.right = divPos - mHalfDiv;
            rect2.left = divPos + mHalfDiv;
        } else {
            rect1.top = divPos - mHalfDiv;
            rect2.bottom = divPos + mHalfDiv;
        }
        list.add(rect1);
        list.add(rect2);
        mLayouts.put(state, list);
    }

    /**
     * Creates a three-app layout and enters it into the layout map.
     * @param divPos1 The position of the first divider.
     * @param divPos2 The position of the second divider.
     */
    private void createAppLayout(@SplitScreenState int state, float divPos1, float divPos2) {
        List<RectF> list = new ArrayList<>();
        RectF rect1 = new RectF(mUsableArea);
        RectF rect2 = new RectF(mUsableArea);
        RectF rect3 = new RectF(mUsableArea);
        if (mIsLeftRightSplit) {
            rect1.right = divPos1 - mHalfDiv;
            rect2.left = divPos1 + mHalfDiv;
            rect2.right = divPos2 - mHalfDiv;
            rect3.left = divPos2 + mHalfDiv;
        } else {
            rect1.right = divPos1 - mHalfDiv;
            rect2.left = divPos1 + mHalfDiv;
            rect3.right = divPos2 - mHalfDiv;
            rect3.left = divPos2 + mHalfDiv;
        }
        list.add(rect1);
        list.add(rect2);
        list.add(rect3);
        mLayouts.put(state, list);
    }

    /** Logs all calculated layouts */
    private void dump() {
        mLayouts.forEach((k, v) -> {
            Log.d(TAG, stateToString(k));
            v.forEach(rect -> Log.d(TAG, " - " + rect.toShortString()));
        });
    }

    /** Returns the layout associated with a given split state. */
    List<RectF> getSpec(@SplitScreenState int state) {
        return mLayouts.get(state);
    }
}
+18 −0
Original line number Diff line number Diff line
@@ -19,11 +19,17 @@ package com.android.wm.shell.common.split;
import static com.android.wm.shell.shared.split.SplitScreenConstants.NOT_IN_SPLIT;
import static com.android.wm.shell.shared.split.SplitScreenConstants.SplitScreenState;

import android.graphics.Rect;
import android.graphics.RectF;

import java.util.List;

/**
 * A class that manages the "state" of split screen. See {@link SplitScreenState} for definitions.
 */
public class SplitState {
    private @SplitScreenState int mState = NOT_IN_SPLIT;
    private SplitSpec mSplitSpec;

    /** Updates the current state of split screen on this device. */
    public void set(@SplitScreenState int newState) {
@@ -39,4 +45,16 @@ public class SplitState {
    public void exit() {
        set(NOT_IN_SPLIT);
    }

    /** Refresh the valid layouts for this display/orientation. */
    public void populateLayouts(Rect displayBounds, int dividerSize, boolean isLeftRightSplit,
            Rect pinnedTaskbarInsets) {
        mSplitSpec =
                new SplitSpec(displayBounds, dividerSize, isLeftRightSplit, pinnedTaskbarInsets);
    }

    /** Returns the layout associated with a given split state. */
    public List<RectF> getLayout(@SplitScreenState int state) {
        return mSplitSpec.getSpec(state);
    }
}
Loading