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 129924ad5d05e..f77ac81feca8e 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 @@ -143,8 +143,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD - && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode() - == WINDOWING_MODE_FREEFORM); + && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode() + == WINDOWING_MODE_FREEFORM); } private void createWindowDecoration( @@ -175,16 +175,18 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { new CaptionTouchEventListener(taskInfo, taskPositioner); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); windowDecoration.setDragResizeCallback(taskPositioner); + windowDecoration.setDragDetector(touchEventListener.mDragDetector); windowDecoration.relayout(taskInfo, startT, finishT); setupCaptionColor(taskInfo, windowDecoration); } private class CaptionTouchEventListener implements - View.OnClickListener, View.OnTouchListener { + View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler { private final int mTaskId; private final WindowContainerToken mTaskToken; private final DragResizeCallback mDragResizeCallback; + private final DragDetector mDragDetector; private int mDragPointerId = -1; @@ -194,6 +196,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { mTaskId = taskInfo.taskId; mTaskToken = taskInfo.token; mDragResizeCallback = dragResizeCallback; + mDragDetector = new DragDetector(this); } @Override @@ -216,7 +219,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { if (v.getId() != R.id.caption) { return false; } - handleEventForMove(e); + mDragDetector.onMotionEvent(e); if (e.getAction() != MotionEvent.ACTION_DOWN) { return false; @@ -235,10 +238,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { * @param e {@link MotionEvent} to process * @return {@code true} if a drag is happening; or {@code false} if it is not */ - private void handleEventForMove(MotionEvent e) { + @Override + public boolean handleMotionEvent(MotionEvent e) { final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId); if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { - return; + return false; } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { @@ -261,6 +265,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel { break; } } + return true; } } -} +} \ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java index d26f1fc8ef1b3..f94fbfca9bcff 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java @@ -49,7 +49,7 @@ public class CaptionWindowDecoration extends WindowDecoration mResult = @@ -69,7 +69,6 @@ public class CaptionWindowDecoration extends WindowDecoration mResult = @@ -81,7 +81,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration mTouchSlop) { - mIsDragEvent = true; - } + mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop; + } + if (mIsDragEvent) { + return mEventHandler.handleMotionEvent(ev); + } else { + return mResultOfDownAction; } - return mIsDragEvent; - } - case ACTION_UP: { - boolean result = mIsDragEvent; - mIsDragEvent = false; - mInputDownPoint.set(0, 0); - mDragPointerId = -1; - return result; } + case ACTION_UP: case ACTION_CANCEL: { - mIsDragEvent = false; - mInputDownPoint.set(0, 0); - mDragPointerId = -1; - return false; + resetState(); + return mEventHandler.handleMotionEvent(ev); } + default: + return mEventHandler.handleMotionEvent(ev); } - return mIsDragEvent; } - public void setTouchSlop(int touchSlop) { + void setTouchSlop(int touchSlop) { mTouchSlop = touchSlop; } + + private void resetState() { + mIsDragEvent = false; + mInputDownPoint.set(0, 0); + mDragPointerId = -1; + mResultOfDownAction = false; + } + + interface MotionEventHandler { + boolean handleMotionEvent(MotionEvent ev); + } } 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 d3f1332f62247..29637637e23b5 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 @@ -48,7 +48,6 @@ import com.android.internal.view.BaseIWindow; * Task edges are for resizing with a mouse. * Task corners are for resizing with touch input. */ -// TODO(b/251270585): investigate how to pass taps in corners to the tasks class DragResizeInputListener implements AutoCloseable { private static final String TAG = "DragResizeInputListener"; @@ -115,7 +114,8 @@ class DragResizeInputListener implements AutoCloseable { mInputEventReceiver = new TaskResizeInputEventReceiver( mInputChannel, mHandler, mChoreographer); mCallback = callback; - mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop()); + mDragDetector = new DragDetector(mInputEventReceiver); + mDragDetector.setTouchSlop(ViewConfiguration.get(context).getScaledTouchSlop()); } /** @@ -223,12 +223,12 @@ class DragResizeInputListener implements AutoCloseable { } } - private class TaskResizeInputEventReceiver extends InputEventReceiver { + private class TaskResizeInputEventReceiver extends InputEventReceiver + implements DragDetector.MotionEventHandler { private final Choreographer mChoreographer; private final Runnable mConsumeBatchEventRunnable; private boolean mConsumeBatchEventScheduled; private boolean mShouldHandleEvents; - private boolean mDragging; private TaskResizeInputEventReceiver( InputChannel inputChannel, Handler handler, Choreographer choreographer) { @@ -270,15 +270,15 @@ class DragResizeInputListener implements AutoCloseable { if (!(inputEvent instanceof MotionEvent)) { return false; } + return mDragDetector.onMotionEvent((MotionEvent) inputEvent); + } - MotionEvent e = (MotionEvent) inputEvent; + @Override + public boolean handleMotionEvent(MotionEvent e) { boolean result = false; // Check if this is a touch event vs mouse event. // Touch events are tracked in four corners. Other events are tracked in resize edges. boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; - if (isTouch) { - mDragging = mDragDetector.detectDragEvent(e); - } switch (e.getActionMasked()) { case MotionEvent.ACTION_DOWN: { float x = e.getX(0); @@ -305,24 +305,17 @@ class DragResizeInputListener implements AutoCloseable { int dragPointerIndex = e.findPointerIndex(mDragPointerId); float rawX = e.getRawX(dragPointerIndex); float rawY = e.getRawY(dragPointerIndex); - if (!isTouch) { - // For all other types allow immediate dragging. - mDragging = true; - } - if (mDragging) { - mCallback.onDragResizeMove(rawX, rawY); - result = true; - } + mCallback.onDragResizeMove(rawX, rawY); + result = true; break; } case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: { - if (mShouldHandleEvents && mDragging) { + if (mShouldHandleEvents) { int dragPointerIndex = e.findPointerIndex(mDragPointerId); mCallback.onDragResizeEnd( e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex)); } - mDragging = false; mShouldHandleEvents = false; mDragPointerId = -1; result = true; 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 index 20631f85453fa..8cd2a5946e912 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java @@ -40,9 +40,7 @@ class TaskPositioner implements DragResizeCallback { private final Rect mTaskBoundsAtDragStart = new Rect(); private final PointF mResizeStartPoint = new PointF(); private final Rect mResizeTaskBounds = new Rect(); - // Whether the |dragResizing| hint should be sent with the next bounds change WCT. - // Used to optimized fluid resizing of freeform tasks. - private boolean mPendingDragResizeHint = false; + private boolean mHasMoved = false; private int mCtrlType; private DragStartListener mDragStartListener; @@ -60,11 +58,7 @@ class TaskPositioner implements DragResizeCallback { @Override public void onDragResizeStart(int ctrlType, float x, float y) { - if (ctrlType != CTRL_TYPE_UNDEFINED) { - // The task is being resized, send the |dragResizing| hint to core with the first - // bounds-change wct. - mPendingDragResizeHint = true; - } + mHasMoved = false; mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId); mCtrlType = ctrlType; @@ -78,30 +72,44 @@ class TaskPositioner implements DragResizeCallback { public void onDragResizeMove(float x, float y) { final WindowContainerTransaction wct = new WindowContainerTransaction(); if (changeBounds(wct, x, y)) { - if (mPendingDragResizeHint) { + // 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 */); - mPendingDragResizeHint = false; } mTaskOrganizer.applyTransaction(wct); + mHasMoved = true; } } @Override public void onDragResizeEnd(float x, float y) { - final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */); - changeBounds(wct, x, y); - mTaskOrganizer.applyTransaction(wct); + // |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 = 0; + mCtrlType = CTRL_TYPE_UNDEFINED; mTaskBoundsAtDragStart.setEmpty(); mResizeStartPoint.set(0, 0); - mPendingDragResizeHint = false; + mHasMoved = false; } private boolean changeBounds(WindowContainerTransaction wct, float x, float y) { - float deltaX = x - mResizeStartPoint.x; + // |mResizeTaskBounds| is the bounds last reported if |mHasMoved| is true. If it's not true, + // we can compare it against |mTaskBoundsAtDragStart|. + final int oldLeft = mHasMoved ? mResizeTaskBounds.left : mTaskBoundsAtDragStart.left; + final int oldTop = mHasMoved ? mResizeTaskBounds.top : mTaskBoundsAtDragStart.top; + final int oldRight = mHasMoved ? mResizeTaskBounds.right : mTaskBoundsAtDragStart.right; + final int oldBottom = mHasMoved ? mResizeTaskBounds.bottom : mTaskBoundsAtDragStart.bottom; + + final float deltaX = x - mResizeStartPoint.x; + final float deltaY = y - mResizeStartPoint.y; mResizeTaskBounds.set(mTaskBoundsAtDragStart); if ((mCtrlType & CTRL_TYPE_LEFT) != 0) { mResizeTaskBounds.left += deltaX; @@ -109,22 +117,22 @@ class TaskPositioner implements DragResizeCallback { if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) { mResizeTaskBounds.right += deltaX; } - float deltaY = y - mResizeStartPoint.y; if ((mCtrlType & CTRL_TYPE_TOP) != 0) { mResizeTaskBounds.top += deltaY; } if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) { mResizeTaskBounds.bottom += deltaY; } - if (mCtrlType == 0) { + if (mCtrlType == CTRL_TYPE_UNDEFINED) { mResizeTaskBounds.offset((int) deltaX, (int) deltaY); } - if (!mResizeTaskBounds.isEmpty()) { - wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds); - return true; + if (oldLeft == mResizeTaskBounds.left && oldTop == mResizeTaskBounds.top + && oldRight == mResizeTaskBounds.right && oldBottom == mResizeTaskBounds.bottom) { + return false; } - return false; + wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds); + return true; } interface DragStartListener { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt new file mode 100644 index 0000000000000..8f84008e8d2d0 --- /dev/null +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt @@ -0,0 +1,210 @@ +/* + * 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.os.SystemClock +import android.testing.AndroidTestingRunner +import android.view.MotionEvent +import android.view.InputDevice +import androidx.test.filters.SmallTest +import org.junit.After +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` +import org.mockito.Mockito.any +import org.mockito.Mockito.argThat +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +/** + * Tests for [DragDetector]. + * + * Build/Install/Run: + * atest WMShellUnitTests:DragDetectorTest + */ +@SmallTest +@RunWith(AndroidTestingRunner::class) +class DragDetectorTest { + private val motionEvents = mutableListOf() + + @Mock + private lateinit var eventHandler: DragDetector.MotionEventHandler + + private lateinit var dragDetector: DragDetector + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + `when`(eventHandler.handleMotionEvent(any())).thenReturn(true) + + dragDetector = DragDetector(eventHandler) + dragDetector.setTouchSlop(SLOP) + } + + @After + fun tearDown() { + motionEvents.forEach { + it.recycle() + } + motionEvents.clear() + } + + @Test + fun testNoMove_passesDownAndUp() { + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + } + + @Test + fun testMoveInSlop_touch_passesDownAndUp() { + `when`(eventHandler.handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN + })).thenReturn(false) + + assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + val newX = X + SLOP - 1 + assertFalse( + dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) + verify(eventHandler, never()).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + } + + @Test + fun testMoveInSlop_mouse_passesDownMoveAndUp() { + `when`(eventHandler.handleMotionEvent(argThat { + it.action == MotionEvent.ACTION_DOWN + })).thenReturn(false) + + assertFalse(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_DOWN, isTouch = false))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + val newX = X + SLOP - 1 + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y, isTouch = false))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + + assertTrue(dragDetector.onMotionEvent( + createMotionEvent(MotionEvent.ACTION_UP, newX, Y, isTouch = false))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_MOUSE + }) + } + + @Test + fun testMoveBeyondSlop_passesDownMoveAndUp() { + `when`(eventHandler.handleMotionEvent(argThat { + it.action == MotionEvent.ACTION_DOWN + })).thenReturn(false) + + assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_DOWN))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_DOWN && it.x == X && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + val newX = X + SLOP + 1 + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_MOVE, newX, Y))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_MOVE && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_UP, newX, Y))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_UP && it.x == newX && it.y == Y && + it.source == InputDevice.SOURCE_TOUCHSCREEN + }) + } + + @Test + fun testPassesHoverEnter() { + `when`(eventHandler.handleMotionEvent(argThat { + it.action == MotionEvent.ACTION_HOVER_ENTER + })).thenReturn(false) + + assertFalse(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_ENTER))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_HOVER_ENTER && it.x == X && it.y == Y + }) + } + + @Test + fun testPassesHoverMove() { + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_MOVE))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_HOVER_MOVE && it.x == X && it.y == Y + }) + } + + @Test + fun testPassesHoverExit() { + assertTrue(dragDetector.onMotionEvent(createMotionEvent(MotionEvent.ACTION_HOVER_EXIT))) + verify(eventHandler).handleMotionEvent(argThat { + return@argThat it.action == MotionEvent.ACTION_HOVER_EXIT && it.x == X && it.y == Y + }) + } + + private fun createMotionEvent(action: Int, x: Float = X, y: Float = Y, isTouch: Boolean = true): + MotionEvent { + val time = SystemClock.uptimeMillis() + val ev = MotionEvent.obtain(time, time, action, x, y, 0) + ev.source = if (isTouch) InputDevice.SOURCE_TOUCHSCREEN else InputDevice.SOURCE_MOUSE + motionEvents.add(ev) + return ev + } + + companion object { + private const val SLOP = 10 + private const val X = 123f + private const val Y = 234f + } +} \ No newline at end of file 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/TaskPositionerTest.kt index ac10ddb0116a7..804c416f0cf63 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/TaskPositionerTest.kt @@ -1,6 +1,7 @@ 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 @@ -10,6 +11,7 @@ import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTestCase 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 org.junit.Before import org.junit.Test @@ -62,6 +64,90 @@ class TaskPositionerTest : ShellTestCase() { } } + @Test + fun testDragResize_notMove_skipsTransactionOnEnd() { + taskPositioner.onDragResizeStart( + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + taskPositioner.onDragResizeEnd( + STARTING_BOUNDS.left.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + 10 + ) + + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + + @Test + fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() { + taskPositioner.onDragResizeStart( + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + taskPositioner.onDragResizeMove( + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + taskPositioner.onDragResizeEnd( + STARTING_BOUNDS.left.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + 10 + ) + + verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct -> + return@argThat wct.changes.any { (token, change) -> + token == taskBinder && + ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) + } + }) + } + + @Test + fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() { + taskPositioner.onDragResizeStart( + CTRL_TYPE_TOP or CTRL_TYPE_RIGHT, + STARTING_BOUNDS.left.toFloat(), + STARTING_BOUNDS.top.toFloat() + ) + + taskPositioner.onDragResizeMove( + STARTING_BOUNDS.left.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + ) + val rectAfterMove = Rect(STARTING_BOUNDS) + rectAfterMove.right += 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.onDragResizeEnd( + STARTING_BOUNDS.left.toFloat() + 10, + STARTING_BOUNDS.top.toFloat() + 10 + ) + val rectAfterEnd = Rect(rectAfterMove) + rectAfterEnd.top += 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 == rectAfterEnd + } + }) + } + @Test fun testDragResize_move_skipsDragResizingFlag() { taskPositioner.onDragResizeStart(