Merge "Initial implementation of LaunchingBoundsController."

This commit is contained in:
Bryce Lee
2017-10-25 14:18:22 +00:00
committed by Android (Google) Code Review
10 changed files with 654 additions and 103 deletions

View File

@@ -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;
@@ -5053,24 +5051,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);
}

View File

@@ -287,7 +287,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;
@@ -568,6 +568,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) {
@@ -2154,8 +2157,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) {

View File

@@ -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) {

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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) {

View File

@@ -704,7 +704,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);
@@ -2086,7 +2086,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);

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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) {