diff --git a/core/java/android/window/TaskConstants.java b/core/java/android/window/TaskConstants.java
index e18fd50dc38e8..69d79b449615e 100644
--- a/core/java/android/window/TaskConstants.java
+++ b/core/java/android/window/TaskConstants.java
@@ -73,6 +73,13 @@ public class TaskConstants {
*/
public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 4 * TASK_CHILD_LAYER_REGION_SIZE;
+
+ /**
+ * Veil to cover task surface and other window decorations during resizes.
+ * @hide
+ */
+ public static final int TASK_CHILD_LAYER_RESIZE_VEIL = 6 * TASK_CHILD_LAYER_REGION_SIZE;
+
/**
* Z-orders of task child layers other than activities, task fragments and layers interleaved
* with them, e.g. IME windows. [-10000, 10000) is reserved for these layers.
@@ -84,7 +91,8 @@ public class TaskConstants {
TASK_CHILD_LAYER_COMPAT_UI,
TASK_CHILD_LAYER_WINDOW_DECORATIONS,
TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY,
- TASK_CHILD_LAYER_TASK_OVERLAY
+ TASK_CHILD_LAYER_TASK_OVERLAY,
+ TASK_CHILD_LAYER_RESIZE_VEIL
})
public @interface TaskChildLayer {}
}
diff --git a/libs/WindowManager/Shell/res/drawable/desktop_mode_resize_veil_background.xml b/libs/WindowManager/Shell/res/drawable/desktop_mode_resize_veil_background.xml
new file mode 100644
index 0000000000000..1f3e3a4c5b224
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/desktop_mode_resize_veil_background.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_resize_veil.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_resize_veil.xml
new file mode 100644
index 0000000000000..a4bbd8998cc5d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_resize_veil.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 055949fd8c89d..d1760ed755475 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -42,6 +42,12 @@ public class DesktopModeStatus {
private static final boolean IS_PROTO2_ENABLED = SystemProperties.getBoolean(
"persist.wm.debug.desktop_mode_2", false);
+ /**
+ * Flag to indicate whether task resizing is veiled.
+ */
+ private static final boolean IS_VEILED_RESIZE_ENABLED = SystemProperties.getBoolean(
+ "persist.wm.debug.desktop_veiled_resizing", true);
+
/**
* Return {@code true} if desktop mode support is enabled
*/
@@ -64,6 +70,13 @@ public class DesktopModeStatus {
return isProto1Enabled() || isProto2Enabled();
}
+ /**
+ * Return {@code true} if veiled resizing is active. If false, fluid resizing is used.
+ */
+ public static boolean isVeiledResizeEnabled() {
+ return IS_VEILED_RESIZE_ENABLED;
+ }
+
/**
* Check if desktop mode is active
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index e8a6a159cb199..212ad9ebceafd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -185,8 +185,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- final TaskPositioner taskPositioner =
- new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController);
+ final FluidResizeTaskPositioner taskPositioner =
+ new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController);
final CaptionTouchEventListener touchEventListener =
new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 8fb56fc2561b5..4ef3350d06a46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -101,7 +101,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private final SparseArray mWindowDecorByTaskId =
new SparseArray<>();
- private final DragListenerImpl mDragStartListener = new DragListenerImpl();
+ private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
private final InputMonitorFactory mInputMonitorFactory;
private TaskOperations mTaskOperations;
private final Supplier mTransactionFactory;
@@ -777,21 +777,32 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mSyncQueue);
mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
- final TaskPositioner taskPositioner =
- new TaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
- mDragStartListener);
+ final DragPositioningCallback dragPositioningCallback;
+ if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+ dragPositioningCallback =
+ new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
+ mDisplayController, mDragStartListener);
+ } else {
+ windowDecoration.createResizeVeil();
+ dragPositioningCallback =
+ new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
+ mDisplayController, mDragStartListener);
+ }
final DesktopModeTouchEventListener touchEventListener =
- new DesktopModeTouchEventListener(taskInfo, taskPositioner);
+ new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
+
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setCornersListener(mCornersListener);
- windowDecoration.setDragPositioningCallback(taskPositioner);
+ windowDecoration.setDragPositioningCallback(dragPositioningCallback);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT,
false /* applyStartTransactionOnDraw */);
incrementEventReceiverTasks(taskInfo.displayId);
}
- private class DragListenerImpl implements TaskPositioner.DragStartListener {
+
+ private class DragStartListenerImpl
+ implements DragPositioningCallbackUtility.DragStartListener {
@Override
public void onDragStart(int taskId) {
mWindowDecorByTaskId.get(taskId).closeHandleMenu();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 619e963aaea64..a2b9a3188d834 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -30,6 +30,7 @@ import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.os.Handler;
@@ -91,6 +92,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration stableBounds.left)
+ ? candidateLeft : oldLeft;
+ }
+ if ((ctrlType & CTRL_TYPE_RIGHT) != 0) {
+ final int candidateRight = repositionTaskBounds.right + (int) delta.x;
+ repositionTaskBounds.right = (candidateRight < stableBounds.right)
+ ? candidateRight : oldRight;
+ }
+ if ((ctrlType & CTRL_TYPE_TOP) != 0) {
+ final int candidateTop = repositionTaskBounds.top + (int) delta.y;
+ repositionTaskBounds.top = (candidateTop > stableBounds.top)
+ ? candidateTop : oldTop;
+ }
+ if ((ctrlType & CTRL_TYPE_BOTTOM) != 0) {
+ final int candidateBottom = repositionTaskBounds.bottom + (int) delta.y;
+ 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)) {
+ repositionTaskBounds.right = oldRight;
+ repositionTaskBounds.left = oldLeft;
+ }
+ if (repositionTaskBounds.height() < getMinHeight(displayController, windowDecoration)) {
+ repositionTaskBounds.top = oldTop;
+ repositionTaskBounds.bottom = oldBottom;
+ }
+ // If there are no changes to the bounds after checking new bounds against minimum width
+ // and height, do not set bounds and return false
+ if (oldLeft == repositionTaskBounds.left && oldTop == repositionTaskBounds.top
+ && oldRight == repositionTaskBounds.right
+ && oldBottom == repositionTaskBounds.bottom) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * 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
+ */
+ static void applyTaskBoundsChange(WindowContainerTransaction wct,
+ WindowDecoration windowDecoration, Rect taskBounds, ShellTaskOrganizer taskOrganizer) {
+ wct.setBounds(windowDecoration.mTaskInfo.token, taskBounds);
+ taskOrganizer.applyTransaction(wct);
+ }
+
+ private static float getMinWidth(DisplayController displayController,
+ WindowDecoration windowDecoration) {
+ return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize(displayController,
+ windowDecoration)
+ : windowDecoration.mTaskInfo.minWidth;
+ }
+
+ private static float getMinHeight(DisplayController displayController,
+ WindowDecoration windowDecoration) {
+ return windowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinSize(displayController,
+ windowDecoration)
+ : windowDecoration.mTaskInfo.minHeight;
+ }
+
+ private static float getDefaultMinSize(DisplayController displayController,
+ WindowDecoration windowDecoration) {
+ float density = displayController.getDisplayLayout(windowDecoration.mTaskInfo.displayId)
+ .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ return windowDecoration.mTaskInfo.defaultMinSize * density;
+ }
+
+ interface DragStartListener {
+ /**
+ * Inform the implementing class that a drag resize has started
+ * @param taskId id of this positioner's {@link WindowDecoration}
+ */
+ void onDragStart(int taskId);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
index 34c8c087cf957..287d861872885 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -21,6 +21,11 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_LEFT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT;
+import static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP;
+
import android.content.Context;
import android.graphics.Rect;
import android.graphics.Region;
@@ -367,7 +372,7 @@ class DragResizeInputListener implements AutoCloseable {
return calculateResizeHandlesCtrlType(x, y) != 0;
}
- @TaskPositioner.CtrlType
+ @DragPositioningCallback.CtrlType
private int calculateCtrlType(boolean isTouch, float x, float y) {
if (isTouch) {
return calculateCornersCtrlType(x, y);
@@ -375,62 +380,62 @@ class DragResizeInputListener implements AutoCloseable {
return calculateResizeHandlesCtrlType(x, y);
}
- @TaskPositioner.CtrlType
+ @DragPositioningCallback.CtrlType
private int calculateResizeHandlesCtrlType(float x, float y) {
int ctrlType = 0;
if (x < 0) {
- ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
+ ctrlType |= CTRL_TYPE_LEFT;
}
if (x > mTaskWidth) {
- ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
+ ctrlType |= CTRL_TYPE_RIGHT;
}
if (y < 0) {
- ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
+ ctrlType |= CTRL_TYPE_TOP;
}
if (y > mTaskHeight) {
- ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
+ ctrlType |= CTRL_TYPE_BOTTOM;
}
return ctrlType;
}
- @TaskPositioner.CtrlType
+ @DragPositioningCallback.CtrlType
private int calculateCornersCtrlType(float x, float y) {
int xi = (int) x;
int yi = (int) y;
if (mLeftTopCornerBounds.contains(xi, yi)) {
- return TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP;
+ return CTRL_TYPE_LEFT | CTRL_TYPE_TOP;
}
if (mLeftBottomCornerBounds.contains(xi, yi)) {
- return TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM;
+ return CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM;
}
if (mRightTopCornerBounds.contains(xi, yi)) {
- return TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP;
+ return CTRL_TYPE_RIGHT | CTRL_TYPE_TOP;
}
if (mRightBottomCornerBounds.contains(xi, yi)) {
- return TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM;
+ return CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM;
}
return 0;
}
private void updateCursorType(float x, float y) {
- @TaskPositioner.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
+ @DragPositioningCallback.CtrlType int ctrlType = calculateResizeHandlesCtrlType(x, y);
int cursorType = PointerIcon.TYPE_DEFAULT;
switch (ctrlType) {
- case TaskPositioner.CTRL_TYPE_LEFT:
- case TaskPositioner.CTRL_TYPE_RIGHT:
+ case CTRL_TYPE_LEFT:
+ case CTRL_TYPE_RIGHT:
cursorType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
break;
- case TaskPositioner.CTRL_TYPE_TOP:
- case TaskPositioner.CTRL_TYPE_BOTTOM:
+ case CTRL_TYPE_TOP:
+ case CTRL_TYPE_BOTTOM:
cursorType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
break;
- case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP:
- case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM:
+ case CTRL_TYPE_LEFT | CTRL_TYPE_TOP:
+ case CTRL_TYPE_RIGHT | CTRL_TYPE_BOTTOM:
cursorType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
break;
- case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM:
- case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP:
+ case CTRL_TYPE_LEFT | CTRL_TYPE_BOTTOM:
+ case CTRL_TYPE_RIGHT | CTRL_TYPE_TOP:
cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
break;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
new file mode 100644
index 0000000000000..b366c809537f7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+
+/**
+ * A task positioner that resizes/relocates task contents as it is dragged.
+ * Utilizes {@link DragPositioningCallbackUtility} to determine new task bounds.
+ */
+class FluidResizeTaskPositioner implements DragPositioningCallback {
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final WindowDecoration mWindowDecoration;
+ 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();
+ private int mCtrlType;
+ private boolean mHasMoved;
+
+ FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ DisplayController displayController) {
+ this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {});
+ }
+
+ FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+ DisplayController displayController,
+ DragPositioningCallbackUtility.DragStartListener dragStartListener) {
+ mTaskOrganizer = taskOrganizer;
+ mWindowDecoration = windowDecoration;
+ mDisplayController = displayController;
+ mDragStartListener = dragStartListener;
+ }
+
+ @Override
+ public void onDragPositioningStart(int ctrlType, float x, float y) {
+ mCtrlType = ctrlType;
+ mTaskBoundsAtDragStart.set(
+ mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
+ mRepositionStartPoint.set(x, y);
+ mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
+ }
+
+ @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,
+ 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) {
+ // 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;
+ }
+ }
+
+ @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) {
+ 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)) {
+ wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+ }
+ mTaskOrganizer.applyTransaction(wct);
+ }
+
+ mTaskBoundsAtDragStart.setEmpty();
+ mRepositionStartPoint.set(0, 0);
+ mCtrlType = CTRL_TYPE_UNDEFINED;
+ mHasMoved = false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
new file mode 100644
index 0000000000000..fa2d7a87c3cb5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.widget.ImageView;
+import android.window.TaskConstants;
+
+import com.android.wm.shell.R;
+
+import java.util.function.Supplier;
+
+/**
+ * Creates and updates a veil that covers task contents on resize.
+ */
+public class ResizeVeil {
+ private static final int RESIZE_ALPHA_DURATION = 200;
+ private final Context mContext;
+ private final Supplier mSurfaceControlBuilderSupplier;
+ private final Supplier mSurfaceControlTransactionSupplier;
+ private final Drawable mAppIcon;
+ private SurfaceControl mParentSurface;
+ private SurfaceControl mVeilSurface;
+ private final RunningTaskInfo mTaskInfo;
+ private SurfaceControlViewHost mViewHost;
+ private final Display mDisplay;
+
+ public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo,
+ Supplier surfaceControlBuilderSupplier, Display display,
+ Supplier surfaceControlTransactionSupplier) {
+ mContext = context;
+ mAppIcon = appIcon;
+ mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
+ mSurfaceControlTransactionSupplier = surfaceControlTransactionSupplier;
+ mTaskInfo = taskInfo;
+ mDisplay = display;
+ setupResizeVeil();
+ }
+
+ /**
+ * Create the veil in its default invisible state.
+ */
+ private void setupResizeVeil() {
+ SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+ mVeilSurface = builder
+ .setName("Resize veil of Task= " + mTaskInfo.taskId)
+ .setContainerLayer()
+ .build();
+ View v = LayoutInflater.from(mContext)
+ .inflate(R.layout.desktop_mode_resize_veil, null);
+
+ t.setPosition(mVeilSurface, 0, 0)
+ .setLayer(mVeilSurface, TaskConstants.TASK_CHILD_LAYER_RESIZE_VEIL)
+ .apply();
+ Rect taskBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(taskBounds.width(),
+ taskBounds.height(),
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
+ PixelFormat.TRANSPARENT);
+ lp.setTitle("Resize veil of Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
+ mVeilSurface, null /* hostInputToken */);
+ mViewHost = new SurfaceControlViewHost(mContext, mDisplay, windowManager, "ResizeVeil");
+ mViewHost.setView(v, lp);
+
+ final ImageView appIcon = mViewHost.getView().findViewById(R.id.veil_application_icon);
+ appIcon.setImageDrawable(mAppIcon);
+ }
+
+ /**
+ * Animate veil's alpha to 1, fading it in.
+ */
+ public void showVeil(SurfaceControl parentSurface) {
+ // Parent surface can change, ensure it is up to date.
+ SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ if (!parentSurface.equals(mParentSurface)) {
+ t.reparent(mVeilSurface, parentSurface);
+ mParentSurface = parentSurface;
+ }
+ t.show(mVeilSurface)
+ .apply();
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(0f, 1f);
+ animator.setDuration(RESIZE_ALPHA_DURATION);
+ animator.addUpdateListener(animation -> {
+ t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
+ t.apply();
+ });
+ animator.start();
+ }
+
+ /**
+ * Update veil bounds to match bounds changes.
+ * @param newBounds bounds to update veil to.
+ */
+ public void relayout(Rect newBounds) {
+ SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ mViewHost.relayout(newBounds.width(), newBounds.height());
+ t.setWindowCrop(mVeilSurface, newBounds.width(), newBounds.height());
+ t.setPosition(mParentSurface, newBounds.left, newBounds.top);
+ t.setWindowCrop(mParentSurface, newBounds.width(), newBounds.height());
+ mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
+ }
+
+ /**
+ * Animate veil's alpha to 0, fading it out.
+ */
+ public void hideVeil() {
+ final View resizeVeilView = mViewHost.getView();
+ final ValueAnimator animator = new ValueAnimator();
+ animator.setFloatValues(1, 0);
+ animator.setDuration(RESIZE_ALPHA_DURATION);
+ animator.addUpdateListener(animation -> {
+ SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ t.setAlpha(mVeilSurface, 1 - animator.getAnimatedFraction());
+ t.apply();
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ t.hide(mVeilSurface);
+ t.apply();
+ }
+ });
+ animator.start();
+ }
+
+ /**
+ * Dispose of veil when it is no longer needed, likely on close of its container decor.
+ */
+ void dispose() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+ if (mVeilSurface != null) {
+ final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+ t.remove(mVeilSurface);
+ mVeilSurface = null;
+ t.apply();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
deleted file mode 100644
index 0bce3acecb3c8..0000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.windowdecor;
-
-import android.annotation.IntDef;
-import android.graphics.PointF;
-import android.graphics.Rect;
-import android.util.DisplayMetrics;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayController;
-
-class TaskPositioner implements DragPositioningCallback {
-
- @IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
- @interface CtrlType {}
-
- static final int CTRL_TYPE_UNDEFINED = 0;
- static final int CTRL_TYPE_LEFT = 1;
- static final int CTRL_TYPE_RIGHT = 2;
- static final int CTRL_TYPE_TOP = 4;
- static final int CTRL_TYPE_BOTTOM = 8;
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final DisplayController mDisplayController;
- private final WindowDecoration mWindowDecoration;
-
- private final Rect mTempBounds = new Rect();
- private final Rect mTaskBoundsAtDragStart = new Rect();
- private final PointF mRepositionStartPoint = new PointF();
- private final Rect mRepositionTaskBounds = new Rect();
- private boolean mHasMoved = false;
-
- private int mCtrlType;
- private DragStartListener mDragStartListener;
-
- TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DisplayController displayController) {
- this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {});
- }
-
- TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
- DisplayController displayController, DragStartListener dragStartListener) {
- mTaskOrganizer = taskOrganizer;
- mWindowDecoration = windowDecoration;
- mDisplayController = displayController;
- mDragStartListener = dragStartListener;
- }
-
- @Override
- public void onDragPositioningStart(int ctrlType, float x, float y) {
- mHasMoved = false;
-
- mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
- mCtrlType = ctrlType;
-
- mTaskBoundsAtDragStart.set(
- mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
- mRepositionStartPoint.set(x, y);
- }
-
- @Override
- public void onDragPositioningMove(float x, float y) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (changeBounds(wct, x, y)) {
- // The task is being resized, send the |dragResizing| hint to core with the first
- // bounds-change wct.
- if (!mHasMoved && mCtrlType != CTRL_TYPE_UNDEFINED) {
- // This is the first bounds change since drag resize operation started.
- wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
- }
- mTaskOrganizer.applyTransaction(wct);
- mHasMoved = true;
- }
- }
-
- @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) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */);
- changeBounds(wct, x, y);
- mTaskOrganizer.applyTransaction(wct);
- }
-
- mCtrlType = CTRL_TYPE_UNDEFINED;
- mTaskBoundsAtDragStart.setEmpty();
- mRepositionStartPoint.set(0, 0);
- mHasMoved = false;
- }
-
- private boolean changeBounds(WindowContainerTransaction wct, float x, float y) {
- // |mRepositionTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not
- // true, we can compare it against |mTaskBoundsAtDragStart|.
- final int oldLeft = mHasMoved ? mRepositionTaskBounds.left : mTaskBoundsAtDragStart.left;
- final int oldTop = mHasMoved ? mRepositionTaskBounds.top : mTaskBoundsAtDragStart.top;
- final int oldRight = mHasMoved ? mRepositionTaskBounds.right : mTaskBoundsAtDragStart.right;
- final int oldBottom =
- mHasMoved ? mRepositionTaskBounds.bottom : mTaskBoundsAtDragStart.bottom;
-
- final float deltaX = x - mRepositionStartPoint.x;
- final float deltaY = y - mRepositionStartPoint.y;
- mRepositionTaskBounds.set(mTaskBoundsAtDragStart);
-
- final Rect stableBounds = mTempBounds;
- // Make sure the new resizing destination in any direction falls within the stable bounds.
- // If not, set the bounds back to the old location that was valid to avoid conflicts with
- // some regions such as the gesture area.
- mDisplayController.getDisplayLayout(mWindowDecoration.mDisplay.getDisplayId())
- .getStableBounds(stableBounds);
- if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
- final int candidateLeft = mRepositionTaskBounds.left + (int) deltaX;
- mRepositionTaskBounds.left = (candidateLeft > stableBounds.left)
- ? candidateLeft : oldLeft;
- }
- if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
- final int candidateRight = mRepositionTaskBounds.right + (int) deltaX;
- mRepositionTaskBounds.right = (candidateRight < stableBounds.right)
- ? candidateRight : oldRight;
- }
- if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
- final int candidateTop = mRepositionTaskBounds.top + (int) deltaY;
- mRepositionTaskBounds.top = (candidateTop > stableBounds.top)
- ? candidateTop : oldTop;
- }
- if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
- final int candidateBottom = mRepositionTaskBounds.bottom + (int) deltaY;
- mRepositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
- ? candidateBottom : oldBottom;
- }
- if (mCtrlType == CTRL_TYPE_UNDEFINED) {
- mRepositionTaskBounds.offset((int) deltaX, (int) deltaY);
- }
-
- // If width or height are negative or less than the minimum width or height, revert the
- // respective bounds to use previous bound dimensions.
- if (mRepositionTaskBounds.width() < getMinWidth()) {
- mRepositionTaskBounds.right = oldRight;
- mRepositionTaskBounds.left = oldLeft;
- }
- if (mRepositionTaskBounds.height() < getMinHeight()) {
- mRepositionTaskBounds.top = oldTop;
- mRepositionTaskBounds.bottom = oldBottom;
- }
- // If there are no changes to the bounds after checking new bounds against minimum width
- // and height, do not set bounds and return false
- if (oldLeft == mRepositionTaskBounds.left && oldTop == mRepositionTaskBounds.top
- && oldRight == mRepositionTaskBounds.right
- && oldBottom == mRepositionTaskBounds.bottom) {
- return false;
- }
-
- wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
- return true;
- }
-
- private float getMinWidth() {
- return mWindowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize()
- : mWindowDecoration.mTaskInfo.minWidth;
- }
-
- private float getMinHeight() {
- return mWindowDecoration.mTaskInfo.minHeight < 0 ? getDefaultMinSize()
- : mWindowDecoration.mTaskInfo.minHeight;
- }
-
- private float getDefaultMinSize() {
- float density = mDisplayController.getDisplayLayout(mWindowDecoration.mTaskInfo.displayId)
- .densityDpi() * DisplayMetrics.DENSITY_DEFAULT_SCALE;
- return mWindowDecoration.mTaskInfo.defaultMinSize * density;
- }
-
- interface DragStartListener {
- /**
- * Inform the implementing class that a drag resize has started
- * @param taskId id of this positioner's {@link WindowDecoration}
- */
- void onDragStart(int taskId);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
new file mode 100644
index 0000000000000..ef8332f5e3471
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+
+/**
+ * A task positioner that also takes into account resizing a
+ * {@link com.android.wm.shell.windowdecor.ResizeVeil}.
+ * If the drag is resizing the task, we resize the veil instead.
+ * If the drag is repositioning, we update in the typical manner.
+ */
+public class VeiledResizeTaskPositioner implements DragPositioningCallback {
+
+ private DesktopModeWindowDecoration mDesktopWindowDecoration;
+ private ShellTaskOrganizer mTaskOrganizer;
+ 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();
+ private int mCtrlType;
+ private boolean mHasMoved;
+
+
+ public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
+ DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
+ DragPositioningCallbackUtility.DragStartListener dragStartListener) {
+ mTaskOrganizer = taskOrganizer;
+ mDesktopWindowDecoration = windowDecoration;
+ mDisplayController = displayController;
+ mDragStartListener = dragStartListener;
+ }
+
+ @Override
+ public void onDragPositioningStart(int ctrlType, float x, float y) {
+ mCtrlType = ctrlType;
+ mTaskBoundsAtDragStart.set(
+ mDesktopWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
+ mRepositionStartPoint.set(x, y);
+ if (mCtrlType != CTRL_TYPE_UNDEFINED) {
+ mDesktopWindowDecoration.showResizeVeil();
+ }
+ mHasMoved = false;
+ mDragStartListener.onDragStart(mDesktopWindowDecoration.mTaskInfo.taskId);
+ }
+
+ @Override
+ public void onDragPositioningMove(float x, float y) {
+ PointF delta = DragPositioningCallbackUtility.calculateDelta(x, y, mRepositionStartPoint);
+ if (DragPositioningCallbackUtility.changeBounds(mCtrlType, mHasMoved,
+ mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
+ mDisplayController, mDesktopWindowDecoration)) {
+ if (mCtrlType != CTRL_TYPE_UNDEFINED) {
+ mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ } else {
+ DragPositioningCallbackUtility.applyTaskBoundsChange(
+ new WindowContainerTransaction(), mDesktopWindowDecoration,
+ mRepositionTaskBounds, mTaskOrganizer);
+ }
+ mHasMoved = true;
+ }
+ }
+
+ @Override
+ 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) {
+ mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+ mDesktopWindowDecoration.hideResizeVeil();
+ }
+ mCtrlType = CTRL_TYPE_UNDEFINED;
+ mTaskBoundsAtDragStart.setEmpty();
+ mRepositionStartPoint.set(0, 0);
+ mHasMoved = false;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 94c064bda7631..84ccddeb6a767 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -14,10 +14,10 @@ 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.windowdecor.TaskPositioner.CTRL_TYPE_BOTTOM
-import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_RIGHT
-import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP
-import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
+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
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -30,21 +30,21 @@ import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
/**
- * Tests for [TaskPositioner].
+ * Tests for [FluidResizeTaskPositioner].
*
* Build/Install/Run:
- * atest WMShellUnitTests:TaskPositionerTest
+ * atest WMShellUnitTests:FluidResizeTaskPositionerTest
*/
@SmallTest
@RunWith(AndroidTestingRunner::class)
-class TaskPositionerTest : ShellTestCase() {
+class FluidResizeTaskPositionerTest : ShellTestCase() {
@Mock
private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
@Mock
private lateinit var mockWindowDecoration: WindowDecoration<*>
@Mock
- private lateinit var mockDragStartListener: TaskPositioner.DragStartListener
+ private lateinit var mockDragStartListener: DragPositioningCallbackUtility.DragStartListener
@Mock
private lateinit var taskToken: WindowContainerToken
@@ -58,18 +58,19 @@ class TaskPositionerTest : ShellTestCase() {
@Mock
private lateinit var mockDisplay: Display
- private lateinit var taskPositioner: TaskPositioner
+ private lateinit var taskPositioner: FluidResizeTaskPositioner
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- taskPositioner = TaskPositioner(
+ taskPositioner =
+ FluidResizeTaskPositioner(
mockShellTaskOrganizer,
mockWindowDecoration,
mockDisplayController,
mockDragStartListener
- )
+ )
`when`(taskToken.asBinder()).thenReturn(taskBinder)
`when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
new file mode 100644
index 0000000000000..bf365ca782ee9
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.windowdecor
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import android.graphics.Rect
+import android.os.IBinder
+import android.testing.AndroidTestingRunner
+import android.view.Display
+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.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
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.argThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+/**
+ * Tests for [VeiledResizeTaskPositioner].
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:VeiledResizeTaskPositionerTest
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class VeiledResizeTaskPositionerTest : ShellTestCase() {
+
+ @Mock
+ private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
+ @Mock
+ private lateinit var mockDesktopWindowDecoration: DesktopModeWindowDecoration
+ @Mock
+ private lateinit var mockDragStartListener: DragPositioningCallbackUtility.DragStartListener
+
+ @Mock
+ private lateinit var taskToken: WindowContainerToken
+ @Mock
+ private lateinit var taskBinder: IBinder
+
+ @Mock
+ private lateinit var mockDisplayController: DisplayController
+ @Mock
+ private lateinit var mockDisplayLayout: DisplayLayout
+ @Mock
+ private lateinit var mockDisplay: Display
+
+ private lateinit var taskPositioner: VeiledResizeTaskPositioner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ taskPositioner =
+ VeiledResizeTaskPositioner(
+ mockShellTaskOrganizer,
+ mockDesktopWindowDecoration,
+ mockDisplayController,
+ mockDragStartListener
+ )
+
+ `when`(taskToken.asBinder()).thenReturn(taskBinder)
+ `when`(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
+ `when`(mockDisplayLayout.densityDpi()).thenReturn(DENSITY_DPI)
+ `when`(mockDisplayLayout.getStableBounds(any())).thenAnswer { i ->
+ (i.arguments.first() as Rect).set(STABLE_BOUNDS)
+ }
+
+ mockDesktopWindowDecoration.mTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ taskId = TASK_ID
+ token = taskToken
+ minWidth = MIN_WIDTH
+ minHeight = MIN_HEIGHT
+ defaultMinSize = DEFAULT_MIN
+ displayId = DISPLAY_ID
+ configuration.windowConfiguration.bounds = STARTING_BOUNDS
+ }
+ mockDesktopWindowDecoration.mDisplay = mockDisplay
+ `when`(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+ }
+
+ @Test
+ fun testDragResize_noMove_showsResizeVeil() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+ verify(mockDesktopWindowDecoration).showResizeVeil()
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+ verify(mockDesktopWindowDecoration).hideResizeVeil()
+ }
+
+ @Test
+ fun testDragResize_movesTask_doesNotShowResizeVeil() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_UNDEFINED,
+ STARTING_BOUNDS.left.toFloat() + 50,
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningMove(
+ STARTING_BOUNDS.left.toFloat() + 60,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+ 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
+ }
+ })
+
+ 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
+
+ verify(mockDesktopWindowDecoration, never()).createResizeVeil()
+ verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
+ 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 == rectAfterEnd
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_resize_boundsUpdateOnEnd() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+ STARTING_BOUNDS.right.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+ verify(mockDesktopWindowDecoration).showResizeVeil()
+
+ taskPositioner.onDragPositioningMove(
+ STARTING_BOUNDS.right.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+
+ val rectAfterMove = Rect(STARTING_BOUNDS)
+ rectAfterMove.right += 10
+ rectAfterMove.top += 10
+ verify(mockShellTaskOrganizer, never()).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
+ }
+ })
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.right.toFloat() + 20,
+ STARTING_BOUNDS.top.toFloat() + 20
+ )
+ val rectAfterEnd = Rect(rectAfterMove)
+ rectAfterEnd.right += 10
+ rectAfterEnd.top += 10
+ verify(mockDesktopWindowDecoration, times(2)).updateResizeVeil(any())
+ verify(mockDesktopWindowDecoration).hideResizeVeil()
+
+ 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 == rectAfterEnd
+ }
+ })
+ }
+
+ @Test
+ fun testDragResize_noEffectiveMove_skipsTransactionOnEnd() {
+ taskPositioner.onDragPositioningStart(
+ CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+ verify(mockDesktopWindowDecoration).showResizeVeil()
+
+ taskPositioner.onDragPositioningMove(
+ STARTING_BOUNDS.left.toFloat(),
+ STARTING_BOUNDS.top.toFloat()
+ )
+
+ taskPositioner.onDragPositioningEnd(
+ STARTING_BOUNDS.left.toFloat() + 10,
+ STARTING_BOUNDS.top.toFloat() + 10
+ )
+ verify(mockDesktopWindowDecoration).hideResizeVeil()
+
+ 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
+ private const val MIN_HEIGHT = 10
+ private const val DENSITY_DPI = 20
+ private const val DEFAULT_MIN = 40
+ private const val DISPLAY_ID = 1
+ 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_BOUNDS = Rect(
+ DISPLAY_BOUNDS.left,
+ DISPLAY_BOUNDS.top,
+ DISPLAY_BOUNDS.right,
+ DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT
+ )
+ }
+}