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
This commit is contained in:
Jeremy Sim
2024-10-29 17:18:49 -07:00
parent 428aef4059
commit 3029745805
2 changed files with 97 additions and 28 deletions

View File

@@ -71,6 +71,11 @@ public class DividerSnapAlgorithm {
*/ */
private static final int SNAP_MODE_MINIMIZED = 3; 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 mMinFlingVelocityPxPerSecond;
private final float mMinDismissVelocityPxPerSecond; private final float mMinDismissVelocityPxPerSecond;
private final int mDisplayWidth; private final int mDisplayWidth;
@@ -78,6 +83,7 @@ public class DividerSnapAlgorithm {
private final int mDividerSize; private final int mDividerSize;
private final ArrayList<SnapTarget> mTargets = new ArrayList<>(); private final ArrayList<SnapTarget> mTargets = new ArrayList<>();
private final Rect mInsets = new Rect(); private final Rect mInsets = new Rect();
private final Rect mPinnedTaskbarInsets = new Rect();
private final int mSnapMode; private final int mSnapMode;
private final boolean mFreeSnapMode; private final boolean mFreeSnapMode;
private final int mMinimalSizeResizableTask; private final int mMinimalSizeResizableTask;
@@ -88,6 +94,8 @@ public class DividerSnapAlgorithm {
/** Allows split ratios that go offscreen (a.k.a. "flexible split") */ /** Allows split ratios that go offscreen (a.k.a. "flexible split") */
private final boolean mAllowOffscreenRatios; private final boolean mAllowOffscreenRatios;
private final boolean mIsLeftRightSplit; 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 */ /** The first target which is still splitting the screen */
private final SnapTarget mFirstSplitTarget; private final SnapTarget mFirstSplitTarget;
@@ -101,14 +109,14 @@ public class DividerSnapAlgorithm {
public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, 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, 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, public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize,
boolean isLeftRightSplit, Rect insets, int dockSide, boolean isMinimizedMode, boolean isLeftRightSplit, Rect insets, Rect pinnedTaskbarInsets, int dockSide,
boolean isHomeResizable) { boolean isMinimizedMode, boolean isHomeResizable) {
mMinFlingVelocityPxPerSecond = mMinFlingVelocityPxPerSecond =
MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density; MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density;
mMinDismissVelocityPxPerSecond = mMinDismissVelocityPxPerSecond =
@@ -117,10 +125,11 @@ public class DividerSnapAlgorithm {
mDisplayWidth = displayWidth; mDisplayWidth = displayWidth;
mDisplayHeight = displayHeight; mDisplayHeight = displayHeight;
mIsLeftRightSplit = isLeftRightSplit; mIsLeftRightSplit = isLeftRightSplit;
mDockSide = dockSide;
mInsets.set(insets); mInsets.set(insets);
mPinnedTaskbarInsets.set(pinnedTaskbarInsets);
if (Flags.enableFlexibleTwoAppSplit()) { if (Flags.enableFlexibleTwoAppSplit()) {
// In flexible split, we always use a fixed ratio (50%, 66%, or 90%) for splitting mSnapMode = SNAP_FLEXIBLE_SPLIT;
mSnapMode = SNAP_FIXED_RATIO;
} else { } else {
// Set SNAP_MODE_MINIMIZED, SNAP_MODE_16_9, or SNAP_FIXED_RATIO depending on config // Set SNAP_MODE_MINIMIZED, SNAP_MODE_16_9, or SNAP_FIXED_RATIO depending on config
mSnapMode = isMinimizedMode mSnapMode = isMinimizedMode
@@ -140,7 +149,7 @@ public class DividerSnapAlgorithm {
mAllowOffscreenRatios = SplitScreenUtils.allowOffscreenRatios(res); mAllowOffscreenRatios = SplitScreenUtils.allowOffscreenRatios(res);
mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize(
com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; com.android.internal.R.dimen.task_height_of_minimized_mode) : 0;
calculateTargets(isLeftRightSplit, dockSide); calculateTargets();
mFirstSplitTarget = mTargets.get(1); mFirstSplitTarget = mTargets.get(1);
mLastSplitTarget = mTargets.get(mTargets.size() - 2); mLastSplitTarget = mTargets.get(mTargets.size() - 2);
mDismissStartTarget = mTargets.get(0); mDismissStartTarget = mTargets.get(0);
@@ -276,28 +285,31 @@ public class DividerSnapAlgorithm {
return mTargets.get(minIndex); return mTargets.get(minIndex);
} }
private void calculateTargets(boolean isLeftRightSplit, int dockedSide) { private void calculateTargets() {
mTargets.clear(); mTargets.clear();
int dividerMax = isLeftRightSplit int dividerMax = mIsLeftRightSplit
? mDisplayWidth ? mDisplayWidth
: mDisplayHeight; : mDisplayHeight;
int startPos = -mDividerSize; int startPos = -mDividerSize;
if (dockedSide == DOCKED_RIGHT) { if (mDockSide == DOCKED_RIGHT) {
startPos += mInsets.left; startPos += mInsets.left;
} }
mTargets.add(new SnapTarget(startPos, SNAP_TO_START_AND_DISMISS, 0.35f)); mTargets.add(new SnapTarget(startPos, SNAP_TO_START_AND_DISMISS, 0.35f));
switch (mSnapMode) { switch (mSnapMode) {
case SNAP_MODE_16_9: case SNAP_MODE_16_9:
addRatio16_9Targets(isLeftRightSplit, dividerMax); addRatio16_9Targets(mIsLeftRightSplit, dividerMax);
break; break;
case SNAP_FIXED_RATIO: case SNAP_FIXED_RATIO:
addFixedDivisionTargets(isLeftRightSplit, dividerMax); addFixedDivisionTargets(mIsLeftRightSplit, dividerMax);
break; break;
case SNAP_ONLY_1_1: case SNAP_ONLY_1_1:
addMiddleTarget(isLeftRightSplit); addMiddleTarget(mIsLeftRightSplit);
break; break;
case SNAP_MODE_MINIMIZED: case SNAP_MODE_MINIMIZED:
addMinimizedTarget(isLeftRightSplit, dockedSide); addMinimizedTarget(mIsLeftRightSplit, mDockSide);
break;
case SNAP_FLEXIBLE_SPLIT:
addFlexSplitTargets(mIsLeftRightSplit, dividerMax);
break; break;
} }
mTargets.add(new SnapTarget(dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f)); mTargets.add(new SnapTarget(dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f));
@@ -321,18 +333,9 @@ public class DividerSnapAlgorithm {
? mDisplayWidth - mInsets.right ? mDisplayWidth - mInsets.right
: mDisplayHeight - mInsets.bottom; : mDisplayHeight - mInsets.bottom;
int size; int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2;
if (Flags.enableFlexibleTwoAppSplit()) { if (mCalculateRatiosBasedOnAvailableSpace) {
float ratio = areOffscreenRatiosSupported() size = Math.max(size, mMinimalSizeResizableTask);
? 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 topPosition = start + size;
@@ -340,6 +343,24 @@ public class DividerSnapAlgorithm {
addNonDismissingTargets(isLeftRightSplit, topPosition, bottomPosition, dividerMax); 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) { private void addRatio16_9Targets(boolean isLeftRightSplit, int dividerMax) {
int start = isLeftRightSplit ? mInsets.left : mInsets.top; int start = isLeftRightSplit ? mInsets.left : mInsets.top;
int end = isLeftRightSplit int end = isLeftRightSplit

View File

@@ -49,10 +49,13 @@ import android.app.ActivityManager;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Insets;
import android.graphics.Point; import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.os.Handler; import android.os.Handler;
import android.view.Display; import android.view.Display;
import android.view.InsetsController;
import android.view.InsetsSource;
import android.view.InsetsSourceControl; import android.view.InsetsSourceControl;
import android.view.InsetsState; import android.view.InsetsState;
import android.view.RoundedCorner; import android.view.RoundedCorner;
@@ -153,6 +156,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final ResizingEffectPolicy mSurfaceEffectPolicy; private final ResizingEffectPolicy mSurfaceEffectPolicy;
private final ShellTaskOrganizer mTaskOrganizer; private final ShellTaskOrganizer mTaskOrganizer;
private final InsetsState mInsetsState = new InsetsState(); private final InsetsState mInsetsState = new InsetsState();
private Insets mPinnedTaskbarInsets = Insets.NONE;
private Context mContext; private Context mContext;
@VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm; @VisibleForTesting DividerSnapAlgorithm mDividerSnapAlgorithm;
@@ -523,6 +527,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
@Override @Override
public void insetsChanged(InsetsState insetsState) { public void insetsChanged(InsetsState insetsState) {
mInsetsState.set(insetsState); mInsetsState.set(insetsState);
if (!mInitialized) { if (!mInitialized) {
return; return;
} }
@@ -531,9 +536,51 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
// flicker. // flicker.
return; 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); 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 @Override
public void insetsControlChanged(InsetsState insetsState, public void insetsControlChanged(InsetsState insetsState,
InsetsSourceControl[] activeControls) { InsetsSourceControl[] activeControls) {
@@ -631,8 +678,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
} }
/** /**
* Same as {@link #snapToTarget(int, SnapTarget)}, with default animation duration and * Same as {@link #snapToTarget(int, SnapTarget, int, Interpolator)}, with default animation
* interpolator. * duration and interpolator.
*/ */
public void snapToTarget(int currentPosition, SnapTarget snapTarget) { public void snapToTarget(int currentPosition, SnapTarget snapTarget) {
snapToTarget(currentPosition, snapTarget, FLING_RESIZE_DURATION, snapToTarget(currentPosition, snapTarget, FLING_RESIZE_DURATION,
@@ -683,6 +730,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
mDividerSize, mDividerSize,
mIsLeftRightSplit, mIsLeftRightSplit,
insets, insets,
mPinnedTaskbarInsets.toRect(),
mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */); mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
} }