diff options
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 |