Merge "Implement veiled resizing." into udc-dev
This commit is contained in:
@@ -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 {}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
<shape android:shape="rectangle"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@android:color/white" />
|
||||
</shape>
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/desktop_mode_resize_veil_background">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/veil_application_icon"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/app_icon_text" />
|
||||
</FrameLayout>
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -101,7 +101,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
|
||||
|
||||
private final SparseArray<DesktopModeWindowDecoration> 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<SurfaceControl.Transaction> 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();
|
||||
|
||||
@@ -26,6 +26,7 @@ import android.content.res.Configuration;
|
||||
import android.content.res.TypedArray;
|
||||
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;
|
||||
@@ -83,6 +84,8 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
private AdditionalWindow mHandleMenuMoreActionsPill;
|
||||
private HandleMenu mHandleMenu;
|
||||
|
||||
private ResizeVeil mResizeVeil;
|
||||
|
||||
private Drawable mAppIcon;
|
||||
private CharSequence mAppName;
|
||||
|
||||
@@ -285,6 +288,42 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
mDragResizeListener = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the resize veil for this task. Note the veil's visibility is View.GONE by default
|
||||
* until a resize event calls showResizeVeil below.
|
||||
*/
|
||||
void createResizeVeil() {
|
||||
mResizeVeil = new ResizeVeil(mContext, mAppIcon, mTaskInfo,
|
||||
mSurfaceControlBuilderSupplier, mDisplay, mSurfaceControlTransactionSupplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade in the resize veil
|
||||
*/
|
||||
void showResizeVeil() {
|
||||
mResizeVeil.showVeil(mTaskSurface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new bounds for the resize veil
|
||||
*/
|
||||
void updateResizeVeil(Rect newBounds) {
|
||||
mResizeVeil.relayout(newBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fade the resize veil out.
|
||||
*/
|
||||
void hideResizeVeil() {
|
||||
mResizeVeil.hideVeil();
|
||||
}
|
||||
|
||||
private void disposeResizeVeil() {
|
||||
if (mResizeVeil == null) return;
|
||||
mResizeVeil.dispose();
|
||||
mResizeVeil = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and display handle menu window
|
||||
*/
|
||||
@@ -412,6 +451,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
|
||||
closeDragResizeListener();
|
||||
closeHandleMenu();
|
||||
mCornersListener.onTaskCornersRemoved(mTaskInfo.taskId);
|
||||
disposeResizeVeil();
|
||||
super.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -16,19 +16,29 @@
|
||||
|
||||
package com.android.wm.shell.windowdecor;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
|
||||
/**
|
||||
* Callback called when receiving drag-resize or drag-move related input events.
|
||||
*/
|
||||
public interface DragPositioningCallback {
|
||||
@IntDef({CTRL_TYPE_UNDEFINED, CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
|
||||
@interface CtrlType {}
|
||||
|
||||
int CTRL_TYPE_UNDEFINED = 0;
|
||||
int CTRL_TYPE_LEFT = 1;
|
||||
int CTRL_TYPE_RIGHT = 2;
|
||||
int CTRL_TYPE_TOP = 4;
|
||||
int CTRL_TYPE_BOTTOM = 8;
|
||||
/**
|
||||
* Called when a drag-resize or drag-move starts.
|
||||
*
|
||||
* @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use
|
||||
* @param ctrlType {@link CtrlType} indicating the direction of resizing, use
|
||||
* {@code 0} to indicate it's a move
|
||||
* @param x x coordinate in window decoration coordinate system where the drag starts
|
||||
* @param y y coordinate in window decoration coordinate system where the drag starts
|
||||
*/
|
||||
void onDragPositioningStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
|
||||
void onDragPositioningStart(@CtrlType int ctrlType, float x, float y);
|
||||
|
||||
/**
|
||||
* Called when the pointer moves during a drag-resize or drag-move.
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* 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 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 static com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED;
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* Utility class that contains logic common to classes implementing {@link DragPositioningCallback}
|
||||
* Specifically, this class contains logic for determining changed bounds from a drag input
|
||||
* and applying that change to the task bounds when applicable.
|
||||
*/
|
||||
public class DragPositioningCallbackUtility {
|
||||
|
||||
/**
|
||||
* Determine the delta between input's current point and the input start point.
|
||||
* @param inputX current input x coordinate
|
||||
* @param inputY current input y coordinate
|
||||
* @param repositionStartPoint initial input coordinate
|
||||
* @return delta between these two points
|
||||
*/
|
||||
static PointF calculateDelta(float inputX, float inputY, PointF repositionStartPoint) {
|
||||
final float deltaX = inputX - repositionStartPoint.x;
|
||||
final float deltaY = inputY - repositionStartPoint.y;
|
||||
return new PointF(deltaX, deltaY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on type of drag 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
|
||||
* @param delta difference between start input and current input in x/y coordinates
|
||||
* @param displayController task's display controller
|
||||
* @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;
|
||||
|
||||
|
||||
repositionTaskBounds.set(taskBoundsAtDragStart);
|
||||
|
||||
// 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.
|
||||
displayController.getDisplayLayout(windowDecoration.mDisplay.getDisplayId())
|
||||
.getStableBounds(stableBounds);
|
||||
if ((ctrlType & CTRL_TYPE_LEFT) != 0) {
|
||||
final int candidateLeft = repositionTaskBounds.left + (int) delta.x;
|
||||
repositionTaskBounds.left = (candidateLeft > 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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
|
||||
private final Supplier<SurfaceControl.Transaction> 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<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display,
|
||||
Supplier<SurfaceControl.Transaction> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user