From 8aa9d4d288b58bfc72b9eda56ebe81c1a3dae5bc Mon Sep 17 00:00:00 2001 From: Maryam Dehaini Date: Thu, 6 Apr 2023 15:52:10 -0700 Subject: [PATCH] Update positioning during drag using SurfaceControl.Transaction When the bounds are updated pixel by pixel using a WCT in task positioner, it updates mLastNonFullscreenBounds. So, when the task is dragged to the top to transition into fullscreen, mLastNonFullscreenBounds is updated, and, when we transition back to freeform using the handle menu, the positioning of the task is at the top. By using SurfaceControl.Transactions to update the positioning, mLastNonFullscreenBounds is left untouched when transitioning to fullscreen via dragging. Bug: 277137896 Test: Drag task from freeform to fullscreen and then transition task back to freeform using handle menu. Task should be in positiom before drag. Change-Id: Idba8523e3c8851b30ef5938b921e9b4ed23bcefa --- .../desktopmode/DesktopTasksController.kt | 18 +- .../EnterDesktopTaskTransitionHandler.java | 14 +- .../ExitDesktopTaskTransitionHandler.java | 11 +- .../CaptionWindowDecorViewModel.java | 20 +- .../DesktopModeWindowDecorViewModel.java | 63 ++-- .../DragPositioningCallbackUtility.java | 52 ++- .../FluidResizeTaskPositioner.java | 60 +++- .../VeiledResizeTaskPositioner.java | 80 +++-- .../ExitDesktopTaskTransitionHandlerTest.java | 6 +- .../DesktopModeWindowDecorViewModelTests.java | 7 + .../DragPositioningCallbackUtilityTest.kt | 34 +- .../FluidResizeTaskPositionerTest.kt | 336 +++++++++++++++++- .../VeiledResizeTaskPositionerTest.kt | 77 ++-- 13 files changed, 626 insertions(+), 152 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index ee94f30cfa693..b3109388da2ce 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -16,7 +16,6 @@ package com.android.wm.shell.desktopmode -import android.app.ActivityManager import android.app.ActivityManager.RunningTaskInfo import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD @@ -202,16 +201,15 @@ class DesktopTasksController( } } - /** * Move a task to fullscreen after being dragged from fullscreen and released back into * status bar area */ - fun cancelMoveToFreeform(task: RunningTaskInfo, startPosition: Point) { + fun cancelMoveToFreeform(task: RunningTaskInfo, position: Point) { val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task.token) if (Transitions.ENABLE_SHELL_TRANSITIONS) { - enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, startPosition, + enterDesktopTaskTransitionHandler.startCancelMoveToDesktopMode(wct, position, mOnAnimationFinishedCallback) } else { shellTaskOrganizer.applyTransaction(wct) @@ -219,13 +217,13 @@ class DesktopTasksController( } } - private fun moveToFullscreenWithAnimation(task: ActivityManager.RunningTaskInfo) { + private fun moveToFullscreenWithAnimation(task: RunningTaskInfo, position: Point) { val wct = WindowContainerTransaction() addMoveToFullscreenChanges(wct, task.token) if (Transitions.ENABLE_SHELL_TRANSITIONS) { exitDesktopTaskTransitionHandler.startTransition( - Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct, mOnAnimationFinishedCallback) + Transitions.TRANSIT_EXIT_DESKTOP_MODE, wct, position, mOnAnimationFinishedCallback) } else { shellTaskOrganizer.applyTransaction(wct) releaseVisualIndicator() @@ -499,15 +497,15 @@ class DesktopTasksController( * Perform checks required on drag end. Move to fullscreen if drag ends in status bar area. * * @param taskInfo the task being dragged. - * @param y height of drag, to be checked against status bar height. + * @param position position of surface when drag ends */ fun onDragPositioningEnd( taskInfo: RunningTaskInfo, - y: Float + position: Point ) { val statusBarHeight = getStatusBarHeight(taskInfo) - if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { - moveToFullscreenWithAnimation(taskInfo) + if (position.y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) { + moveToFullscreenWithAnimation(taskInfo, position) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java index d55fdddf4fea8..3733b919e3660 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/EnterDesktopTaskTransitionHandler.java @@ -58,7 +58,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition public static final int FREEFORM_ANIMATION_DURATION = 336; private final List mPendingTransitionTokens = new ArrayList<>(); - private Point mStartPosition; + private Point mPosition; private Consumer mOnAnimationFinishedCallback; public EnterDesktopTaskTransitionHandler( @@ -90,15 +90,15 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition /** * Starts Transition of type TRANSIT_CANCEL_ENTERING_DESKTOP_MODE * @param wct WindowContainerTransaction for transition - * @param startPosition Position of task when transition is triggered + * @param position Position of task when transition is triggered * @param onAnimationEndCallback to be called after animation */ public void startCancelMoveToDesktopMode(@NonNull WindowContainerTransaction wct, - Point startPosition, + Point position, Consumer onAnimationEndCallback) { - mStartPosition = startPosition; + mPosition = position; startTransition(Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE, wct, - mOnAnimationFinishedCallback); + onAnimationEndCallback); } @Override @@ -201,7 +201,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition if (type == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN - && mStartPosition != null) { + && mPosition != null) { // This Transition animates a task to fullscreen after being dragged from the status // bar and then released back into the status bar area final SurfaceControl sc = change.getLeash(); @@ -217,7 +217,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition final SurfaceControl.Transaction t = mTransactionSupplier.get(); animator.addUpdateListener(animation -> { final float scale = (float) animation.getAnimatedValue(); - t.setPosition(sc, mStartPosition.x * (1 - scale), mStartPosition.y * (1 - scale)) + t.setPosition(sc, mPosition.x * (1 - scale), mPosition.y * (1 - scale)) .setScale(sc, scale, scale) .show(sc) .apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java index 160a83d7ed366..3ad5edf0e6046 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandler.java @@ -57,6 +57,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH private final List mPendingTransitionTokens = new ArrayList<>(); private Consumer mOnAnimationFinishedCallback; private Supplier mTransactionSupplier; + private Point mPosition; public ExitDesktopTaskTransitionHandler( Transitions transitions, @@ -77,11 +78,13 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH * Starts Transition of a given type * @param type Transition type * @param wct WindowContainerTransaction for transition + * @param position Position of the task when transition is started * @param onAnimationEndCallback to be called after animation */ public void startTransition(@WindowManager.TransitionType int type, - @NonNull WindowContainerTransaction wct, + @NonNull WindowContainerTransaction wct, Point position, Consumer onAnimationEndCallback) { + mPosition = position; mOnAnimationFinishedCallback = onAnimationEndCallback; final IBinder token = mTransitions.startTransition(type, wct, this); mPendingTransitionTokens.add(token); @@ -143,17 +146,17 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH final ValueAnimator animator = new ValueAnimator(); animator.setFloatValues(0f, 1f); animator.setDuration(FULLSCREEN_ANIMATION_DURATION); + // The start bounds contain the correct dimensions of the task but hold the positioning + // before being dragged to the status bar to transition into fullscreen final Rect startBounds = change.getStartAbsBounds(); final float scaleX = (float) startBounds.width() / screenWidth; final float scaleY = (float) startBounds.height() / screenHeight; final SurfaceControl.Transaction t = mTransactionSupplier.get(); - Point startPos = new Point(startBounds.left, - startBounds.top); animator.addUpdateListener(animation -> { float fraction = animation.getAnimatedFraction(); float currentScaleX = scaleX + ((1 - scaleX) * fraction); float currentScaleY = scaleY + ((1 - scaleY) * fraction); - t.setPosition(sc, startPos.x * (1 - fraction), startPos.y * (1 - fraction)) + t.setPosition(sc, mPosition.x * (1 - fraction), mPosition.y * (1 - fraction)) .setScale(sc, currentScaleX, currentScaleY) .show(sc) .apply(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java index 212ad9ebceafd..2bb3cceb257fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import android.app.ActivityManager.RunningTaskInfo; import android.content.Context; +import android.graphics.Rect; import android.os.Handler; import android.os.IBinder; import android.util.SparseArray; @@ -185,18 +186,29 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - final FluidResizeTaskPositioner taskPositioner = - new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController); + final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback( + windowDecoration, taskInfo); final CaptionTouchEventListener touchEventListener = - new CaptionTouchEventListener(taskInfo, taskPositioner); + new CaptionTouchEventListener(taskInfo, dragPositioningCallback); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); - windowDecoration.setDragPositioningCallback(taskPositioner); + windowDecoration.setDragPositioningCallback(dragPositioningCallback); windowDecoration.setDragDetector(touchEventListener.mDragDetector); windowDecoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */); setupCaptionColor(taskInfo, windowDecoration); } + private FluidResizeTaskPositioner createDragPositioningCallback( + CaptionWindowDecoration windowDecoration, RunningTaskInfo taskInfo) { + final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width(); + final int statusBarHeight = mDisplayController.getDisplayLayout(taskInfo.displayId) + .stableInsets().top; + final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth, + statusBarHeight); + return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, + mDisplayController, disallowedAreaForEndBounds); + } + private class CaptionTouchEventListener implements View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java index dffbedd197cf4..0b821844e46ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java @@ -33,7 +33,6 @@ import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager; import android.content.Context; -import android.content.res.Resources; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -41,7 +40,6 @@ import android.hardware.input.InputManager; import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.util.DisplayMetrics; import android.util.SparseArray; import android.view.Choreographer; import android.view.InputChannel; @@ -63,6 +61,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.R; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopModeStatus; @@ -392,10 +391,16 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { final int dragPointerIdx = e.findPointerIndex(mDragPointerId); + // Position of the task is calculated by subtracting the raw location of the + // motion event (the location of the motion relative to the display) by the + // location of the motion event relative to the task's bounds + final Point position = new Point( + (int) (e.getRawX(dragPointerIdx) - e.getX(dragPointerIdx)), + (int) (e.getRawY(dragPointerIdx) - e.getY(dragPointerIdx))); mDragPositioningCallback.onDragPositioningEnd( e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx)); mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo, - e.getRawY(dragPointerIdx))); + position)); final boolean wasDragging = mIsDragging; mIsDragging = false; return wasDragging; @@ -560,10 +565,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mDragToDesktopAnimationStarted = false; return; } else if (mDragToDesktopAnimationStarted) { - Point startPosition = new Point((int) ev.getX(), (int) ev.getY()); + Point position = new Point((int) ev.getX(), (int) ev.getY()); mDesktopTasksController.ifPresent( c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, - startPosition)); + position)); mDragToDesktopAnimationStarted = false; return; } @@ -615,11 +620,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { * Gets bounds of a scaled window centered relative to the screen bounds * @param scale the amount to scale to relative to the Screen Bounds */ - private Rect calculateFreeformBounds(float scale) { - final Resources resources = mContext.getResources(); - final DisplayMetrics metrics = resources.getDisplayMetrics(); - final int screenWidth = metrics.widthPixels; - final int screenHeight = metrics.heightPixels; + private Rect calculateFreeformBounds(int displayId, float scale) { + final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(displayId); + final int screenWidth = displayLayout.width(); + final int screenHeight = displayLayout.height(); final float adjustmentPercentage = (1f - scale) / 2; final Rect endBounds = new Rect((int) (screenWidth * adjustmentPercentage), @@ -648,7 +652,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); animator.setDuration(FREEFORM_ANIMATION_DURATION); final SurfaceControl sc = relevantDecor.mTaskSurface; - final Rect endBounds = calculateFreeformBounds(DRAG_FREEFORM_SCALE); + final Rect endBounds = calculateFreeformBounds(ev.getDisplayId(), DRAG_FREEFORM_SCALE); final Transaction t = mTransactionFactory.get(); final float diffX = endBounds.centerX() - ev.getX(); final float diffY = endBounds.top - ev.getY(); @@ -665,9 +669,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { animator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { - mDesktopTasksController.ifPresent(c -> - c.onDragPositioningEndThroughStatusBar(relevantDecor.mTaskInfo, - calculateFreeformBounds(FINAL_FREEFORM_SCALE))); + mDesktopTasksController.ifPresent( + c -> c.onDragPositioningEndThroughStatusBar( + relevantDecor.mTaskInfo, + calculateFreeformBounds(ev.getDisplayId(), FINAL_FREEFORM_SCALE))); } }); animator.start(); @@ -783,17 +788,8 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { mSyncQueue); mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration); - final DragPositioningCallback dragPositioningCallback; - if (!DesktopModeStatus.isVeiledResizeEnabled()) { - dragPositioningCallback = - new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, - mDisplayController, mDragStartListener); - } else { - windowDecoration.createResizeVeil(); - dragPositioningCallback = - new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration, - mDisplayController, mDragStartListener); - } + final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback( + windowDecoration, taskInfo); final DesktopModeTouchEventListener touchEventListener = new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback); @@ -805,7 +801,22 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { false /* applyStartTransactionOnDraw */); incrementEventReceiverTasks(taskInfo.displayId); } - + private DragPositioningCallback createDragPositioningCallback( + @NonNull DesktopModeWindowDecoration windowDecoration, + @NonNull RunningTaskInfo taskInfo) { + final int screenWidth = mDisplayController.getDisplayLayout(taskInfo.displayId).width(); + final Rect disallowedAreaForEndBounds = new Rect(0, 0, screenWidth, + getStatusBarHeight(taskInfo.displayId)); + if (!DesktopModeStatus.isVeiledResizeEnabled()) { + return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, + mDisplayController, disallowedAreaForEndBounds, mDragStartListener, + mTransactionFactory); + } else { + windowDecoration.createResizeVeil(); + return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration, + mDisplayController, disallowedAreaForEndBounds, mDragStartListener); + } + } private class DragStartListenerImpl implements DragPositioningCallbackUtility.DragStartListener { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java index ae93a43f78e93..09e29bcbcf9f7 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java @@ -25,6 +25,7 @@ import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; +import android.view.SurfaceControl; import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTaskOrganizer; @@ -51,9 +52,9 @@ public class DragPositioningCallbackUtility { } /** - * Based on type of drag and delta provided, calculate the new bounds to display for this task. + * Based on type of resize and delta provided, calculate the new bounds to display for this + * task. * @param ctrlType type of drag being performed - * @param hasMoved whether the current drag has moved on a prior input event * @param repositionTaskBounds the bounds the task is being repositioned to * @param taskBoundsAtDragStart the bounds of the task on the first drag input event * @param stableBounds bounds that represent the resize limit of this task @@ -62,16 +63,19 @@ public class DragPositioningCallbackUtility { * @param windowDecoration window decoration of the task being dragged * @return whether this method changed repositionTaskBounds */ - static boolean changeBounds(int ctrlType, boolean hasMoved, - Rect repositionTaskBounds, Rect taskBoundsAtDragStart, Rect stableBounds, - PointF delta, DisplayController displayController, WindowDecoration windowDecoration) { - // |mRepositionTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not - // true, we can compare it against |mTaskBoundsAtDragStart|. - final int oldLeft = hasMoved ? repositionTaskBounds.left : taskBoundsAtDragStart.left; - final int oldTop = hasMoved ? repositionTaskBounds.top : taskBoundsAtDragStart.top; - final int oldRight = hasMoved ? repositionTaskBounds.right : taskBoundsAtDragStart.right; - final int oldBottom = - hasMoved ? repositionTaskBounds.bottom : taskBoundsAtDragStart.bottom; + static boolean changeBounds(int ctrlType, Rect repositionTaskBounds, Rect taskBoundsAtDragStart, + Rect stableBounds, PointF delta, DisplayController displayController, + WindowDecoration windowDecoration) { + // If task is being dragged rather than resized, return since this method only handles + // with resizing + if (ctrlType == CTRL_TYPE_UNDEFINED) { + return false; + } + + final int oldLeft = repositionTaskBounds.left; + final int oldTop = repositionTaskBounds.top; + final int oldRight = repositionTaskBounds.right; + final int oldBottom = repositionTaskBounds.bottom; repositionTaskBounds.set(taskBoundsAtDragStart); @@ -101,10 +105,6 @@ public class DragPositioningCallbackUtility { repositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom) ? candidateBottom : oldBottom; } - if (ctrlType == CTRL_TYPE_UNDEFINED) { - repositionTaskBounds.offset((int) delta.x, (int) delta.y); - } - // If width or height are negative or less than the minimum width or height, revert the // respective bounds to use previous bound dimensions. if (repositionTaskBounds.width() < getMinWidth(displayController, windowDecoration)) { @@ -125,9 +125,27 @@ public class DragPositioningCallbackUtility { return true; } + /** + * Set bounds using a {@link SurfaceControl.Transaction}. + */ + static void setPositionOnDrag(WindowDecoration decoration, Rect repositionTaskBounds, + Rect taskBoundsAtDragStart, PointF repositionStartPoint, SurfaceControl.Transaction t, + float x, float y) { + updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, x, y); + t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, + repositionTaskBounds.top); + } + + static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart, + PointF repositionStartPoint, float x, float y) { + final float deltaX = x - repositionStartPoint.x; + final float deltaY = y - repositionStartPoint.y; + repositionTaskBounds.set(taskBoundsAtDragStart); + repositionTaskBounds.offset((int) deltaX, (int) deltaY); + } + /** * Apply a bounds change to a task. - * @param wct provided {@link WindowContainerTransaction} that may contain other changes * @param windowDecoration decor of task we are changing bounds for * @param taskBounds new bounds of this task * @param taskOrganizer applies the provided WindowContainerTransaction diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java index b366c809537f7..9bcb77f03abdb 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java @@ -18,11 +18,14 @@ package com.android.wm.shell.windowdecor; import android.graphics.PointF; import android.graphics.Rect; +import android.view.SurfaceControl; import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import java.util.function.Supplier; + /** * A task positioner that resizes/relocates task contents as it is dragged. * Utilizes {@link DragPositioningCallbackUtility} to determine new task bounds. @@ -30,27 +33,35 @@ import com.android.wm.shell.common.DisplayController; class FluidResizeTaskPositioner implements DragPositioningCallback { private final ShellTaskOrganizer mTaskOrganizer; private final WindowDecoration mWindowDecoration; + private final Supplier mTransactionSupplier; private DisplayController mDisplayController; private DragPositioningCallbackUtility.DragStartListener mDragStartListener; private final Rect mStableBounds = new Rect(); private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); + // If a task move (not resize) finishes in this region, the positioner will not attempt to + // finalize the bounds there using WCT#setBounds + private final Rect mDisallowedAreaForEndBounds = new Rect(); + private boolean mHasDragResized; private int mCtrlType; - private boolean mHasMoved; FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController) { - this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {}); + DisplayController displayController, Rect disallowedAreaForEndBounds) { + this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds, + dragStartListener -> {}, SurfaceControl.Transaction::new); } FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration, - DisplayController displayController, - DragPositioningCallbackUtility.DragStartListener dragStartListener) { + DisplayController displayController, Rect disallowedAreaForEndBounds, + DragPositioningCallbackUtility.DragStartListener dragStartListener, + Supplier supplier) { mTaskOrganizer = taskOrganizer; mWindowDecoration = windowDecoration; mDisplayController = displayController; + mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds); mDragStartListener = dragStartListener; + mTransactionSupplier = supplier; } @Override @@ -60,47 +71,66 @@ class FluidResizeTaskPositioner implements DragPositioningCallback { mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); mRepositionStartPoint.set(x, y); mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId); + mRepositionTaskBounds.set(mTaskBoundsAtDragStart); } @Override public void onDragPositioningMove(float x, float y) { final WindowContainerTransaction wct = new WindowContainerTransaction(); PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); - if (DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved, + if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, mDisplayController, mWindowDecoration)) { // The task is being resized, send the |dragResizing| hint to core with the first // bounds-change wct. - if (!mHasMoved && mCtrlType != CTRL_TYPE_UNDEFINED) { + if (!mHasDragResized) { // This is the first bounds change since drag resize operation started. wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */); } DragPositioningCallbackUtility.applyTaskBoundsChange(wct, mWindowDecoration, mRepositionTaskBounds, mTaskOrganizer); - mHasMoved = true; + mHasDragResized = true; + } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + DragPositioningCallbackUtility.setPositionOnDrag(mWindowDecoration, + mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, x, y); + t.apply(); } } @Override public void onDragPositioningEnd(float x, float y) { - // |mHasMoved| being false means there is no real change to the task bounds in WM core, so - // we don't need a WCT to finish it. - if (mHasMoved) { + // If task has been resized or task was dragged into area outside of + // mDisallowedAreaForEndBounds, apply WCT to finish it. + if (isResizing() && mHasDragResized) { final WindowContainerTransaction wct = new WindowContainerTransaction(); wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */); PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); - if (DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved, - mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, - mDisplayController, mWindowDecoration)) { + if (DragPositioningCallbackUtility.changeBounds(mCtrlType, mRepositionTaskBounds, + mTaskBoundsAtDragStart, mStableBounds, delta, mDisplayController, + mWindowDecoration)) { wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); } mTaskOrganizer.applyTransaction(wct); + } else if (mCtrlType == CTRL_TYPE_UNDEFINED + && !mDisallowedAreaForEndBounds.contains((int) x, (int) y)) { + final WindowContainerTransaction wct = new WindowContainerTransaction(); + DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, + mTaskBoundsAtDragStart, mRepositionStartPoint, x, y); + wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds); + mTaskOrganizer.applyTransaction(wct); } mTaskBoundsAtDragStart.setEmpty(); mRepositionStartPoint.set(0, 0); mCtrlType = CTRL_TYPE_UNDEFINED; - mHasMoved = false; + mHasDragResized = false; } + + private boolean isResizing() { + return (mCtrlType & CTRL_TYPE_TOP) != 0 || (mCtrlType & CTRL_TYPE_BOTTOM) != 0 + || (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0; + } + } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java index 1d416c65851ba..5253fb84a4be1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java @@ -18,11 +18,14 @@ package com.android.wm.shell.windowdecor; import android.graphics.PointF; import android.graphics.Rect; +import android.view.SurfaceControl; import android.window.WindowContainerTransaction; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; +import java.util.function.Supplier; + /** * A task positioner that also takes into account resizing a * {@link com.android.wm.shell.windowdecor.ResizeVeil}. @@ -39,17 +42,32 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback { private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mRepositionStartPoint = new PointF(); private final Rect mRepositionTaskBounds = new Rect(); + // If a task move (not resize) finishes in this region, the positioner will not attempt to + // finalize the bounds there using WCT#setBounds + private final Rect mDisallowedAreaForEndBounds = new Rect(); + private final Supplier mTransactionSupplier; + private boolean mHasDragResized; private int mCtrlType; - private boolean mHasMoved; - public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, DesktopModeWindowDecoration windowDecoration, DisplayController displayController, + Rect disallowedAreaForEndBounds, DragPositioningCallbackUtility.DragStartListener dragStartListener) { + this(taskOrganizer, windowDecoration, displayController, disallowedAreaForEndBounds, + dragStartListener, SurfaceControl.Transaction::new); + } + + public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, + DesktopModeWindowDecoration windowDecoration, DisplayController displayController, + Rect disallowedAreaForEndBounds, + DragPositioningCallbackUtility.DragStartListener dragStartListener, + Supplier supplier) { mTaskOrganizer = taskOrganizer; mDesktopWindowDecoration = windowDecoration; mDisplayController = displayController; mDragStartListener = dragStartListener; + mDisallowedAreaForEndBounds.set(disallowedAreaForEndBounds); + mTransactionSupplier = supplier; } @Override @@ -58,27 +76,28 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback { mTaskBoundsAtDragStart.set( mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds()); mRepositionStartPoint.set(x, y); - if (mCtrlType != CTRL_TYPE_UNDEFINED) { + if (isResizing()) { mDesktopWindowDecoration.showResizeVeil(); } - mHasMoved = false; + mHasDragResized = false; mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId); + mRepositionTaskBounds.set(mTaskBoundsAtDragStart); } @Override public void onDragPositioningMove(float x, float y) { PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); - if (DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved, + if (isResizing() && DragPositioningCallbackUtility.changeBounds(mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, mDisplayController, mDesktopWindowDecoration)) { - if (mCtrlType != CTRL_TYPE_UNDEFINED) { - mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds); - } else { - DragPositioningCallbackUtility.applyTaskBoundsChange( - new WindowContainerTransaction(), mDesktopWindowDecoration, - mRepositionTaskBounds, mTaskOrganizer); - } - mHasMoved = true; + mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds); + mHasDragResized = true; + } else if (mCtrlType == CTRL_TYPE_UNDEFINED) { + final SurfaceControl.Transaction t = mTransactionSupplier.get(); + DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration, + mRepositionTaskBounds, mTaskBoundsAtDragStart, mRepositionStartPoint, t, + x, y); + t.apply(); } } @@ -86,22 +105,35 @@ public class VeiledResizeTaskPositioner implements DragPositioningCallback { public void onDragPositioningEnd(float x, float y) { PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint); - if (mHasMoved) { - DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved, - mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta, - mDisplayController, mDesktopWindowDecoration); - DragPositioningCallbackUtility.applyTaskBoundsChange( - new WindowContainerTransaction(), mDesktopWindowDecoration, - mRepositionTaskBounds, mTaskOrganizer); - } - // TODO: (b/279062291) Synchronize the start of hide to the end of the draw triggered above. - if (mCtrlType != CTRL_TYPE_UNDEFINED) { + if (isResizing()) { + if (mHasDragResized) { + DragPositioningCallbackUtility.changeBounds( + mCtrlType, mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, + delta, mDisplayController, mDesktopWindowDecoration); + DragPositioningCallbackUtility.applyTaskBoundsChange( + new WindowContainerTransaction(), mDesktopWindowDecoration, + mRepositionTaskBounds, mTaskOrganizer); + } + // TODO: (b/279062291) Synchronize the start of hide to the end of the draw triggered + // above. mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds); mDesktopWindowDecoration.hideResizeVeil(); + } else if (!mDisallowedAreaForEndBounds.contains((int) x, (int) y)) { + DragPositioningCallbackUtility.updateTaskBounds(mRepositionTaskBounds, + mTaskBoundsAtDragStart, mRepositionStartPoint, x, y); + DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(), + mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer); } + mCtrlType = CTRL_TYPE_UNDEFINED; mTaskBoundsAtDragStart.setEmpty(); mRepositionStartPoint.set(0, 0); - mHasMoved = false; + mHasDragResized = false; } + + private boolean isResizing() { + return (mCtrlType & CTRL_TYPE_TOP) != 0 || (mCtrlType & CTRL_TYPE_BOTTOM) != 0 + || (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0; + } + } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java index 265b10df1945a..0d0a08cb0ffba 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/ExitDesktopTaskTransitionHandlerTest.java @@ -31,6 +31,7 @@ import android.app.ActivityManager; import android.app.WindowConfiguration; import android.content.Context; import android.content.res.Resources; +import android.graphics.Point; import android.os.IBinder; import android.util.DisplayMetrics; import android.view.SurfaceControl; @@ -75,6 +76,7 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase { @Mock ShellExecutor mExecutor; + private Point mPoint; private ExitDesktopTaskTransitionHandler mExitDesktopTaskTransitionHandler; @Before @@ -90,6 +92,7 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase { mExitDesktopTaskTransitionHandler = new ExitDesktopTaskTransitionHandler(mTransitions, mContext); + mPoint = new Point(0, 0); } @Test @@ -100,7 +103,8 @@ public class ExitDesktopTaskTransitionHandlerTest extends ShellTestCase { doReturn(mToken).when(mTransitions) .startTransition(transitionType, wct, mExitDesktopTaskTransitionHandler); - mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct, null); + mExitDesktopTaskTransitionHandler.startTransition(transitionType, wct, mPoint, + null); TransitionInfo.Change change = createChange(WindowManager.TRANSIT_CHANGE, taskId, WINDOWING_MODE_FULLSCREEN); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java index 9a90996b786c5..4c27706a39a3a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModelTests.java @@ -20,6 +20,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -29,6 +30,7 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.app.WindowConfiguration; +import android.graphics.Rect; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.hardware.input.InputManager; @@ -47,6 +49,7 @@ import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.TestRunningTaskInfoBuilder; import com.android.wm.shell.common.DisplayController; +import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.SyncTransactionQueue; import com.android.wm.shell.desktopmode.DesktopModeController; import com.android.wm.shell.desktopmode.DesktopTasksController; @@ -68,6 +71,7 @@ import java.util.function.Supplier; public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { private static final String TAG = "DesktopModeWindowDecorViewModelTests"; + private static final Rect STABLE_INSETS = new Rect(0, 100, 0, 0); @Mock private DesktopModeWindowDecoration mDesktopModeWindowDecoration; @Mock private DesktopModeWindowDecoration.Factory mDesktopModeWindowDecorFactory; @@ -76,6 +80,7 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { @Mock private Choreographer mMainChoreographer; @Mock private ShellTaskOrganizer mTaskOrganizer; @Mock private DisplayController mDisplayController; + @Mock private DisplayLayout mDisplayLayout; @Mock private SplitScreenController mSplitScreenController; @Mock private SyncTransactionQueue mSyncQueue; @Mock private DesktopModeController mDesktopModeController; @@ -113,6 +118,8 @@ public class DesktopModeWindowDecorViewModelTests extends ShellTestCase { .when(mDesktopModeWindowDecorFactory) .create(any(), any(), any(), any(), any(), any(), any(), any()); doReturn(mTransaction).when(mTransactionFactory).get(); + doReturn(mDisplayLayout).when(mDisplayController).getDisplayLayout(anyInt()); + doReturn(STABLE_INSETS).when(mDisplayLayout).stableInsets(); when(mMockInputMonitorFactory.create(any(), any())).thenReturn(mInputMonitor); // InputChannel cannot be mocked because it passes to InputEventReceiver. diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt index 348b3659e8648..de46b31879ed0 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt @@ -85,7 +85,7 @@ class DragPositioningCallbackUtilityTest { @Test fun testChangeBoundsDoesNotChangeHeightWhenLessThanMin() { val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat()) - val repositionTaskBounds = Rect() + val repositionTaskBounds = Rect(STARTING_BOUNDS) // Resize to width of 95px and height of 5px with min width of 10px val newX = STARTING_BOUNDS.right.toFloat() - 5 @@ -93,8 +93,8 @@ class DragPositioningCallbackUtilityTest { val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, - false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, - mockDisplayController, mockWindowDecoration) + repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, + mockDisplayController, mockWindowDecoration) assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left) assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top) @@ -105,7 +105,7 @@ class DragPositioningCallbackUtilityTest { @Test fun testChangeBoundsDoesNotChangeWidthWhenLessThanMin() { val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat()) - val repositionTaskBounds = Rect() + val repositionTaskBounds = Rect(STARTING_BOUNDS) // Resize to height of 95px and width of 5px with min width of 10px val newX = STARTING_BOUNDS.right.toFloat() - 95 @@ -113,8 +113,8 @@ class DragPositioningCallbackUtilityTest { val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, - false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, - mockDisplayController, mockWindowDecoration) + repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, + mockDisplayController, mockWindowDecoration) assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left) assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 5) @@ -125,7 +125,7 @@ class DragPositioningCallbackUtilityTest { @Test fun testChangeBoundsDoesNotChangeHeightWhenNegative() { val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat()) - val repositionTaskBounds = Rect() + val repositionTaskBounds = Rect(STARTING_BOUNDS) // Resize to width of 95px and width of -5px with minimum of 10px val newX = STARTING_BOUNDS.right.toFloat() - 5 @@ -133,8 +133,8 @@ class DragPositioningCallbackUtilityTest { val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, - false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, - mockDisplayController, mockWindowDecoration) + repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, + mockDisplayController, mockWindowDecoration) assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left) assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top) @@ -145,7 +145,7 @@ class DragPositioningCallbackUtilityTest { @Test fun testChangeBoundsRunsWhenResizeBoundsValid() { val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat()) - val repositionTaskBounds = Rect() + val repositionTaskBounds = Rect(STARTING_BOUNDS) // Shrink to height 20px and width 20px with both min height/width equal to 10px val newX = STARTING_BOUNDS.right.toFloat() - 80 @@ -153,7 +153,7 @@ class DragPositioningCallbackUtilityTest { val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, - false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, + repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController, mockWindowDecoration) assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left) assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 80) @@ -164,7 +164,7 @@ class DragPositioningCallbackUtilityTest { @Test fun testChangeBoundsDoesNotRunWithNegativeHeightAndWidth() { val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat()) - val repositionTaskBounds = Rect() + val repositionTaskBounds = Rect(STARTING_BOUNDS) // Shrink to height -5px and width -5px with both min height/width equal to 10px val newX = STARTING_BOUNDS.right.toFloat() - 105 val newY = STARTING_BOUNDS.top.toFloat() + 105 @@ -172,7 +172,7 @@ class DragPositioningCallbackUtilityTest { val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, - false /* hasMoved */, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, + repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController, mockWindowDecoration) assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left) assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top) @@ -185,21 +185,21 @@ class DragPositioningCallbackUtilityTest { var hasMoved = false val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat()) - val repositionTaskBounds = Rect() + val repositionTaskBounds = Rect(STARTING_BOUNDS) // Initial resize to width and height 110px. var newX = STARTING_BOUNDS.right.toFloat() + 10 var newY = STARTING_BOUNDS.bottom.toFloat() + 10 var delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, - hasMoved, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, - mockDisplayController, mockWindowDecoration)) + repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, + mockDisplayController, mockWindowDecoration)) hasMoved = true // Resize width to 120px, height to disallowed area which should not result in a change. newX += 10 newY = DISALLOWED_RESIZE_AREA.top.toFloat() delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint) assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, - hasMoved, repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, + repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController, mockWindowDecoration)) assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left) assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt index 5bea8f2d1c452..282a19e8e9a5f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt @@ -6,14 +6,16 @@ import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner import android.view.Display +import android.view.SurfaceControl import android.window.WindowContainerToken import android.window.WindowContainerTransaction import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING import androidx.test.filters.SmallTest -import com.android.wm.shell.common.DisplayController -import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayLayout +import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED @@ -21,12 +23,14 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.any import org.mockito.Mockito.argThat import org.mockito.Mockito.never import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import java.util.function.Supplier +import org.mockito.Mockito.`when` as whenever /** * Tests for [FluidResizeTaskPositioner]. @@ -56,6 +60,10 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { private lateinit var mockDisplayLayout: DisplayLayout @Mock private lateinit var mockDisplay: Display + @Mock + private lateinit var mockTransactionFactory: Supplier + @Mock + private lateinit var mockTransaction: SurfaceControl.Transaction private lateinit var taskPositioner: FluidResizeTaskPositioner @@ -68,8 +76,10 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { mockShellTaskOrganizer, mockWindowDecoration, mockDisplayController, - mockDragStartListener - ) + DISALLOWED_AREA_FOR_END_BOUNDS, + mockDragStartListener, + mockTransactionFactory + ) whenever(taskToken.asBinder()).thenReturn(taskBinder) whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout) @@ -77,6 +87,8 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } + `when`(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS) + `when`(mockTransactionFactory.get()).thenReturn(mockTransaction) mockWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = TASK_ID @@ -236,6 +248,318 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { }) } + @Test + fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenLessThanMin() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + // Resize to width of 95px and height of 5px with min width of 10px + val newX = STARTING_BOUNDS.right.toFloat() - 5 + val newY = STARTING_BOUNDS.top.toFloat() + 95 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) + != 0) && change.configuration.windowConfiguration.bounds.top == + STARTING_BOUNDS.top && + change.configuration.windowConfiguration.bounds.bottom == + STARTING_BOUNDS.bottom + } + }) + } + + @Test + fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenLessThanMin() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + // Resize to height of 95px and width of 5px with min width of 10px + val newX = STARTING_BOUNDS.right.toFloat() - 95 + val newY = STARTING_BOUNDS.top.toFloat() + 5 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) + != 0) && change.configuration.windowConfiguration.bounds.right == + STARTING_BOUNDS.right && + change.configuration.windowConfiguration.bounds.left == + STARTING_BOUNDS.left + } + }) + } + + @Test + fun testDragResize_resize_setBoundsDoesNotChangeHeightWhenNegative() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + // Resize to height of -5px and width of 95px + val newX = STARTING_BOUNDS.right.toFloat() - 5 + val newY = STARTING_BOUNDS.top.toFloat() + 105 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) + != 0) && change.configuration.windowConfiguration.bounds.top == + STARTING_BOUNDS.top && + change.configuration.windowConfiguration.bounds.bottom == + STARTING_BOUNDS.bottom + } + }) + } + + @Test + fun testDragResize_resize_setBoundsDoesNotChangeWidthWhenNegative() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + // Resize to width of -5px and height of 95px + val newX = STARTING_BOUNDS.right.toFloat() - 105 + val newY = STARTING_BOUNDS.top.toFloat() + 5 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) + != 0) && change.configuration.windowConfiguration.bounds.right == + STARTING_BOUNDS.right && + change.configuration.windowConfiguration.bounds.left == + STARTING_BOUNDS.left + } + }) + } + + @Test + fun testDragResize_resize_setBoundsRunsWhenResizeBoundsValid() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + // Shrink to height 20px and width 20px with both min height/width equal to 10px + val newX = STARTING_BOUNDS.right.toFloat() - 80 + val newY = STARTING_BOUNDS.top.toFloat() + 80 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + + @Test + fun testDragResize_resize_setBoundsDoesNotRunWithNegativeHeightAndWidth() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + // Shrink to height 5px and width 5px with both min height/width equal to 10px + val newX = STARTING_BOUNDS.right.toFloat() - 95 + val newY = STARTING_BOUNDS.top.toFloat() + 95 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + + @Test + fun testDragResize_resize_useDefaultMinWhenMinWidthInvalid() { + mockWindowDecoration.mTaskInfo.minWidth = -1 + + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + // Shrink to width and height of 3px with invalid minWidth = -1 and defaultMinSize = 5px + val newX = STARTING_BOUNDS.right.toFloat() - 97 + val newY = STARTING_BOUNDS.top.toFloat() + 97 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + + @Test + fun testDragResize_resize_useMinWidthWhenValid() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_TOP, // Resize right and top + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + // Shrink to width and height of 7px with valid minWidth = 10px and defaultMinSize = 5px + val newX = STARTING_BOUNDS.right.toFloat() - 93 + val newY = STARTING_BOUNDS.top.toFloat() + 93 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + + fun testDragResize_toDisallowedBounds_freezesAtLimit() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM, // Resize right-bottom corner + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.bottom.toFloat() + ) + + // Resize the task by 10px to the right and bottom, a valid destination + val newBounds = Rect( + STARTING_BOUNDS.left, + STARTING_BOUNDS.top, + STARTING_BOUNDS.right + 10, + STARTING_BOUNDS.bottom + 10) + taskPositioner.onDragPositioningMove( + newBounds.right.toFloat(), + newBounds.bottom.toFloat() + ) + + // Resize the task by another 10px to the right (allowed) and to just in the disallowed + // area of the Y coordinate. + val newBounds2 = Rect( + newBounds.left, + newBounds.top, + newBounds.right + 10, + DISALLOWED_RESIZE_AREA.top + ) + taskPositioner.onDragPositioningMove( + newBounds2.right.toFloat(), + newBounds2.bottom.toFloat() + ) + + taskPositioner.onDragPositioningEnd(newBounds2.right.toFloat(), newBounds2.bottom.toFloat()) + + // The first resize falls in the allowed area, verify there's a change for it. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && change.ofBounds(newBounds) + } + }) + // The second resize falls in the disallowed area, verify there's no change for it. + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && change.ofBounds(newBounds2) + } + }) + // Instead, there should be a change for its allowed portion (the X movement) with the Y + // staying frozen in the last valid resize position. + verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && change.ofBounds( + Rect( + newBounds2.left, + newBounds2.top, + newBounds2.right, + newBounds.bottom // Stayed at the first resize destination. + ) + ) + } + }) + } + + @Test + fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, // drag + STARTING_BOUNDS.right.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + val newX = STARTING_BOUNDS.right.toFloat() + 5 + val newY = STARTING_BOUNDS.top.toFloat() + 5 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean { return ((windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) && bounds == configuration.windowConfiguration.bounds @@ -251,6 +575,8 @@ class FluidResizeTaskPositionerTest : ShellTestCase() { private const val NAVBAR_HEIGHT = 50 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) private val STARTING_BOUNDS = Rect(0, 0, 100, 100) + private val STABLE_INSETS = Rect(0, 50, 0, 0) + private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 300, 300) private val DISALLOWED_RESIZE_AREA = Rect( DISPLAY_BOUNDS.left, DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt index 498082bd53e59..217bdbb07447f 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt @@ -21,12 +21,13 @@ import android.graphics.Rect import android.os.IBinder import android.testing.AndroidTestingRunner import android.view.Display +import android.view.SurfaceControl import android.window.WindowContainerToken import androidx.test.filters.SmallTest -import com.android.wm.shell.common.DisplayController -import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase +import com.android.wm.shell.common.DisplayController +import com.android.wm.shell.common.DisplayLayout import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED @@ -34,13 +35,16 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.any import org.mockito.Mockito.argThat +import org.mockito.Mockito.eq import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations +import java.util.function.Supplier +import org.mockito.Mockito.`when` as whenever /** * Tests for [VeiledResizeTaskPositioner]. @@ -70,6 +74,10 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private lateinit var mockDisplayLayout: DisplayLayout @Mock private lateinit var mockDisplay: Display + @Mock + private lateinit var mockTransactionFactory: Supplier + @Mock + private lateinit var mockTransaction: SurfaceControl.Transaction private lateinit var taskPositioner: VeiledResizeTaskPositioner @@ -82,7 +90,9 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { mockShellTaskOrganizer, mockDesktopWindowDecoration, mockDisplayController, - mockDragStartListener + DISALLOWED_AREA_FOR_END_BOUNDS, + mockDragStartListener, + mockTransactionFactory ) whenever(taskToken.asBinder()).thenReturn(taskBinder) @@ -91,6 +101,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { whenever(mockDisplayLayout.getStableBounds(any())).thenAnswer { i -> (i.arguments.first() as Rect).set(STABLE_BOUNDS) } + `when`(mockTransactionFactory.get()).thenReturn(mockTransaction) mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply { taskId = TASK_ID @@ -125,36 +136,31 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { fun testDragResize_movesTask_doesNotShowResizeVeil() { taskPositioner.onDragPositioningStart( CTRL_TYPE_UNDEFINED, - STARTING_BOUNDS.left.toFloat() + 50, + STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat() ) taskPositioner.onDragPositioningMove( STARTING_BOUNDS.left.toFloat() + 60, - STARTING_BOUNDS.top.toFloat() + 10 + STARTING_BOUNDS.top.toFloat() + 100 ) val rectAfterMove = Rect(STARTING_BOUNDS) - rectAfterMove.left += 10 - rectAfterMove.right += 10 - rectAfterMove.top += 10 - rectAfterMove.bottom += 10 - verify(mockShellTaskOrganizer).applyTransaction(argThat { wct -> - return@argThat wct.changes.any { (token, change) -> - token == taskBinder && - (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 && - change.configuration.windowConfiguration.bounds == rectAfterMove - } - }) + rectAfterMove.left += 60 + rectAfterMove.right += 60 + rectAfterMove.top += 100 + rectAfterMove.bottom += 100 + verify(mockTransaction).setPosition(any(), eq(rectAfterMove.left.toFloat()), + eq(rectAfterMove.top.toFloat())) taskPositioner.onDragPositioningEnd( STARTING_BOUNDS.left.toFloat() + 70, STARTING_BOUNDS.top.toFloat() + 20 ) - val rectAfterEnd = Rect(rectAfterMove) - rectAfterEnd.left += 10 - rectAfterEnd.top += 10 - rectAfterEnd.right += 10 - rectAfterEnd.bottom += 10 + val rectAfterEnd = Rect(STARTING_BOUNDS) + rectAfterEnd.left += 70 + rectAfterEnd.right += 70 + rectAfterEnd.top += 20 + rectAfterEnd.bottom += 20 verify(mockDesktopWindowDecoration, never()).createResizeVeil() verify(mockDesktopWindowDecoration, never()).hideResizeVeil() @@ -239,6 +245,32 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { }) } + + @Test + fun testDragResize_drag_setBoundsNotRunIfDragEndsInDisallowedEndArea() { + taskPositioner.onDragPositioningStart( + CTRL_TYPE_UNDEFINED, // drag + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + val newX = STARTING_BOUNDS.left.toFloat() + 5 + val newY = STARTING_BOUNDS.top.toFloat() + 5 + taskPositioner.onDragPositioningMove( + newX, + newY + ) + + taskPositioner.onDragPositioningEnd(newX, newY) + + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + companion object { private const val TASK_ID = 5 private const val MIN_WIDTH = 10 @@ -249,6 +281,7 @@ class VeiledResizeTaskPositionerTest : ShellTestCase() { private const val NAVBAR_HEIGHT = 50 private val DISPLAY_BOUNDS = Rect(0, 0, 2400, 1600) private val STARTING_BOUNDS = Rect(0, 0, 100, 100) + private val DISALLOWED_AREA_FOR_END_BOUNDS = Rect(0, 0, 50, 50) private val STABLE_BOUNDS = Rect( DISPLAY_BOUNDS.left, DISPLAY_BOUNDS.top,