Loading services/core/java/com/android/server/wm/DragDropController.java +3 −2 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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 Loading services/core/java/com/android/server/wm/DragState.java +42 −3 Original line number Diff line number Diff line Loading @@ -128,11 +128,18 @@ class DragState { * without having a WM lock. */ 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); Loading Loading @@ -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(); Loading Loading @@ -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. Loading Loading @@ -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; } Loading @@ -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, Loading services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +68 −17 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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); Loading Loading @@ -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()); Loading Loading @@ -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); Loading Loading @@ -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()); Loading Loading @@ -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()); Loading Loading @@ -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); } Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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(), Loading @@ -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(), Loading @@ -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(), Loading @@ -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 */, Loading @@ -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); Loading @@ -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(); } Loading Loading
services/core/java/com/android/server/wm/DragDropController.java +3 −2 Original line number Diff line number Diff line Loading @@ -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); Loading Loading @@ -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 Loading
services/core/java/com/android/server/wm/DragState.java +42 −3 Original line number Diff line number Diff line Loading @@ -128,11 +128,18 @@ class DragState { * without having a WM lock. */ 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); Loading Loading @@ -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(); Loading Loading @@ -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. Loading Loading @@ -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; } Loading @@ -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, Loading
services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java +68 −17 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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. Loading Loading @@ -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); Loading Loading @@ -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()); Loading Loading @@ -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); Loading Loading @@ -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()); Loading Loading @@ -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()); Loading Loading @@ -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); } Loading Loading @@ -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 Loading Loading @@ -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 Loading Loading @@ -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(), Loading @@ -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(), Loading @@ -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(), Loading @@ -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 */, Loading @@ -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); Loading @@ -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(); } Loading