diff options
3 files changed, 113 insertions, 22 deletions
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java index 3c60d8296577..db058cafe5fe 100644 --- a/services/core/java/com/android/server/wm/DragDropController.java +++ b/services/core/java/com/android/server/wm/DragDropController.java @@ -215,7 +215,8 @@ class DragDropController { mDragState.mOriginalAlpha = alpha; mDragState.mAnimatedScale = callingWin.mGlobalScale; mDragState.mToken = dragToken; - mDragState.mDisplayContent = displayContent; + mDragState.mStartDragDisplayContent = displayContent; + mDragState.mCurrentDisplayContent = displayContent; mDragState.mData = data; mDragState.mCallingTaskIdToHide = shouldMoveCallingTaskToBack(callingWin, flags); @@ -273,7 +274,7 @@ class DragDropController { InputManagerGlobal.getInstance().setPointerIcon( PointerIcon.getSystemIcon( mService.mContext, PointerIcon.TYPE_GRABBING), - mDragState.mDisplayContent.getDisplayId(), touchDeviceId, + mDragState.mCurrentDisplayContent.getDisplayId(), touchDeviceId, touchPointerId, mDragState.getInputToken()); } // remember the thumb offsets for later diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java index b3e9244d108d..4a4e2461c2ca 100644 --- a/services/core/java/com/android/server/wm/DragState.java +++ b/services/core/java/com/android/server/wm/DragState.java @@ -129,10 +129,17 @@ class DragState { */ volatile boolean mAnimationCompleted = false; /** + * The display on which the drag originally started. Note that it's possible for either/both + * mStartDragDisplayContent and mCurrentDisplayContent to be invalid if DisplayTopology was + * changed or removed in the middle of the drag. In this case, drag will also be cancelled as + * soon as listener is notified. + */ + DisplayContent mStartDragDisplayContent; + /** * The display on which the drag is happening. If it goes into a different display this will * be updated. */ - DisplayContent mDisplayContent; + DisplayContent mCurrentDisplayContent; @Nullable private ValueAnimator mAnimator; private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); @@ -181,7 +188,7 @@ class DragState { .setContainerLayer() .setName("Drag and Drop Input Consumer") .setCallsite("DragState.showInputSurface") - .setParent(mDisplayContent.getOverlayLayer()) + .setParent(mCurrentDisplayContent.getOverlayLayer()) .build(); } final InputWindowHandle h = getInputWindowHandle(); @@ -549,7 +556,7 @@ class DragState { PointF relativeToWindowCoords = new PointF(newWin.translateToWindowX(touchX), newWin.translateToWindowY(touchY)); if (Flags.enableConnectedDisplaysDnd() - && mDisplayContent.getDisplayId() != newWin.getDisplayId()) { + && mCurrentDisplayContent.getDisplayId() != newWin.getDisplayId()) { // Currently DRAG_STARTED coords are sent relative to the window target in **px** // coordinates. However, this cannot be extended to connected displays scenario, // as there's only global **dp** coordinates and no global **px** coordinates. @@ -720,6 +727,20 @@ class DragState { mCurrentDisplayX = displayX; mCurrentDisplayY = displayY; + final DisplayContent lastSetDisplayContent = mCurrentDisplayContent; + boolean cursorMovedToDifferentDisplay = false; + // Keep latest display up-to-date even when drag has stopped. + if (Flags.enableConnectedDisplaysDnd() && mCurrentDisplayContent.mDisplayId != displayId) { + final DisplayContent newDisplay = mService.mRoot.getDisplayContent(displayId); + if (newDisplay == null) { + Slog.e(TAG_WM, "Target displayId=" + displayId + " was not found, ending drag."); + endDragLocked(false /* dropConsumed */, + false /* relinquishDragSurfaceToDropTarget */); + return; + } + cursorMovedToDifferentDisplay = true; + mCurrentDisplayContent = newDisplay; + } if (!keepHandling) { return; } @@ -728,6 +749,24 @@ class DragState { if (SHOW_LIGHT_TRANSACTIONS) { Slog.i(TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked"); } + if (cursorMovedToDifferentDisplay) { + mAnimatedScale = mAnimatedScale * mCurrentDisplayContent.mBaseDisplayDensity + / lastSetDisplayContent.mBaseDisplayDensity; + mThumbOffsetX = mThumbOffsetX * mCurrentDisplayContent.mBaseDisplayDensity + / lastSetDisplayContent.mBaseDisplayDensity; + mThumbOffsetY = mThumbOffsetY * mCurrentDisplayContent.mBaseDisplayDensity + / lastSetDisplayContent.mBaseDisplayDensity; + mTransaction.reparent(mSurfaceControl, mCurrentDisplayContent.getSurfaceControl()); + mTransaction.setScale(mSurfaceControl, mAnimatedScale, mAnimatedScale); + + final InputWindowHandle inputWindowHandle = getInputWindowHandle(); + if (inputWindowHandle == null) { + Slog.w(TAG_WM, "Drag is in progress but there is no drag window handle."); + return; + } + inputWindowHandle.displayId = displayId; + mTransaction.setInputWindowInfo(mInputSurface, inputWindowHandle); + } mTransaction.setPosition(mSurfaceControl, displayX - mThumbOffsetX, displayY - mThumbOffsetY).apply(); ProtoLog.i(WM_SHOW_TRANSACTIONS, "DRAG %s: displayId=%d, pos=(%d,%d)", mSurfaceControl, diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java index 23dcb65eb30f..63c745ea6cd0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java @@ -43,6 +43,7 @@ import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -92,6 +93,7 @@ import org.mockito.Mockito; import java.util.ArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; /** * Tests for the {@link DragDropController} class. @@ -255,7 +257,7 @@ public class DragDropControllerTests extends WindowTestsBase { iwindow.setDragEventJournal(dragEvents); startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, - ClipData.newPlainText("label", "text"), () -> { + ClipData.newPlainText("label", "text"), (unused) -> { // Verify the start-drag event is sent for invisible windows final DragEvent dragEvent = dragEvents.get(0); assertTrue(dragEvent.getAction() == ACTION_DRAG_STARTED); @@ -297,7 +299,7 @@ public class DragDropControllerTests extends WindowTestsBase { globalInterceptIWindow.setDragEventJournal(globalInterceptWindowDragEvents); startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, - createClipDataForActivity(null, mock(UserHandle.class)), () -> { + createClipDataForActivity(null, mock(UserHandle.class)), (unused) -> { // Verify the start-drag event is sent for the local and global intercept window // but not the other window assertTrue(nonLocalWindowDragEvents.isEmpty()); @@ -340,7 +342,7 @@ public class DragDropControllerTests extends WindowTestsBase { iwindow.setDragEventJournal(dragEvents); startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG, - ClipData.newPlainText("label", "text"), () -> { + ClipData.newPlainText("label", "text"), (unused) -> { // Verify the start-drag event has the drag flags final DragEvent dragEvent = dragEvents.get(0); assertTrue(dragEvent.getAction() == ACTION_DRAG_STARTED); @@ -386,7 +388,7 @@ public class DragDropControllerTests extends WindowTestsBase { iwindow2.setDragEventJournal(dragEvents2); startDrag(dragStartX, dragStartY, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, - ClipData.newPlainText("label", "text"), () -> { + ClipData.newPlainText("label", "text"), (unused) -> { // Verify the start-drag event is sent as-is for the drag origin window. final DragEvent dragEvent = dragEvents.get(0); assertEquals(ACTION_DRAG_STARTED, dragEvent.getAction()); @@ -441,7 +443,7 @@ public class DragDropControllerTests extends WindowTestsBase { iwindow2.setDragEventJournal(dragEvents2); startDrag(dragStartX, dragStartY, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, - ClipData.newPlainText("label", "text"), () -> { + ClipData.newPlainText("label", "text"), (unused) -> { // Verify the start-drag event is sent as-is for the drag origin window. final DragEvent dragEvent = dragEvents.get(0); assertEquals(ACTION_DRAG_STARTED, dragEvent.getAction()); @@ -477,6 +479,56 @@ public class DragDropControllerTests extends WindowTestsBase { }); } + @Test + public void testDragMove() { + startDrag(0, 0, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, + ClipData.newPlainText("label", "text"), (surface) -> { + int dragMoveX = mWindow.getBounds().centerX(); + int dragMoveY = mWindow.getBounds().centerY(); + final SurfaceControl.Transaction transaction = + mSystemServicesTestRule.mTransaction; + clearInvocations(transaction); + + mTarget.handleMotionEvent(true, mWindow.getDisplayId(), dragMoveX, dragMoveY); + verify(transaction).setPosition(surface, dragMoveX, dragMoveY); + + // Clean-up. + mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0); + mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(), 0, + 0); + mToken = mWindow.mClient.asBinder(); + }); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_CONNECTED_DISPLAYS_DND) + public void testConnectedDisplaysDragMoveToOtherDisplay() { + final float testDensityMultiplier = 1.5f; + final DisplayContent testDisplay = createMockSimulatedDisplay(); + testDisplay.mBaseDisplayDensity = + (int) (mDisplayContent.mBaseDisplayDensity * testDensityMultiplier); + WindowState testWindow = createDropTargetWindow("App drag test window", testDisplay); + + // Test starts from mWindow which is on default display. + startDrag(0, 0, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, + ClipData.newPlainText("label", "text"), (surface) -> { + final SurfaceControl.Transaction transaction = + mSystemServicesTestRule.mTransaction; + clearInvocations(transaction); + mTarget.handleMotionEvent(true, testWindow.getDisplayId(), 0, 0); + + verify(transaction).reparent(surface, testDisplay.getSurfaceControl()); + verify(transaction).setScale(surface, testDensityMultiplier, + testDensityMultiplier); + + // Clean-up. + mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0); + mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(), 0, + 0); + mToken = mWindow.mClient.asBinder(); + }); + } + private DragEvent last(ArrayList<DragEvent> list) { return list.get(list.size() - 1); } @@ -645,7 +697,7 @@ public class DragDropControllerTests extends WindowTestsBase { startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION, - ClipData.newPlainText("label", "text"), () -> { + ClipData.newPlainText("label", "text"), (unused) -> { assertTrue(dragEvents.get(0).getAction() == ACTION_DRAG_STARTED); // Verify after consuming that the drag surface is relinquished @@ -676,7 +728,7 @@ public class DragDropControllerTests extends WindowTestsBase { startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION, - ClipData.newPlainText("label", "text"), () -> { + ClipData.newPlainText("label", "text"), (unused) -> { assertTrue(dragEvents.get(0).getAction() == ACTION_DRAG_STARTED); // Verify after consuming that the drag surface is relinquished @@ -713,7 +765,7 @@ public class DragDropControllerTests extends WindowTestsBase { mTarget.setGlobalDragListener(listener); final int invalidXY = 100_000; startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG, - ClipData.newPlainText("label", "Test"), () -> { + ClipData.newPlainText("label", "Test"), (unused) -> { // Trigger an unhandled drop and verify the global drag listener was called mTarget.reportDropWindow(mWindow.mInputChannelToken, invalidXY, invalidXY); mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(), @@ -738,7 +790,7 @@ public class DragDropControllerTests extends WindowTestsBase { mTarget.setGlobalDragListener(listener); final int invalidXY = 100_000; startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG, - ClipData.newPlainText("label", "Test"), () -> { + ClipData.newPlainText("label", "Test"), (unused) -> { // Trigger an unhandled drop and verify the global drag listener was called mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY); mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(), @@ -761,7 +813,7 @@ public class DragDropControllerTests extends WindowTestsBase { doReturn(mock(Binder.class)).when(listener).asBinder(); mTarget.setGlobalDragListener(listener); final int invalidXY = 100_000; - startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), () -> { + startDrag(View.DRAG_FLAG_GLOBAL, ClipData.newPlainText("label", "Test"), (unused) -> { // Trigger an unhandled drop and verify the global drag listener was not called mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY); mTarget.handleMotionEvent(false /* keepHandling */, mDisplayContent.getDisplayId(), @@ -784,7 +836,7 @@ public class DragDropControllerTests extends WindowTestsBase { mTarget.setGlobalDragListener(listener); final int invalidXY = 100_000; startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_START_INTENT_SENDER_ON_UNHANDLED_DRAG, - ClipData.newPlainText("label", "Test"), () -> { + ClipData.newPlainText("label", "Test"), (unused) -> { // Trigger an unhandled drop and verify the global drag listener was called mTarget.reportDropWindow(mock(IBinder.class), invalidXY, invalidXY); mTarget.handleMotionEvent(false /* keepHandling */, @@ -805,7 +857,7 @@ public class DragDropControllerTests extends WindowTestsBase { } private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) { - startDrag(flags, data, () -> { + startDrag(flags, data, (unused) -> { mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY); mTarget.handleMotionEvent(false /* keepHandling */, mWindow.getDisplayId(), dropX, dropY); @@ -816,27 +868,26 @@ public class DragDropControllerTests extends WindowTestsBase { /** * Starts a drag with the given parameters, calls Runnable `r` after drag is started. */ - private void startDrag(int flag, ClipData data, Runnable r) { - startDrag(0, 0, flag, data, r); + private void startDrag(int flag, ClipData data, Consumer<SurfaceControl> c) { + startDrag(0, 0, flag, data, c); } /** * Starts a drag with the given parameters, calls Runnable `r` after drag is started. */ private void startDrag(float startInWindowX, float startInWindowY, int flag, ClipData data, - Runnable r) { + Consumer<SurfaceControl> c) { final SurfaceSession appSession = new SurfaceSession(); try { final SurfaceControl surface = new SurfaceControl.Builder(appSession).setName( "drag surface").setBufferSize(100, 100).setFormat( PixelFormat.TRANSLUCENT).build(); - assertTrue(mWm.mInputManager.startDragAndDrop(new Binder(), new Binder())); mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient, flag, surface, 0, 0, 0, startInWindowX, startInWindowY, 0, 0, data); assertNotNull(mToken); - r.run(); + c.accept(surface); } finally { appSession.kill(); } |