summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Garfield Tan <xutan@google.com> 2023-01-19 15:38:10 -0800
committer Garfield Tan <xutan@google.com> 2023-01-23 11:24:21 -0800
commit7ea23aa271477e38b102b1e0cb788c3db300c2b0 (patch)
treea4409d1d3e6831ad2b9e55aacac30468af35f0d9
parentf7347b4978948ced45f09945a4e394195164c974 (diff)
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
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java27
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java82
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java54
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragDetectorTest.kt210
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/TaskPositionerTest.kt86
9 files changed, 424 insertions, 99 deletions
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 129924ad5d05..f77ac81feca8 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 d26f1fc8ef1b..f94fbfca9bcf 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<WindowDecorLinearL
private View.OnTouchListener mOnCaptionTouchListener;
private DragResizeCallback mDragResizeCallback;
private DragResizeInputListener mDragResizeListener;
- private final DragDetector mDragDetector;
+ private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
private final RelayoutResult<WindowDecorLinearLayout> mResult =
@@ -69,7 +69,6 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mHandler = handler;
mChoreographer = choreographer;
mSyncQueue = syncQueue;
- mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
void setCaptionListeners(
@@ -83,6 +82,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mDragResizeCallback = dragResizeCallback;
}
+ void setDragDetector(DragDetector dragDetector) {
+ mDragDetector = dragDetector;
+ mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
+ }
+
@Override
void relayout(RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2863adcc8f5e..bd3afa946355 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -205,7 +205,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
}
private class DesktopModeTouchEventListener implements
- View.OnClickListener, View.OnTouchListener {
+ View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId;
private final WindowContainerToken mTaskToken;
@@ -216,12 +216,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo,
- DragResizeCallback dragResizeCallback,
- DragDetector dragDetector) {
+ DragResizeCallback dragResizeCallback) {
mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token;
mDragResizeCallback = dragResizeCallback;
- mDragDetector = dragDetector;
+ mDragDetector = new DragDetector(this);
}
@Override
@@ -254,8 +253,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return false;
}
if (id == R.id.caption_handle) {
- isDrag = mDragDetector.detectDragEvent(e);
- handleEventForMove(e);
+ isDrag = mDragDetector.onMotionEvent(e);
}
if (e.getAction() != MotionEvent.ACTION_DOWN) {
return isDrag;
@@ -272,19 +270,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
/**
* @param e {@link MotionEvent} to process
- * @return {@code true} if a drag is happening; or {@code false} if it is not
+ * @return {@code true} if the motion event is handled.
*/
- private void handleEventForMove(MotionEvent e) {
+ @Override
+ public boolean handleMotionEvent(MotionEvent e) {
final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
- return;
+ return false;
}
if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
- && mDesktopModeController.get().getDisplayAreaWindowingMode(
- taskInfo.displayId)
+ && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
== WINDOWING_MODE_FULLSCREEN) {
- return;
+ return false;
}
switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
@@ -324,6 +322,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
break;
}
}
+ return true;
}
}
@@ -560,10 +559,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final TaskPositioner taskPositioner =
new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
final DesktopModeTouchEventListener touchEventListener =
- new DesktopModeTouchEventListener(
- taskInfo, taskPositioner, windowDecoration.getDragDetector());
+ new DesktopModeTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner);
+ windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT);
incrementEventReceiverTasks(taskInfo.displayId);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 1a38d24a4ab1..9550937b9f6c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -57,7 +57,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private View.OnTouchListener mOnCaptionTouchListener;
private DragResizeCallback mDragResizeCallback;
private DragResizeInputListener mDragResizeListener;
- private final DragDetector mDragDetector;
+ private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams();
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
@@ -81,7 +81,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mChoreographer = choreographer;
mSyncQueue = syncQueue;
mDesktopActive = DesktopModeStatus.isActive(mContext);
- mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
}
void setCaptionListeners(
@@ -95,8 +94,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mDragResizeCallback = dragResizeCallback;
}
- DragDetector getDragDetector() {
- return mDragDetector;
+ void setDragDetector(DragDetector dragDetector) {
+ mDragDetector = dragDetector;
+ mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
index 0abe8ab2e30b..4fac843b05db 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragDetector.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor;
+import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
@@ -25,63 +26,82 @@ import android.graphics.PointF;
import android.view.MotionEvent;
/**
- * A detector for touch inputs that differentiates between drag and click inputs.
+ * A detector for touch inputs that differentiates between drag and click inputs. It receives a flow
+ * of {@link MotionEvent} and generates a new flow of motion events with slop in consideration to
+ * the event handler. In particular, it always passes down, up and cancel events. It'll pass move
+ * events only when there is at least one move event that's beyond the slop threshold. For the
+ * purpose of convenience it also passes all events of other actions.
+ *
* All touch events must be passed through this class to track a drag event.
*/
-public class DragDetector {
+class DragDetector {
+ private final MotionEventHandler mEventHandler;
+
+ private final PointF mInputDownPoint = new PointF();
private int mTouchSlop;
- private PointF mInputDownPoint;
private boolean mIsDragEvent;
private int mDragPointerId;
- public DragDetector(int touchSlop) {
- mTouchSlop = touchSlop;
- mInputDownPoint = new PointF();
- mIsDragEvent = false;
- mDragPointerId = -1;
+
+ private boolean mResultOfDownAction;
+
+ DragDetector(MotionEventHandler eventHandler) {
+ resetState();
+ mEventHandler = eventHandler;
}
/**
- * Determine if {@link MotionEvent} is part of a drag event.
- * @return {@code true} if this is a drag event, {@code false} if not
- */
- public boolean detectDragEvent(MotionEvent ev) {
- switch (ev.getAction()) {
+ * The receiver of the {@link MotionEvent} flow.
+ *
+ * @return the result returned by {@link #mEventHandler}, or the result when
+ * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
+ */
+ boolean onMotionEvent(MotionEvent ev) {
+ switch (ev.getActionMasked()) {
case ACTION_DOWN: {
+ // Only touch screens generate noisy moves.
+ mIsDragEvent = (ev.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN;
mDragPointerId = ev.getPointerId(0);
float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0);
mInputDownPoint.set(rawX, rawY);
- return false;
+ mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
+ return mResultOfDownAction;
}
case ACTION_MOVE: {
if (!mIsDragEvent) {
int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
- if (Math.hypot(dx, dy) > 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 d3f1332f6224..29637637e23b 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 20631f85453f..8cd2a5946e91 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 000000000000..8f84008e8d2d
--- /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<MotionEvent>()
+
+ @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 ac10ddb0116a..804c416f0cf6 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
@@ -63,6 +65,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(
CTRL_TYPE_UNDEFINED, // Move