From 302974580522c284812e992cd8121093750ae404 Mon Sep 17 00:00:00 2001 From: Jeremy Sim Date: Tue, 29 Oct 2024 17:18:49 -0700 Subject: [PATCH] Flexible 2-app split: Pinned Taskbar Adds the ability for DividerSnapAlgorithm to recalculate when Taskbar is pinned or unpinned. The divider will animate appropriately, if needed. Also cleans up and adds a specific SNAP_MODE for flexible split (no longer shares a SNAP_MODE with SNAP_FIXED_RATIO). Bug: 349828130 Test: When flag is off, behavior is unchanged and continues to pass tests Flag: com.android.wm.shell.enable_flexible_two_app_split Change-Id: I1a3276b23acd7c2d22df1388004abd3a60a6d10f --- .../common/split/DividerSnapAlgorithm.java | 73 ++++++++++++------- .../wm/shell/common/split/SplitLayout.java | 52 ++++++++++++- 2 files changed, 97 insertions(+), 28 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java index b29e49a484289..813772f20a8a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java @@ -71,6 +71,11 @@ public class DividerSnapAlgorithm { */ private static final int SNAP_MODE_MINIMIZED = 3; + /** + * A mode where apps can be "flexibly offscreen" on smaller displays. + */ + private static final int SNAP_FLEXIBLE_SPLIT = 4; + private final float mMinFlingVelocityPxPerSecond; private final float mMinDismissVelocityPxPerSecond; private final int mDisplayWidth; @@ -78,6 +83,7 @@ public class DividerSnapAlgorithm { private final int mDividerSize; private final ArrayList mTargets = new ArrayList<>(); private final Rect mInsets = new Rect(); + private final Rect mPinnedTaskbarInsets = new Rect(); private final int mSnapMode; private final boolean mFreeSnapMode; private final int mMinimalSizeResizableTask; @@ -88,6 +94,8 @@ public class DividerSnapAlgorithm { /** Allows split ratios that go offscreen (a.k.a. "flexible split") */ private final boolean mAllowOffscreenRatios; private final boolean mIsLeftRightSplit; + /** In SNAP_MODE_MINIMIZED, the side of the screen on which an app will "dock" when minimized */ + private final int mDockSide; /** The first target which is still splitting the screen */ private final SnapTarget mFirstSplitTarget; @@ -101,14 +109,14 @@ public class DividerSnapAlgorithm { public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, - boolean isLeftRightSplit, Rect insets, int dockSide) { + boolean isLeftRightSplit, Rect insets, Rect pinnedTaskbarInsets, int dockSide) { this(res, displayWidth, displayHeight, dividerSize, isLeftRightSplit, insets, - dockSide, false /* minimized */, true /* resizable */); + pinnedTaskbarInsets, dockSide, false /* minimized */, true /* resizable */); } public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, - boolean isLeftRightSplit, Rect insets, int dockSide, boolean isMinimizedMode, - boolean isHomeResizable) { + boolean isLeftRightSplit, Rect insets, Rect pinnedTaskbarInsets, int dockSide, + boolean isMinimizedMode, boolean isHomeResizable) { mMinFlingVelocityPxPerSecond = MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density; mMinDismissVelocityPxPerSecond = @@ -117,10 +125,11 @@ public class DividerSnapAlgorithm { mDisplayWidth = displayWidth; mDisplayHeight = displayHeight; mIsLeftRightSplit = isLeftRightSplit; + mDockSide = dockSide; mInsets.set(insets); + mPinnedTaskbarInsets.set(pinnedTaskbarInsets); if (Flags.enableFlexibleTwoAppSplit()) { - // In flexible split, we always use a fixed ratio (50%, 66%, or 90%) for splitting - mSnapMode = SNAP_FIXED_RATIO; + mSnapMode = SNAP_FLEXIBLE_SPLIT; } else { // Set SNAP_MODE_MINIMIZED, SNAP_MODE_16_9, or SNAP_FIXED_RATIO depending on config mSnapMode = isMinimizedMode @@ -140,7 +149,7 @@ public class DividerSnapAlgorithm { mAllowOffscreenRatios = SplitScreenUtils.allowOffscreenRatios(res); mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; - calculateTargets(isLeftRightSplit, dockSide); + calculateTargets(); mFirstSplitTarget = mTargets.get(1); mLastSplitTarget = mTargets.get(mTargets.size() - 2); mDismissStartTarget = mTargets.get(0); @@ -276,28 +285,31 @@ public class DividerSnapAlgorithm { return mTargets.get(minIndex); } - private void calculateTargets(boolean isLeftRightSplit, int dockedSide) { + private void calculateTargets() { mTargets.clear(); - int dividerMax = isLeftRightSplit + int dividerMax = mIsLeftRightSplit ? mDisplayWidth : mDisplayHeight; int startPos = -mDividerSize; - if (dockedSide == DOCKED_RIGHT) { + if (mDockSide == DOCKED_RIGHT) { startPos += mInsets.left; } mTargets.add(new SnapTarget(startPos, SNAP_TO_START_AND_DISMISS, 0.35f)); switch (mSnapMode) { case SNAP_MODE_16_9: - addRatio16_9Targets(isLeftRightSplit, dividerMax); + addRatio16_9Targets(mIsLeftRightSplit, dividerMax); break; case SNAP_FIXED_RATIO: - addFixedDivisionTargets(isLeftRightSplit, dividerMax); + addFixedDivisionTargets(mIsLeftRightSplit, dividerMax); break; case SNAP_ONLY_1_1: - addMiddleTarget(isLeftRightSplit); + addMiddleTarget(mIsLeftRightSplit); break; case SNAP_MODE_MINIMIZED: - addMinimizedTarget(isLeftRightSplit, dockedSide); + addMinimizedTarget(mIsLeftRightSplit, mDockSide); + break; + case SNAP_FLEXIBLE_SPLIT: + addFlexSplitTargets(mIsLeftRightSplit, dividerMax); break; } mTargets.add(new SnapTarget(dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f)); @@ -321,18 +333,9 @@ public class DividerSnapAlgorithm { ? mDisplayWidth - mInsets.right : mDisplayHeight - mInsets.bottom; - 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 size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2; + if (mCalculateRatiosBasedOnAvailableSpace) { + size = Math.max(size, mMinimalSizeResizableTask); } int topPosition = start + size; @@ -340,6 +343,24 @@ public class DividerSnapAlgorithm { addNonDismissingTargets(isLeftRightSplit, topPosition, bottomPosition, dividerMax); } + private void addFlexSplitTargets(boolean isLeftRightSplit, int dividerMax) { + int start = 0; + int end = isLeftRightSplit ? mDisplayWidth : mDisplayHeight; + int pinnedTaskbarShiftStart = isLeftRightSplit + ? mPinnedTaskbarInsets.left : mPinnedTaskbarInsets.top; + int pinnedTaskbarShiftEnd = isLeftRightSplit + ? mPinnedTaskbarInsets.right : mPinnedTaskbarInsets.bottom; + + float ratio = areOffscreenRatiosSupported() + ? SplitLayout.OFFSCREEN_ASYMMETRIC_RATIO + : SplitLayout.ONSCREEN_ONLY_ASYMMETRIC_RATIO; + int size = (int) (ratio * (end - start)) - mDividerSize / 2; + + int leftTopPosition = start + pinnedTaskbarShiftStart + size; + int rightBottomPosition = end - pinnedTaskbarShiftEnd - size - mDividerSize; + addNonDismissingTargets(isLeftRightSplit, leftTopPosition, rightBottomPosition, dividerMax); + } + private void addRatio16_9Targets(boolean isLeftRightSplit, int dividerMax) { int start = isLeftRightSplit ? mInsets.left : mInsets.top; int end = isLeftRightSplit diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index b1e0e9eab5d0d..dab30b0f0b96a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -49,10 +49,13 @@ import android.app.ActivityManager; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; +import android.graphics.Insets; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; import android.view.Display; +import android.view.InsetsController; +import android.view.InsetsSource; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.RoundedCorner; @@ -153,6 +156,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange private final ResizingEffectPolicy mSurfaceEffectPolicy; private final ShellTaskOrganizer mTaskOrganizer; private final InsetsState mInsetsState = new InsetsState(); + private Insets mPinnedTaskbarInsets = Insets.NONE; private Context mContext; @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm; @@ -523,6 +527,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange @Override public void insetsChanged(InsetsState insetsState) { mInsetsState.set(insetsState); + if (!mInitialized) { return; } @@ -531,9 +536,51 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange // flicker. return; } + + // Check to see if insets changed in such a way that the divider algorithm needs to be + // recalculated. + Insets pinnedTaskbarInsets = calculatePinnedTaskbarInsets(insetsState); + if (!mPinnedTaskbarInsets.equals(pinnedTaskbarInsets)) { + mPinnedTaskbarInsets = pinnedTaskbarInsets; + // Refresh the DividerSnapAlgorithm. + mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds); + // 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 */); + if (snapTarget.position != mDividerPosition) { + snapToTarget(mDividerPosition, snapTarget, + InsetsController.ANIMATION_DURATION_RESIZE, + InsetsController.RESIZE_INTERPOLATOR); + } + } + mSplitWindowManager.onInsetsChanged(insetsState); } + /** + * Calculates the insets that might trigger a divider algorithm recalculation. Currently, only + * pinned Taskbar does this, and only when the IME is not showing. + */ + private Insets calculatePinnedTaskbarInsets(InsetsState insetsState) { + if (insetsState.isSourceOrDefaultVisible(InsetsSource.ID_IME, WindowInsets.Type.ime())) { + return Insets.NONE; + } + + // If IME is not showing... + for (int i = insetsState.sourceSize() - 1; i >= 0; i--) { + final InsetsSource source = insetsState.sourceAt(i); + // and Taskbar is pinned... + if (source.getType() == WindowInsets.Type.navigationBars() + && source.hasFlags(InsetsSource.FLAG_INSETS_ROUNDED_CORNER)) { + // Return Insets representing the pinned taskbar state. + return source.calculateVisibleInsets(mRootBounds); + } + } + + // Else, divider can calculate based on the full display. + return Insets.NONE; + } + @Override public void insetsControlChanged(InsetsState insetsState, InsetsSourceControl[] activeControls) { @@ -631,8 +678,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange } /** - * Same as {@link #snapToTarget(int, SnapTarget)}, with default animation duration and - * interpolator. + * Same as {@link #snapToTarget(int, SnapTarget, int, Interpolator)}, with default animation + * duration and interpolator. */ public void snapToTarget(int currentPosition, SnapTarget snapTarget) { snapToTarget(currentPosition, snapTarget, FLING_RESIZE_DURATION, @@ -683,6 +730,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange mDividerSize, mIsLeftRightSplit, insets, + mPinnedTaskbarInsets.toRect(), mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */); }