summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/ActivityInfo.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityLaunchParamsModifier.java65
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java3
-rw-r--r--services/core/java/com/android/server/am/ActivityStarter.java65
-rw-r--r--services/core/java/com/android/server/am/LaunchParamsController.java66
-rw-r--r--services/core/java/com/android/server/am/TaskLaunchParamsModifier.java862
-rw-r--r--services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java144
-rw-r--r--services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java1189
8 files changed, 1760 insertions, 642 deletions
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 5926af6f9082..0a4f4eb8506c 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1408,5 +1408,13 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
* @attr ref android.R.styleable#AndroidManifestLayout_minHeight
*/
public final int minHeight;
+
+ /**
+ * Returns if this {@link WindowLayout} has specified bounds.
+ * @hide
+ */
+ public boolean hasSpecifiedSize() {
+ return width >= 0 || height >= 0 || widthFraction >= 0 || heightFraction >= 0;
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActivityLaunchParamsModifier.java b/services/core/java/com/android/server/am/ActivityLaunchParamsModifier.java
deleted file mode 100644
index f44ee7a234ca..000000000000
--- a/services/core/java/com/android/server/am/ActivityLaunchParamsModifier.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 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.server.am;
-
-import android.app.ActivityOptions;
-import android.content.pm.ActivityInfo;
-import android.graphics.Rect;
-
-import com.android.server.am.LaunchParamsController.LaunchParams;
-import com.android.server.am.LaunchParamsController.LaunchParamsModifier;
-
-/**
- * An implementation of {@link LaunchParamsModifier}, which applies the launch bounds specified
- * inside {@link ActivityOptions#getLaunchBounds()}.
- */
-public class ActivityLaunchParamsModifier implements LaunchParamsModifier {
- private final ActivityStackSupervisor mSupervisor;
-
- ActivityLaunchParamsModifier(ActivityStackSupervisor activityStackSupervisor) {
- mSupervisor = activityStackSupervisor;
- }
-
- @Override
- public int onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout,
- ActivityRecord activity, ActivityRecord source, ActivityOptions options,
- LaunchParams currentParams, LaunchParams outParams) {
- // We only care about figuring out bounds for activities.
- if (activity == null) {
- return RESULT_SKIP;
- }
-
- // Activity must be resizeable in the specified task.
- if (!(mSupervisor.canUseActivityOptionsLaunchBounds(options)
- && (activity.isResizeable() || (task != null && task.isResizeable())))) {
- return RESULT_SKIP;
- }
-
- final Rect bounds = options.getLaunchBounds();
-
- // Bounds weren't valid.
- if (bounds == null || bounds.isEmpty()) {
- return RESULT_SKIP;
- }
-
- outParams.mBounds.set(bounds);
-
- // When this is the most explicit position specification so we should not allow further
- // modification of the position.
- return RESULT_DONE;
- }
-}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index ea807adc7d4f..a368fa5f6620 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -163,7 +163,6 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -228,7 +227,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
}
@Override
- protected ConfigurationContainer getChildAt(int index) {
+ protected TaskRecord getChildAt(int index) {
return mTaskHistory.get(index);
}
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index de3b9cf3ccde..0acd079c4217 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1606,13 +1606,18 @@ class ActivityStarter {
mVoiceSession = voiceSession;
mVoiceInteractor = voiceInteractor;
- mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options);
-
mLaunchParams.reset();
mSupervisor.getLaunchParamsController().calculate(inTask, null /*layout*/, r, sourceRecord,
options, mLaunchParams);
+ if (mLaunchParams.hasPreferredDisplay()) {
+ mPreferredDisplayId = mLaunchParams.mPreferredDisplayId;
+ } else {
+ mPreferredDisplayId = DEFAULT_DISPLAY;
+ }
+ ensureValidPreferredDisplayId(r);
+
mLaunchMode = r.launchMode;
mLaunchFlags = adjustLaunchFlagsToDocumentMode(
@@ -1704,6 +1709,24 @@ class ActivityStarter {
mNoAnimation = (mLaunchFlags & FLAG_ACTIVITY_NO_ANIMATION) != 0;
}
+ /**
+ * Ensure preferred display ID matches the starting activity.
+ */
+ private void ensureValidPreferredDisplayId(ActivityRecord startingActivity) {
+ // Check if the Activity is a VR activity. If so, the activity should be launched in
+ // main display.
+ if (startingActivity != null && startingActivity.requestedVrComponent != null) {
+ mPreferredDisplayId = DEFAULT_DISPLAY;
+ }
+
+ // Get the virtual display ID from ActivityStackManagerService. If that's set we should
+ // always use that.
+ final int displayId = mService.mVr2dDisplayId;
+ if (displayId != INVALID_DISPLAY) {
+ mPreferredDisplayId = displayId;
+ }
+ }
+
private void sendNewTaskResultRequestIfNeeded() {
final ActivityStack sourceStack = mStartActivity.resultTo != null
? mStartActivity.resultTo.getStack() : null;
@@ -1883,44 +1906,6 @@ class ActivityStarter {
}
/**
- * Returns the ID of the display to use for a new activity. If the device is in VR mode,
- * then return the Vr mode's virtual display ID. If not, if the activity was started with
- * a launchDisplayId, use that. Otherwise, if the source activity has a explicit display ID
- * set, use that to launch the activity.
- */
- private int getPreferedDisplayId(
- ActivityRecord sourceRecord, ActivityRecord startingActivity, ActivityOptions options) {
- // Check if the Activity is a VR activity. If so, the activity should be launched in
- // main display.
- if (startingActivity != null && startingActivity.requestedVrComponent != null) {
- return DEFAULT_DISPLAY;
- }
-
- // Get the virtual display id from ActivityManagerService.
- int displayId = mService.mVr2dDisplayId;
- if (displayId != INVALID_DISPLAY) {
- if (DEBUG_STACK) {
- Slog.d(TAG, "getSourceDisplayId :" + displayId);
- }
- return displayId;
- }
-
- // If the caller requested a display, prefer that display.
- final int launchDisplayId =
- (options != null) ? options.getLaunchDisplayId() : INVALID_DISPLAY;
- if (launchDisplayId != INVALID_DISPLAY) {
- return launchDisplayId;
- }
-
- displayId = sourceRecord != null ? sourceRecord.getDisplayId() : INVALID_DISPLAY;
- // If the activity has a displayId set explicitly, launch it on the same displayId.
- if (displayId != INVALID_DISPLAY) {
- return displayId;
- }
- return DEFAULT_DISPLAY;
- }
-
- /**
* Figure out which task and activity to bring to front when we have found an existing matching
* activity record in history. May also clear the task if needed.
* @param intentActivity Existing matching activity.
diff --git a/services/core/java/com/android/server/am/LaunchParamsController.java b/services/core/java/com/android/server/am/LaunchParamsController.java
index 6415c3ee7f72..218d9080c2c0 100644
--- a/services/core/java/com/android/server/am/LaunchParamsController.java
+++ b/services/core/java/com/android/server/am/LaunchParamsController.java
@@ -16,6 +16,13 @@
package com.android.server.am;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
+import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
+import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
+
import android.annotation.IntDef;
import android.app.ActivityOptions;
import android.content.pm.ActivityInfo.WindowLayout;
@@ -26,13 +33,6 @@ import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.view.Display.INVALID_DISPLAY;
-
-import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
-import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
-import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
-
/**
* {@link LaunchParamsController} calculates the {@link LaunchParams} by coordinating between
* registered {@link LaunchParamsModifier}s.
@@ -58,11 +58,7 @@ class LaunchParamsController {
*/
void registerDefaultModifiers(ActivityStackSupervisor supervisor) {
// {@link TaskLaunchParamsModifier} handles window layout preferences.
- registerModifier(new TaskLaunchParamsModifier());
-
- // {@link ActivityLaunchParamsModifier} is the most specific modifier and thus should be
- // registered last (applied first) out of the defaults.
- registerModifier(new ActivityLaunchParamsModifier(supervisor));
+ registerModifier(new TaskLaunchParamsModifier(supervisor));
}
/**
@@ -226,27 +222,41 @@ class LaunchParamsController {
@IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE})
@interface Result {}
- // Returned when the modifier does not want to influence the bounds calculation
+ /** Returned when the modifier does not want to influence the bounds calculation */
int RESULT_SKIP = 0;
- // Returned when the modifier has changed the bounds and would like its results to be the
- // final bounds applied.
+ /**
+ * Returned when the modifier has changed the bounds and would like its results to be the
+ * final bounds applied.
+ */
int RESULT_DONE = 1;
- // Returned when the modifier has changed the bounds but is okay with other modifiers
- // influencing the bounds.
+ /**
+ * Returned when the modifier has changed the bounds but is okay with other modifiers
+ * influencing the bounds.
+ */
int RESULT_CONTINUE = 2;
/**
- * Called when asked to calculate {@link LaunchParams}.
- * @param task The {@link TaskRecord} currently being positioned.
- * @param layout The specified {@link WindowLayout}.
- * @param activity The {@link ActivityRecord} currently being positioned.
- * @param source The {@link ActivityRecord} activity was started from.
- * @param options The {@link ActivityOptions} specified for the activity.
- * @param currentParams The current {@link LaunchParams}. This can differ from the initial
- * params as it represents the modified params up to this point.
- * @param outParams The resulting {@link LaunchParams} after all calculations.
- * @return A {@link Result} representing the result of the
- * {@link LaunchParams} calculation.
+ * Returns the launch params that the provided activity launch params should be overridden
+ * to. {@link LaunchParamsModifier} can use this for various purposes, including: 1)
+ * Providing default bounds if the launch bounds have not been provided. 2) Repositioning
+ * the task so it doesn't get placed over an existing task. 3) Resizing the task so that its
+ * dimensions match the activity's requested orientation.
+ *
+ * @param task Can be: 1) the target task in which the source activity wants to
+ * launch the target activity; 2) a newly created task that Android
+ * gives a chance to override its launching bounds; 3) {@code null} if
+ * this is called to override an activity's launching bounds.
+ * @param layout Desired layout when activity is first launched.
+ * @param activity Activity that is being started. This can be {@code null} on
+ * re-parenting an activity to a new task (e.g. for
+ * Picture-In-Picture). Tasks being created because an activity was
+ * launched should have this be non-null.
+ * @param source the Activity that launched a new task. Could be {@code null}.
+ * @param options {@link ActivityOptions} used to start the activity with.
+ * @param currentParams launching params after the process of last {@link
+ * LaunchParamsModifier}.
+ * @param outParams the result params to be set.
+ * @return see {@link LaunchParamsModifier.Result}
*/
@Result
int onCalculate(TaskRecord task, WindowLayout layout, ActivityRecord activity,
diff --git a/services/core/java/com/android/server/am/TaskLaunchParamsModifier.java b/services/core/java/com/android/server/am/TaskLaunchParamsModifier.java
index 92f1cc34be92..fd34d180ebc0 100644
--- a/services/core/java/com/android/server/am/TaskLaunchParamsModifier.java
+++ b/services/core/java/com/android/server/am/TaskLaunchParamsModifier.java
@@ -16,304 +16,770 @@
package com.android.server.am;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
import android.graphics.Rect;
+import android.os.Build;
import android.util.Slog;
import android.view.Gravity;
-import com.android.internal.annotations.VisibleForTesting;
+
import com.android.server.am.LaunchParamsController.LaunchParams;
import com.android.server.am.LaunchParamsController.LaunchParamsModifier;
import java.util.ArrayList;
+import java.util.List;
/**
- * Determines where a launching task should be positioned and sized on the display.
- *
- * The modifier is fairly simple. For the new task it tries default position based on the gravity
- * and compares corners of the task with corners of existing tasks. If some two pairs of corners are
- * sufficiently close enough, it shifts the bounds of the new task and tries again. When it exhausts
- * all possible shifts, it gives up and puts the task in the original position.
- *
- * Note that the only gravities of concern are the corners and the center.
+ * The class that defines the default launch params for tasks.
*/
class TaskLaunchParamsModifier implements LaunchParamsModifier {
private static final String TAG = TAG_WITH_CLASS_NAME ? "TaskLaunchParamsModifier" : TAG_AM;
+ private static final boolean DEBUG = false;
- // Determines how close window frames/corners have to be to call them colliding.
- private static final int BOUNDS_CONFLICT_MIN_DISTANCE = 4;
+ // A mask for SUPPORTS_SCREEN that indicates the activity supports resize.
+ private static final int SUPPORTS_SCREEN_RESIZEABLE_MASK =
+ ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES
+ | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS
+ | ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS
+ | ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS
+ | ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES
+ | ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS;
- // Task will receive dimensions based on available dimensions divided by this.
- private static final int WINDOW_SIZE_DENOMINATOR = 2;
+ // Screen size of Nexus 5x
+ private static final int DEFAULT_PORTRAIT_PHONE_WIDTH_DP = 412;
+ private static final int DEFAULT_PORTRAIT_PHONE_HEIGHT_DP = 732;
- // Task will receive margins based on available dimensions divided by this.
- private static final int MARGIN_SIZE_DENOMINATOR = 4;
+ // Allowance of size matching.
+ private static final int EPSILON = 2;
- // If task bounds collide with some other, we will step and try again until we find a good
- // position. The step will be determined by using dimensions and dividing it by this.
+ // Cascade window offset.
+ private static final int CASCADING_OFFSET_DP = 75;
+
+ // Threshold how close window corners have to be to call them colliding.
+ private static final int BOUNDS_CONFLICT_THRESHOLD = 4;
+
+ // Divide display size by this number to get each step to adjust bounds to avoid conflict.
private static final int STEP_DENOMINATOR = 16;
// We always want to step by at least this.
private static final int MINIMAL_STEP = 1;
- // Used to indicate if positioning algorithm is allowed to restart from the beginning, when it
- // reaches the end of stack bounds.
- private static final boolean ALLOW_RESTART = true;
+ private final ActivityStackSupervisor mSupervisor;
+ private final Rect mTmpBounds = new Rect();
+ private final int[] mTmpDirections = new int[2];
- private static final int SHIFT_POLICY_DIAGONAL_DOWN = 1;
- private static final int SHIFT_POLICY_HORIZONTAL_RIGHT = 2;
- private static final int SHIFT_POLICY_HORIZONTAL_LEFT = 3;
+ private StringBuilder mLogBuilder;
- private final Rect mAvailableRect = new Rect();
- private final Rect mTmpProposal = new Rect();
- private final Rect mTmpOriginal = new Rect();
+ TaskLaunchParamsModifier(ActivityStackSupervisor supervisor) {
+ mSupervisor = supervisor;
+ }
- /**
- * Tries to set task's bound in a way that it won't collide with any other task. By colliding
- * we mean that two tasks have left-top corner very close to each other, so one might get
- * obfuscated by the other one.
- */
@Override
public int onCalculate(TaskRecord task, ActivityInfo.WindowLayout layout,
ActivityRecord activity, ActivityRecord source, ActivityOptions options,
LaunchParams currentParams, LaunchParams outParams) {
- // We can only apply positioning if we're in a freeform stack.
- if (task == null || task.getStack() == null || !task.inFreeformWindowingMode()) {
- return RESULT_SKIP;
- }
+ initLogBuilder(task, activity);
+ final int result = calculate(task, layout, activity, source, options, currentParams,
+ outParams);
+ outputLog();
+ return result;
+ }
- final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks();
+ private int calculate(TaskRecord task, ActivityInfo.WindowLayout layout,
+ ActivityRecord activity, ActivityRecord source, ActivityOptions options,
+ LaunchParams currentParams, LaunchParams outParams) {
+ // STEP 1: Determine the display to launch the activity/task.
+ final int displayId = getPreferredLaunchDisplay(options, source, currentParams);
+ outParams.mPreferredDisplayId = displayId;
+ ActivityDisplay display = mSupervisor.getActivityDisplay(displayId);
+ if (DEBUG) {
+ appendLog("display-id=" + outParams.mPreferredDisplayId + " display-windowing-mode="
+ + display.getWindowingMode());
+ }
- mAvailableRect.set(task.getParent().getBounds());
+ final ActivityRecord root;
+ if (task != null) {
+ root = (task.getRootActivity() == null ? activity : task.getRootActivity());
+ } else {
+ root = activity;
+ }
+ // STEP 2: Resolve launch windowing mode.
+ // STEP 2.1: Determine if any parameter has specified initial bounds. That might be the
+ // launch bounds from activity options, or size/gravity passed in layout. It also treat the
+ // launch windowing mode in options as a suggestion for future resolution.
+ int launchMode = options != null ? options.getLaunchWindowingMode()
+ : WINDOWING_MODE_UNDEFINED;
+ // hasInitialBounds is set if either activity options or layout has specified bounds. If
+ // that's set we'll skip some adjustments later to avoid overriding the initial bounds.
+ boolean hasInitialBounds = false;
+ final boolean canApplyFreeformPolicy =
+ canApplyFreeformWindowPolicy(display, root, launchMode);
+ if (mSupervisor.canUseActivityOptionsLaunchBounds(options) && canApplyFreeformPolicy) {
+ hasInitialBounds = true;
+ launchMode = launchMode == WINDOWING_MODE_UNDEFINED
+ ? WINDOWING_MODE_FREEFORM
+ : launchMode;
+ outParams.mBounds.set(options.getLaunchBounds());
+ if (DEBUG) appendLog("activity-options-bounds=" + outParams.mBounds);
+ } else if (launchMode == WINDOWING_MODE_PINNED) {
+ // System controls PIP window's bounds, so don't apply launch bounds.
+ if (DEBUG) appendLog("empty-window-layout-for-pip");
+ } else if (launchMode == WINDOWING_MODE_FULLSCREEN) {
+ if (DEBUG) appendLog("activity-options-fullscreen=" + outParams.mBounds);
+ } else if (layout != null && canApplyFreeformPolicy) {
+ getLayoutBounds(display, root, layout, mTmpBounds);
+ if (!mTmpBounds.isEmpty()) {
+ launchMode = WINDOWING_MODE_FREEFORM;
+ outParams.mBounds.set(mTmpBounds);
+ hasInitialBounds = true;
+ if (DEBUG) appendLog("bounds-from-layout=" + outParams.mBounds);
+ } else {
+ if (DEBUG) appendLog("empty-window-layout");
+ }
+ }
- final Rect resultBounds = outParams.mBounds;
+ // STEP 2.2: Check if previous modifier or the controller (referred as "callers" below) has
+ // some opinions on launch mode and launch bounds. If they have opinions and there is no
+ // initial bounds set in parameters. Note the check on display ID is also input param
+ // related because we always defer to callers' suggestion if there is no specific display ID
+ // in options or from source activity.
+ //
+ // If opinions from callers don't need any further resolution, we try to honor that as is as
+ // much as possible later.
+
+ // Flag to indicate if current param needs no further resolution. It's true it current
+ // param isn't freeform mode, or it already has launch bounds.
+ boolean fullyResolvedCurrentParam = false;
+ // We inherit launch params from previous modifiers or LaunchParamsController if options,
+ // layout and display conditions are not contradictory to their suggestions. It's important
+ // to carry over their values because LaunchParamsController doesn't automatically do that.
+ if (!currentParams.isEmpty() && !hasInitialBounds
+ && (!currentParams.hasPreferredDisplay()
+ || displayId == currentParams.mPreferredDisplayId)) {
+ if (currentParams.hasWindowingMode()) {
+ launchMode = currentParams.mWindowingMode;
+ fullyResolvedCurrentParam = (launchMode != WINDOWING_MODE_FREEFORM);
+ if (DEBUG) {
+ appendLog("inherit-" + WindowConfiguration.windowingModeToString(launchMode));
+ }
+ }
- if (layout == null) {
- positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
- getFreeformHeight(mAvailableRect), resultBounds);
- return RESULT_CONTINUE;
+ if (!currentParams.mBounds.isEmpty()) {
+ outParams.mBounds.set(currentParams.mBounds);
+ fullyResolvedCurrentParam = true;
+ if (DEBUG) appendLog("inherit-bounds=" + outParams.mBounds);
+ }
}
- int width = getFinalWidth(layout, mAvailableRect);
- int height = getFinalHeight(layout, mAvailableRect);
- int verticalGravity = layout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
- int horizontalGravity = layout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
- if (verticalGravity == Gravity.TOP) {
- if (horizontalGravity == Gravity.RIGHT) {
- positionTopRight(tasks, mAvailableRect, width, height, resultBounds);
- } else {
- positionTopLeft(tasks, mAvailableRect, width, height, resultBounds);
- }
- } else if (verticalGravity == Gravity.BOTTOM) {
- if (horizontalGravity == Gravity.RIGHT) {
- positionBottomRight(tasks, mAvailableRect, width, height, resultBounds);
- } else {
- positionBottomLeft(tasks, mAvailableRect, width, height, resultBounds);
+ // STEP 2.3: Adjust launch parameters as needed for freeform display. We enforce the policy
+ // that legacy (pre-D) apps and those apps that can't handle multiple screen density well
+ // are forced to be maximized. The rest of this step is to define the default policy when
+ // there is no initial bounds or a fully resolved current params from callers. Right now we
+ // launch all possible tasks/activities that can handle freeform into freeform mode.
+ if (display.inFreeformWindowingMode()) {
+ if (launchMode == WINDOWING_MODE_PINNED) {
+ if (DEBUG) appendLog("picture-in-picture");
+ } else if (isTaskForcedMaximized(root)) {
+ // We're launching an activity that probably can't handle resizing nicely, so force
+ // it to be maximized even someone suggests launching it in freeform using launch
+ // options.
+ launchMode = WINDOWING_MODE_FULLSCREEN;
+ outParams.mBounds.setEmpty();
+ if (DEBUG) appendLog("forced-maximize");
+ } else if (fullyResolvedCurrentParam) {
+ // Don't adjust launch mode if that's inherited, except when we're launching an
+ // activity that should be forced to maximize.
+ if (DEBUG) appendLog("skip-adjustment-fully-resolved-params");
+ } else if (launchMode != WINDOWING_MODE_FREEFORM
+ && (isNOrGreater(root) || isPreNResizeable(root))) {
+ // We're launching a pre-N and post-D activity that supports resizing, or a post-N
+ // activity. They can handle freeform nicely so launch them in freeform.
+ // Use undefined because we know we're in a freeform display.
+ launchMode = WINDOWING_MODE_UNDEFINED;
+ if (DEBUG) appendLog("should-be-freeform");
}
} else {
- // Some fancy gravity setting that we don't support yet. We just put the activity in the
- // center.
- Slog.w(TAG, "Received unsupported gravity: " + layout.gravity
- + ", positioning in the center instead.");
- positionCenter(tasks, mAvailableRect, width, height, resultBounds);
+ if (DEBUG) appendLog("non-freeform-display");
+ }
+ // If launch mode matches display windowing mode, let it inherit from display.
+ outParams.mWindowingMode = launchMode == display.getWindowingMode()
+ ? WINDOWING_MODE_UNDEFINED : launchMode;
+
+ // STEP 3: Determine final launch bounds based on resolved windowing mode and activity
+ // requested orientation. We set bounds to empty for fullscreen mode and keep bounds as is
+ // for all other windowing modes that's not freeform mode. One can read comments in
+ // relevant methods to further understand this step.
+ //
+ // We skip making adjustments if the params are fully resolved from previous results and
+ // trust that they are valid.
+ if (!fullyResolvedCurrentParam) {
+ final int resolvedMode = (launchMode != WINDOWING_MODE_UNDEFINED) ? launchMode
+ : display.getWindowingMode();
+ if (source != null && source.inFreeformWindowingMode()
+ && resolvedMode == WINDOWING_MODE_FREEFORM
+ && outParams.mBounds.isEmpty()
+ && source.getDisplayId() == display.mDisplayId) {
+ // Set bounds to be not very far from source activity.
+ cascadeBounds(source.getBounds(), display, outParams.mBounds);
+ }
+ getTaskBounds(root, display, layout, resolvedMode, hasInitialBounds, outParams.mBounds);
}
return RESULT_CONTINUE;
}
- @VisibleForTesting
- static int getFreeformStartLeft(Rect bounds) {
- return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR;
+ private int getPreferredLaunchDisplay(@Nullable ActivityOptions options,
+ ActivityRecord source, LaunchParams currentParams) {
+ int displayId = INVALID_DISPLAY;
+ final int optionLaunchId = options != null ? options.getLaunchDisplayId() : INVALID_DISPLAY;
+ if (optionLaunchId != INVALID_DISPLAY) {
+ if (DEBUG) appendLog("display-from-option=" + optionLaunchId);
+ displayId = optionLaunchId;
+ }
+
+ if (displayId == INVALID_DISPLAY && source != null) {
+ final int sourceDisplayId = source.getDisplayId();
+ if (DEBUG) appendLog("display-from-source=" + sourceDisplayId);
+ displayId = sourceDisplayId;
+ }
+
+ if (displayId != INVALID_DISPLAY && mSupervisor.getActivityDisplay(displayId) == null) {
+ displayId = INVALID_DISPLAY;
+ }
+ displayId = (displayId == INVALID_DISPLAY) ? currentParams.mPreferredDisplayId : displayId;
+
+ displayId = (displayId == INVALID_DISPLAY) ? DEFAULT_DISPLAY : displayId;
+
+ return displayId;
+ }
+
+ private boolean canApplyFreeformWindowPolicy(@NonNull ActivityDisplay display,
+ @NonNull ActivityRecord root, int launchMode) {
+ return display.inFreeformWindowingMode() || launchMode == WINDOWING_MODE_FREEFORM
+ || root.isResizeable();
}
- @VisibleForTesting
- static int getFreeformStartTop(Rect bounds) {
- return bounds.top + bounds.height() / MARGIN_SIZE_DENOMINATOR;
+ private void getLayoutBounds(@NonNull ActivityDisplay display, @NonNull ActivityRecord root,
+ @NonNull ActivityInfo.WindowLayout windowLayout, @NonNull Rect outBounds) {
+ final int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK;
+ final int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (!windowLayout.hasSpecifiedSize() && verticalGravity == 0 && horizontalGravity == 0) {
+ outBounds.setEmpty();
+ return;
+ }
+
+ final Rect bounds = display.getBounds();
+ final int defaultWidth = bounds.width();
+ final int defaultHeight = bounds.height();
+
+ int width;
+ int height;
+ if (!windowLayout.hasSpecifiedSize()) {
+ outBounds.setEmpty();
+ getTaskBounds(root, display, windowLayout, WINDOWING_MODE_FREEFORM,
+ /* hasInitialBounds */ false, outBounds);
+ width = outBounds.width();
+ height = outBounds.height();
+ } else {
+ width = defaultWidth;
+ if (windowLayout.width > 0 && windowLayout.width < defaultWidth) {
+ width = windowLayout.width;
+ } else if (windowLayout.widthFraction > 0 && windowLayout.widthFraction < 1.0f) {
+ width = (int) (width * windowLayout.widthFraction);
+ }
+
+ height = defaultHeight;
+ if (windowLayout.height > 0 && windowLayout.height < defaultHeight) {
+ height = windowLayout.height;
+ } else if (windowLayout.heightFraction > 0 && windowLayout.heightFraction < 1.0f) {
+ height = (int) (height * windowLayout.heightFraction);
+ }
+ }
+
+ final float fractionOfHorizontalOffset;
+ switch (horizontalGravity) {
+ case Gravity.LEFT:
+ fractionOfHorizontalOffset = 0f;
+ break;
+ case Gravity.RIGHT:
+ fractionOfHorizontalOffset = 1f;
+ break;
+ default:
+ fractionOfHorizontalOffset = 0.5f;
+ }
+
+ final float fractionOfVerticalOffset;
+ switch (verticalGravity) {
+ case Gravity.TOP:
+ fractionOfVerticalOffset = 0f;
+ break;
+ case Gravity.BOTTOM:
+ fractionOfVerticalOffset = 1f;
+ break;
+ default:
+ fractionOfVerticalOffset = 0.5f;
+ }
+
+ outBounds.set(0, 0, width, height);
+ final int xOffset = (int) (fractionOfHorizontalOffset * (defaultWidth - width));
+ final int yOffset = (int) (fractionOfVerticalOffset * (defaultHeight - height));
+ outBounds.offset(xOffset, yOffset);
}
- @VisibleForTesting
- static int getFreeformWidth(Rect bounds) {
- return bounds.width() / WINDOW_SIZE_DENOMINATOR;
+ /**
+ * Returns if task is forced to maximize.
+ *
+ * There are several cases where we force a task to maximize:
+ * 1) Root activity is targeting pre-Donut, which by default can't handle multiple screen
+ * densities, so resizing will likely cause issues;
+ * 2) Root activity doesn't declare any flag that it supports any screen density, so resizing
+ * may also cause issues;
+ * 3) Root activity is not resizeable, for which we shouldn't allow user resize it.
+ *
+ * @param root the root activity to check against.
+ * @return {@code true} if it should be forced to maximize; {@code false} otherwise.
+ */
+ private boolean isTaskForcedMaximized(@NonNull ActivityRecord root) {
+ if (root.appInfo.targetSdkVersion < Build.VERSION_CODES.DONUT
+ || (root.appInfo.flags & SUPPORTS_SCREEN_RESIZEABLE_MASK) == 0) {
+ return true;
+ }
+
+ return !root.isResizeable();
}
- @VisibleForTesting
- static int getFreeformHeight(Rect bounds) {
- return bounds.height() / WINDOW_SIZE_DENOMINATOR;
+ private boolean isNOrGreater(@NonNull ActivityRecord root) {
+ return root.appInfo.targetSdkVersion >= Build.VERSION_CODES.N;
+ }
+
+ /**
+ * Resolves activity requested orientation to 4 categories:
+ * 1) {@link ActivityInfo#SCREEN_ORIENTATION_LOCKED} indicating app wants to lock down
+ * orientation;
+ * 2) {@link ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE} indicating app wants to be in landscape;
+ * 3) {@link ActivityInfo#SCREEN_ORIENTATION_PORTRAIT} indicating app wants to be in portrait;
+ * 4) {@link ActivityInfo#SCREEN_ORIENTATION_UNSPECIFIED} indicating app can handle any
+ * orientation.
+ *
+ * @param activity the activity to check
+ * @return corresponding resolved orientation value.
+ */
+ private int resolveOrientation(@NonNull ActivityRecord activity) {
+ int orientation = activity.info.screenOrientation;
+ switch (orientation) {
+ case SCREEN_ORIENTATION_NOSENSOR:
+ case SCREEN_ORIENTATION_LOCKED:
+ orientation = SCREEN_ORIENTATION_LOCKED;
+ break;
+ case SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
+ case SCREEN_ORIENTATION_REVERSE_LANDSCAPE:
+ case SCREEN_ORIENTATION_USER_LANDSCAPE:
+ case SCREEN_ORIENTATION_LANDSCAPE:
+ if (DEBUG) appendLog("activity-requested-landscape");
+ orientation = SCREEN_ORIENTATION_LANDSCAPE;
+ break;
+ case SCREEN_ORIENTATION_SENSOR_PORTRAIT:
+ case SCREEN_ORIENTATION_REVERSE_PORTRAIT:
+ case SCREEN_ORIENTATION_USER_PORTRAIT:
+ case SCREEN_ORIENTATION_PORTRAIT:
+ if (DEBUG) appendLog("activity-requested-portrait");
+ orientation = SCREEN_ORIENTATION_PORTRAIT;
+ break;
+ default:
+ orientation = SCREEN_ORIENTATION_UNSPECIFIED;
+ }
+
+ return orientation;
}
- @VisibleForTesting
- static int getHorizontalStep(Rect bounds) {
- return Math.max(bounds.width() / STEP_DENOMINATOR, MINIMAL_STEP);
+ private boolean isPreNResizeable(ActivityRecord root) {
+ return root.appInfo.targetSdkVersion < Build.VERSION_CODES.N && root.isResizeable();
}
- @VisibleForTesting
- static int getVerticalStep(Rect bounds) {
- return Math.max(bounds.height() / STEP_DENOMINATOR, MINIMAL_STEP);
+ private void cascadeBounds(@NonNull Rect srcBounds, @NonNull ActivityDisplay display,
+ @NonNull Rect outBounds) {
+ outBounds.set(srcBounds);
+ float density = (float) display.getConfiguration().densityDpi / DENSITY_DEFAULT;
+ final int defaultOffset = (int) (CASCADING_OFFSET_DP * density + 0.5f);
+
+ display.getBounds(mTmpBounds);
+ final int dx = Math.min(defaultOffset, Math.max(0, mTmpBounds.right - srcBounds.right));
+ final int dy = Math.min(defaultOffset, Math.max(0, mTmpBounds.bottom - srcBounds.bottom));
+ outBounds.offset(dx, dy);
}
+ private void getTaskBounds(@NonNull ActivityRecord root, @NonNull ActivityDisplay display,
+ @NonNull ActivityInfo.WindowLayout layout, int resolvedMode, boolean hasInitialBounds,
+ @NonNull Rect inOutBounds) {
+ if (resolvedMode == WINDOWING_MODE_FULLSCREEN) {
+ // We don't handle letterboxing here. Letterboxing will be handled by valid checks
+ // later.
+ inOutBounds.setEmpty();
+ if (DEBUG) appendLog("maximized-bounds");
+ return;
+ }
+ if (resolvedMode != WINDOWING_MODE_FREEFORM) {
+ // We don't apply freeform bounds adjustment to other windowing modes.
+ if (DEBUG) {
+ appendLog("skip-bounds-" + WindowConfiguration.windowingModeToString(resolvedMode));
+ }
+ return;
+ }
- private int getFinalWidth(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
- int width = getFreeformWidth(availableRect);
- if (windowLayout.width > 0) {
- width = windowLayout.width;
+ final int orientation = resolveOrientation(root, display, inOutBounds);
+ if (orientation != SCREEN_ORIENTATION_PORTRAIT
+ && orientation != SCREEN_ORIENTATION_LANDSCAPE) {
+ throw new IllegalStateException(
+ "Orientation must be one of portrait or landscape, but it's "
+ + ActivityInfo.screenOrientationToString(orientation));
}
- if (windowLayout.widthFraction > 0) {
- width = (int) (availableRect.width() * windowLayout.widthFraction);
+
+ // First we get the default size we want.
+ getDefaultFreeformSize(display, layout, orientation, mTmpBounds);
+ if (hasInitialBounds || sizeMatches(inOutBounds, mTmpBounds)) {
+ // We're here because either input parameters specified initial bounds, or the suggested
+ // bounds have the same size of the default freeform size. We should use the suggested
+ // bounds if possible -- so if app can handle the orientation we just use it, and if not
+ // we transpose the suggested bounds in-place.
+ if (orientation == orientationFromBounds(inOutBounds)) {
+ if (DEBUG) appendLog("freeform-size-orientation-match=" + inOutBounds);
+ } else {
+ // Meh, orientation doesn't match. Let's rotate inOutBounds in-place.
+ centerBounds(display, inOutBounds.height(), inOutBounds.width(), inOutBounds);
+ if (DEBUG) appendLog("freeform-orientation-mismatch=" + inOutBounds);
+ }
+ } else {
+ // We are here either because there is no suggested bounds, or the suggested bounds is
+ // a cascade from source activity. We should use the default freeform size and center it
+ // to the center of suggested bounds (or the display if no suggested bounds). The
+ // default size might be too big to center to source activity bounds in display, so we
+ // may need to move it back to the display.
+ centerBounds(display, mTmpBounds.width(), mTmpBounds.height(), inOutBounds);
+ adjustBoundsToFitInDisplay(display, inOutBounds);
+ if (DEBUG) appendLog("freeform-size-mismatch=" + inOutBounds);
}
- return width;
+
+ // Lastly we adjust bounds to avoid conflicts with other tasks as much as possible.
+ adjustBoundsToAvoidConflict(display, inOutBounds);
}
- private int getFinalHeight(ActivityInfo.WindowLayout windowLayout, Rect availableRect) {
- int height = getFreeformHeight(availableRect);
- if (windowLayout.height > 0) {
- height = windowLayout.height;
+ private int resolveOrientation(@NonNull ActivityRecord root, @NonNull ActivityDisplay display,
+ @NonNull Rect bounds) {
+ int orientation = resolveOrientation(root);
+
+ if (orientation == SCREEN_ORIENTATION_LOCKED) {
+ orientation = bounds.isEmpty() ? display.getConfiguration().orientation
+ : orientationFromBounds(bounds);
+ if (DEBUG) {
+ appendLog(bounds.isEmpty() ? "locked-orientation-from-display=" + orientation
+ : "locked-orientation-from-bounds=" + bounds);
+ }
}
- if (windowLayout.heightFraction > 0) {
- height = (int) (availableRect.height() * windowLayout.heightFraction);
+
+ if (orientation == SCREEN_ORIENTATION_UNSPECIFIED) {
+ orientation = bounds.isEmpty() ? SCREEN_ORIENTATION_PORTRAIT
+ : orientationFromBounds(bounds);
+ if (DEBUG) {
+ appendLog(bounds.isEmpty() ? "default-portrait"
+ : "orientation-from-bounds=" + bounds);
+ }
}
- return height;
- }
- private void positionBottomLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
- int height, Rect result) {
- mTmpProposal.set(availableRect.left, availableRect.bottom - height,
- availableRect.left + width, availableRect.bottom);
- position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
- result);
+ return orientation;
}
- private void positionBottomRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
- int height, Rect result) {
- mTmpProposal.set(availableRect.right - width, availableRect.bottom - height,
- availableRect.right, availableRect.bottom);
- position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
- result);
+ private void getDefaultFreeformSize(@NonNull ActivityDisplay display,
+ @NonNull ActivityInfo.WindowLayout layout, int orientation, @NonNull Rect bounds) {
+ // Default size, which is letterboxing/pillarboxing in display. That's to say the large
+ // dimension of default size is the small dimension of display size, and the small dimension
+ // of default size is calculated to keep the same aspect ratio as the display's.
+ Rect displayBounds = display.getBounds();
+ final int portraitHeight = Math.min(displayBounds.width(), displayBounds.height());
+ final int otherDimension = Math.max(displayBounds.width(), displayBounds.height());
+ final int portraitWidth = (portraitHeight * portraitHeight) / otherDimension;
+ final int defaultWidth = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? portraitHeight
+ : portraitWidth;
+ final int defaultHeight = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? portraitWidth
+ : portraitHeight;
+
+ // Get window size based on Nexus 5x screen, we assume that this is enough to show content
+ // of activities.
+ final float density = (float) display.getConfiguration().densityDpi / DENSITY_DEFAULT;
+ final int phonePortraitWidth = (int) (DEFAULT_PORTRAIT_PHONE_WIDTH_DP * density + 0.5f);
+ final int phonePortraitHeight = (int) (DEFAULT_PORTRAIT_PHONE_HEIGHT_DP * density + 0.5f);
+ final int phoneWidth = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? phonePortraitHeight
+ : phonePortraitWidth;
+ final int phoneHeight = (orientation == SCREEN_ORIENTATION_LANDSCAPE) ? phonePortraitWidth
+ : phonePortraitHeight;
+
+ // Minimum layout requirements.
+ final int layoutMinWidth = (layout == null) ? -1 : layout.minWidth;
+ final int layoutMinHeight = (layout == null) ? -1 : layout.minHeight;
+
+ // Final result.
+ final int width = Math.min(defaultWidth, Math.max(phoneWidth, layoutMinWidth));
+ final int height = Math.min(defaultHeight, Math.max(phoneHeight, layoutMinHeight));
+
+ bounds.set(0, 0, width, height);
}
- private void positionTopLeft(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
- int height, Rect result) {
- mTmpProposal.set(availableRect.left, availableRect.top,
- availableRect.left + width, availableRect.top + height);
- position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT,
- result);
+ /**
+ * Gets centered bounds of width x height. If inOutBounds is not empty, the result bounds
+ * centers at its center or display's center if inOutBounds is empty.
+ */
+ private void centerBounds(@NonNull ActivityDisplay display, int width, int height,
+ @NonNull Rect inOutBounds) {
+ if (inOutBounds.isEmpty()) {
+ display.getBounds(inOutBounds);
+ }
+ final int left = inOutBounds.centerX() - width / 2;
+ final int top = inOutBounds.centerY() - height / 2;
+ inOutBounds.set(left, top, left + width, top + height);
}
- private void positionTopRight(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
- int height, Rect result) {
- mTmpProposal.set(availableRect.right - width, availableRect.top,
- availableRect.right, availableRect.top + height);
- position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT,
- result);
- }
+ private void adjustBoundsToFitInDisplay(@NonNull ActivityDisplay display,
+ @NonNull Rect inOutBounds) {
+ final Rect displayBounds = display.getBounds();
+
+ if (displayBounds.width() < inOutBounds.width()
+ || displayBounds.height() < inOutBounds.height()) {
+ // There is no way for us to fit the bounds in the display without changing width
+ // or height. Don't even try it.
+ return;
+ }
+
+ final int dx;
+ if (inOutBounds.right > displayBounds.right) {
+ // Right edge is out of display.
+ dx = displayBounds.right - inOutBounds.right;
+ } else if (inOutBounds.left < displayBounds.left) {
+ // Left edge is out of display.
+ dx = displayBounds.left - inOutBounds.left;
+ } else {
+ // Vertical edges are all in display.
+ dx = 0;
+ }
- private void positionCenter(ArrayList<TaskRecord> tasks, Rect availableRect, int width,
- int height, Rect result) {
- final int defaultFreeformLeft = getFreeformStartLeft(availableRect);
- final int defaultFreeformTop = getFreeformStartTop(availableRect);
- mTmpProposal.set(defaultFreeformLeft, defaultFreeformTop,
- defaultFreeformLeft + width, defaultFreeformTop + height);
- position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN,
- result);
+ final int dy;
+ if (inOutBounds.top < displayBounds.top) {
+ // Top edge is out of display.
+ dy = displayBounds.top - inOutBounds.top;
+ } else if (inOutBounds.bottom > displayBounds.bottom) {
+ // Bottom edge is out of display.
+ dy = displayBounds.bottom - inOutBounds.bottom;
+ } else {
+ // Horizontal edges are all in display.
+ dy = 0;
+ }
+ inOutBounds.offset(dx, dy);
}
- private void position(ArrayList<TaskRecord> tasks, Rect availableRect,
- Rect proposal, boolean allowRestart, int shiftPolicy, Rect result) {
- mTmpOriginal.set(proposal);
- boolean restarted = false;
- while (boundsConflict(proposal, tasks)) {
- // Unfortunately there is already a task at that spot, so we need to look for some
- // other place.
- shiftStartingPoint(proposal, availableRect, shiftPolicy);
- if (shiftedTooFar(proposal, availableRect, shiftPolicy)) {
- // We don't want the task to go outside of the stack, because it won't look
- // nice. Depending on the starting point we either restart, or immediately give up.
- if (!allowRestart) {
- proposal.set(mTmpOriginal);
- break;
- }
- // We must have started not from the top. Let's restart from there because there
- // might be some space there.
- proposal.set(availableRect.left, availableRect.top,
- availableRect.left + proposal.width(),
- availableRect.top + proposal.height());
- restarted = true;
+ /**
+ * Adjusts input bounds to avoid conflict with existing tasks in the display.
+ *
+ * If the input bounds conflict with existing tasks, this method scans the bounds in a series of
+ * directions to find a location where the we can put the bounds in display without conflict
+ * with any other tasks.
+ *
+ * It doesn't try to adjust bounds that's not fully in the given display.
+ *
+ * @param display the display which tasks are to check
+ * @param inOutBounds the bounds used to input initial bounds and output result bounds
+ */
+ private void adjustBoundsToAvoidConflict(@NonNull ActivityDisplay display,
+ @NonNull Rect inOutBounds) {
+ final Rect displayBounds = display.getBounds();
+ if (!displayBounds.contains(inOutBounds)) {
+ // The initial bounds are already out of display. The scanning algorithm below doesn't
+ // work so well with them.
+ return;
+ }
+
+ final List<TaskRecord> tasksToCheck = new ArrayList<>();
+ for (int i = 0; i < display.getChildCount(); ++i) {
+ ActivityStack<?> stack = display.getChildAt(i);
+ if (!stack.inFreeformWindowingMode()) {
+ continue;
}
- if (restarted && (proposal.left > getFreeformStartLeft(availableRect)
- || proposal.top > getFreeformStartTop(availableRect))) {
- // If we restarted and crossed the initial position, let's not struggle anymore.
- // The user already must have ton of tasks visible, we can just smack the new
- // one in the center.
- proposal.set(mTmpOriginal);
+
+ for (int j = 0; j < stack.getChildCount(); ++j) {
+ tasksToCheck.add(stack.getChildAt(j));
+ }
+ }
+
+ if (!boundsConflict(tasksToCheck, inOutBounds)) {
+ // Current proposal doesn't conflict with any task. Early return to avoid unnecessary
+ // calculation.
+ return;
+ }
+
+ calculateCandidateShiftDirections(displayBounds, inOutBounds);
+ for (int direction : mTmpDirections) {
+ if (direction == Gravity.NO_GRAVITY) {
+ // We exhausted candidate directions, give up.
break;
}
+
+ mTmpBounds.set(inOutBounds);
+ while (boundsConflict(tasksToCheck, mTmpBounds) && displayBounds.contains(mTmpBounds)) {
+ shiftBounds(direction, displayBounds, mTmpBounds);
+ }
+
+ if (!boundsConflict(tasksToCheck, mTmpBounds) && displayBounds.contains(mTmpBounds)) {
+ // Found a candidate. Just use this.
+ inOutBounds.set(mTmpBounds);
+ if (DEBUG) appendLog("avoid-bounds-conflict=" + inOutBounds);
+ return;
+ }
+
+ // Didn't find a conflict free bounds here. Try the next candidate direction.
}
- result.set(proposal);
+
+ // We failed to find a conflict free location. Just keep the original result.
}
- private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) {
- switch (shiftPolicy) {
- case SHIFT_POLICY_HORIZONTAL_LEFT:
- return start.left < availableRect.left;
- case SHIFT_POLICY_HORIZONTAL_RIGHT:
- return start.right > availableRect.right;
- default: // SHIFT_POLICY_DIAGONAL_DOWN
- return start.right > availableRect.right || start.bottom > availableRect.bottom;
+ /**
+ * Determines scanning directions and their priorities to avoid bounds conflict.
+ *
+ * @param availableBounds bounds that the result must be in
+ * @param initialBounds initial bounds when start scanning
+ */
+ private void calculateCandidateShiftDirections(@NonNull Rect availableBounds,
+ @NonNull Rect initialBounds) {
+ for (int i = 0; i < mTmpDirections.length; ++i) {
+ mTmpDirections[i] = Gravity.NO_GRAVITY;
+ }
+
+ final int oneThirdWidth = (2 * availableBounds.left + availableBounds.right) / 3;
+ final int twoThirdWidth = (availableBounds.left + 2 * availableBounds.right) / 3;
+ final int centerX = initialBounds.centerX();
+ if (centerX < oneThirdWidth) {
+ // Too close to left, just scan to the right.
+ mTmpDirections[0] = Gravity.RIGHT;
+ return;
+ } else if (centerX > twoThirdWidth) {
+ // Too close to right, just scan to the left.
+ mTmpDirections[0] = Gravity.LEFT;
+ return;
}
+
+ final int oneThirdHeight = (2 * availableBounds.top + availableBounds.bottom) / 3;
+ final int twoThirdHeight = (availableBounds.top + 2 * availableBounds.bottom) / 3;
+ final int centerY = initialBounds.centerY();
+ if (centerY < oneThirdHeight || centerY > twoThirdHeight) {
+ // Too close to top or bottom boundary and we're in the middle horizontally, scan
+ // horizontally in both directions.
+ mTmpDirections[0] = Gravity.RIGHT;
+ mTmpDirections[1] = Gravity.LEFT;
+ return;
+ }
+
+ // We're in the center region both horizontally and vertically. Scan in both directions of
+ // primary diagonal.
+ mTmpDirections[0] = Gravity.BOTTOM | Gravity.RIGHT;
+ mTmpDirections[1] = Gravity.TOP | Gravity.LEFT;
}
- private void shiftStartingPoint(Rect posposal, Rect availableRect, int shiftPolicy) {
- final int defaultFreeformStepHorizontal = getHorizontalStep(availableRect);
- final int defaultFreeformStepVertical = getVerticalStep(availableRect);
+ private boolean boundsConflict(@NonNull List<TaskRecord> tasks, @NonNull Rect bounds) {
+ for (TaskRecord task : tasks) {
+ final Rect taskBounds = task.getBounds();
+ final boolean leftClose = Math.abs(taskBounds.left - bounds.left)
+ < BOUNDS_CONFLICT_THRESHOLD;
+ final boolean topClose = Math.abs(taskBounds.top - bounds.top)
+ < BOUNDS_CONFLICT_THRESHOLD;
+ final boolean rightClose = Math.abs(taskBounds.right - bounds.right)
+ < BOUNDS_CONFLICT_THRESHOLD;
+ final boolean bottomClose = Math.abs(taskBounds.bottom - bounds.bottom)
+ < BOUNDS_CONFLICT_THRESHOLD;
+
+ if ((leftClose && topClose) || (leftClose && bottomClose) || (rightClose && topClose)
+ || (rightClose && bottomClose)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
- switch (shiftPolicy) {
- case SHIFT_POLICY_HORIZONTAL_LEFT:
- posposal.offset(-defaultFreeformStepHorizontal, 0);
+ private void shiftBounds(int direction, @NonNull Rect availableRect,
+ @NonNull Rect inOutBounds) {
+ final int horizontalOffset;
+ switch (direction & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.LEFT:
+ horizontalOffset = -Math.max(MINIMAL_STEP,
+ availableRect.width() / STEP_DENOMINATOR);
break;
- case SHIFT_POLICY_HORIZONTAL_RIGHT:
- posposal.offset(defaultFreeformStepHorizontal, 0);
+ case Gravity.RIGHT:
+ horizontalOffset = Math.max(MINIMAL_STEP, availableRect.width() / STEP_DENOMINATOR);
break;
- default: // SHIFT_POLICY_DIAGONAL_DOWN:
- posposal.offset(defaultFreeformStepHorizontal, defaultFreeformStepVertical);
+ default:
+ horizontalOffset = 0;
+ }
+
+ final int verticalOffset;
+ switch (direction & Gravity.VERTICAL_GRAVITY_MASK) {
+ case Gravity.TOP:
+ verticalOffset = -Math.max(MINIMAL_STEP, availableRect.height() / STEP_DENOMINATOR);
break;
+ case Gravity.BOTTOM:
+ verticalOffset = Math.max(MINIMAL_STEP, availableRect.height() / STEP_DENOMINATOR);
+ break;
+ default:
+ verticalOffset = 0;
}
+
+ inOutBounds.offset(horizontalOffset, verticalOffset);
}
- private static boolean boundsConflict(Rect proposal, ArrayList<TaskRecord> tasks) {
- for (int i = tasks.size() - 1; i >= 0; i--) {
- final TaskRecord task = tasks.get(i);
- if (!task.mActivities.isEmpty() && !task.matchParentBounds()) {
- final Rect bounds = task.getOverrideBounds();
- if (closeLeftTopCorner(proposal, bounds) || closeRightTopCorner(proposal, bounds)
- || closeLeftBottomCorner(proposal, bounds)
- || closeRightBottomCorner(proposal, bounds)) {
- return true;
- }
- }
+ private void initLogBuilder(TaskRecord task, ActivityRecord activity) {
+ if (DEBUG) {
+ mLogBuilder = new StringBuilder("TaskLaunchParamsModifier:task=" + task
+ + " activity=" + activity);
}
- return false;
}
- private static final boolean closeLeftTopCorner(Rect first, Rect second) {
- return Math.abs(first.left - second.left) < BOUNDS_CONFLICT_MIN_DISTANCE
- && Math.abs(first.top - second.top) < BOUNDS_CONFLICT_MIN_DISTANCE;
+ private void appendLog(String log) {
+ if (DEBUG) mLogBuilder.append(" ").append(log);
}
- private static final boolean closeRightTopCorner(Rect first, Rect second) {
- return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE
- && Math.abs(first.top - second.top) < BOUNDS_CONFLICT_MIN_DISTANCE;
+ private void outputLog() {
+ if (DEBUG) Slog.d(TAG, mLogBuilder.toString());
}
- private static final boolean closeLeftBottomCorner(Rect first, Rect second) {
- return Math.abs(first.left - second.left) < BOUNDS_CONFLICT_MIN_DISTANCE
- && Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE;
+ private static int orientationFromBounds(Rect bounds) {
+ return bounds.width() > bounds.height() ? SCREEN_ORIENTATION_LANDSCAPE
+ : SCREEN_ORIENTATION_PORTRAIT;
}
- private static final boolean closeRightBottomCorner(Rect first, Rect second) {
- return Math.abs(first.right - second.right) < BOUNDS_CONFLICT_MIN_DISTANCE
- && Math.abs(first.bottom - second.bottom) < BOUNDS_CONFLICT_MIN_DISTANCE;
+ private static boolean sizeMatches(Rect left, Rect right) {
+ return (Math.abs(right.width() - left.width()) < EPSILON)
+ && (Math.abs(right.height() - left.height()) < EPSILON);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java
deleted file mode 100644
index 9de64f2cf211..000000000000
--- a/services/tests/servicestests/src/com/android/server/am/ActivityLaunchParamsModifierTests.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2017 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.server.am;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-
-import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_DONE;
-import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.app.ActivityOptions;
-import android.content.pm.ActivityInfo;
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-
-import androidx.test.filters.MediumTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.am.LaunchParamsController.LaunchParams;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Tests for exercising resizing bounds due to activity options.
- *
- * Build/Install/Run:
- * atest FrameworksServicesTests:ActivityLaunchParamsModifierTests
- */
-@MediumTest
-@Presubmit
-@RunWith(AndroidJUnit4.class)
-public class ActivityLaunchParamsModifierTests extends ActivityTestsBase {
- private ActivityLaunchParamsModifier mModifier;
- private ActivityTaskManagerService mService;
- private ActivityStack mStack;
- private TaskRecord mTask;
- private ActivityRecord mActivity;
-
- private LaunchParams mCurrent;
- private LaunchParams mResult;
-
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mService = createActivityTaskManagerService();
- mModifier = new ActivityLaunchParamsModifier(mService.mStackSupervisor);
- mCurrent = new LaunchParams();
- mResult = new LaunchParams();
-
-
- mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
- WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
- mActivity = new ActivityBuilder(mService).setTask(mTask).build();
- }
-
-
- @Test
- public void testSkippedInvocations() throws Exception {
- // No specified activity should be ignored
- assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
- null /*activity*/, null /*source*/, null /*options*/, mCurrent, mResult));
-
- // No specified activity options should be ignored
- assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
- mActivity, null /*source*/, null /*options*/, mCurrent, mResult));
-
- // launch bounds specified should be ignored.
- final ActivityOptions options = ActivityOptions.makeBasic();
- assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
- mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
-
- // Non-resizeable records should be ignored
- mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
- assertFalse(mActivity.isResizeable());
- assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
- mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
-
- // make record resizeable
- mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
- assertTrue(mActivity.isResizeable());
-
- assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
- mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
-
- // Does not support freeform
- mService.mSupportsFreeformWindowManagement = false;
- assertFalse(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
- assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
- mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
-
- mService.mSupportsFreeformWindowManagement = true;
- options.setLaunchBounds(new Rect());
- assertTrue(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options));
-
- // Invalid bounds
- assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
- mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
- options.setLaunchBounds(new Rect(0, 0, -1, -1));
- assertEquals(RESULT_SKIP, mModifier.onCalculate(null /*task*/, null /*layout*/,
- mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
-
- // Valid bounds should cause the positioner to be applied.
- options.setLaunchBounds(new Rect(0, 0, 100, 100));
- assertEquals(RESULT_DONE, mModifier.onCalculate(null /*task*/, null /*layout*/,
- mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
- }
-
- @Test
- public void testBoundsExtraction() throws Exception {
- // Make activity resizeable and enable freeform mode.
- mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
- mService.mSupportsFreeformWindowManagement = true;
-
- ActivityOptions options = ActivityOptions.makeBasic();
- final Rect proposedBounds = new Rect(20, 30, 45, 40);
- options.setLaunchBounds(proposedBounds);
-
- assertEquals(RESULT_DONE, mModifier.onCalculate(null /*task*/, null /*layout*/,
- mActivity, null /*source*/, options /*options*/, mCurrent, mResult));
- assertEquals(mResult.mBounds, proposedBounds);
- }
-}
diff --git a/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java b/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java
index f5b8f78cfd53..0d1302f78316 100644
--- a/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/TaskLaunchParamsModifierTests.java
@@ -11,239 +11,1098 @@
* 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
+ * limitations under the License.
*/
package com.android.server.am;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.util.DisplayMetrics.DENSITY_DEFAULT;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.server.am.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
-import android.content.pm.ActivityInfo.WindowLayout;
+import android.app.ActivityOptions;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
import android.graphics.Rect;
+import android.os.Build;
import android.platform.test.annotations.Presubmit;
import android.view.Gravity;
-import androidx.test.filters.MediumTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.am.LaunchParamsController.LaunchParams;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Locale;
+
/**
- * Tests for exercising resizing task bounds.
+ * Tests for default task bounds.
*
* Build/Install/Run:
* atest FrameworksServicesTests:TaskLaunchParamsModifierTests
*/
-@MediumTest
+@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
+@FlakyTest(detail = "Confirm stable in post-submit before removing")
public class TaskLaunchParamsModifierTests extends ActivityTestsBase {
- private final static int STACK_WIDTH = 100;
- private final static int STACK_HEIGHT = 200;
-
- private final static Rect STACK_BOUNDS = new Rect(0, 0, STACK_WIDTH, STACK_HEIGHT);
- private ActivityTaskManagerService mService;
- private ActivityStack mStack;
- private TaskRecord mTask;
+ private ActivityRecord mActivity;
- private TaskLaunchParamsModifier mPositioner;
+ private TaskLaunchParamsModifier mTarget;
- private LaunchParamsController.LaunchParams mCurrent;
- private LaunchParamsController.LaunchParams mResult;
+ private LaunchParams mCurrent;
+ private LaunchParams mResult;
@Before
@Override
public void setUp() throws Exception {
super.setUp();
- mService = createActivityTaskManagerService();
- mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
- WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
- mStack.requestResize(STACK_BOUNDS);
+ setupActivityTaskManagerService();
+ mService.mSupportsFreeformWindowManagement = true;
+ when(mSupervisor.canUseActivityOptionsLaunchBounds(any())).thenCallRealMethod();
+
+ mActivity = new ActivityBuilder(mService).build();
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.N_MR1;
+ mActivity.info.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
+
+ mTarget = new TaskLaunchParamsModifier(mSupervisor);
+
+ mCurrent = new LaunchParams();
+ mCurrent.reset();
+ mResult = new LaunchParams();
+ mResult.reset();
+ }
- // We must create the task after resizing to make sure it does not inherit the stack
- // dimensions on resize.
- mTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
+ // =============================
+ // Display ID Related Tests
+ // =============================
+ @Test
+ public void testDefaultToPrimaryDisplay() {
+ createNewActivityDisplay(WINDOWING_MODE_FREEFORM);
- mPositioner = new TaskLaunchParamsModifier();
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
- mResult = new LaunchParamsController.LaunchParams();
- mCurrent = new LaunchParamsController.LaunchParams();
+ assertEquals(DEFAULT_DISPLAY, mResult.mPreferredDisplayId);
}
- /**
- * Ensures that the setup bounds are set as expected with the stack bounds set and the task
- * bounds still {@code null}.
- * @throws Exception
- */
@Test
- public void testInitialBounds() throws Exception {
- assertEquals(mStack.getOverrideBounds(), STACK_BOUNDS);
- assertEquals(mTask.getOverrideBounds(), new Rect());
+ public void testUsesPreviousDisplayIdIfSet() {
+ createNewActivityDisplay(WINDOWING_MODE_FREEFORM);
+ final TestActivityDisplay display = createNewActivityDisplay(WINDOWING_MODE_FULLSCREEN);
+
+ mCurrent.mPreferredDisplayId = display.mDisplayId;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(display.mDisplayId, mResult.mPreferredDisplayId);
}
- /**
- * Ensures that a task positioned with no {@link WindowLayout} receives the default launch
- * position.
- * @throws Exception
- */
@Test
- public void testLaunchNoWindowLayout() throws Exception {
- assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask, null /*layout*/,
- null /*record*/, null /*source*/, null /*options*/, mCurrent, mResult));
- assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult.mBounds);
+ public void testUsesSourcesDisplayIdIfSet() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+ final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FULLSCREEN);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ ActivityRecord source = createSourceActivity(fullscreenDisplay);
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, source, /* options */ null, mCurrent, mResult));
+
+ assertEquals(fullscreenDisplay.mDisplayId, mResult.mPreferredDisplayId);
}
- /**
- * Ensures that a task positioned with an empty {@link WindowLayout} receives the default launch
- * position.
- * @throws Exception
- */
@Test
- public void testlaunchEmptyWindowLayout() throws Exception {
- assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask,
- new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/,
- null /*source*/, null /*options*/, mCurrent, mResult));
- assertEquals(mResult.mBounds, getDefaultBounds(Gravity.NO_GRAVITY));
+ public void testUsesOptionsDisplayIdIfSet() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+ final TestActivityDisplay fullscreenDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FULLSCREEN);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+ ActivityRecord source = createSourceActivity(freeformDisplay);
+
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(fullscreenDisplay.mDisplayId);
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, source, options, mCurrent, mResult));
+
+ assertEquals(fullscreenDisplay.mDisplayId, mResult.mPreferredDisplayId);
}
- /**
- * Ensures that a task positioned with a {@link WindowLayout} gravity specified is positioned
- * according to specification.
- * @throws Exception
- */
+ // =====================================
+ // Launch Windowing Mode Related Tests
+ // =====================================
@Test
- public void testlaunchWindowLayoutGravity() throws Exception {
- // Unspecified gravity should be ignored
- testGravity(Gravity.NO_GRAVITY);
+ public void testBoundsInOptionsInfersFreeformOnFreeformDisplay() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchBounds(new Rect(0, 0, 100, 100));
- // Unsupported gravity should be ignored
- testGravity(Gravity.LEFT);
- testGravity(Gravity.RIGHT);
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
- // Test defaults for vertical gravities
- testGravity(Gravity.TOP);
- testGravity(Gravity.BOTTOM);
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
- // Test corners
- testGravity(Gravity.TOP | Gravity.LEFT);
- testGravity(Gravity.TOP | Gravity.RIGHT);
- testGravity(Gravity.BOTTOM | Gravity.LEFT);
- testGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
}
- private void testGravity(int gravity) {
- try {
- assertEquals(RESULT_CONTINUE, mPositioner.onCalculate(mTask,
- new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/,
- null /*source*/, null /*options*/, mCurrent, mResult));
- assertEquals(mResult.mBounds, getDefaultBounds(gravity));
- } finally {
- mCurrent.reset();
- mResult.reset();
- }
+ @Test
+ public void testBoundsInOptionsInfersFreeformWithResizeableActivity() {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchBounds(new Rect(0, 0, 100, 100));
+
+ mCurrent.mPreferredDisplayId = DEFAULT_DISPLAY;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void testKeepsPictureInPictureLaunchModeInOptions() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_PINNED, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testKeepsPictureInPictureLaunchModeWithBoundsInOptions() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
+ options.setLaunchBounds(new Rect(0, 0, 100, 100));
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_PINNED, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testKeepsFullscreenLaunchModeInOptionsOnNonFreeformDisplay() {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+ mCurrent.mPreferredDisplayId = DEFAULT_DISPLAY;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void testNonEmptyLayoutInfersFreeformOnFreeformDisplay() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testNonEmptyLayoutInfersFreeformWithEmptySize() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setGravity(Gravity.LEFT).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testNonEmptyLayoutInfersFreeformWithResizeableActivity() {
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).build();
+
+ mCurrent.mPreferredDisplayId = DEFAULT_DISPLAY;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void testRespectsFullyResolvedCurrentParam_Fullscreen() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+ mCurrent.mWindowingMode = WINDOWING_MODE_FULLSCREEN;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testRespectsModeFromFullyResolvedCurrentParam_NonEmptyBounds() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+ mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+ mCurrent.mBounds.set(0, 0, 200, 100);
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testForceMaximizesPreDApp() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+ options.setLaunchBounds(new Rect(0, 0, 200, 100));
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+ mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+ mCurrent.mBounds.set(0, 0, 200, 100);
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.CUPCAKE;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testForceMaximizesAppWithoutMultipleDensitySupport() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+ options.setLaunchBounds(new Rect(0, 0, 200, 100));
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+ mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+ mCurrent.mBounds.set(0, 0, 200, 100);
+
+ mActivity.appInfo.flags = 0;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testForceMaximizesUnresizeableApp() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+ options.setLaunchBounds(new Rect(0, 0, 200, 100));
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+ mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+ mCurrent.mBounds.set(0, 0, 200, 100);
+
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testSkipsForceMaximizingAppsOnNonFreeformDisplay() {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_FREEFORM);
+ options.setLaunchBounds(new Rect(0, 0, 200, 100));
+
+ mCurrent.mPreferredDisplayId = DEFAULT_DISPLAY;
+ mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+ mCurrent.mBounds.set(0, 0, 200, 100);
+
+ mActivity.info.resizeMode = RESIZE_MODE_UNRESIZEABLE;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void testUsesFullscreenOnNonFreeformDisplay() {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(DEFAULT_DISPLAY);
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ }
+
+ @Test
+ public void testUsesFreeformByDefaultForPostNApp() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testUsesFreeformByDefaultForPreNResizeableAppWithoutOrientationRequest() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode,
+ WINDOWING_MODE_FREEFORM);
+ }
+
+ @Test
+ public void testSkipsFreeformForPreNResizeableAppOnNonFullscreenDisplay() {
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(DEFAULT_DISPLAY);
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquivalentWindowingMode(WINDOWING_MODE_FULLSCREEN, mResult.mWindowingMode,
+ WINDOWING_MODE_FULLSCREEN);
+ }
+
+ // ================================
+ // Launching Bounds Related Tests
+ // ===============================
+ @Test
+ public void testKeepsBoundsWithPictureInPictureLaunchModeInOptions() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WINDOWING_MODE_PINNED);
+
+ final Rect expected = new Rect(0, 0, 100, 100);
+ options.setLaunchBounds(expected);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquals(expected, mResult.mBounds);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_LeftGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setGravity(Gravity.LEFT).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(0, mResult.mBounds.left);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setGravity(Gravity.TOP).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(0, mResult.mBounds.top);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_TopLeftGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setGravity(Gravity.TOP | Gravity.LEFT).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(0, mResult.mBounds.left);
+ assertEquals(0, mResult.mBounds.top);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_RightGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setGravity(Gravity.RIGHT).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(1920, mResult.mBounds.right);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setGravity(Gravity.BOTTOM).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(1080, mResult.mBounds.bottom);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsRespectsGravityWithEmptySize_BottomRightGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setGravity(Gravity.BOTTOM | Gravity.RIGHT).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(1920, mResult.mBounds.right);
+ assertEquals(1080, mResult.mBounds.bottom);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsOnFreeformDisplay_CenterToDisplay() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(900, 500, 1020, 580), mResult.mBounds);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsOnFreeformDisplay_LeftGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).setGravity(Gravity.LEFT).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(0, 500, 120, 580), mResult.mBounds);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsOnFreeformDisplay_TopGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).setGravity(Gravity.TOP).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(900, 0, 1020, 80), mResult.mBounds);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsOnFreeformDisplay_TopLeftGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).setGravity(Gravity.TOP | Gravity.LEFT).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(0, 0, 120, 80), mResult.mBounds);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsOnFreeformDisplay_RightGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).setGravity(Gravity.RIGHT).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(1800, 500, 1920, 580), mResult.mBounds);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsOnFreeformDisplay_BottomGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).setGravity(Gravity.BOTTOM).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(900, 1000, 1020, 1080), mResult.mBounds);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsOnFreeformDisplay_RightBottomGravity() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).setGravity(Gravity.BOTTOM | Gravity.RIGHT).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(1800, 1000, 1920, 1080), mResult.mBounds);
+ }
+
+ @Test
+ public void testNonEmptyLayoutFractionBoundsOnFreeformDisplay() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidthFraction(0.0625f).setHeightFraction(0.1f).build();
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(900, 486, 1020, 594), mResult.mBounds);
+ }
+
+ @Test
+ public void testNonEmptyLayoutBoundsWithResizeableActivity() {
+ final ActivityDisplay display = mSupervisor.getActivityDisplay(DEFAULT_DISPLAY);
+ display.setBounds(new Rect(0, 0, 1920, 1080));
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setWidth(120).setHeight(80).build();
+
+ mCurrent.mPreferredDisplayId = DEFAULT_DISPLAY;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(900, 500, 1020, 580), mResult.mBounds);
+ }
+
+ @Test
+ public void testRespectBoundsFromFullyResolvedCurrentParam_NonEmptyBounds() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ mCurrent.mPreferredDisplayId = freeformDisplay.mDisplayId;
+ mCurrent.mWindowingMode = WINDOWING_MODE_FREEFORM;
+ mCurrent.mBounds.set(0, 0, 200, 100);
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, /* options */ null, mCurrent, mResult));
+
+ assertEquals(new Rect(0, 0, 200, 100), mResult.mBounds);
+ }
+
+ @Test
+ public void testDefaultSizeSmallerThanBigScreen() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ final int resultWidth = mResult.mBounds.width();
+ final int displayWidth = freeformDisplay.getBounds().width();
+ assertTrue("Result width " + resultWidth + " is not smaller than " + displayWidth,
+ resultWidth < displayWidth);
+
+ final int resultHeight = mResult.mBounds.height();
+ final int displayHeight = freeformDisplay.getBounds().height();
+ assertTrue("Result width " + resultHeight + " is not smaller than "
+ + displayHeight, resultHeight < displayHeight);
+ }
+
+ @Test
+ public void testDefaultFreeformSizeCenteredToDisplay() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertEquals("Distance to left and right should be equal.",
+ mResult.mBounds.left - displayBounds.left,
+ displayBounds.right - mResult.mBounds.right);
+ assertEquals("Distance to top and bottom should be equal.",
+ mResult.mBounds.top - displayBounds.top,
+ displayBounds.bottom - mResult.mBounds.bottom);
+ }
+
+ @Test
+ public void testCascadesToSourceSizeForFreeform() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(0, 0, 412, 732);
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, source, options, mCurrent, mResult));
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("Left bounds should be larger than 0.", mResult.mBounds.left > 0);
+ assertTrue("Top bounds should be larger than 0.", mResult.mBounds.top > 0);
+ assertTrue("Bounds should be centered at somewhere in the left half, but it's "
+ + "centerX is " + mResult.mBounds.centerX(),
+ mResult.mBounds.centerX() < displayBounds.centerX());
+ assertTrue("Bounds should be centered at somewhere in the top half, but it's "
+ + "centerY is " + mResult.mBounds.centerY(),
+ mResult.mBounds.centerY() < displayBounds.centerY());
+ }
+
+ @Test
+ public void testAdjustBoundsToFitDisplay_TopLeftOutOfDisplay() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(0, 0, 200, 400);
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, source, options, mCurrent, mResult));
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("display bounds doesn't contain result. display bounds: "
+ + displayBounds + " result: " + mResult.mBounds,
+ displayBounds.contains(mResult.mBounds));
+ }
+
+ @Test
+ public void testAdjustBoundsToFitDisplay_BottomRightOutOfDisplay() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(1720, 680, 1920, 1080);
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, source, options, mCurrent, mResult));
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("display bounds doesn't contain result. display bounds: "
+ + displayBounds + " result: " + mResult.mBounds,
+ displayBounds.contains(mResult.mBounds));
+ }
+
+ @Test
+ public void testAdjustBoundsToFitDisplay_LargerThanDisplay() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ Configuration overrideConfig = new Configuration();
+ overrideConfig.setTo(mSupervisor.getOverrideConfiguration());
+ overrideConfig.setLayoutDirection(new Locale("ar"));
+ mSupervisor.onConfigurationChanged(overrideConfig);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityRecord source = createSourceActivity(freeformDisplay);
+ source.setBounds(1720, 680, 1920, 1080);
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, source, options, mCurrent, mResult));
+
+ final Rect displayBounds = freeformDisplay.getBounds();
+ assertTrue("display bounds doesn't contain result. display bounds: "
+ + displayBounds + " result: " + mResult.mBounds,
+ displayBounds.contains(mResult.mBounds));
}
- /**
- * Ensures that a task which causes a conflict with another task when positioned is adjusted as
- * expected.
- * @throws Exception
- */
- @Test
- public void testLaunchWindowCenterConflict() throws Exception {
- testConflict(Gravity.NO_GRAVITY);
- testConflict(Gravity.TOP);
- testConflict(Gravity.BOTTOM);
- testConflict(Gravity.TOP | Gravity.LEFT);
- testConflict(Gravity.TOP | Gravity.RIGHT);
- testConflict(Gravity.BOTTOM | Gravity.LEFT);
- testConflict(Gravity.BOTTOM | Gravity.RIGHT);
- }
-
- private void testConflict(int gravity) {
- final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0);
-
- // layout first task
- mService.mStackSupervisor.getLaunchParamsController().layoutTask(mTask, layout);
-
- // Second task will be laid out on top of the first so starting bounds is the same.
- final Rect expectedBounds = new Rect(mTask.getOverrideBounds());
-
- ActivityRecord activity = null;
- TaskRecord secondTask = null;
-
- // wrap with try/finally to ensure cleanup of activity/stack.
- try {
- // empty tasks are ignored in conflicts
- activity = new ActivityBuilder(mService).setTask(mTask).build();
-
- // Create secondary task
- secondTask = new TaskBuilder(mService.mStackSupervisor).setStack(mStack).build();
-
- // layout second task
- assertEquals(RESULT_CONTINUE,
- mPositioner.onCalculate(secondTask, layout, null /*activity*/,
- null /*source*/, null /*options*/, mCurrent, mResult));
-
- if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)
- || (gravity & (Gravity.BOTTOM | Gravity.RIGHT))
- == (Gravity.BOTTOM | Gravity.RIGHT)) {
- expectedBounds.offset(-TaskLaunchParamsModifier.getHorizontalStep(
- mStack.getOverrideBounds()), 0);
- } else if ((gravity & Gravity.TOP) == Gravity.TOP
- || (gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
- expectedBounds.offset(
- TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()), 0);
- } else {
- expectedBounds.offset(
- TaskLaunchParamsModifier.getHorizontalStep(mStack.getOverrideBounds()),
- TaskLaunchParamsModifier.getVerticalStep(mStack.getOverrideBounds()));
- }
-
- assertEquals(mResult.mBounds, expectedBounds);
- } finally {
- // Remove task and activity to prevent influencing future tests
- if (activity != null) {
- mTask.removeActivity(activity);
- }
-
- if (secondTask != null) {
- mStack.removeTask(secondTask, "cleanup", ActivityStack.REMOVE_TASK_MODE_DESTROYING);
- }
+ @Test
+ public void testRespectsLayoutMinDimensions() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+
+ final ActivityInfo.WindowLayout layout = new WindowLayoutBuilder()
+ .setMinWidth(500).setMinHeight(800).build();
+
+ mActivity.appInfo.targetSdkVersion = Build.VERSION_CODES.LOLLIPOP;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, layout, mActivity,
+ /* source */ null, options, mCurrent, mResult));
+
+ assertEquals(500, mResult.mBounds.width());
+ assertEquals(800, mResult.mBounds.height());
+ }
+
+ @Test
+ public void testRotatesInPlaceInitialBoundsMismatchOrientation() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+ options.setLaunchBounds(new Rect(100, 100, 500, 300));
+
+ mActivity.info.screenOrientation = SCREEN_ORIENTATION_PORTRAIT;
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquals(new Rect(200, 0, 400, 400), mResult.mBounds);
+ }
+
+ @Test
+ public void testShiftsToRightForCloseToLeftBoundsWhenConflict() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ addFreeformTaskTo(freeformDisplay, new Rect(50, 50, 100, 150));
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+ options.setLaunchBounds(new Rect(50, 50, 500, 300));
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquals(new Rect(170, 50, 620, 300), mResult.mBounds);
+ }
+
+ @Test
+ public void testShiftsToLeftForCloseToRightBoundsWhenConflict() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ addFreeformTaskTo(freeformDisplay, new Rect(1720, 50, 1830, 150));
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+ options.setLaunchBounds(new Rect(1720, 50, 1850, 300));
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquals(new Rect(1600, 50, 1730, 300), mResult.mBounds);
+ }
+
+ @Test
+ public void testShiftsToRightFirstForHorizontallyCenteredAndCloseToTopWhenConflict() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ addFreeformTaskTo(freeformDisplay, new Rect(0, 0, 100, 300));
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+ options.setLaunchBounds(new Rect(0, 0, 1800, 200));
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquals(new Rect(120, 0, 1920, 200), mResult.mBounds);
+ }
+
+ @Test
+ public void testShiftsToLeftNoSpaceOnRightForHorizontallyCenteredAndCloseToTopWhenConflict() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ addFreeformTaskTo(freeformDisplay, new Rect(120, 0, 240, 300));
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+ options.setLaunchBounds(new Rect(120, 0, 1860, 200));
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquals(new Rect(0, 0, 1740, 200), mResult.mBounds);
+ }
+
+ @Test
+ public void testShiftsToBottomRightFirstForCenteredBoundsWhenConflict() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ addFreeformTaskTo(freeformDisplay, new Rect(120, 0, 240, 100));
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+ options.setLaunchBounds(new Rect(120, 0, 1800, 1013));
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquals(new Rect(240, 67, 1920, 1080), mResult.mBounds);
+ }
+
+ @Test
+ public void testShiftsToTopLeftIfNoSpaceOnBottomRightForCenteredBoundsWhenConflict() {
+ final TestActivityDisplay freeformDisplay = createNewActivityDisplay(
+ WINDOWING_MODE_FREEFORM);
+
+ addFreeformTaskTo(freeformDisplay, new Rect(120, 67, 240, 100));
+
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(freeformDisplay.mDisplayId);
+ options.setLaunchBounds(new Rect(120, 67, 1800, 1020));
+
+ assertEquals(RESULT_CONTINUE, mTarget.onCalculate(/* task */ null, /* layout */ null,
+ mActivity, /* source */ null, options, mCurrent, mResult));
+
+ assertEquals(new Rect(0, 0, 1680,
+ 953), mResult.mBounds);
+ }
+
+ private TestActivityDisplay createNewActivityDisplay(int windowingMode) {
+ final TestActivityDisplay display = addNewActivityDisplayAt(ActivityDisplay.POSITION_TOP);
+ display.setWindowingMode(windowingMode);
+ display.setBounds(/* left */ 0, /* top */ 0, /* right */ 1920, /* bottom */ 1080);
+ display.getConfiguration().densityDpi = DENSITY_DEFAULT;
+ return display;
+ }
+
+ private ActivityRecord createSourceActivity(TestActivityDisplay display) {
+ final TestActivityStack stack = display.createStack(display.getWindowingMode(),
+ ACTIVITY_TYPE_STANDARD, true);
+ return new ActivityBuilder(mService).setStack(stack).setCreateTask(true).build();
+ }
+
+ private void addFreeformTaskTo(TestActivityDisplay display, Rect bounds) {
+ final TestActivityStack stack = display.createStack(display.getWindowingMode(),
+ ACTIVITY_TYPE_STANDARD, true);
+ stack.setWindowingMode(WINDOWING_MODE_FREEFORM);
+ final TaskRecord task = new TaskBuilder(mSupervisor).setStack(stack).build();
+ task.setBounds(bounds);
+ }
+
+ private void assertEquivalentWindowingMode(int expected, int actual, int parentWindowingMode) {
+ if (expected != parentWindowingMode) {
+ assertEquals(expected, actual);
+ } else {
+ assertEquals(WINDOWING_MODE_UNDEFINED, actual);
}
}
- private Rect getDefaultBounds(int gravity) {
- final Rect bounds = new Rect();
- bounds.set(mStack.getOverrideBounds());
+ private static class WindowLayoutBuilder {
+ private int mWidth = -1;
+ private int mHeight = -1;
+ private float mWidthFraction = -1f;
+ private float mHeightFraction = -1f;
+ private int mGravity = Gravity.NO_GRAVITY;
+ private int mMinWidth = -1;
+ private int mMinHeight = -1;
- final int verticalInset =
- TaskLaunchParamsModifier.getFreeformStartTop(mStack.getOverrideBounds());
- final int horizontalInset =
- TaskLaunchParamsModifier.getFreeformStartLeft(mStack.getOverrideBounds());
+ private WindowLayoutBuilder setWidth(int width) {
+ mWidth = width;
+ return this;
+ }
+
+ private WindowLayoutBuilder setHeight(int height) {
+ mHeight = height;
+ return this;
+ }
- bounds.inset(horizontalInset, verticalInset);
+ private WindowLayoutBuilder setWidthFraction(float widthFraction) {
+ mWidthFraction = widthFraction;
+ return this;
+ }
+
+ private WindowLayoutBuilder setHeightFraction(float heightFraction) {
+ mHeightFraction = heightFraction;
+ return this;
+ }
- if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)) {
- bounds.offsetTo(horizontalInset * 2, 0);
- } else if ((gravity & Gravity.TOP) == Gravity.TOP) {
- bounds.offsetTo(0, 0);
- } else if ((gravity & (Gravity.BOTTOM | Gravity.RIGHT))
- == (Gravity.BOTTOM | Gravity.RIGHT)) {
- bounds.offsetTo(horizontalInset * 2, verticalInset * 2);
- } else if ((gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
- bounds.offsetTo(0, verticalInset * 2);
+ private WindowLayoutBuilder setGravity(int gravity) {
+ mGravity = gravity;
+ return this;
}
- return bounds;
+ private WindowLayoutBuilder setMinWidth(int minWidth) {
+ mMinWidth = minWidth;
+ return this;
+ }
+
+ private WindowLayoutBuilder setMinHeight(int minHeight) {
+ mMinHeight = minHeight;
+ return this;
+ }
+
+ private ActivityInfo.WindowLayout build() {
+ return new ActivityInfo.WindowLayout(mWidth, mWidthFraction, mHeight, mHeightFraction,
+ mGravity, mMinWidth, mMinHeight);
+ }
}
}