Merge "Refactor DragDetector and fix a few resizing bugs" into tm-qpr-dev

This commit is contained in:
Garfield Tan
2023-01-25 01:44:40 +00:00
committed by Android (Google) Code Review
9 changed files with 424 additions and 99 deletions

View File

@@ -143,8 +143,8 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) { private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM return taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM
|| (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD || (taskInfo.getActivityType() == ACTIVITY_TYPE_STANDARD
&& taskInfo.configuration.windowConfiguration.getDisplayWindowingMode() && taskInfo.configuration.windowConfiguration.getDisplayWindowingMode()
== WINDOWING_MODE_FREEFORM); == WINDOWING_MODE_FREEFORM);
} }
private void createWindowDecoration( private void createWindowDecoration(
@@ -175,16 +175,18 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
new CaptionTouchEventListener(taskInfo, taskPositioner); new CaptionTouchEventListener(taskInfo, taskPositioner);
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner); windowDecoration.setDragResizeCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT); windowDecoration.relayout(taskInfo, startT, finishT);
setupCaptionColor(taskInfo, windowDecoration); setupCaptionColor(taskInfo, windowDecoration);
} }
private class CaptionTouchEventListener implements private class CaptionTouchEventListener implements
View.OnClickListener, View.OnTouchListener { View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId; private final int mTaskId;
private final WindowContainerToken mTaskToken; private final WindowContainerToken mTaskToken;
private final DragResizeCallback mDragResizeCallback; private final DragResizeCallback mDragResizeCallback;
private final DragDetector mDragDetector;
private int mDragPointerId = -1; private int mDragPointerId = -1;
@@ -194,6 +196,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
mTaskId = taskInfo.taskId; mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token; mTaskToken = taskInfo.token;
mDragResizeCallback = dragResizeCallback; mDragResizeCallback = dragResizeCallback;
mDragDetector = new DragDetector(this);
} }
@Override @Override
@@ -216,7 +219,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
if (v.getId() != R.id.caption) { if (v.getId() != R.id.caption) {
return false; return false;
} }
handleEventForMove(e); mDragDetector.onMotionEvent(e);
if (e.getAction() != MotionEvent.ACTION_DOWN) { if (e.getAction() != MotionEvent.ACTION_DOWN) {
return false; return false;
@@ -235,10 +238,11 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
* @param e {@link MotionEvent} to process * @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 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); final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return; return false;
} }
switch (e.getActionMasked()) { switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: { case MotionEvent.ACTION_DOWN: {
@@ -261,6 +265,7 @@ public class CaptionWindowDecorViewModel implements WindowDecorViewModel {
break; break;
} }
} }
return true;
} }
} }
} }

View File

