Adding orientation preserving resizing
Adding freeform resizing to activities which require
a certain orientation. This is needed for e.g. ARC++.
Bug: 33267688
Test: runtest frameworks-services -c com.android.server.wm.TaskPositionerTests
Test: Visually on ARC++
Change-Id: If708c1602cb2ff464174389af4648ad767b0b079
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index dde948f..c7aa5f2 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2322,6 +2322,11 @@
return true;
}
+ if (!task.canResizeToBounds(bounds)) {
+ throw new IllegalArgumentException("resizeTaskLocked: Can not resize task=" + task
+ + " to bounds=" + bounds + " resizeMode=" + task.mResizeMode);
+ }
+
// Do not move the task to another stack here.
// This method assumes that the task is already placed in the right stack.
// we do not mess with that decision and we only do the resize!
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 5c352e1..383f106 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -73,6 +73,9 @@
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION;
@@ -1077,12 +1080,32 @@
&& !mTemporarilyUnresizable;
}
+ /**
+ * Check that a given bounds matches the application requested orientation.
+ *
+ * @param bounds The bounds to be tested.
+ * @return True if the requested bounds are okay for a resizing request.
+ */
+ boolean canResizeToBounds(Rect bounds) {
+ if (bounds == null || getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
+ // Note: If not on the freeform workspace, we ignore the bounds.
+ return true;
+ }
+ final boolean landscape = bounds.width() > bounds.height();
+ if (mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION) {
+ return mBounds == null || landscape == (mBounds.width() > mBounds.height());
+ }
+ return (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY || !landscape)
+ && (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY || landscape);
+ }
+
boolean isOnTopLauncher() {
return isHomeTask() && mIsOnTopLauncher;
}
boolean canGoInDockedStack() {
- return isResizeable();
+ return isResizeable() &&
+ !ActivityInfo.isPreserveOrientationMode(mResizeMode);
}
/**
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 29afc8d..612af75 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -19,7 +19,9 @@
import static android.app.ActivityManager.RESIZE_MODE_SYSTEM_SCREEN_ROTATION;
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -280,6 +282,17 @@
return ActivityInfo.isResizeableMode(mResizeMode) || mService.mForceResizableTasks;
}
+ /**
+ * Tests if the orientation should be preserved upon user interactive resizig operations.
+
+ * @return true if orientation should not get changed upon resizing operation.
+ */
+ boolean preserveOrientationOnResize() {
+ return mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY
+ || mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY
+ || mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
+ }
+
boolean isOnTopLauncher() {
return mIsOnTopLauncher;
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 6887312..267566b 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -50,9 +50,9 @@
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
-import android.view.SurfaceControl;
import android.view.WindowManager;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.input.InputApplicationHandle;
import com.android.server.input.InputWindowHandle;
import com.android.server.wm.WindowManagerService.H;
@@ -61,6 +61,7 @@
import java.lang.annotation.RetentionPolicy;
class TaskPositioner implements DimLayer.DimLayerUser {
+ private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
private static final String TAG_LOCAL = "TaskPositioner";
private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
@@ -89,6 +90,12 @@
public static final int RESIZING_HINT_DURATION_MS = 0;
+ // The minimal aspect ratio which needs to be met to count as landscape (or 1/.. for portrait).
+ // Note: We do not use the 1.33 from the CDD here since the user is allowed to use what ever
+ // aspect he desires.
+ @VisibleForTesting
+ static final float MIN_ASPECT = 1.2f;
+
private final WindowManagerService mService;
private WindowPositionerEventReceiver mInputEventReceiver;
private Display mDisplay;
@@ -103,8 +110,11 @@
private Task mTask;
private boolean mResizing;
+ private boolean mPreserveOrientation;
+ private boolean mStartOrientationWasLandscape;
private final Rect mWindowOriginalBounds = new Rect();
private final Rect mWindowDragBounds = new Rect();
+ private final Point mMaxVisibleSize = new Point();
private float mStartDragX;
private float mStartDragY;
@CtrlType
@@ -226,6 +236,11 @@
mService = service;
}
+ @VisibleForTesting
+ Rect getWindowDragBounds() {
+ return mWindowDragBounds;
+ }
+
/**
* @param display The Display that the window being dragged is on.
*/
@@ -294,6 +309,7 @@
mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
+ mDisplay.getRealSize(mMaxVisibleSize);
mDragEnded = false;
}
@@ -335,44 +351,57 @@
mService.resumeRotationLocked();
}
- void startDragLocked(WindowState win, boolean resize, float startX, float startY) {
+ void startDrag(WindowState win, boolean resize, boolean preserveOrientation, float startX,
+ float startY) {
if (DEBUG_TASK_POSITIONING) {
- Slog.d(TAG, "startDragLocked: win=" + win + ", resize=" + resize
- + ", {" + startX + ", " + startY + "}");
+ Slog.d(TAG, "startDrag: win=" + win + ", resize=" + resize
+ + ", preserveOrientation=" + preserveOrientation + ", {" + startX + ", "
+ + startY + "}");
}
- mCtrlType = CTRL_NONE;
mTask = win.getTask();
- mStartDragX = startX;
- mStartDragY = startY;
-
// Use the dim bounds, not the original task bounds. The cursor
// movement should be calculated relative to the visible bounds.
// Also, use the dim bounds of the task which accounts for
// multiple app windows. Don't use any bounds from win itself as it
// may not be the same size as the task.
mTask.getDimBounds(mTmpRect);
+ startDrag(resize, preserveOrientation, startX, startY, mTmpRect);
+ }
+
+ @VisibleForTesting
+ void startDrag(boolean resize, boolean preserveOrientation,
+ float startX, float startY, Rect startBounds) {
+ mCtrlType = CTRL_NONE;
+ mStartDragX = startX;
+ mStartDragY = startY;
+ mPreserveOrientation = preserveOrientation;
if (resize) {
- if (startX < mTmpRect.left) {
+ if (startX < startBounds.left) {
mCtrlType |= CTRL_LEFT;
}
- if (startX > mTmpRect.right) {
+ if (startX > startBounds.right) {
mCtrlType |= CTRL_RIGHT;
}
- if (startY < mTmpRect.top) {
+ if (startY < startBounds.top) {
mCtrlType |= CTRL_TOP;
}
- if (startY > mTmpRect.bottom) {
+ if (startY > startBounds.bottom) {
mCtrlType |= CTRL_BOTTOM;
}
- mResizing = true;
+ mResizing = mCtrlType != CTRL_NONE;
}
- mWindowOriginalBounds.set(mTmpRect);
+ // In case of !isDockedInEffect we are using the union of all task bounds. These might be
+ // made up out of multiple windows which are only partially overlapping. When that happens,
+ // the orientation from the window of interest to the entire stack might diverge. However
+ // for now we treat them as the same.
+ mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
+ mWindowOriginalBounds.set(startBounds);
// Make sure we always have valid drag bounds even if the drag ends before any move events
// have been handled.
- mWindowDragBounds.set(mTmpRect);
+ mWindowDragBounds.set(startBounds);
}
private void endDragLocked() {
@@ -387,26 +416,7 @@
}
if (mCtrlType != CTRL_NONE) {
- // This is a resizing operation.
- final int deltaX = Math.round(x - mStartDragX);
- final int deltaY = Math.round(y - mStartDragY);
- int left = mWindowOriginalBounds.left;
- int top = mWindowOriginalBounds.top;
- int right = mWindowOriginalBounds.right;
- int bottom = mWindowOriginalBounds.bottom;
- if ((mCtrlType & CTRL_LEFT) != 0) {
- left = Math.min(left + deltaX, right - mMinVisibleWidth);
- }
- if ((mCtrlType & CTRL_TOP) != 0) {
- top = Math.min(top + deltaY, bottom - mMinVisibleHeight);
- }
- if ((mCtrlType & CTRL_RIGHT) != 0) {
- right = Math.max(left + mMinVisibleWidth, right + deltaX);
- }
- if ((mCtrlType & CTRL_BOTTOM) != 0) {
- bottom = Math.max(top + mMinVisibleHeight, bottom + deltaY);
- }
- mWindowDragBounds.set(left, top, right, bottom);
+ resizeDrag(x, y);
mTask.setDragResizing(true, DRAG_RESIZE_MODE_FREEFORM);
return false;
}
@@ -428,6 +438,168 @@
return false;
}
+ /**
+ * The user is drag - resizing the window.
+ *
+ * @param x The x coordinate of the current drag coordinate.
+ * @param y the y coordinate of the current drag coordinate.
+ */
+ @VisibleForTesting
+ void resizeDrag(float x, float y) {
+ // This is a resizing operation.
+ // We need to keep various constraints:
+ // 1. mMinVisible[Width/Height] <= [width/height] <= mMaxVisibleSize.[x/y]
+ // 2. The orientation is kept - if required.
+ final int deltaX = Math.round(x - mStartDragX);
+ final int deltaY = Math.round(y - mStartDragY);
+ int left = mWindowOriginalBounds.left;
+ int top = mWindowOriginalBounds.top;
+ int right = mWindowOriginalBounds.right;
+ int bottom = mWindowOriginalBounds.bottom;
+
+ // The aspect which we have to respect. Note that if the orientation does not need to be
+ // preserved the aspect will be calculated as 1.0 which neutralizes the following
+ // computations.
+ final float minAspect = !mPreserveOrientation
+ ? 1.0f
+ : (mStartOrientationWasLandscape ? MIN_ASPECT : (1.0f / MIN_ASPECT));
+ // Calculate the resulting width and height of the drag operation.
+ int width = right - left;
+ int height = bottom - top;
+ if ((mCtrlType & CTRL_LEFT) != 0) {
+ width = Math.max(mMinVisibleWidth, width - deltaX);
+ } else if ((mCtrlType & CTRL_RIGHT) != 0) {
+ width = Math.max(mMinVisibleWidth, width + deltaX);
+ }
+ if ((mCtrlType & CTRL_TOP) != 0) {
+ height = Math.max(mMinVisibleHeight, height - deltaY);
+ } else if ((mCtrlType & CTRL_BOTTOM) != 0) {
+ height = Math.max(mMinVisibleHeight, height + deltaY);
+ }
+
+ // If we have to preserve the orientation - check that we are doing so.
+ final float aspect = (float) width / (float) height;
+ if (mPreserveOrientation && ((mStartOrientationWasLandscape && aspect < MIN_ASPECT)
+ || (!mStartOrientationWasLandscape && aspect > (1.0 / MIN_ASPECT)))) {
+ // Calculate 2 rectangles fulfilling all requirements for either X or Y being the major
+ // drag axis. What ever is producing the bigger rectangle will be chosen.
+ int width1;
+ int width2;
+ int height1;
+ int height2;
+ if (mStartOrientationWasLandscape) {
+ // Assuming that the width is our target we calculate the height.
+ width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
+ height1 = Math.min(height, Math.round((float)width1 / MIN_ASPECT));
+ if (height1 < mMinVisibleHeight) {
+ // If the resulting height is too small we adjust to the minimal size.
+ height1 = mMinVisibleHeight;
+ width1 = Math.max(mMinVisibleWidth,
+ Math.min(mMaxVisibleSize.x, Math.round((float)height1 * MIN_ASPECT)));
+ }
+ // Assuming that the height is our target we calculate the width.
+ height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
+ width2 = Math.max(width, Math.round((float)height2 * MIN_ASPECT));
+ if (width2 < mMinVisibleWidth) {
+ // If the resulting width is too small we adjust to the minimal size.
+ width2 = mMinVisibleWidth;
+ height2 = Math.max(mMinVisibleHeight,
+ Math.min(mMaxVisibleSize.y, Math.round((float)width2 / MIN_ASPECT)));
+ }
+ } else {
+ // Assuming that the width is our target we calculate the height.
+ width1 = Math.max(mMinVisibleWidth, Math.min(mMaxVisibleSize.x, width));
+ height1 = Math.max(height, Math.round((float)width1 * MIN_ASPECT));
+ if (height1 < mMinVisibleHeight) {
+ // If the resulting height is too small we adjust to the minimal size.
+ height1 = mMinVisibleHeight;
+ width1 = Math.max(mMinVisibleWidth,
+ Math.min(mMaxVisibleSize.x, Math.round((float)height1 / MIN_ASPECT)));
+ }
+ // Assuming that the height is our target we calculate the width.
+ height2 = Math.max(mMinVisibleHeight, Math.min(mMaxVisibleSize.y, height));
+ width2 = Math.min(width, Math.round((float)height2 / MIN_ASPECT));
+ if (width2 < mMinVisibleWidth) {
+ // If the resulting width is too small we adjust to the minimal size.
+ width2 = mMinVisibleWidth;
+ height2 = Math.max(mMinVisibleHeight,
+ Math.min(mMaxVisibleSize.y, Math.round((float)width2 * MIN_ASPECT)));
+ }
+ }
+
+ // Use the bigger of the two rectangles if the major change was positive, otherwise
+ // do the opposite.
+ final boolean grows = width > (right - left) || height > (bottom - top);
+ if (grows == (width1 * height1 > width2 * height2)) {
+ width = width1;
+ height = height1;
+ } else {
+ width = width2;
+ height = height2;
+ }
+ }
+
+ // Update mWindowDragBounds to the new drag size.
+ updateDraggedBounds(left, top, right, bottom, width, height);
+ }
+
+ /**
+ * Given the old coordinates and the new width and height, update the mWindowDragBounds.
+ *
+ * @param left The original left bound before the user started dragging.
+ * @param top The original top bound before the user started dragging.
+ * @param right The original right bound before the user started dragging.
+ * @param bottom The original bottom bound before the user started dragging.
+ * @param newWidth The new dragged width.
+ * @param newHeight The new dragged height.
+ */
+ void updateDraggedBounds(int left, int top, int right, int bottom, int newWidth,
+ int newHeight) {
+ // Generate the final bounds by keeping the opposite drag edge constant.
+ if ((mCtrlType & CTRL_LEFT) != 0) {
+ left = right - newWidth;
+ } else { // Note: The right might have changed - if we pulled at the right or not.
+ right = left + newWidth;
+ }
+ if ((mCtrlType & CTRL_TOP) != 0) {
+ top = bottom - newHeight;
+ } else { // Note: The height might have changed - if we pulled at the bottom or not.
+ bottom = top + newHeight;
+ }
+
+ mWindowDragBounds.set(left, top, right, bottom);
+
+ checkBoundsForOrientationViolations(mWindowDragBounds);
+ }
+
+ /**
+ * Validate bounds against orientation violations (if DEBUG_ORIENTATION_VIOLATIONS is set).
+ *
+ * @param bounds The bounds to be checked.
+ */
+ private void checkBoundsForOrientationViolations(Rect bounds) {
+ // When using debug check that we are not violating the given constraints.
+ if (DEBUG_ORIENTATION_VIOLATIONS) {
+ if (mStartOrientationWasLandscape != (bounds.width() >= bounds.height())) {
+ Slog.e(TAG, "Orientation violation detected! should be "
+ + (mStartOrientationWasLandscape ? "landscape" : "portrait")
+ + " but is the other");
+ } else {
+ Slog.v(TAG, "new bounds size: " + bounds.width() + " x " + bounds.height());
+ }
+ if (mMinVisibleWidth > bounds.width() || mMinVisibleHeight > bounds.height()) {
+ Slog.v(TAG, "Minimum requirement violated: Width(min, is)=(" + mMinVisibleWidth
+ + ", " + bounds.width() + ") Height(min,is)=("
+ + mMinVisibleHeight + ", " + bounds.height() + ")");
+ }
+ if (mMaxVisibleSize.x < bounds.width() || mMaxVisibleSize.y < bounds.height()) {
+ Slog.v(TAG, "Maximum requirement violated: Width(min, is)=(" + mMaxVisibleSize.x
+ + ", " + bounds.width() + ") Height(min,is)=("
+ + mMaxVisibleSize.y + ", " + bounds.height() + ")");
+ }
+ }
+ }
+
private void updateWindowDragBounds(int x, int y, Rect stackBounds) {
final int offsetX = Math.round(x - mStartDragX);
final int offsetY = Math.round(y - mStartDragY);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 04cf2e3..b3d1b13 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5726,7 +5726,8 @@
win = windowForClientLocked(null, window, false);
// win shouldn't be null here, pass it down to startPositioningLocked
// to get warning if it's null.
- if (!startPositioningLocked(win, false /*resize*/, startX, startY)) {
+ if (!startPositioningLocked(
+ win, false /*resize*/, false /*preserveOrientation*/, startX, startY)) {
return false;
}
}
@@ -5741,8 +5742,8 @@
synchronized (mWindowMap) {
final Task task = displayContent.findTaskForResizePoint(x, y);
if (task != null) {
- if (!startPositioningLocked(
- task.getTopVisibleAppMainWindow(), true /*resize*/, x, y)) {
+ if (!startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/,
+ task.preserveOrientationOnResize(), x, y)) {
return;
}
taskId = task.mTaskId;
@@ -5757,10 +5758,12 @@
}
}
- private boolean startPositioningLocked(
- WindowState win, boolean resize, float startX, float startY) {
- if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "startPositioningLocked: "
- + "win=" + win + ", resize=" + resize + ", {" + startX + ", " + startY + "}");
+ private boolean startPositioningLocked(WindowState win, boolean resize,
+ boolean preserveOrientation, float startX, float startY) {
+ if (DEBUG_TASK_POSITIONING)
+ Slog.d(TAG_WM, "startPositioningLocked: "
+ + "win=" + win + ", resize=" + resize + ", preserveOrientation="
+ + preserveOrientation + ", {" + startX + ", " + startY + "}");
if (win == null || win.getAppToken() == null) {
Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win);
@@ -5801,7 +5804,7 @@
return false;
}
- mTaskPositioner.startDragLocked(win, resize, startX, startY);
+ mTaskPositioner.startDrag(win, resize, preserveOrientation, startX, startY);
return true;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
new file mode 100644
index 0000000..aec6dec
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/TaskPositionerTests.java
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2016 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.wm;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.graphics.Rect;
+import android.os.Binder;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.server.wm.TaskPositioner.MIN_ASPECT;
+import static com.android.server.wm.WindowManagerService.dipToPixel;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP;
+import static com.android.server.wm.WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for the {@link TaskPositioner} class.
+ *
+ * runtest frameworks-services -c com.android.server.wm.TaskPositionerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class TaskPositionerTests extends WindowTestsBase {
+
+ private final boolean DEBUGGING = false;
+ private final String TAG = "TaskPositionerTest";
+
+ private final static int MOUSE_DELTA_X = 5;
+ private final static int MOUSE_DELTA_Y = 5;
+
+ private int mMinVisibleWidth;
+ private int mMinVisibleHeight;
+ private TaskPositioner mPositioner;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ final Display display = sDisplayContent.getDisplay();
+ final DisplayMetrics dm = new DisplayMetrics();
+ display.getMetrics(dm);
+
+ // This should be the same calculation as the TaskPositioner uses.
+ mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, dm);
+ mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, dm);
+
+ mPositioner = new TaskPositioner(sWm);
+ mPositioner.register(display);
+ }
+
+ /**
+ * This tests that free resizing will allow to change the orientation as well
+ * as does some basic tests (e.g. dragging in Y only will keep X stable).
+ */
+ @Test
+ public void testBasicFreeWindowResizing() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+ final int midY = (r.top + r.bottom) / 2;
+
+ // Start a drag resize starting upper left.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+ // Drag to a good landscape size.
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a good portrait size.
+ mPositioner.resizeDrag(400.0f, 0.0f);
+ assertBoundsEquals(new Rect(400 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the width.
+ mPositioner.resizeDrag(2000.0f, r.top);
+ assertBoundsEquals(
+ new Rect(r.right - mMinVisibleWidth, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the height.
+ mPositioner.resizeDrag(r.left, 2000.0f);
+ assertBoundsEquals(
+ new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Start a drag resize left and see that only the left coord changes..
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+ // Drag to the left.
+ mPositioner.resizeDrag(0.0f, midY);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right.
+ mPositioner.resizeDrag(200.0f, midY);
+ assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top
+ mPositioner.resizeDrag(r.left, 0.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that by dragging any edge, the fixed / opposite edge(s) remains anchored.
+ */
+ @Test
+ public void testFreeWindowResizingTestAllEdges() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+ final int midX = (r.left + r.right) / 2;
+ final int midY = (r.top + r.bottom) / 2;
+
+ // Drag upper left.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+ // Drag upper.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+ // Drag upper right.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(r.right + 100, 0.0f);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+ assertTrue(r.top != mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+ // Drag right.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.right + MOUSE_DELTA_X, midY, r);
+ mPositioner.resizeDrag(r.right + 100, 0.0f);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+
+ // Drag bottom right.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/,
+ r.right + MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertTrue(r.right != mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+ // Drag bottom.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, midX, r.bottom + MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(r.right + 100, r.bottom + 100);
+ assertEquals(r.left, mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+ // Drag bottom left.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.bottom + MOUSE_DELTA_Y, r);
+ mPositioner.resizeDrag(0.0f, r.bottom + 100);
+ assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertTrue(r.bottom != mPositioner.getWindowDragBounds().bottom);
+
+ // Drag left.
+ mPositioner.startDrag(true /*resizing*/,
+ false /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midX, r);
+ mPositioner.resizeDrag(0.0f, r.bottom + 100);
+ assertTrue(r.left != mPositioner.getWindowDragBounds().left);
+ assertEquals(r.right, mPositioner.getWindowDragBounds().right);
+ assertEquals(r.top, mPositioner.getWindowDragBounds().top);
+ assertEquals(r.bottom, mPositioner.getWindowDragBounds().bottom);
+ }
+
+ /**
+ * This tests that a constrained landscape window will keep the aspect and do the
+ * right things upon resizing when dragged from the top left corner.
+ */
+ @Test
+ public void testLandscapePreservedWindowResizingDragTopLeft() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+ // Drag to a good landscape size.
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a good portrait size.
+ mPositioner.resizeDrag(400.0f, 0.0f);
+ int width = Math.round((float) (r.bottom - MOUSE_DELTA_Y) * MIN_ASPECT);
+ assertBoundsEquals(new Rect(r.right - width, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the width.
+ mPositioner.resizeDrag(2000.0f, r.top);
+ final int w = mMinVisibleWidth;
+ final int h = Math.round(w / MIN_ASPECT);
+ assertBoundsEquals(new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the height.
+ mPositioner.resizeDrag(r.left, 2000.0f);
+ assertBoundsEquals(
+ new Rect(r.left + MOUSE_DELTA_X, r.bottom - mMinVisibleHeight, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained landscape window will keep the aspect and do the
+ * right things upon resizing when dragged from the left corner.
+ */
+ @Test
+ public void testLandscapePreservedWindowResizingDragLeft() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+ final int midY = (r.top + r.bottom) / 2;
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+ // Drag to the left.
+ mPositioner.resizeDrag(0.0f, midY);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right.
+ mPositioner.resizeDrag(200.0f, midY);
+ assertBoundsEquals(new Rect(200 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag all the way to the right and see the height also shrinking.
+ mPositioner.resizeDrag(2000.0f, midY);
+ final int w = mMinVisibleWidth;
+ final int h = Math.round((float)w / MIN_ASPECT);
+ assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top.
+ mPositioner.resizeDrag(r.left, 0.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom.
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained landscape window will keep the aspect and do the
+ * right things upon resizing when dragged from the top corner.
+ */
+ @Test
+ public void testLandscapePreservedWindowResizingDragTop() throws Exception {
+ final Rect r = new Rect(100, 220, 700, 520);
+ final int midX = (r.left + r.right) / 2;
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+
+ // Drag to the left (no change).
+ mPositioner.resizeDrag(0.0f, r.top);
+ assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right (no change).
+ mPositioner.resizeDrag(2000.0f, r.top);
+ assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top.
+ mPositioner.resizeDrag(300.0f, 0.0f);
+ int h = r.bottom - MOUSE_DELTA_Y;
+ int w = Math.max(r.right - r.left, Math.round(h * MIN_ASPECT));
+ assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom.
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ h = mMinVisibleHeight;
+ assertBoundsEquals(new Rect(r.left, r.bottom - h, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained portrait window will keep the aspect and do the
+ * right things upon resizing when dragged from the top left corner.
+ */
+ @Test
+ public void testPortraitPreservedWindowResizingDragTopLeft() throws Exception {
+ final Rect r = new Rect(330, 100, 630, 600);
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, r.top - MOUSE_DELTA_Y, r);
+ assertBoundsEquals(r, mPositioner.getWindowDragBounds());
+
+ // Drag to a good landscape size.
+ mPositioner.resizeDrag(0.0f, 0.0f);
+ int height = Math.round((float) (r.right - MOUSE_DELTA_X) * MIN_ASPECT);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.bottom - height, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a good portrait size.
+ mPositioner.resizeDrag(500.0f, 0.0f);
+ assertBoundsEquals(new Rect(500 + MOUSE_DELTA_X, MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to a too small size for the height and the the width shrinking.
+ mPositioner.resizeDrag(r.left + MOUSE_DELTA_X, 2000.0f);
+ final int w = Math.max(mMinVisibleWidth, Math.round(mMinVisibleHeight / MIN_ASPECT));
+ final int h = Math.max(mMinVisibleHeight, Math.round(w * MIN_ASPECT));
+ assertBoundsEquals(
+ new Rect(r.right - w, r.bottom - h, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained portrait window will keep the aspect and do the
+ * right things upon resizing when dragged from the left corner.
+ */
+ @Test
+ public void testPortraitPreservedWindowResizingDragLeft() throws Exception {
+ final Rect r = new Rect(330, 100, 630, 600);
+ final int midY = (r.top + r.bottom) / 2;
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, r.left - MOUSE_DELTA_X, midY, r);
+
+ // Drag to the left.
+ mPositioner.resizeDrag(0.0f, midY);
+ int w = r.right - MOUSE_DELTA_X;
+ int h = Math.round(w * MIN_ASPECT);
+ assertBoundsEquals(new Rect(MOUSE_DELTA_X, r.top, r.right, r.top + h),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right.
+ mPositioner.resizeDrag(450.0f, midY);
+ assertBoundsEquals(new Rect(450 + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag all the way to the right.
+ mPositioner.resizeDrag(2000.0f, midY);
+ w = mMinVisibleWidth;
+ h = Math.max(Math.round((float)w * MIN_ASPECT), r.height());
+ assertBoundsEquals(new Rect(r.right - w, r.top, r.right, r.top + h),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top.
+ mPositioner.resizeDrag(r.left, 0.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom.
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ assertBoundsEquals(new Rect(r.left + MOUSE_DELTA_X, r.top, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ /**
+ * This tests that a constrained portrait window will keep the aspect and do the
+ * right things upon resizing when dragged from the top corner.
+ */
+ @Test
+ public void testPortraitPreservedWindowResizingDragTop() throws Exception {
+ final Rect r = new Rect(330, 100, 630, 600);
+ final int midX = (r.left + r.right) / 2;
+
+ mPositioner.startDrag(true /*resizing*/,
+ true /*preserveOrientation*/, midX, r.top - MOUSE_DELTA_Y, r);
+
+ // Drag to the left (no change).
+ mPositioner.resizeDrag(0.0f, r.top);
+ assertBoundsEquals(new Rect(r.left, r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the right (no change).
+ mPositioner.resizeDrag(2000.0f, r.top);
+ assertBoundsEquals(new Rect(r.left , r.top + MOUSE_DELTA_Y, r.right, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the top.
+ mPositioner.resizeDrag(300.0f, 0.0f);
+ int h = r.bottom - MOUSE_DELTA_Y;
+ int w = Math.min(r.width(), Math.round(h / MIN_ASPECT));
+ assertBoundsEquals(new Rect(r.left, MOUSE_DELTA_Y, r.left + w, r.bottom),
+ mPositioner.getWindowDragBounds());
+
+ // Drag to the bottom.
+ mPositioner.resizeDrag(r.left, 1000.0f);
+ h = Math.max(mMinVisibleHeight, Math.round(mMinVisibleWidth * MIN_ASPECT));
+ w = Math.round(h / MIN_ASPECT);
+ assertBoundsEquals(new Rect(r.left, r.bottom - h, r.left + w, r.bottom),
+ mPositioner.getWindowDragBounds());
+ }
+
+ private void assertBoundsEquals(Rect expected, Rect actual) {
+ if (DEBUGGING) {
+ if (!expected.equals(actual)) {
+ Log.e(TAG, "rect(" + actual.toString() + ") != isRect(" + actual.toString()
+ + ") " + Log.getStackTraceString(new Throwable()));
+ }
+ }
+ assertEquals(expected.left, actual.left);
+ assertEquals(expected.right, actual.right);
+ assertEquals(expected.top, actual.top);
+ assertEquals(expected.bottom, actual.bottom);
+ }
+}