[1/n] Enforce Shell desktop cascading in Launch Params
Desktop taskbar and app drawer launches now start in shell, meaning
after the intent is created and started, shell does not have another
opportunity to impact the launch. This means the initial bounds of the
task can potentially be wrong as Shell does not have enough information
about the activity to resolve its correct initial bounds.
Core has the ability in the `DesktopLaunchParamsModifier`, to calculate
the correct initial bounds for an activity but does not have the ability
to calculate the correct cascading position for the Task (as cascading
is done in Shell).
This change introduces a way for Shell to pass the cascading position to
Core (via the `ActivityOptions`) and have it respected in the Launch
Params calculations. The cascading position will be passed to Core via
the `ActivityOptions#launchBounds` which will be set to the default
desktop bounds size (as a place holder) and cascaded to the correct
location. Traditionally the activity options bounds are respected,
however setting the newly introduced `flexibleLaunchSize` boolean in the
activity options will signal Core that the
`ActivityOptions#launchBounds` size can be updated. In the
`DesktopLaunchParamsModifier` the initial size of the activity will be
calculated and cascaded according to the extracted position from the
`ActivityOptions#launchBounds`, forming the final initial launch bounds.
Flag: com.android.window.flags.enable_shell_initial_bounds_regression_bug_fix
Fixes: 396075922
Test: atest WMShellUnitTests:DesktopTasksControllerTest,
atest WmTests:DesktopModeLaunchParamsModifierTests
Change-Id: Ib0099b0f20d5e1f16bbe651cf390568026dc5f8d
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index b8c20bd..cc37456 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -488,6 +488,8 @@
private static final String KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE =
"android.activity.allowPassThroughOnTouchOutside";
+ private static final String KEY_FLEXIBLE_LAUNCH_SIZE = "android.activity.flexibleLaunchSize";
+
/**
* @see #setLaunchCookie
* @hide
@@ -588,6 +590,7 @@
@BackgroundActivityStartMode
private int mPendingIntentCreatorBackgroundActivityStartMode =
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+ private boolean mFlexibleLaunchSize = false;
private boolean mDisableStartingWindow;
private boolean mAllowPassThroughOnTouchOutside;
@@ -1451,6 +1454,7 @@
mPendingIntentCreatorBackgroundActivityStartMode = opts.getInt(
KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+ mFlexibleLaunchSize = opts.getBoolean(KEY_FLEXIBLE_LAUNCH_SIZE, /* defaultValue = */ false);
mDisableStartingWindow = opts.getBoolean(KEY_DISABLE_STARTING_WINDOW);
mAllowPassThroughOnTouchOutside = opts.getBoolean(KEY_ALLOW_PASS_THROUGH_ON_TOUCH_OUTSIDE);
mAnimationAbortListener = IRemoteCallback.Stub.asInterface(
@@ -2346,6 +2350,24 @@
}
/**
+ * Sets whether the size of the launch bounds is flexible, meaning it can be overridden to a
+ * different size during the launch params calculation.
+ * @hide
+ */
+ public ActivityOptions setFlexibleLaunchSize(boolean isFlexible) {
+ mFlexibleLaunchSize = isFlexible;
+ return this;
+ }
+
+ /**
+ * Gets whether the size of the launch bounds is flexible.
+ * @hide
+ */
+ public boolean getFlexibleLaunchSize() {
+ return mFlexibleLaunchSize;
+ }
+
+ /**
* Update the current values in this ActivityOptions from those supplied
* in <var>otherOptions</var>. Any values
* defined in <var>otherOptions</var> replace those in the base options.
@@ -2601,6 +2623,9 @@
b.putInt(KEY_PENDING_INTENT_CREATOR_BACKGROUND_ACTIVITY_START_MODE,
mPendingIntentCreatorBackgroundActivityStartMode);
}
+ if (mFlexibleLaunchSize) {
+ b.putBoolean(KEY_FLEXIBLE_LAUNCH_SIZE, mFlexibleLaunchSize);
+ }
if (mDisableStartingWindow) {
b.putBoolean(KEY_DISABLE_STARTING_WINDOW, mDisableStartingWindow);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 7e63250..5de3be4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -1246,6 +1246,10 @@
pendingIntentBackgroundActivityStartMode =
ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
launchBounds = bounds
+ if (DesktopModeFlags.ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX.isTrue) {
+ // Sets launch bounds size as flexible so core can recalculate.
+ flexibleLaunchSize = true
+ }
}
wct.sendPendingIntent(pendingIntent, intent, ops.toBundle())
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 0106a64..7a959c1 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -52,6 +52,13 @@
.getInt("persist.wm.debug.desktop_mode_landscape_app_padding", 25);
/**
+ * Proportion of window height top offset with respect to bottom offset, used for central task
+ * positioning. Should be kept in sync with constant in
+ * {@link com.android.wm.shell.desktopmode.DesktopTaskPosition}
+ */
+ public static final float WINDOW_HEIGHT_PROPORTION = 0.375f;
+
+ /**
* Updates launch bounds for an activity with respect to its activity options, window layout,
* android manifest and task configuration.
*/
@@ -63,7 +70,16 @@
final Rect stableBounds = new Rect();
task.getDisplayArea().getStableRect(stableBounds);
- if (options != null && options.getLaunchBounds() != null) {
+ // If the options bounds size is flexible, update size with calculated desired size.
+ final boolean updateOptionBoundsSize = options != null
+ && options.getFlexibleLaunchSize();
+ // If cascading is also enabled, the position of the options bounds must be respected
+ // during the size update.
+ final boolean shouldRespectOptionPosition =
+ updateOptionBoundsSize && DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue();
+
+ if (options != null && options.getLaunchBounds() != null
+ && !updateOptionBoundsSize) {
outBounds.set(options.getLaunchBounds());
logger.accept("inherit-from-options=" + outBounds);
} else if (layout != null) {
@@ -76,26 +92,34 @@
stableBounds);
logger.accept("layout specifies sizes, inheriting size and applying gravity");
} else if (verticalGravity > 0 || horizontalGravity > 0) {
- outBounds.set(calculateInitialBounds(task, activity, stableBounds));
+ outBounds.set(calculateInitialBounds(task, activity, stableBounds, options,
+ shouldRespectOptionPosition));
applyLayoutGravity(verticalGravity, horizontalGravity, outBounds,
stableBounds);
logger.accept("layout specifies gravity, applying desired bounds and gravity");
+ logger.accept("respecting option bounds cascaded position="
+ + shouldRespectOptionPosition);
}
} else {
- outBounds.set(calculateInitialBounds(task, activity, stableBounds));
+ outBounds.set(calculateInitialBounds(task, activity, stableBounds, options,
+ shouldRespectOptionPosition));
logger.accept("layout not specified, applying desired bounds");
+ logger.accept("respecting option bounds cascaded position="
+ + shouldRespectOptionPosition);
}
}
/**
* Calculates the initial bounds required for an application to fill a scale of the display
* bounds without any letterboxing. This is done by taking into account the applications
- * fullscreen size, aspect ratio, orientation and resizability to calculate an area this is
- * compatible with the applications previous configuration.
+ * fullscreen size, aspect ratio, orientation and resizability to calculate an area that is
+ * compatible with the applications previous configuration. These bounds are then cascaded to
+ * either the center or to a cascading position supplied by Shell via the option bounds.
*/
@NonNull
private static Rect calculateInitialBounds(@NonNull Task task,
- @NonNull ActivityRecord activity, @NonNull Rect stableBounds
+ @NonNull ActivityRecord activity, @NonNull Rect stableBounds,
+ @Nullable ActivityOptions options, boolean shouldRespectOptionPosition
) {
// Display bounds not taking into account insets.
final TaskDisplayArea displayArea = task.getDisplayArea();
@@ -172,6 +196,9 @@
}
default -> idealSize;
};
+ if (shouldRespectOptionPosition) {
+ return respectShellCascading(initialSize, stableBounds, options.getLaunchBounds());
+ }
return centerInScreen(initialSize, screenBounds);
}
@@ -266,14 +293,65 @@
* Adjusts bounds to be positioned in the middle of the screen.
*/
@NonNull
- private static Rect centerInScreen(@NonNull Size desiredSize,
+ static Rect centerInScreen(@NonNull Size desiredSize,
@NonNull Rect screenBounds) {
- // TODO(b/325240051): Position apps with bottom heavy offset
- final int heightOffset = (screenBounds.height() - desiredSize.getHeight()) / 2;
+ final int heightOffset = (int)
+ ((screenBounds.height() - desiredSize.getHeight()) * WINDOW_HEIGHT_PROPORTION);
final int widthOffset = (screenBounds.width() - desiredSize.getWidth()) / 2;
final Rect resultBounds = new Rect(0, 0,
desiredSize.getWidth(), desiredSize.getHeight());
resultBounds.offset(screenBounds.left + widthOffset, screenBounds.top + heightOffset);
return resultBounds;
}
+
+ /**
+ * Calculates final initial bounds based on the task's desired size and the cascading anchor
+ * point extracted from the option bounds. Cascading behaviour should be kept in sync with
+ * logic in {@link com.android.wm.shell.desktopmode.DesktopTaskPosition}.
+ */
+ @NonNull
+ private static Rect respectShellCascading(@NonNull Size desiredSize, @NonNull Rect stableBounds,
+ @NonNull Rect optionBounds) {
+ final boolean leftAligned = stableBounds.left == optionBounds.left;
+ final boolean rightAligned = stableBounds.right == optionBounds.right;
+ final boolean topAligned = stableBounds.top == optionBounds.top;
+ final boolean bottomAligned = stableBounds.bottom == optionBounds.bottom;
+ if (leftAligned && topAligned) {
+ // Bounds cascaded to top left, respect top left position anchor point.
+ return new Rect(
+ optionBounds.left,
+ optionBounds.top,
+ optionBounds.left + desiredSize.getWidth(),
+ optionBounds.top + desiredSize.getHeight()
+ );
+ }
+ if (leftAligned && bottomAligned) {
+ // Bounds cascaded to bottom left, respect bottom left position anchor point.
+ return new Rect(
+ optionBounds.left,
+ optionBounds.bottom - desiredSize.getHeight(),
+ optionBounds.left + desiredSize.getWidth(),
+ optionBounds.bottom
+ );
+ }
+ if (rightAligned && topAligned) {
+ // Bounds cascaded to top right, respect top right position anchor point.
+ return new Rect(
+ optionBounds.right - desiredSize.getWidth(),
+ optionBounds.top,
+ optionBounds.right,
+ optionBounds.top + desiredSize.getHeight()
+ );
+ }
+ if (rightAligned && bottomAligned) {
+ // Bounds cascaded to bottom right, respect bottom right position anchor point.
+ return new Rect(
+ optionBounds.right - desiredSize.getWidth(),
+ optionBounds.bottom - desiredSize.getHeight(),
+ optionBounds.right,
+ optionBounds.bottom);
+ }
+ // Bounds not cascaded to any corner, default to center position.
+ return centerInScreen(desiredSize, stableBounds);
+ }
}
diff --git a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
index a4eeb68..d466a64 100644
--- a/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/wm/DesktopModeLaunchParamsModifier.java
@@ -42,6 +42,7 @@
private static final String TAG =
TAG_WITH_CLASS_NAME ? "DesktopModeLaunchParamsModifier" : TAG_ATM;
+
private static final boolean DEBUG = false;
private StringBuilder mLogBuilder;
@@ -133,6 +134,14 @@
DesktopModeBoundsCalculator.updateInitialBounds(task, layout, activity, options,
outParams.mBounds, this::appendLog);
appendLog("final desktop mode task bounds set to %s", outParams.mBounds);
+ if (options != null && options.getFlexibleLaunchSize()) {
+ // Return result done to prevent other modifiers from respecting option bounds and
+ // applying further cascading. Since other modifiers are being skipped in this case,
+ // this modifier is now also responsible to respecting the options launch windowing
+ // mode.
+ outParams.mWindowingMode = options.getLaunchWindowingMode();
+ return RESULT_DONE;
+ }
return RESULT_CONTINUE;
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 3a06fff..bc37496 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -42,8 +42,10 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING;
+import static com.android.server.wm.DesktopModeBoundsCalculator.centerInScreen;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
+import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
import static org.junit.Assert.assertEquals;
@@ -60,6 +62,7 @@
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
+import android.util.Size;
import android.view.Gravity;
import androidx.test.filters.SmallTest;
@@ -1099,6 +1102,110 @@
}
@Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSize_windowingModeSet() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchBounds(new Rect(
+ DISPLAY_STABLE_BOUNDS.left,
+ DISPLAY_STABLE_BOUNDS.top,
+ /* right = */ 500,
+ /* bottom = */ 500))
+ .setFlexibleLaunchSize(true);
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSize_boundsSizeModified() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchBounds(new Rect(
+ DISPLAY_STABLE_BOUNDS.left,
+ DISPLAY_STABLE_BOUNDS.top,
+ /* right = */ 500,
+ /* bottom = */ 500))
+ .setFlexibleLaunchSize(true);
+ final int modifiedWidth =
+ (int) (DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int modifiedHeight =
+ (int) (DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(modifiedWidth, mResult.mBounds.width());
+ assertEquals(modifiedHeight, mResult.mBounds.height());
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSizeWithCascading_cornerCascadeRespected() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+ // Set launch bounds with corner cascade.
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchBounds(new Rect(
+ DISPLAY_STABLE_BOUNDS.left,
+ DISPLAY_STABLE_BOUNDS.top,
+ /* right = */ 500,
+ /* bottom = */ 500))
+ .setFlexibleLaunchSize(true);
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(DISPLAY_STABLE_BOUNDS.left, mResult.mBounds.left);
+ assertEquals(DISPLAY_STABLE_BOUNDS.top, mResult.mBounds.top);
+ }
+
+ @Test
+ @EnableFlags({Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE,
+ Flags.FLAG_ENABLE_SHELL_INITIAL_BOUNDS_REGRESSION_BUG_FIX})
+ public void testOptionsBoundsSet_flexibleLaunchSize_centerCascadeRespected() {
+ setupDesktopModeLaunchParamsModifier();
+
+ final TestDisplayContent display = createNewDisplayContent(WINDOWING_MODE_FULLSCREEN);
+ final Task task = new TaskBuilder(mSupervisor).setActivityType(
+ ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
+ // Set launch bounds with center cascade.
+ final ActivityOptions options = ActivityOptions.makeBasic()
+ .setLaunchBounds(new Rect(
+ /* left = */ 320,
+ /* top = */ 100,
+ /* right = */ 640,
+ /* bottom = */ 200))
+ .setFlexibleLaunchSize(true);
+ final int modifiedWidth =
+ (int) (DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final int modifiedHeight =
+ (int) (DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+ final Rect centerCascadedBounds = centerInScreen(
+ new Size(modifiedWidth, modifiedHeight), DISPLAY_STABLE_BOUNDS);
+
+ assertEquals(RESULT_DONE,
+ new CalculateRequestBuilder().setTask(task).setOptions(options).calculate());
+ assertEquals(centerCascadedBounds, mResult.mBounds);
+ assertEquals(centerCascadedBounds.top, mResult.mBounds.top);
+ }
+
+ @Test
@EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
public void testNonEmptyLayoutBounds_CenterToDisplay() {
setupDesktopModeLaunchParamsModifier();