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
This commit is contained in:
Maryam Dehaini
2023-04-06 15:52:10 -07:00
parent ec5540c745
commit 8aa9d4d288
13 changed files with 626 additions and 152 deletions

View File

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

View File

@@ -58,7 +58,7 @@ public class EnterDesktopTaskTransitionHandler implements Transitions.Transition
public static final int FREEFORM_ANIMATION_DURATION = 336;
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Point mStartPosition;
private Point mPosition;
private Consumer<SurfaceControl.Transaction> 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<SurfaceControl.Transaction> 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();

View File

@@ -57,6 +57,7 @@ public class ExitDesktopTaskTransitionHandler implements Transitions.TransitionH
private final List<IBinder> mPendingTransitionTokens = new ArrayList<>();
private Consumer<SurfaceControl.Transaction> mOnAnimationFinishedCallback;
private Supplier<SurfaceControl.Transaction> 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<SurfaceControl.Transaction> 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();

View File

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

View File

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

View File

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

View File

@@ -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<SurfaceControl.Transaction> 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<SurfaceControl.Transaction> 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;
}
}

View File

@@ -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<SurfaceControl.Transaction> 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<SurfaceControl.Transaction> 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;
}
}

View File

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

View File

@@ -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.

View File

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

View File

@@ -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<SurfaceControl.Transaction>
@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,

View File

@@ -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<SurfaceControl.Transaction>
@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,