Merge "Update positioning during drag using SurfaceControl.Transaction" into udc-dev

This commit is contained in:
Maryam Dehaini
2023-05-03 17:39:20 +00:00
committed by Android (Google) Code Review
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,