summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Jeremy Sim <jeremysim@google.com> 2024-12-14 01:10:48 -0800
committer Jeremy Sim <jeremysim@google.com> 2025-01-02 11:50:29 -0800
commita250cad88c48940a257286bb4083878cf97c9a9c (patch)
treea233ab3afba418d7907e4118e881dd6b2135719d
parent22c82b50571ea4d24da89dd378dda8e1d6da17a8 (diff)
Flexible 2-app split: Parallax (landscape)
This CL implements the correct parallax effects for apps going offscreen and getting pulled back onscreen in landscape mode. Portrait mode looks good with this patch (no visual bugs), but there is an additional tweak needed (center-aligning the surface) that is complicated enough to defer to another CL. Bug: 349828130 Flag: com.android.wm.shell.enable_flexible_two_app_split Test: Visually correct for all landscape CUJs Change-Id: Iffa8a8bbe55045b15697f325ff3a1f686dfcc565
-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);
}
}