@@ -49,7 +49,7 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
private View.OnTouchListener mOnCaptionTouchListener; private View.OnTouchListener mOnCaptionTouchListener;
private DragResizeCallback mDragResizeCallback; private DragResizeCallback mDragResizeCallback;
private DragResizeInputListener mDragResizeListener; private DragResizeInputListener mDragResizeListener;
private final DragDetector mDragDetector; private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams(); private RelayoutParams mRelayoutParams = new RelayoutParams();
private final RelayoutResult<WindowDecorLinearLayout> mResult = private final RelayoutResult<WindowDecorLinearLayout> mResult =
@@ -69,7 +69,6 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mHandler = handler; mHandler = handler;
mChoreographer = choreographer; mChoreographer = choreographer;
mSyncQueue = syncQueue; mSyncQueue = syncQueue;
mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
} }
void setCaptionListeners( void setCaptionListeners(
@@ -83,6 +82,11 @@ public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearL
mDragResizeCallback = dragResizeCallback; mDragResizeCallback = dragResizeCallback;
} }
void setDragDetector(DragDetector dragDetector) {
mDragDetector = dragDetector;
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
}
@Override @Override
void relayout(RunningTaskInfo taskInfo) { void relayout(RunningTaskInfo taskInfo) {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); final SurfaceControl.Transaction t = new SurfaceControl.Transaction();

View File

@@ -205,7 +205,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
} }
private class DesktopModeTouchEventListener implements private class DesktopModeTouchEventListener implements
View.OnClickListener, View.OnTouchListener { View.OnClickListener, View.OnTouchListener, DragDetector.MotionEventHandler {
private final int mTaskId; private final int mTaskId;
private final WindowContainerToken mTaskToken; private final WindowContainerToken mTaskToken;
@@ -216,12 +216,11 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
private DesktopModeTouchEventListener( private DesktopModeTouchEventListener(
RunningTaskInfo taskInfo, RunningTaskInfo taskInfo,
DragResizeCallback dragResizeCallback, DragResizeCallback dragResizeCallback) {
DragDetector dragDetector) {
mTaskId = taskInfo.taskId; mTaskId = taskInfo.taskId;
mTaskToken = taskInfo.token; mTaskToken = taskInfo.token;
mDragResizeCallback = dragResizeCallback; mDragResizeCallback = dragResizeCallback;
mDragDetector = dragDetector; mDragDetector = new DragDetector(this);
} }
@Override @Override
@@ -254,8 +253,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return false; return false;
} }
if (id == R.id.caption_handle) { if (id == R.id.caption_handle) {
isDrag = mDragDetector.detectDragEvent(e); isDrag = mDragDetector.onMotionEvent(e);
handleEventForMove(e);
} }
if (e.getAction() != MotionEvent.ACTION_DOWN) { if (e.getAction() != MotionEvent.ACTION_DOWN) {
return isDrag; return isDrag;
@@ -272,19 +270,19 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
/** /**
* @param e {@link MotionEvent} to process * @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); final RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
if (DesktopModeStatus.isProto2Enabled() if (DesktopModeStatus.isProto2Enabled()
&& taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) { && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
return; return false;
} }
if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent() if (DesktopModeStatus.isProto1Enabled() && mDesktopModeController.isPresent()
&& mDesktopModeController.get().getDisplayAreaWindowingMode( && mDesktopModeController.get().getDisplayAreaWindowingMode(taskInfo.displayId)
taskInfo.displayId)
== WINDOWING_MODE_FULLSCREEN) { == WINDOWING_MODE_FULLSCREEN) {
return; return false;
} }
switch (e.getActionMasked()) { switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: { case MotionEvent.ACTION_DOWN: {
@@ -324,6 +322,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
break; break;
} }
} }
return true;
} }
} }
@@ -560,10 +559,10 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
final TaskPositioner taskPositioner = final TaskPositioner taskPositioner =
new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener); new TaskPositioner(mTaskOrganizer, windowDecoration, mDragStartListener);
final DesktopModeTouchEventListener touchEventListener = final DesktopModeTouchEventListener touchEventListener =
new DesktopModeTouchEventListener( new DesktopModeTouchEventListener(taskInfo, taskPositioner);
taskInfo, taskPositioner, windowDecoration.getDragDetector());
windowDecoration.setCaptionListeners(touchEventListener, touchEventListener); windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
windowDecoration.setDragResizeCallback(taskPositioner); windowDecoration.setDragResizeCallback(taskPositioner);
windowDecoration.setDragDetector(touchEventListener.mDragDetector);
windowDecoration.relayout(taskInfo, startT, finishT); windowDecoration.relayout(taskInfo, startT, finishT);
incrementEventReceiverTasks(taskInfo.displayId); incrementEventReceiverTasks(taskInfo.displayId);
} }

View File

@@ -57,7 +57,7 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
private View.OnTouchListener mOnCaptionTouchListener; private View.OnTouchListener mOnCaptionTouchListener;
private DragResizeCallback mDragResizeCallback; private DragResizeCallback mDragResizeCallback;
private DragResizeInputListener mDragResizeListener; private DragResizeInputListener mDragResizeListener;
private final DragDetector mDragDetector; private DragDetector mDragDetector;
private RelayoutParams mRelayoutParams = new RelayoutParams(); private RelayoutParams mRelayoutParams = new RelayoutParams();
private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult = private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
@@ -81,7 +81,6 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mChoreographer = choreographer; mChoreographer = choreographer;
mSyncQueue = syncQueue; mSyncQueue = syncQueue;
mDesktopActive = DesktopModeStatus.isActive(mContext); mDesktopActive = DesktopModeStatus.isActive(mContext);
mDragDetector = new DragDetector(ViewConfiguration.get(context).getScaledTouchSlop());
} }
void setCaptionListeners( void setCaptionListeners(
@@ -95,8 +94,9 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin
mDragResizeCallback = dragResizeCallback; mDragResizeCallback = dragResizeCallback;
} }
DragDetector getDragDetector() { void setDragDetector(DragDetector dragDetector) {
return mDragDetector; mDragDetector = dragDetector;
mDragDetector.setTouchSlop(ViewConfiguration.get(mContext).getScaledTouchSlop());
} }
@Override @Override

View File

@@ -16,6 +16,7 @@
package com.android.wm.shell.windowdecor; 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_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN; import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE; import static android.view.MotionEvent.ACTION_MOVE;
@@ -25,63 +26,82 @@ import android.graphics.PointF;
import android.view.MotionEvent; 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. * 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 int mTouchSlop;
private PointF mInputDownPoint;
private boolean mIsDragEvent; private boolean mIsDragEvent;
private int mDragPointerId; private int mDragPointerId;
public DragDetector(int touchSlop) {
mTouchSlop = touchSlop; private boolean mResultOfDownAction;
mInputDownPoint = new PointF();
mIsDragEvent = false; DragDetector(MotionEventHandler eventHandler) {
mDragPointerId = -1; resetState();
mEventHandler = eventHandler;
} }
/** /**
* Determine if {@link MotionEvent} is part of a drag event. * The receiver of the {@link MotionEvent} flow.
* @return {@code true} if this is a drag event, {@code false} if not *
*/ * @return the result returned by {@link #mEventHandler}, or the result when
public boolean detectDragEvent(MotionEvent ev) { * {@link #mEventHandler} handles the previous down event if the event shouldn't be passed
switch (ev.getAction()) { */
boolean onMotionEvent(MotionEvent ev) {
switch (ev.getActionMasked()) {
case ACTION_DOWN: { case ACTION_DOWN: {
// Only touch screens generate noisy moves.
mIsDragEvent = (ev.getSource() & SOURCE_TOUCHSCREEN) != SOURCE_TOUCHSCREEN;
mDragPointerId = ev.getPointerId(0); mDragPointerId = ev.getPointerId(0);
float rawX = ev.getRawX(0); float rawX = ev.getRawX(0);
float rawY = ev.getRawY(0); float rawY = ev.getRawY(0);
mInputDownPoint.set(rawX, rawY); mInputDownPoint.set(rawX, rawY);
return false; mResultOfDownAction = mEventHandler.handleMotionEvent(ev);
return mResultOfDownAction;
} }
case ACTION_MOVE: { case ACTION_MOVE: {
if (!mIsDragEvent) { if (!mIsDragEvent) {
int dragPointerIndex = ev.findPointerIndex(mDragPointerId); int dragPointerIndex = ev.findPointerIndex(mDragPointerId);
float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x; float dx = ev.getRawX(dragPointerIndex) - mInputDownPoint.x;
float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y; float dy = ev.getRawY(dragPointerIndex) - mInputDownPoint.y;
if (Math.hypot(dx, dy) > mTouchSlop) { mIsDragEvent = Math.hypot(dx, dy) > mTouchSlop;
mIsDragEvent = true; }
} 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: { case ACTION_CANCEL: {
mIsDragEvent = false; resetState();
mInputDownPoint.set(0, 0); return mEventHandler.handleMotionEvent(ev);
mDragPointerId = -1;
return false;
} }
default:
return mEventHandler.handleMotionEvent(ev);
} }
return mIsDragEvent;
} }
public void setTouchSlop(int touchSlop) { void setTouchSlop(int touchSlop) {
mTouchSlop = touchSlop; mTouchSlop = touchSlop;
} }
private void resetState() {
mIsDragEvent = false;
mInputDownPoint.set(0, 0);
mDragPointerId = -1;
mResultOfDownAction = false;
}
interface MotionEventHandler {
boolean handleMotionEvent(MotionEvent ev);
}
} }

View File

@@ -48,7 +48,6 @@ import com.android.internal.view.BaseIWindow;
* Task edges are for resizing with a mouse. * Task edges are for resizing with a mouse.
* Task corners are for resizing with touch input. * 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 { class DragResizeInputListener implements AutoCloseable {
private static final String TAG = "DragResizeInputListener"; private static final String TAG = "DragResizeInputListener";
@@ -115,7 +114,8 @@ class DragResizeInputListener implements AutoCloseable {
mInputEventReceiver = new TaskResizeInputEventReceiver( mInputEventReceiver = new TaskResizeInputEventReceiver(
mInputChannel, mHandler, mChoreographer); mInputChannel, mHandler, mChoreographer);
mCallback = callback; 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 Choreographer mChoreographer;
private final Runnable mConsumeBatchEventRunnable; private final Runnable mConsumeBatchEventRunnable;
private boolean mConsumeBatchEventScheduled; private boolean mConsumeBatchEventScheduled;
private boolean mShouldHandleEvents; private boolean mShouldHandleEvents;
private boolean mDragging;
private TaskResizeInputEventReceiver( private TaskResizeInputEventReceiver(
InputChannel inputChannel, Handler handler, Choreographer choreographer) { InputChannel inputChannel, Handler handler, Choreographer choreographer) {
@@ -270,15 +270,15 @@ class DragResizeInputListener implements AutoCloseable {
if (!(inputEvent instanceof MotionEvent)) { if (!(inputEvent instanceof MotionEvent)) {
return false; return false;
} }
return mDragDetector.onMotionEvent((MotionEvent) inputEvent);
}
MotionEvent e = (MotionEvent) inputEvent; @Override
public boolean handleMotionEvent(MotionEvent e) {
boolean result = false; boolean result = false;
// Check if this is a touch event vs mouse event. // Check if this is a touch event vs mouse event.
// Touch events are tracked in four corners. Other events are tracked in resize edges. // Touch events are tracked in four corners. Other events are tracked in resize edges.
boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN; boolean isTouch = (e.getSource() & SOURCE_TOUCHSCREEN) == SOURCE_TOUCHSCREEN;
if (isTouch) {
mDragging = mDragDetector.detectDragEvent(e);
}
switch (e.getActionMasked()) { switch (e.getActionMasked()) {
case MotionEvent.ACTION_DOWN: { case MotionEvent.ACTION_DOWN: {
float x = e.getX(0); float x = e.getX(0);
@@ -305,24 +305,17 @@ class DragResizeInputListener implements AutoCloseable {
int dragPointerIndex = e.findPointerIndex(mDragPointerId); int dragPointerIndex = e.findPointerIndex(mDragPointerId);
float rawX = e.getRawX(dragPointerIndex); float rawX = e.getRawX(dragPointerIndex);
float rawY = e.getRawY(dragPointerIndex); float rawY = e.getRawY(dragPointerIndex);
if (!isTouch) { mCallback.onDragResizeMove(rawX, rawY);
// For all other types allow immediate dragging. result = true;
mDragging = true;
}
if (mDragging) {
mCallback.onDragResizeMove(rawX, rawY);
result = true;
}
break; break;
} }
case MotionEvent.ACTION_UP: case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: { case MotionEvent.ACTION_CANCEL: {
if (mShouldHandleEvents && mDragging) { if (mShouldHandleEvents) {
int dragPointerIndex = e.findPointerIndex(mDragPointerId); int dragPointerIndex = e.findPointerIndex(mDragPointerId);
mCallback.onDragResizeEnd( mCallback.onDragResizeEnd(
e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex)); e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
} }
mDragging = false;
mShouldHandleEvents = false; mShouldHandleEvents = false;
mDragPointerId = -1; mDragPointerId = -1;
result = true; result = true;

View File

@@ -40,9 +40,7 @@ class TaskPositioner implements DragResizeCallback {
private final Rect mTaskBoundsAtDragStart = new Rect(); private final Rect mTaskBoundsAtDragStart = new Rect();
private final PointF mResizeStartPoint = new PointF(); private final PointF mResizeStartPoint = new PointF();
private final Rect mResizeTaskBounds = new Rect(); private final Rect mResizeTaskBounds = new Rect();
// Whether the |dragResizing| hint should be sent with the next bounds change WCT. private boolean mHasMoved = false;
// Used to optimized fluid resizing of freeform tasks.
private boolean mPendingDragResizeHint = false;
private int mCtrlType; private int mCtrlType;
private DragStartListener mDragStartListener; private DragStartListener mDragStartListener;
@@ -60,11 +58,7 @@ class TaskPositioner implements DragResizeCallback {
@Override @Override
public void onDragResizeStart(int ctrlType, float x, float y) { public void onDragResizeStart(int ctrlType, float x, float y) {
if (ctrlType != CTRL_TYPE_UNDEFINED) { mHasMoved = false;
// The task is being resized, send the |dragResizing| hint to core with the first
// bounds-change wct.
mPendingDragResizeHint = true;
}
mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId); mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
mCtrlType = ctrlType; mCtrlType = ctrlType;
@@ -78,30 +72,44 @@ class TaskPositioner implements DragResizeCallback {
public void onDragResizeMove(float x, float y) { public void onDragResizeMove(float x, float y) {
final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction wct = new WindowContainerTransaction();
if (changeBounds(wct, x, y)) { 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. // This is the first bounds change since drag resize operation started.
wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */); wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
mPendingDragResizeHint = false;
} }
mTaskOrganizer.applyTransaction(wct); mTaskOrganizer.applyTransaction(wct);
mHasMoved = true;
} }
} }
@Override @Override
public void onDragResizeEnd(float x, float y) { public void onDragResizeEnd(float x, float y) {
final WindowContainerTransaction wct = new WindowContainerTransaction(); // |mHasMoved| being false means there is no real change to the task bounds in WM core, so
wct.setDragResizing(mWindowDecoration.mTaskInfo.token, false /* dragResizing */); // we don't need a WCT to finish it.
changeBounds(wct, x, y); if (mHasMoved) {
mTaskOrganizer.applyTransaction(wct); 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(); mTaskBoundsAtDragStart.setEmpty();
mResizeStartPoint.set(0, 0); mResizeStartPoint.set(0, 0);
mPendingDragResizeHint = false; mHasMoved = false;
} }
private boolean changeBounds(WindowContainerTransaction wct, float x, float y) { 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); mResizeTaskBounds.set(mTaskBoundsAtDragStart);
if ((mCtrlType & CTRL_TYPE_LEFT) != 0) { if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
mResizeTaskBounds.left += deltaX; mResizeTaskBounds.left += deltaX;
@@ -109,22 +117,22 @@ class TaskPositioner implements DragResizeCallback {
if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) { if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
mResizeTaskBounds.right += deltaX; mResizeTaskBounds.right += deltaX;
} }
float deltaY = y - mResizeStartPoint.y;
if ((mCtrlType & CTRL_TYPE_TOP) != 0) { if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
mResizeTaskBounds.top += deltaY; mResizeTaskBounds.top += deltaY;
} }
if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) { if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
mResizeTaskBounds.bottom += deltaY; mResizeTaskBounds.bottom += deltaY;
} }
if (mCtrlType == 0) { if (mCtrlType == CTRL_TYPE_UNDEFINED) {
mResizeTaskBounds.offset((int) deltaX, (int) deltaY); mResizeTaskBounds.offset((int) deltaX, (int) deltaY);
} }
if (!mResizeTaskBounds.isEmpty()) { if (oldLeft == mResizeTaskBounds.left && oldTop == mResizeTaskBounds.top
wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds); && oldRight == mResizeTaskBounds.right && oldBottom == mResizeTaskBounds.bottom) {
return true; return false;
} }
return false; wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
return true;
} }
interface DragStartListener { interface DragStartListener {

View File

@@ -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
}
}

View File

@@ -1,6 +1,7 @@
package com.android.wm.shell.windowdecor package com.android.wm.shell.windowdecor
import android.app.ActivityManager import android.app.ActivityManager
import android.app.WindowConfiguration
import android.graphics.Rect import android.graphics.Rect
import android.os.IBinder import android.os.IBinder
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
@@ -10,6 +11,7 @@ import androidx.test.filters.SmallTest
import com.android.wm.shell.ShellTaskOrganizer import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.ShellTestCase 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_RIGHT
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_TOP
import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED import com.android.wm.shell.windowdecor.TaskPositioner.CTRL_TYPE_UNDEFINED
import org.junit.Before import org.junit.Before
import org.junit.Test 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 @Test
fun testDragResize_move_skipsDragResizingFlag() { fun testDragResize_move_skipsDragResizingFlag() {
taskPositioner.onDragResizeStart( taskPositioner.onDragResizeStart(