diff options
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); } } |