summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java324
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java188
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java8
6 files changed, 393 insertions, 191 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java
new file mode 100644
index 000000000000..3f76fd0220ff
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/ResizingEffectPolicy.java
@@ -0,0 +1,324 @@
+/*
+ * 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 android.view.WindowManager.DOCKED_BOTTOM;
+import static android.view.WindowManager.DOCKED_INVALID;
+import static android.view.WindowManager.DOCKED_LEFT;
+import static android.view.WindowManager.DOCKED_RIGHT;
+import static android.view.WindowManager.DOCKED_TOP;
+
+import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
+import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_DISMISSING;
+import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_FLEX;
+import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_NONE;
+import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLATOR;
+import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+
+/**
+ * This class governs how and when parallax and dimming effects are applied to task surfaces,
+ * usually when the divider is being moved around by the user (or during an animation).
+ */
+class ResizingEffectPolicy {
+ private final SplitLayout mSplitLayout;
+ /** The parallax algorithm we are currently using. */
+ private final int mParallaxType;
+
+ int mShrinkSide = DOCKED_INVALID;
+
+ // The current dismissing side.
+ int mDismissingSide = DOCKED_INVALID;
+
+ /**
+ * A {@link Point} that stores a single x and y value, representing the parallax translation
+ * we use on the app that the divider is moving toward. The app is either shrinking in size or
+ * getting pushed off the screen.
+ */
+ final Point mRetreatingSideParallax = new Point();
+ /**
+ * A {@link Point} that stores a single x and y value, representing the parallax translation
+ * we use on the app that the divider is moving away from. The app is either growing in size or
+ * getting pulled onto the screen.
+ */
+ final Point mAdvancingSideParallax = new Point();
+
+ // The dimming value to hint the dismissing side and progress.
+ float mDismissingDimValue = 0.0f;
+
+ /**
+ * Content bounds for the app that the divider is moving toward. This is the content that is
+ * currently drawn at the start of the divider movement. It stays unchanged throughout the
+ * divider's movement.
+ */
+ final Rect mRetreatingContent = new Rect();
+ /**
+ * Surface bounds for the app that the divider is moving toward. This is the "canvas" on
+ * which an app could potentially be drawn. It changes on every frame as the divider moves
+ * around.
+ */
+ final Rect mRetreatingSurface = new Rect();
+ /**
+ * Content bounds for the app that the divider is moving away from. This is the content that
+ * is currently drawn at the start of the divider movement. It stays unchanged throughout
+ * the divider's movement.
+ */
+ final Rect mAdvancingContent = new Rect();
+ /**
+ * Surface bounds for the app that the divider is moving away from. This is the "canvas" on
+ * which an app could potentially be drawn. It changes on every frame as the divider moves
+ * around.
+ */
+ final Rect mAdvancingSurface = new Rect();
+
+ final Rect mTempRect = new Rect();
+ final Rect mTempRect2 = new Rect();
+
+ ResizingEffectPolicy(int parallaxType, SplitLayout splitLayout) {
+ mParallaxType = parallaxType;
+ mSplitLayout = splitLayout;
+ }
+
+ /**
+ * Calculates the desired parallax values and stores them in {@link #mRetreatingSideParallax}
+ * and {@link #mAdvancingSideParallax}. These values will be then be applied in
+ * {@link #adjustRootSurface}.
+ *
+ * @param position The divider's position on the screen (x-coordinate in left-right split,
+ * y-coordinate in top-bottom split).
+ */
+ void applyDividerPosition(
+ int position, boolean isLeftRightSplit, DividerSnapAlgorithm snapAlgorithm) {
+ mDismissingSide = DOCKED_INVALID;
+ mRetreatingSideParallax.set(0, 0);
+ mAdvancingSideParallax.set(0, 0);
+ mDismissingDimValue = 0;
+ Rect displayBounds = mSplitLayout.getRootBounds();
+
+ int totalDismissingDistance = 0;
+ if (position < snapAlgorithm.getFirstSplitTarget().position) {
+ mDismissingSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
+ totalDismissingDistance = snapAlgorithm.getDismissStartTarget().position
+ - snapAlgorithm.getFirstSplitTarget().position;
+ } else if (position > snapAlgorithm.getLastSplitTarget().position) {
+ mDismissingSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ totalDismissingDistance = snapAlgorithm.getLastSplitTarget().position
+ - snapAlgorithm.getDismissEndTarget().position;
+ }
+
+ final boolean topLeftShrink = isLeftRightSplit
+ ? position < mSplitLayout.getTopLeftContentBounds().right
+ : position < mSplitLayout.getTopLeftContentBounds().bottom;
+ if (topLeftShrink) {
+ mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
+ mRetreatingContent.set(mSplitLayout.getTopLeftContentBounds());
+ mRetreatingSurface.set(mSplitLayout.getTopLeftBounds());
+ mAdvancingContent.set(mSplitLayout.getBottomRightContentBounds());
+ mAdvancingSurface.set(mSplitLayout.getBottomRightBounds());
+ } else {
+ mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
+ mRetreatingContent.set(mSplitLayout.getBottomRightContentBounds());
+ mRetreatingSurface.set(mSplitLayout.getBottomRightBounds());
+ mAdvancingContent.set(mSplitLayout.getTopLeftContentBounds());
+ mAdvancingSurface.set(mSplitLayout.getTopLeftBounds());
+ }
+
+ if (mDismissingSide != DOCKED_INVALID) {
+ float fraction =
+ Math.max(0, Math.min(snapAlgorithm.calculateDismissingFraction(position), 1f));
+ mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction);
+ if (mParallaxType == PARALLAX_DISMISSING) {
+ fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide);
+ if (isLeftRightSplit) {
+ mRetreatingSideParallax.x = (int) (fraction * totalDismissingDistance);
+ } else {
+ mRetreatingSideParallax.y = (int) (fraction * totalDismissingDistance);
+ }
+ }
+ }
+
+ if (mParallaxType == PARALLAX_ALIGN_CENTER) {
+ if (isLeftRightSplit) {
+ mRetreatingSideParallax.x =
+ (mRetreatingSurface.width() - mRetreatingContent.width()) / 2;
+ } else {
+ mRetreatingSideParallax.y =
+ (mRetreatingSurface.height() - mRetreatingContent.height()) / 2;
+ }
+ } else if (mParallaxType == PARALLAX_FLEX) {
+ // Whether an app is getting pushed offscreen by the divider.
+ boolean isRetreatingOffscreen = !displayBounds.contains(mRetreatingSurface);
+ // Whether an app was getting pulled onscreen at the beginning of the drag.
+ boolean advancingSideStartedOffscreen = !displayBounds.contains(mAdvancingContent);
+
+ // The simpler case when an app gets pushed offscreen (e.g. 50:50 -> 90:10)
+ if (isRetreatingOffscreen && !advancingSideStartedOffscreen) {
+ // On the left side, we use parallax to simulate the contents sticking to the
+ // divider. This is because surfaces naturally expand to the bottom and right,
+ // so when a surface's area expands, the contents stick to the left. This is
+ // correct behavior on the right-side surface, but not the left.
+ if (topLeftShrink) {
+ if (isLeftRightSplit) {
+ mRetreatingSideParallax.x =
+ mRetreatingSurface.width() - mRetreatingContent.width();
+ } else {
+ mRetreatingSideParallax.y =
+ mRetreatingSurface.height() - mRetreatingContent.height();
+ }
+ }
+ // All other cases (e.g. 10:90 -> 50:50, 10:90 -> 90:10, 10:90 -> dismiss)
+ } else {
+ mTempRect.set(mRetreatingSurface);
+ Point rootOffset = new Point();
+ // 10:90 -> 50:50, 10:90, or dismiss right
+ if (advancingSideStartedOffscreen) {
+ // We have to handle a complicated case here to keep the parallax smooth.
+ // When the divider crosses the 50% mark, the retreating-side app surface
+ // will start expanding offscreen. This is expected and unavoidable, but
+ // makes the parallax look disjointed. In order to preserve the illusion,
+ // we add another offset (rootOffset) to simulate the surface staying
+ // onscreen.
+ mTempRect.intersect(displayBounds);
+ if (mRetreatingSurface.left < displayBounds.left) {
+ rootOffset.x = displayBounds.left - mRetreatingSurface.left;
+ }
+ if (mRetreatingSurface.top < displayBounds.top) {
+ rootOffset.y = displayBounds.top - mRetreatingSurface.top;
+ }
+
+ // On the left side, we again have to simulate the contents sticking to the
+ // divider.
+ if (!topLeftShrink) {
+ if (isLeftRightSplit) {
+ mAdvancingSideParallax.x =
+ mAdvancingSurface.width() - mAdvancingContent.width();
+ } else {
+ mAdvancingSideParallax.y =
+ mAdvancingSurface.height() - mAdvancingContent.height();
+ }
+ }
+ }
+
+ // In all these cases, the shrinking app also receives a center parallax.
+ if (isLeftRightSplit) {
+ mRetreatingSideParallax.x = rootOffset.x
+ + ((mTempRect.width() - mRetreatingContent.width()) / 2);
+ } else {
+ mRetreatingSideParallax.y = rootOffset.y
+ + ((mTempRect.height() - mRetreatingContent.height()) / 2);
+ }
+ }
+ }
+ }
+
+ /**
+ * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
+ * slowing down parallax effect
+ */
+ private float calculateParallaxDismissingFraction(float fraction, int dockSide) {
+ float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
+
+ // Less parallax at the top, just because.
+ if (dockSide == WindowManager.DOCKED_TOP) {
+ result /= 2f;
+ }
+ return result;
+ }
+
+ /** Applies the calculated parallax and dimming values to task surfaces. */
+ void adjustRootSurface(SurfaceControl.Transaction t,
+ SurfaceControl leash1, SurfaceControl leash2) {
+ SurfaceControl retreatingLeash = null;
+ SurfaceControl advancingLeash = null;
+
+ if (mParallaxType == PARALLAX_DISMISSING) {
+ switch (mDismissingSide) {
+ case DOCKED_TOP:
+ case DOCKED_LEFT:
+ retreatingLeash = leash1;
+ mTempRect.set(mSplitLayout.getTopLeftBounds());
+ advancingLeash = leash2;
+ mTempRect2.set(mSplitLayout.getBottomRightBounds());
+ break;
+ case DOCKED_BOTTOM:
+ case DOCKED_RIGHT:
+ retreatingLeash = leash2;
+ mTempRect.set(mSplitLayout.getBottomRightBounds());
+ advancingLeash = leash1;
+ mTempRect2.set(mSplitLayout.getTopLeftBounds());
+ break;
+ }
+ } else if (mParallaxType == PARALLAX_ALIGN_CENTER || mParallaxType == PARALLAX_FLEX) {
+ switch (mShrinkSide) {
+ case DOCKED_TOP:
+ case DOCKED_LEFT:
+ retreatingLeash = leash1;
+ mTempRect.set(mSplitLayout.getTopLeftBounds());
+ advancingLeash = leash2;
+ mTempRect2.set(mSplitLayout.getBottomRightBounds());
+ break;
+ case DOCKED_BOTTOM:
+ case DOCKED_RIGHT:
+ retreatingLeash = leash2;
+ mTempRect.set(mSplitLayout.getBottomRightBounds());
+ advancingLeash = leash1;
+ mTempRect2.set(mSplitLayout.getTopLeftBounds());
+ break;
+ }
+ }
+ if (mParallaxType != PARALLAX_NONE
+ && retreatingLeash != null && advancingLeash != null) {
+ t.setPosition(retreatingLeash, mTempRect.left + mRetreatingSideParallax.x,
+ mTempRect.top + mRetreatingSideParallax.y);
+ // Transform the screen-based split bounds to surface-based crop bounds.
+ mTempRect.offsetTo(-mRetreatingSideParallax.x, -mRetreatingSideParallax.y);
+ t.setWindowCrop(retreatingLeash, mTempRect);
+
+ t.setPosition(advancingLeash, mTempRect2.left + mAdvancingSideParallax.x,
+ mTempRect2.top + mAdvancingSideParallax.y);
+ // Transform the screen-based split bounds to surface-based crop bounds.
+ mTempRect2.offsetTo(-mAdvancingSideParallax.x, -mAdvancingSideParallax.y);
+ t.setWindowCrop(advancingLeash, mTempRect2);
+ }
+ }
+
+ void adjustDimSurface(SurfaceControl.Transaction t,
+ SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
+ SurfaceControl targetDimLayer;
+ switch (mDismissingSide) {
+ case DOCKED_TOP:
+ case DOCKED_LEFT:
+ targetDimLayer = dimLayer1;
+ break;
+ case DOCKED_BOTTOM:
+ case DOCKED_RIGHT:
+ targetDimLayer = dimLayer2;
+ break;
+ case DOCKED_INVALID:
+ default:
+ t.setAlpha(dimLayer1, 0).hide(dimLayer1);
+ t.setAlpha(dimLayer2, 0).hide(dimLayer2);
+ return;
+ }
+ t.setAlpha(targetDimLayer, mDismissingDimValue)
+ .setVisibility(targetDimLayer, mDismissingDimValue > 0.001f);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 9fb36b36ff29..a73d43c54064 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -227,10 +227,37 @@ public class SplitDecorManager extends WindowlessWindowManager {
mInstantaneousBounds.setEmpty();
}
- /** Showing resizing hint. */
+ /**
+ * Called on every frame when an app is getting resized, and controls the showing & hiding of
+ * the app veil. IMPORTANT: There is one SplitDecorManager for each task, so if two tasks are
+ * getting resized simultaneously, this method is called in parallel on the other
+ * SplitDecorManager too. In general, we want to hide the app behind a veil when:
+ * a) the app is stretching past its original bounds (because app content layout doesn't
+ * update mid-stretch).
+ * b) the app is resizing down from fullscreen (because there is no parallax effect that
+ * makes every app look good in this scenario).
+ * In the world of flexible split, where apps can go offscreen, there is an exception to this:
+ * - We do NOT hide the app when it is going offscreen, even though it is technically
+ * getting larger and would qualify for condition (a). Instead, we use parallax to give
+ * the illusion that the app is getting pushed offscreen by the divider.
+ *
+ * @param resizingTask The task that is getting resized.
+ * @param newBounds The bounds that that we are updating this surface to. This can be an
+ * instantaneous bounds, just for a frame, during a drag or animation.
+ * @param sideBounds The bounds of the OPPOSITE task in the split layout. This is used just for
+ * reference/calculation, the surface of the other app won't be set here.
+ * @param displayBounds The bounds of the entire display.
+ * @param t The transaction on which these changes will be bundled.
+ * @param offsetX The x-translation applied to the task surface for parallax. Will be used to
+ * position the task screenshot and/or icon veil.
+ * @param offsetY The x-translation applied to the task surface for parallax. Will be used to
+ * position the task screenshot and/or icon veil.
+ * @param immediately {@code true} if the veil should transition in/out instantly, with no
+ * animation.
+ */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
- Rect sideBounds, SurfaceControl.Transaction t, int offsetX, int offsetY,
- boolean immediately) {
+ Rect sideBounds, Rect displayBounds, SurfaceControl.Transaction t, int offsetX,
+ int offsetY, boolean immediately) {
if (mVeilIconView == null) {
return;
}
@@ -252,7 +279,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
final boolean isStretchingPastOriginalBounds =
newBounds.width() > mOldMainBounds.width()
|| newBounds.height() > mOldMainBounds.height();
- final boolean showVeil = isResizingDownFromFullscreen || isStretchingPastOriginalBounds;
+ final boolean isFullyOnscreen = displayBounds.contains(newBounds);
+ boolean showVeil = isFullyOnscreen
+ && (isResizingDownFromFullscreen || isStretchingPastOriginalBounds);
+
final boolean update = showVeil != mShown;
if (update && mFadeAnimator != null && mFadeAnimator.isRunning()) {
// If we need to animate and animator still running, cancel it before we ensure both
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 4bcec702281d..fc274d6e7174 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
@@ -18,19 +18,14 @@ package com.android.wm.shell.common.split;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
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;
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;
@@ -52,7 +47,6 @@ 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.util.Log;
@@ -103,9 +97,17 @@ import java.util.function.Consumer;
*/
public final class SplitLayout implements DisplayInsetsController.OnInsetsChangedListener {
private static final String TAG = "SplitLayout";
+ /** No parallax effect when the user is dragging the divider */
public static final int PARALLAX_NONE = 0;
public static final int PARALLAX_DISMISSING = 1;
+ /** Parallax effect (center-aligned) when the user is dragging the divider */
public static final int PARALLAX_ALIGN_CENTER = 2;
+ /**
+ * A custom parallax effect for flexible split. When an app is being pushed/pulled offscreen,
+ * we use a specific parallax to give the impression that it is stuck to the divider.
+ * Otherwise, we fall back to PARALLAX_ALIGN_CENTER behavior.
+ */
+ public static final int PARALLAX_FLEX = 3;
public static final int FLING_RESIZE_DURATION = 250;
private static final int FLING_ENTER_DURATION = 450;
@@ -146,6 +148,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private int mDividerSize;
private final Rect mTempRect = new Rect();
+ private final Rect mTempRect2 = new Rect();
private final Rect mRootBounds = new Rect();
private final Rect mDividerBounds = new Rect();
/**
@@ -219,7 +222,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
parentContainerCallbacks);
mTaskOrganizer = taskOrganizer;
mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
- mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
+ mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType, this);
mSplitState = splitState;
final Resources res = mContext.getResources();
@@ -580,7 +583,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
if (setEffectBounds) {
- mSurfaceEffectPolicy.applyDividerPosition(position, mIsLeftRightSplit);
+ mSurfaceEffectPolicy.applyDividerPosition(
+ position, mIsLeftRightSplit, mDividerSnapAlgorithm);
}
}
@@ -710,8 +714,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
*/
void updateDividerBounds(int position, boolean shouldUseParallaxEffect) {
updateBounds(position);
- mSplitLayoutHandler.onLayoutSizeChanging(this, mSurfaceEffectPolicy.mParallaxOffset.x,
- mSurfaceEffectPolicy.mParallaxOffset.y, shouldUseParallaxEffect);
+ mSplitLayoutHandler.onLayoutSizeChanging(this,
+ mSurfaceEffectPolicy.mRetreatingSideParallax.x,
+ mSurfaceEffectPolicy.mRetreatingSideParallax.y, shouldUseParallaxEffect);
}
void setDividerPosition(int position, boolean applyLayoutChange) {
@@ -1361,169 +1366,6 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
int getSplitItemPosition(WindowContainerToken token);
}
- /**
- * Calculates and applies proper dismissing parallax offset and dimming value to hint users
- * dismissing gesture.
- */
- private class ResizingEffectPolicy {
- /** Indicates whether to offset splitting bounds to hint dismissing progress or not. */
- private final int mParallaxType;
-
- int mShrinkSide = DOCKED_INVALID;
-
- // The current dismissing side.
- int mDismissingSide = DOCKED_INVALID;
-
- // The parallax offset to hint the dismissing side and progress.
- final Point mParallaxOffset = new Point();
-
- // The dimming value to hint the dismissing side and progress.
- float mDismissingDimValue = 0.0f;
- final Rect mContentBounds = new Rect();
- final Rect mSurfaceBounds = new Rect();
-
- ResizingEffectPolicy(int parallaxType) {
- mParallaxType = parallaxType;
- }
-
- /**
- * Applies a parallax to the task to hint dismissing progress.
- *
- * @param position the split position to apply dismissing parallax effect
- * @param isLeftRightSplit indicates whether it's splitting horizontally or vertically
- */
- void applyDividerPosition(int position, boolean isLeftRightSplit) {
- mDismissingSide = DOCKED_INVALID;
- mParallaxOffset.set(0, 0);
- mDismissingDimValue = 0;
-
- int totalDismissingDistance = 0;
- if (position < mDividerSnapAlgorithm.getFirstSplitTarget().position) {
- mDismissingSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
- totalDismissingDistance = mDividerSnapAlgorithm.getDismissStartTarget().position
- - mDividerSnapAlgorithm.getFirstSplitTarget().position;
- } else if (position > mDividerSnapAlgorithm.getLastSplitTarget().position) {
- mDismissingSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
- totalDismissingDistance = mDividerSnapAlgorithm.getLastSplitTarget().position
- - mDividerSnapAlgorithm.getDismissEndTarget().position;
- }
-
- final boolean topLeftShrink = isLeftRightSplit
- ? position < getTopLeftContentBounds().right
- : position < getTopLeftContentBounds().bottom;
- if (topLeftShrink) {
- mShrinkSide = isLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP;
- mContentBounds.set(getTopLeftContentBounds());
- mSurfaceBounds.set(getTopLeftBounds());
- } else {
- mShrinkSide = isLeftRightSplit ? DOCKED_RIGHT : DOCKED_BOTTOM;
- mContentBounds.set(getBottomRightContentBounds());
- mSurfaceBounds.set(getBottomRightBounds());
- }
-
- if (mDismissingSide != DOCKED_INVALID) {
- float fraction = Math.max(0,
- Math.min(mDividerSnapAlgorithm.calculateDismissingFraction(position), 1f));
- mDismissingDimValue = DIM_INTERPOLATOR.getInterpolation(fraction);
- if (mParallaxType == PARALLAX_DISMISSING) {
- fraction = calculateParallaxDismissingFraction(fraction, mDismissingSide);
- if (isLeftRightSplit) {
- mParallaxOffset.x = (int) (fraction * totalDismissingDistance);
- } else {
- mParallaxOffset.y = (int) (fraction * totalDismissingDistance);
- }
- }
- }
-
- if (mParallaxType == PARALLAX_ALIGN_CENTER) {
- if (isLeftRightSplit) {
- mParallaxOffset.x =
- (mSurfaceBounds.width() - mContentBounds.width()) / 2;
- } else {
- mParallaxOffset.y =
- (mSurfaceBounds.height() - mContentBounds.height()) / 2;
- }
- }
- }
-
- /**
- * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
- * slowing down parallax effect
- */
- private float calculateParallaxDismissingFraction(float fraction, int dockSide) {
- float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
-
- // Less parallax at the top, just because.
- if (dockSide == WindowManager.DOCKED_TOP) {
- result /= 2f;
- }
- return result;
- }
-
- /** Applies parallax offset and dimming value to the root surface at the dismissing side. */
- void adjustRootSurface(SurfaceControl.Transaction t,
- SurfaceControl leash1, SurfaceControl leash2) {
- SurfaceControl targetLeash = null;
-
- if (mParallaxType == PARALLAX_DISMISSING) {
- switch (mDismissingSide) {
- case DOCKED_TOP:
- case DOCKED_LEFT:
- targetLeash = leash1;
- mTempRect.set(getTopLeftBounds());
- break;
- case DOCKED_BOTTOM:
- case DOCKED_RIGHT:
- targetLeash = leash2;
- mTempRect.set(getBottomRightBounds());
- break;
- }
- } else if (mParallaxType == PARALLAX_ALIGN_CENTER) {
- switch (mShrinkSide) {
- case DOCKED_TOP:
- case DOCKED_LEFT:
- targetLeash = leash1;
- mTempRect.set(getTopLeftBounds());
- break;
- case DOCKED_BOTTOM:
- case DOCKED_RIGHT:
- targetLeash = leash2;
- mTempRect.set(getBottomRightBounds());
- break;
- }
- }
- if (mParallaxType != PARALLAX_NONE && targetLeash != null) {
- t.setPosition(targetLeash,
- mTempRect.left + mParallaxOffset.x, mTempRect.top + mParallaxOffset.y);
- // Transform the screen-based split bounds to surface-based crop bounds.
- mTempRect.offsetTo(-mParallaxOffset.x, -mParallaxOffset.y);
- t.setWindowCrop(targetLeash, mTempRect);
- }
- }
-
- void adjustDimSurface(SurfaceControl.Transaction t,
- SurfaceControl dimLayer1, SurfaceControl dimLayer2) {
- SurfaceControl targetDimLayer;
- switch (mDismissingSide) {
- case DOCKED_TOP:
- case DOCKED_LEFT:
- targetDimLayer = dimLayer1;
- break;
- case DOCKED_BOTTOM:
- case DOCKED_RIGHT:
- targetDimLayer = dimLayer2;
- break;
- case DOCKED_INVALID:
- default:
- t.setAlpha(dimLayer1, 0).hide(dimLayer1);
- t.setAlpha(dimLayer2, 0).hide(dimLayer2);
- return;
- }
- t.setAlpha(targetDimLayer, mDismissingDimValue)
- .setVisibility(targetDimLayer, mDismissingDimValue > 0.001f);
- }
- }
-
/** Records IME top offset changes and updates SplitLayout correspondingly. */
private class ImePositionProcessor implements DisplayImeController.ImePositionProcessor {
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java
index 9c951bd89876..6f1dc56e273a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitSpec.java
@@ -43,7 +43,7 @@ import java.util.Map;
*/
public class SplitSpec {
private static final String TAG = "SplitSpec";
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
/** A split ratio used on larger screens, where we can fit both apps onscreen. */
public static final float ONSCREEN_ONLY_ASYMMETRIC_RATIO = 0.33f;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 246760e361cd..0a18369558a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -35,7 +35,9 @@ import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.wm.shell.Flags.enableFlexibleSplit;
+import static com.android.wm.shell.Flags.enableFlexibleTwoAppSplit;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
+import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_FLEX;
import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
@@ -127,7 +129,6 @@ import com.android.internal.logging.InstanceId;
import com.android.internal.policy.FoldLockSettingsObserver;
import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.Flags;
import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
@@ -2006,7 +2007,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// If all stages are filled, create new SplitBounds and update Recents.
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
int currentSnapPosition = mSplitLayout.calculateCurrentSnapPosition();
- if (Flags.enableFlexibleTwoAppSplit()) {
+ if (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.
@@ -2063,10 +2064,11 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRootTaskLeash = leash;
if (mSplitLayout == null) {
+ int parallaxType = enableFlexibleTwoAppSplit() ? PARALLAX_FLEX : PARALLAX_ALIGN_CENTER;
mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
mRootTaskInfo.configuration, this, mParentContainerCallbacks,
- mDisplayController, mDisplayImeController, mTaskOrganizer,
- PARALLAX_ALIGN_CENTER /* parallaxType */, mSplitState, mMainHandler);
+ mDisplayController, mDisplayImeController, mTaskOrganizer, parallaxType,
+ mSplitState, mMainHandler);
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
@@ -2406,6 +2408,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
updateSurfaceBounds(layout, t, shouldUseParallaxEffect);
getMainStageBounds(mTempRect1);
getSideStageBounds(mTempRect2);
+ Rect displayBounds = mSplitLayout.getRootBounds();
+
if (enableFlexibleSplit()) {
StageTaskListener ltStage =
mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_TOP_OR_LEFT,
@@ -2413,12 +2417,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
StageTaskListener brStage =
mStageOrderOperator.getStageForLegacyPosition(SPLIT_POSITION_BOTTOM_OR_RIGHT,
false /*checkAllStagesIfNotActive*/);
- ltStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY, mShowDecorImmediately);
- brStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY, mShowDecorImmediately);
+ ltStage.onResizing(mTempRect1, mTempRect2, displayBounds, t, offsetX, offsetY,
+ mShowDecorImmediately);
+ brStage.onResizing(mTempRect2, mTempRect1, displayBounds, t, offsetX, offsetY,
+ mShowDecorImmediately);
} else {
- mMainStage.onResizing(mTempRect1, mTempRect2, t, offsetX, offsetY,
+ mMainStage.onResizing(mTempRect1, mTempRect2, displayBounds, t, offsetX, offsetY,
mShowDecorImmediately);
- mSideStage.onResizing(mTempRect2, mTempRect1, t, offsetX, offsetY,
+ mSideStage.onResizing(mTempRect2, mTempRect1, displayBounds, t, offsetX, offsetY,
mShowDecorImmediately);
}
t.apply();
@@ -2465,7 +2471,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.populateTouchZones();
}, mainDecor, sideDecor, decorManagers);
- if (Flags.enableFlexibleTwoAppSplit()) {
+ if (enableFlexibleTwoAppSplit()) {
switch (layout.calculateCurrentSnapPosition()) {
case SNAP_TO_2_10_90 -> grantFocusToPosition(false /* leftOrTop */);
case SNAP_TO_2_90_10 -> grantFocusToPosition(true /* leftOrTop */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 816f51f997d5..29751986959b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -339,11 +339,11 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener {
return mRootTaskInfo != null && mRootTaskInfo.taskId == taskId;
}
- void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t, int offsetX,
- int offsetY, boolean immediately) {
+ void onResizing(Rect newBounds, Rect sideBounds, Rect displayBounds,
+ SurfaceControl.Transaction t, int offsetX, int offsetY, boolean immediately) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
- mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t, offsetX,
- offsetY, immediately);
+ mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, displayBounds, t,
+ offsetX, offsetY, immediately);
}
}