From 7ea23aa271477e38b102b1e0cb788c3db300c2b0 Mon Sep 17 00:00:00 2001 From: Garfield Tan Date: Thu, 19 Jan 2023 15:38:10 -0800 Subject: [PATCH] Refactor DragDetector and fix a few resizing bugs There are a few things this CL does: 1. It refactors DragDetector to filter out moves in the slop instead of returning a boolean value to let the caller to decide what to do with it. 2. It fixes the bug that window decors respond to window moves in the slop. 3. It skips the unnecessary transaction at the end of drag resizing gesture in TaskPositioner if nothing has changed. 4. It fixes the bug that TaskPositioner uses if the new task bounds is empty to decide if there is no change in the task bounds. Bug: 266448890 Test: Drag resize windows with mice and fingers. Test: atest WMShellUnitTests Change-Id: I8e9e1204e841bfd8f730d4091e37955f1f376018 --- .../CaptionWindowDecorViewModel.java | 19 +- .../windowdecor/CaptionWindowDecoration.java | 8 +- .../DesktopModeWindowDecorViewModel.java | 27 ++- .../DesktopModeWindowDecoration.java | 8 +- .../wm/shell/windowdecor/DragDetector.java | 82 ++++--- .../windowdecor/DragResizeInputListener.java | 29 +-- .../wm/shell/windowdecor/TaskPositioner.java | 54 +++-- .../wm/shell/windowdecor/DragDetectorTest.kt | 210 ++++++++++++++++++ .../shell/windowdecor/TaskPositionerTest.kt | 86 +++++++ 9 files changed, 424 insertions(+), 99 deletions(-) create mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt 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(