diff options
| -rw-r--r-- | services/core/java/com/android/server/am/ActivityStack.java | 16 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/ActivityStackSupervisor.java | 9 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/ActivityStarter.java | 25 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/LaunchingActivityPositioner.java | 62 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/LaunchingBoundsController.java | 160 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/LaunchingTaskPositioner.java | 100 | ||||
| -rw-r--r-- | services/core/java/com/android/server/am/TaskRecord.java | 4 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java | 146 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java | 173 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java (renamed from services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java) | 62 |
10 files changed, 654 insertions, 103 deletions
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 941c371b717d..038549775a7e 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -107,7 +107,6 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.res.Configuration; -import android.graphics.Point; import android.graphics.Rect; import android.net.Uri; import android.os.Binder; @@ -346,7 +345,6 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai private final SparseArray<Rect> mTmpBounds = new SparseArray<>(); private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>(); private final Rect mTmpRect2 = new Rect(); - private final Point mTmpSize = new Point(); /** Run all ActivityStacks through this */ protected final ActivityStackSupervisor mStackSupervisor; @@ -5123,24 +5121,14 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai addTask(task, toTop, "createTaskRecord"); final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY); - if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable() - && !isLockscreenShown) { + if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout) + && mBounds != null && task.isResizeable() && !isLockscreenShown) { task.updateOverrideConfiguration(mBounds); } task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0); return task; } - boolean layoutTaskInStack(TaskRecord task, ActivityInfo.WindowLayout windowLayout) { - if (!task.inFreeformWindowingMode()) { - return false; - } - mStackSupervisor.getLaunchingTaskPositioner() - .updateDefaultBounds(task, mTaskHistory, windowLayout); - - return true; - } - ArrayList<TaskRecord> getAllTasks() { return new ArrayList<>(mTaskHistory); } diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index c15b5e2e9bbd..c5065f1084ce 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -295,7 +295,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D WindowManagerService mWindowManager; DisplayManager mDisplayManager; - LaunchingTaskPositioner mTaskPositioner = new LaunchingTaskPositioner(); + private final LaunchingBoundsController mLaunchingBoundsController; /** Counter for next free stack ID to use for dynamic activity stacks. */ private int mNextFreeStackId = 0; @@ -575,6 +575,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D mHandler = new ActivityStackSupervisorHandler(looper); mActivityMetricsLogger = new ActivityMetricsLogger(this, mService.mContext); mKeyguardController = new KeyguardController(service, this); + + mLaunchingBoundsController = new LaunchingBoundsController(); + mLaunchingBoundsController.registerDefaultPositioners(this); } void setRecentTasks(RecentTasks recentTasks) { @@ -2161,8 +2164,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D || mService.mSupportsFreeformWindowManagement; } - LaunchingTaskPositioner getLaunchingTaskPositioner() { - return mTaskPositioner; + LaunchingBoundsController getLaunchingBoundsController() { + return mLaunchingBoundsController; } protected <T extends ActivityStack> T getStack(int stackId) { diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 6f74d85115c9..4ea51f4c88b9 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -151,7 +151,7 @@ class ActivityStarter { private boolean mLaunchTaskBehind; private int mLaunchFlags; - private Rect mLaunchBounds; + private Rect mLaunchBounds = new Rect(); private ActivityRecord mNotTop; private boolean mDoResume; @@ -210,7 +210,7 @@ class ActivityStarter { mLaunchFlags = 0; mLaunchMode = INVALID_LAUNCH_MODE; - mLaunchBounds = null; + mLaunchBounds.setEmpty(); mNotTop = null; mDoResume = false; @@ -1254,7 +1254,10 @@ class ActivityStarter { mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options); - mLaunchBounds = getOverrideBounds(r, options, inTask); + mLaunchBounds.setEmpty(); + + mSupervisor.getLaunchingBoundsController().calculateBounds(inTask, null /*layout*/, r, + options, mLaunchBounds); mLaunchMode = r.launchMode; @@ -1725,7 +1728,7 @@ class ActivityStarter { // Target stack got cleared when we all activities were removed above. // Go ahead and reset it. mTargetStack = computeStackFocus(mSourceRecord, false /* newTask */, - null /* bounds */, mLaunchFlags, mOptions); + mLaunchFlags, mOptions); mTargetStack.addTask(task, !mLaunchTaskBehind /* toTop */, "startActivityUnchecked"); } @@ -1776,8 +1779,7 @@ class ActivityStarter { private int setTaskFromReuseOrCreateNewTask( TaskRecord taskToAffiliate, ActivityStack topStack) { - mTargetStack = computeStackFocus( - mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions); + mTargetStack = computeStackFocus(mStartActivity, true, mLaunchFlags, mOptions); // Do no move the target stack to front yet, as we might bail if // isLockTaskModeViolation fails below. @@ -1962,7 +1964,7 @@ class ActivityStarter { return START_TASK_TO_FRONT; } - if (mLaunchBounds != null) { + if (!mLaunchBounds.isEmpty()) { // TODO: Shouldn't we already know what stack to use by the time we get here? ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP); if (stack != mInTask.getStack()) { @@ -1985,7 +1987,7 @@ class ActivityStarter { } void updateBounds(TaskRecord task, Rect bounds) { - if (bounds == null) { + if (bounds.isEmpty()) { return; } @@ -1998,8 +2000,7 @@ class ActivityStarter { } private void setTaskToCurrentTopOrCreateNewTask() { - mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags, - mOptions); + mTargetStack = computeStackFocus(mStartActivity, false, mLaunchFlags, mOptions); if (mDoResume) { mTargetStack.moveToFront("addingToTopTask"); } @@ -2062,8 +2063,8 @@ class ActivityStarter { } } - private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds, - int launchFlags, ActivityOptions aOptions) { + private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, int launchFlags, + ActivityOptions aOptions) { final TaskRecord task = r.getTask(); ActivityStack stack = getLaunchStack(r, launchFlags, task, aOptions); if (stack != null) { diff --git a/services/core/java/com/android/server/am/LaunchingActivityPositioner.java b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java new file mode 100644 index 000000000000..5815e9809756 --- /dev/null +++ b/services/core/java/com/android/server/am/LaunchingActivityPositioner.java @@ -0,0 +1,62 @@ +/* + * 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.LaunchingBoundsController.LaunchingBoundsPositioner; + +/** + * An implementation of {@link LaunchingBoundsPositioner}, which applies the launch bounds specified + * inside {@link ActivityOptions#getLaunchBounds()}. + */ +public class LaunchingActivityPositioner implements LaunchingBoundsPositioner { + private final ActivityStackSupervisor mSupervisor; + + LaunchingActivityPositioner(ActivityStackSupervisor activityStackSupervisor) { + mSupervisor = activityStackSupervisor; + } + + @Override + public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout, + ActivityRecord activity, ActivityOptions options, Rect current, Rect result) { + // 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 = TaskRecord.validateBounds(options.getLaunchBounds()); + + // Bounds weren't valid. + if (bounds == null) { + return RESULT_SKIP; + } + + result.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/LaunchingBoundsController.java b/services/core/java/com/android/server/am/LaunchingBoundsController.java new file mode 100644 index 000000000000..8345ba657c2d --- /dev/null +++ b/services/core/java/com/android/server/am/LaunchingBoundsController.java @@ -0,0 +1,160 @@ +/* + * 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.annotation.IntDef; +import android.app.ActivityOptions; +import android.content.pm.ActivityInfo.WindowLayout; +import android.graphics.Rect; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.List; + +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP; + +/** + * {@link LaunchingBoundsController} calculates the launch bounds by coordinating between registered + * {@link LaunchingBoundsPositioner}. + */ +class LaunchingBoundsController { + private final List<LaunchingBoundsPositioner> mPositioners = new ArrayList<>(); + + // Temporary {@link Rect} for calculations. This is kept separate from {@code mTmpCurrent} and + // {@code mTmpResult} to prevent clobbering values. + private final Rect mTmpRect = new Rect(); + + private final Rect mTmpCurrent = new Rect(); + private final Rect mTmpResult = new Rect(); + + /** + * Creates a {@link LaunchingBoundsController} with default registered + * {@link LaunchingBoundsPositioner}s. + */ + void registerDefaultPositioners(ActivityStackSupervisor supervisor) { + // {@link LaunchingTaskPositioner} handles window layout preferences. + registerPositioner(new LaunchingTaskPositioner()); + + // {@link LaunchingActivityPositioner} is the most specific positioner and thus should be + // registered last (applied first) out of the defaults. + registerPositioner(new LaunchingActivityPositioner(supervisor)); + } + + /** + * Returns the position calculated by the registered positioners + * @param task The {@link TaskRecord} currently being positioned. + * @param layout The specified {@link WindowLayout}. + * @param activity The {@link ActivityRecord} currently being positioned. + * @param options The {@link ActivityOptions} specified for the activity. + * @param result The resulting bounds. If no bounds are set, {@link Rect#isEmpty()} will be + * true. + */ + void calculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity, + ActivityOptions options, Rect result) { + result.setEmpty(); + + // We start at the last registered {@link LaunchingBoundsPositioner} as this represents + // The positioner closest to the product level. Moving back through the list moves closer to + // the platform logic. + for (int i = mPositioners.size() - 1; i >= 0; --i) { + mTmpResult.setEmpty(); + mTmpCurrent.set(result); + final LaunchingBoundsPositioner positioner = mPositioners.get(i); + + switch(positioner.onCalculateBounds(task, layout, activity, options, mTmpCurrent, + mTmpResult)) { + case RESULT_SKIP: + // Do not apply any results when we are told to skip + continue; + case RESULT_DONE: + // Set result and return immediately. + result.set(mTmpResult); + return; + case RESULT_CONTINUE: + // Set result and continue + result.set(mTmpResult); + break; + } + } + } + + /** + * A convenience method for laying out a task. + * @return {@code true} if bounds were set on the task. {@code false} otherwise. + */ + boolean layoutTask(TaskRecord task, WindowLayout layout) { + calculateBounds(task, layout, null /*activity*/, null /*options*/, mTmpRect); + + if (mTmpRect.isEmpty()) { + return false; + } + + task.updateOverrideConfiguration(mTmpRect); + + return true; + } + + /** + * Adds a positioner to participate in future bounds calculation. Note that the last registered + * {@link LaunchingBoundsPositioner} will be the first to calculate the bounds. + */ + void registerPositioner(LaunchingBoundsPositioner positioner) { + if (mPositioners.contains(positioner)) { + return; + } + + mPositioners.add(positioner); + } + + /** + * An interface implemented by those wanting to participate in bounds calculation. + */ + interface LaunchingBoundsPositioner { + @Retention(RetentionPolicy.SOURCE) + @IntDef({RESULT_SKIP, RESULT_DONE, RESULT_CONTINUE}) + @interface Result {} + + // Returned when the positioner does not want to influence the bounds calculation + int RESULT_SKIP = 0; + // Returned when the positioner has changed the bounds and would like its results to be the + // final bounds applied. + int RESULT_DONE = 1; + // Returned when the positioner has changed the bounds but is okay with other positioners + // influencing the bounds. + int RESULT_CONTINUE = 2; + + /** + * Called when asked to calculate bounds. + * @param task The {@link TaskRecord} currently being positioned. + * @param layout The specified {@link WindowLayout}. + * @param activity The {@link ActivityRecord} currently being positioned. + * @param options The {@link ActivityOptions} specified for the activity. + * @param current The current bounds. This can differ from the initial bounds as it + * represents the modified bounds up to this point. + * @param result The {@link Rect} which the positioner should return its modified bounds. + * Any merging of the current bounds should be already applied to this + * value as well before returning. + * @return A {@link Result} representing the result of the bounds calculation. + */ + @Result + int onCalculateBounds(TaskRecord task, WindowLayout layout, ActivityRecord activity, + ActivityOptions options, Rect current, Rect result); + } +} diff --git a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java index 0dc73e98f492..6389075a9b3f 100644 --- a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java +++ b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java @@ -19,7 +19,7 @@ package com.android.server.am; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; -import android.annotation.Nullable; +import android.app.ActivityOptions; import android.content.pm.ActivityInfo; import android.graphics.Point; import android.graphics.Rect; @@ -36,8 +36,10 @@ import java.util.ArrayList; * 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. */ -class LaunchingTaskPositioner { +class LaunchingTaskPositioner implements LaunchingBoundsController.LaunchingBoundsPositioner { private static final String TAG = TAG_WITH_CLASS_NAME ? "LaunchingTaskPositioner" : TAG_AM; // Determines how close window frames/corners have to be to call them colliding. @@ -74,44 +76,50 @@ class LaunchingTaskPositioner { * 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. - * - * @param task Task for which we want to find bounds that won't collide with other. - * @param tasks Existing tasks with which we don't want to collide. - * @param windowLayout Optional information from the client about how it would like to be sized - * and positioned. */ - void updateDefaultBounds(TaskRecord task, ArrayList<TaskRecord> tasks, - @Nullable ActivityInfo.WindowLayout windowLayout) { + @Override + public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout, + ActivityRecord activity, ActivityOptions options, Rect current, Rect result) { + // We can only apply positioning if we're in a freeform stack. + if (task == null || task.getStack() == null || !task.inFreeformWindowingMode()) { + return RESULT_SKIP; + } + + final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks(); + updateAvailableRect(task, mAvailableRect); - if (windowLayout == null) { - positionCenter(task, tasks, mAvailableRect, getFreeformWidth(mAvailableRect), - getFreeformHeight(mAvailableRect)); - return; + if (layout == null) { + positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect), + getFreeformHeight(mAvailableRect), result); + return RESULT_CONTINUE; } - int width = getFinalWidth(windowLayout, mAvailableRect); - int height = getFinalHeight(windowLayout, mAvailableRect); - int verticalGravity = windowLayout.gravity & Gravity.VERTICAL_GRAVITY_MASK; - int horizontalGravity = windowLayout.gravity & Gravity.HORIZONTAL_GRAVITY_MASK; + + 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(task, tasks, mAvailableRect, width, height); + positionTopRight(tasks, mAvailableRect, width, height, result); } else { - positionTopLeft(task, tasks, mAvailableRect, width, height); + positionTopLeft(tasks, mAvailableRect, width, height, result); } } else if (verticalGravity == Gravity.BOTTOM) { if (horizontalGravity == Gravity.RIGHT) { - positionBottomRight(task, tasks, mAvailableRect, width, height); + positionBottomRight(tasks, mAvailableRect, width, height, result); } else { - positionBottomLeft(task, tasks, mAvailableRect, width, height); + positionBottomLeft(tasks, mAvailableRect, width, height, result); } } 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: " + windowLayout.gravity + Slog.w(TAG, "Received unsupported gravity: " + layout.gravity + ", positioning in the center instead."); - positionCenter(task, tasks, mAvailableRect, width, height); + positionCenter(tasks, mAvailableRect, width, height, result); } + + return RESULT_CONTINUE; } private void updateAvailableRect(TaskRecord task, Rect availableRect) { @@ -179,50 +187,50 @@ class LaunchingTaskPositioner { return height; } - private void positionBottomLeft(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int 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(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_RIGHT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT, + result); } - private void positionBottomRight(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + 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(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_LEFT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT, + result); } - private void positionTopLeft(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int 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(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_RIGHT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_RIGHT, + result); } - private void positionTopRight(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int 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(task, tasks, availableRect, mTmpProposal, !ALLOW_RESTART, - SHIFT_POLICY_HORIZONTAL_LEFT); + position(tasks, availableRect, mTmpProposal, !ALLOW_RESTART, SHIFT_POLICY_HORIZONTAL_LEFT, + result); } - private void positionCenter(TaskRecord task, ArrayList<TaskRecord> tasks, - Rect availableRect, int width, int height) { + 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(task, tasks, availableRect, mTmpProposal, ALLOW_RESTART, - SHIFT_POLICY_DIAGONAL_DOWN); + position(tasks, availableRect, mTmpProposal, ALLOW_RESTART, SHIFT_POLICY_DIAGONAL_DOWN, + result); } - private void position(TaskRecord task, ArrayList<TaskRecord> tasks, Rect availableRect, - Rect proposal, boolean allowRestart, int shiftPolicy) { + 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)) { @@ -252,7 +260,7 @@ class LaunchingTaskPositioner { break; } } - task.updateOverrideConfiguration(proposal); + result.set(proposal); } private boolean shiftedTooFar(Rect start, Rect availableRect, int shiftPolicy) { diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index c4512353db94..899bf795b5aa 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -707,7 +707,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) { Rect bounds = getLaunchBounds(); if (bounds == null) { - toStack.layoutTaskInStack(this, null); + mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null); bounds = mBounds; } kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume); @@ -2089,7 +2089,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi if (mLastNonFullscreenBounds != null) { updateOverrideConfiguration(mLastNonFullscreenBounds); } else { - inStack.layoutTaskInStack(this, null); + mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null); } } else { updateOverrideConfiguration(inStack.mBounds); diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java new file mode 100644 index 000000000000..0007e8ae1aba --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/LaunchingActivityPositionerTests.java @@ -0,0 +1,146 @@ +/* + * 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.ComponentName; +import android.content.pm.ActivityInfo; +import android.graphics.Rect; +import android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.runner.RunWith; +import org.junit.Before; +import org.junit.Test; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.doAnswer; + +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP; + +/** + * Tests for exercising resizing bounds due to activity options. + * + * Build/Install/Run: + * bit FrameworksServicesTests:com.android.server.am.LaunchingActivityPositionerTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class LaunchingActivityPositionerTests extends ActivityTestsBase { + private final ComponentName testActivityComponent = + ComponentName.unflattenFromString("com.foo/.BarActivity"); + + private LaunchingActivityPositioner mPositioner; + private ActivityManagerService mService; + private ActivityStack mStack; + private TaskRecord mTask; + private ActivityRecord mActivity; + + private Rect mCurrent; + private Rect mResult; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mService = createActivityManagerService(); + mPositioner = new LaunchingActivityPositioner(mService.mStackSupervisor); + mCurrent = new Rect(); + mResult = new Rect(); + + + mStack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); + mTask = createTask(mService.mStackSupervisor, testActivityComponent, mStack); + mActivity = createActivity(mService, testActivityComponent, mTask); + } + + + @Test + public void testSkippedInvocations() throws Exception { + // No specified activity should be ignored + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + null /*activity*/, null /*options*/, mCurrent, mResult)); + + // No specified activity options should be ignored + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, null /*options*/, mCurrent, mResult)); + + // launch bounds specified should be ignored. + final ActivityOptions options = ActivityOptions.makeBasic(); + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + // Non-resizeable records should be ignored + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_UNRESIZEABLE; + assertFalse(mActivity.isResizeable()); + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + // make record resizeable + mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE; + assertTrue(mActivity.isResizeable()); + + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + // Does not support freeform + mService.mSupportsFreeformWindowManagement = false; + assertFalse(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options)); + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + mService.mSupportsFreeformWindowManagement = true; + options.setLaunchBounds(new Rect()); + assertTrue(mService.mStackSupervisor.canUseActivityOptionsLaunchBounds(options)); + + // Invalid bounds + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + options.setLaunchBounds(new Rect(0, 0, -1, -1)); + assertEquals(RESULT_SKIP, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + + // Valid bounds should cause the positioner to be applied. + options.setLaunchBounds(new Rect(0, 0, 100, 100)); + assertEquals(RESULT_DONE, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, 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, mPositioner.onCalculateBounds(null /*task*/, null /*layout*/, + mActivity, options /*options*/, mCurrent, mResult)); + assertEquals(mResult, proposedBounds); + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java new file mode 100644 index 000000000000..f24a273d66d1 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/am/LaunchingBoundsControllerTests.java @@ -0,0 +1,173 @@ +/* + * 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 android.platform.test.annotations.Presubmit; +import android.support.test.filters.MediumTest; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.runner.RunWith; +import org.junit.Before; +import org.junit.Test; + +import com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner; + +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_DONE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE; +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_SKIP; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for exercising {@link LaunchingBoundsController}. + * + * Build/Install/Run: + * bit FrameworksServicesTests:com.android.server.am.LaunchingBoundsControllerTests + */ +@MediumTest +@Presubmit +@RunWith(AndroidJUnit4.class) +public class LaunchingBoundsControllerTests extends ActivityTestsBase { + private LaunchingBoundsController mController; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + mController = new LaunchingBoundsController(); + } + + /** + * Ensures positioners further down the chain are not called when RESULT_DONE is returned. + */ + @Test + public void testEarlyExit() { + final LaunchingBoundsPositioner ignoredPositioner = mock(LaunchingBoundsPositioner.class); + final LaunchingBoundsPositioner earlyExitPositioner = + (task, layout, activity, options, current, result) -> RESULT_DONE; + + mController.registerPositioner(ignoredPositioner); + mController.registerPositioner(earlyExitPositioner); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, new Rect()); + verify(ignoredPositioner, never()).onCalculateBounds(any(), any(), any(), any(), any(), + any()); + } + + /** + * Ensures that positioners are called in the correct order. + */ + @Test + public void testRegistration() { + LaunchingBoundsPositioner earlyExitPositioner = + new InstrumentedPositioner(RESULT_DONE, new Rect()); + + final LaunchingBoundsPositioner firstPositioner = spy(earlyExitPositioner); + + mController.registerPositioner(firstPositioner); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, new Rect()); + verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(), + any()); + + final LaunchingBoundsPositioner secondPositioner = spy(earlyExitPositioner); + + mController.registerPositioner(secondPositioner); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, new Rect()); + verify(firstPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(), + any()); + verify(secondPositioner, times(1)).onCalculateBounds(any(), any(), any(), any(), any(), + any()); + } + + /** + * Makes sure positioners further down the registration chain are called. + */ + @Test + public void testPassThrough() { + final LaunchingBoundsPositioner positioner1 = mock(LaunchingBoundsPositioner.class); + final InstrumentedPositioner positioner2 = new InstrumentedPositioner(RESULT_CONTINUE, + new Rect (0, 0, 30, 20)); + + mController.registerPositioner(positioner1); + mController.registerPositioner(positioner2); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, new Rect()); + + verify(positioner1, times(1)).onCalculateBounds(any(), any(), any(), any(), + eq(positioner2.getLaunchBounds()), any()); + } + + /** + * Ensures skipped results are not propagated. + */ + @Test + public void testSkip() { + final InstrumentedPositioner positioner1 = + new InstrumentedPositioner(RESULT_SKIP, new Rect(0, 0, 10, 10)); + + + final InstrumentedPositioner positioner2 = + new InstrumentedPositioner(RESULT_CONTINUE, new Rect(0, 0, 20, 30)); + + mController.registerPositioner(positioner1); + mController.registerPositioner(positioner2); + + final Rect resultBounds = new Rect(); + + mController.calculateBounds(null /*task*/, null /*layout*/, null /*activity*/, + null /*options*/, resultBounds); + + assertEquals(resultBounds, positioner2.getLaunchBounds()); + } + + public static class InstrumentedPositioner implements LaunchingBoundsPositioner { + private int mReturnVal; + private Rect mBounds; + InstrumentedPositioner(int returnVal, Rect bounds) { + mReturnVal = returnVal; + mBounds = bounds; + } + + @Override + public int onCalculateBounds(TaskRecord task, ActivityInfo.WindowLayout layout, + ActivityRecord activity, ActivityOptions options, Rect current, Rect result) { + result.set(mBounds); + return mReturnVal; + } + + Rect getLaunchBounds() { + return mBounds; + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java index e6d683120e79..0d64981c5a16 100644 --- a/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java +++ b/services/tests/servicestests/src/com/android/server/am/LaunchingTaskPositionerTests.java @@ -17,15 +17,12 @@ package com.android.server.am; import android.content.ComponentName; -import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo.WindowLayout; -import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.support.test.filters.MediumTest; import android.support.test.runner.AndroidJUnit4; -import android.view.Display; import android.view.Gravity; import org.junit.runner.RunWith; import org.junit.Before; @@ -33,10 +30,11 @@ import org.junit.Test; import org.mockito.invocation.InvocationOnMock; -import java.util.ArrayList; - import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; + +import static com.android.server.am.LaunchingBoundsController.LaunchingBoundsPositioner.RESULT_CONTINUE; + import static org.mockito.Mockito.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -48,16 +46,14 @@ import static org.junit.Assert.assertEquals; * Tests for exercising resizing bounds. * * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.am.LaunchBoundsTests + * bit FrameworksServicesTests:com.android.server.am.LaunchingTaskPositionerTests */ @MediumTest @Presubmit @RunWith(AndroidJUnit4.class) -public class LaunchBoundsTests extends ActivityTestsBase { +public class LaunchingTaskPositionerTests extends ActivityTestsBase { private final ComponentName testActivityComponent = ComponentName.unflattenFromString("com.foo/.BarActivity"); - private final ComponentName testActivityComponent2 = - ComponentName.unflattenFromString("com.foo/.BarActivity2"); private final static int STACK_WIDTH = 100; private final static int STACK_HEIGHT = 200; @@ -68,6 +64,11 @@ public class LaunchBoundsTests extends ActivityTestsBase { private ActivityStack mStack; private TaskRecord mTask; + private LaunchingTaskPositioner mPositioner; + + private Rect mCurrent; + private Rect mResult; + @Before @Override public void setUp() throws Exception { @@ -81,6 +82,11 @@ public class LaunchBoundsTests extends ActivityTestsBase { // We must create the task after resizing to make sure it does not inherit the stack // dimensions on resize. mTask = createTask(mService.mStackSupervisor, testActivityComponent, mStack); + + mPositioner = new LaunchingTaskPositioner(); + + mResult = new Rect(); + mCurrent = new Rect(); } /** @@ -101,12 +107,9 @@ public class LaunchBoundsTests extends ActivityTestsBase { */ @Test public void testLaunchNoWindowLayout() throws Exception { - final Rect expectedTaskBounds = getDefaultBounds(Gravity.NO_GRAVITY); - - mStack.layoutTaskInStack(mTask, null); - - // We expect the task to be placed in the middle of the screen with margins applied. - assertEquals(mTask.mBounds, expectedTaskBounds); + assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask, null /*layout*/, + null /*record*/, null /*options*/, mCurrent, mResult)); + assertEquals(getDefaultBounds(Gravity.NO_GRAVITY), mResult); } /** @@ -116,11 +119,10 @@ public class LaunchBoundsTests extends ActivityTestsBase { */ @Test public void testlaunchEmptyWindowLayout() throws Exception { - final Rect expectedTaskBounds = getDefaultBounds(Gravity.NO_GRAVITY); - - WindowLayout layout = new WindowLayout(0, 0, 0, 0, 0, 0, 0); - mStack.layoutTaskInStack(mTask, layout); - assertEquals(mTask.mBounds, expectedTaskBounds); + assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask, + new WindowLayout(0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0), null /*activity*/, + null /*options*/, mCurrent, mResult)); + assertEquals(mResult, getDefaultBounds(Gravity.NO_GRAVITY)); } /** @@ -149,9 +151,15 @@ public class LaunchBoundsTests extends ActivityTestsBase { } private void testGravity(int gravity) { - final WindowLayout gravityLayout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0); - mStack.layoutTaskInStack(mTask, gravityLayout); - assertEquals(mTask.mBounds, getDefaultBounds(gravity)); + try { + assertEquals(RESULT_CONTINUE, mPositioner.onCalculateBounds(mTask, + new WindowLayout(0, 0, 0, 0, gravity, 0, 0), null /*activity*/, + null /*options*/, mCurrent, mResult)); + assertEquals(mResult, getDefaultBounds(gravity)); + } finally { + mCurrent.setEmpty(); + mResult.setEmpty(); + } } /** @@ -174,7 +182,7 @@ public class LaunchBoundsTests extends ActivityTestsBase { final WindowLayout layout = new WindowLayout(0, 0, 0, 0, gravity, 0, 0); // layout first task - mStack.layoutTaskInStack(mTask, layout /*windowLayout*/); + mService.mStackSupervisor.getLaunchingBoundsController().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.mBounds); @@ -192,7 +200,9 @@ public class LaunchBoundsTests extends ActivityTestsBase { mStack); // layout second task - mStack.layoutTaskInStack(secondTask, layout /*windowLayout*/); + assertEquals(RESULT_CONTINUE, + mPositioner.onCalculateBounds(secondTask, layout, null /*activity*/, + null /*options*/, mCurrent, mResult)); if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT) || (gravity & (Gravity.BOTTOM | Gravity.RIGHT)) @@ -207,7 +217,7 @@ public class LaunchBoundsTests extends ActivityTestsBase { LaunchingTaskPositioner.getVerticalStep(mStack.mBounds)); } - assertEquals(secondTask.mBounds, expectedBounds); + assertEquals(mResult, expectedBounds); } finally { // Remove task and activity to prevent influencing future tests if (activity != null) { |