summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java5
-rw-r--r--services/core/java/com/android/server/am/LaunchingTaskPositioner.java81
-rw-r--r--services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java245
3 files changed, 303 insertions, 28 deletions
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 1940ca2b4c31..a67256337fee 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -110,6 +110,7 @@ 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;
@@ -352,6 +353,7 @@ 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;
@@ -503,7 +505,8 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai
mBounds = bounds != null ? new Rect(bounds) : null;
mFullscreen = mBounds == null;
if (mTaskPositioner != null) {
- mTaskPositioner.setDisplay(activityDisplay.mDisplay);
+ activityDisplay.mDisplay.getSize(mTmpSize);
+ mTaskPositioner.setDisplaySize(mTmpSize);
mTaskPositioner.configure(mBounds);
}
onParentChanged();
diff --git a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
index d652341874f1..2c161cd934b4 100644
--- a/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
+++ b/services/core/java/com/android/server/am/LaunchingTaskPositioner.java
@@ -24,8 +24,8 @@ import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Slog;
-import android.view.Display;
import android.view.Gravity;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
@@ -75,31 +75,10 @@ class LaunchingTaskPositioner {
private int mDefaultFreeformHeight;
private int mDefaultFreeformStepHorizontal;
private int mDefaultFreeformStepVertical;
- private int mDisplayWidth;
- private int mDisplayHeight;
-
- void setDisplay(Display display) {
- Point size = new Point();
- display.getSize(size);
- mDisplayWidth = size.x;
- mDisplayHeight = size.y;
- }
+ private final Point mDisplaySize = new Point();
- void configure(Rect stackBounds) {
- if (stackBounds == null) {
- mAvailableRect.set(0, 0, mDisplayWidth, mDisplayHeight);
- } else {
- mAvailableRect.set(stackBounds);
- }
- int width = mAvailableRect.width();
- int height = mAvailableRect.height();
- mDefaultFreeformStartX = mAvailableRect.left + width / MARGIN_SIZE_DENOMINATOR;
- mDefaultFreeformStartY = mAvailableRect.top + height / MARGIN_SIZE_DENOMINATOR;
- mDefaultFreeformWidth = width / WINDOW_SIZE_DENOMINATOR;
- mDefaultFreeformHeight = height / WINDOW_SIZE_DENOMINATOR;
- mDefaultFreeformStepHorizontal = Math.max(width / STEP_DENOMINATOR, MINIMAL_STEP);
- mDefaultFreeformStepVertical = Math.max(height / STEP_DENOMINATOR, MINIMAL_STEP);
- mDefaultStartBoundsConfigurationSet = true;
+ void setDisplaySize(Point size) {
+ mDisplaySize.set(size.x, size.y);
}
/**
@@ -146,6 +125,54 @@ class LaunchingTaskPositioner {
}
}
+ void configure(Rect availableSpace) {
+ if (availableSpace == null) {
+ mAvailableRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
+ } else {
+ mAvailableRect.set(availableSpace);
+ }
+
+ mDefaultFreeformStartX = getFreeformStartLeft(mAvailableRect);
+ mDefaultFreeformStartY = getFreeformStartTop(mAvailableRect);
+ mDefaultFreeformWidth = getFreeformWidth(mAvailableRect);
+ mDefaultFreeformHeight = getFreeformHeight(mAvailableRect);
+ mDefaultFreeformStepHorizontal = getHorizontalStep(mAvailableRect);
+ mDefaultFreeformStepVertical = getVerticalStep(mAvailableRect);
+ mDefaultStartBoundsConfigurationSet = true;
+ }
+
+ @VisibleForTesting
+ static int getFreeformStartLeft(Rect bounds) {
+ return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR;
+ }
+
+ @VisibleForTesting
+ static int getFreeformStartTop(Rect bounds) {
+ return bounds.top + bounds.height() / MARGIN_SIZE_DENOMINATOR;
+ }
+
+ @VisibleForTesting
+ static int getFreeformWidth(Rect bounds) {
+ return bounds.width() / WINDOW_SIZE_DENOMINATOR;
+ }
+
+ @VisibleForTesting
+ static int getFreeformHeight(Rect bounds) {
+ return bounds.height() / WINDOW_SIZE_DENOMINATOR;
+ }
+
+ @VisibleForTesting
+ static int getHorizontalStep(Rect bounds) {
+ return Math.max(bounds.width() / STEP_DENOMINATOR, MINIMAL_STEP);
+ }
+
+ @VisibleForTesting
+ static int getVerticalStep(Rect bounds) {
+ return Math.max(bounds.height() / STEP_DENOMINATOR, MINIMAL_STEP);
+ }
+
+
+
private int getFinalWidth(ActivityInfo.WindowLayout windowLayout) {
int width = mDefaultFreeformWidth;
if (windowLayout.width > 0) {
@@ -211,7 +238,7 @@ class LaunchingTaskPositioner {
// Unfortunately there is already a task at that spot, so we need to look for some
// other place.
shiftStartingPoint(proposal, shiftPolicy);
- if (shiftedToFar(proposal, shiftPolicy)) {
+ if (shiftedTooFar(proposal, 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) {
@@ -237,7 +264,7 @@ class LaunchingTaskPositioner {
task.updateOverrideConfiguration(proposal);
}
- private boolean shiftedToFar(Rect start, int shiftPolicy) {
+ private boolean shiftedTooFar(Rect start, int shiftPolicy) {
switch (shiftPolicy) {
case SHIFT_POLICY_HORIZONTAL_LEFT:
return start.left < mAvailableRect.left;
diff --git a/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java b/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java
new file mode 100644
index 000000000000..e6d683120e79
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/LaunchBoundsTests.java
@@ -0,0 +1,245 @@
+/*
+ * 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.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;
+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 org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.doAnswer;
+import static org.junit.Assert.assertEquals;
+
+
+/**
+ * Tests for exercising resizing bounds.
+ *
+ * Build/Install/Run:
+ * bit FrameworksServicesTests:com.android.server.am.LaunchBoundsTests
+ */
+@MediumTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class LaunchBoundsTests 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;
+
+ private final static Rect STACK_BOUNDS = new Rect(0, 0, STACK_WIDTH, STACK_HEIGHT);
+
+ private ActivityManagerService mService;
+ private ActivityStack mStack;
+ private TaskRecord mTask;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+
+ mService = createActivityManagerService();
+ mStack = mService.mStackSupervisor.getDefaultDisplay().createStack(
+ WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD, true /* onTop */);
+ mStack.requestResize(STACK_BOUNDS);
+
+ // 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);
+ }
+
+ /**
+ * 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.mBounds, STACK_BOUNDS);
+ assertEquals(mTask.mBounds, null);
+ }
+
+ /**
+ * Ensures that a task positioned with no {@link WindowLayout} receives the default launch
+ * position.
+ * @throws Exception
+ */
+ @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);
+ }
+
+ /**
+ * Ensures that a task positioned with an empty {@link WindowLayout} receives the default launch
+ * position.
+ * @throws Exception
+ */
+ @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);
+ }
+
+ /**
+ * Ensures that a task positioned with a {@link WindowLayout} gravity specified is positioned
+ * according to specification.
+ * @throws Exception
+ */
+ @Test
+ public void testlaunchWindowLayoutGravity() throws Exception {
+ // Unspecified gravity should be ignored
+ testGravity(Gravity.NO_GRAVITY);
+
+ // Unsupported gravity should be ignored
+ testGravity(Gravity.LEFT);
+ testGravity(Gravity.RIGHT);
+
+ // Test defaults for vertical gravities
+ testGravity(Gravity.TOP);
+ testGravity(Gravity.BOTTOM);
+
+ // Test corners
+ testGravity(Gravity.TOP | Gravity.LEFT);
+ testGravity(Gravity.TOP | Gravity.RIGHT);
+ testGravity(Gravity.BOTTOM | Gravity.LEFT);
+ testGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ }
+
+ 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));
+ }
+
+ /**
+ * 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
+ mStack.layoutTaskInStack(mTask, layout /*windowLayout*/);
+
+ // Second task will be laid out on top of the first so starting bounds is the same.
+ final Rect expectedBounds = new Rect(mTask.mBounds);
+
+ ActivityRecord activity = null;
+ TaskRecord secondTask = null;
+
+ // wrap with try/finally to ensure cleanup of activity/stack.
+ try {
+ // empty tasks are ignored in conflicts
+ activity = createActivity(mService, testActivityComponent, mTask);
+
+ // Create secondary task
+ secondTask = createTask(mService.mStackSupervisor, testActivityComponent,
+ mStack);
+
+ // layout second task
+ mStack.layoutTaskInStack(secondTask, layout /*windowLayout*/);
+
+ if ((gravity & (Gravity.TOP | Gravity.RIGHT)) == (Gravity.TOP | Gravity.RIGHT)
+ || (gravity & (Gravity.BOTTOM | Gravity.RIGHT))
+ == (Gravity.BOTTOM | Gravity.RIGHT)) {
+ expectedBounds.offset(-LaunchingTaskPositioner.getHorizontalStep(mStack.mBounds),
+ 0);
+ } else if ((gravity & Gravity.TOP) == Gravity.TOP
+ || (gravity & Gravity.BOTTOM) == Gravity.BOTTOM) {
+ expectedBounds.offset(LaunchingTaskPositioner.getHorizontalStep(mStack.mBounds), 0);
+ } else {
+ expectedBounds.offset(LaunchingTaskPositioner.getHorizontalStep(mStack.mBounds),
+ LaunchingTaskPositioner.getVerticalStep(mStack.mBounds));
+ }
+
+ assertEquals(secondTask.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);
+ }
+ }
+ }
+
+ private Rect getDefaultBounds(int gravity) {
+ final Rect bounds = new Rect();
+ bounds.set(mStack.mBounds);
+
+ final int verticalInset = LaunchingTaskPositioner.getFreeformStartTop(mStack.mBounds);
+ final int horizontalInset = LaunchingTaskPositioner.getFreeformStartLeft(mStack.mBounds);
+
+ bounds.inset(horizontalInset, verticalInset);
+
+ 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);
+ }
+
+ return bounds;
+ }
+}