1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wm; 18 19 import static android.Manifest.permission.START_TASKS_FROM_RECENTS; 20 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY; 21 import static android.content.ClipDescription.MIMETYPE_APPLICATION_SHORTCUT; 22 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK; 23 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 24 import static android.view.DragEvent.ACTION_DRAG_ENDED; 25 import static android.view.DragEvent.ACTION_DRAG_STARTED; 26 import static android.view.DragEvent.ACTION_DROP; 27 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; 28 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; 29 30 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; 31 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 32 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; 33 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 34 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; 35 36 import static org.junit.Assert.assertFalse; 37 import static org.junit.Assert.assertNotNull; 38 import static org.junit.Assert.assertNull; 39 import static org.junit.Assert.assertTrue; 40 import static org.junit.Assert.fail; 41 import static org.mockito.ArgumentMatchers.anyInt; 42 import static org.mockito.ArgumentMatchers.eq; 43 import static org.mockito.Mockito.verify; 44 45 import android.app.PendingIntent; 46 import android.content.ClipData; 47 import android.content.ClipDescription; 48 import android.content.Intent; 49 import android.content.pm.ShortcutServiceInternal; 50 import android.graphics.PixelFormat; 51 import android.os.Binder; 52 import android.os.IBinder; 53 import android.os.Looper; 54 import android.os.Parcelable; 55 import android.os.UserHandle; 56 import android.platform.test.annotations.Presubmit; 57 import android.view.DragEvent; 58 import android.view.IWindowSessionCallback; 59 import android.view.InputChannel; 60 import android.view.SurfaceControl; 61 import android.view.SurfaceSession; 62 import android.view.View; 63 import android.view.WindowManager; 64 import android.view.accessibility.AccessibilityManager; 65 66 import androidx.test.filters.SmallTest; 67 68 import com.android.server.LocalServices; 69 import com.android.server.pm.UserManagerInternal; 70 71 import org.junit.After; 72 import org.junit.AfterClass; 73 import org.junit.Before; 74 import org.junit.BeforeClass; 75 import org.junit.Test; 76 import org.junit.runner.RunWith; 77 import org.mockito.ArgumentCaptor; 78 import org.mockito.Mockito; 79 80 import java.util.ArrayList; 81 import java.util.concurrent.CountDownLatch; 82 import java.util.concurrent.TimeUnit; 83 84 /** 85 * Tests for the {@link DragDropController} class. 86 * 87 * Build/Install/Run: 88 * atest WmTests:DragDropControllerTests 89 */ 90 @SmallTest 91 @Presubmit 92 @RunWith(WindowTestRunner.class) 93 public class DragDropControllerTests extends WindowTestsBase { 94 private static final int TIMEOUT_MS = 3000; 95 private static final int TEST_UID = 12345; 96 private static final int TEST_PROFILE_UID = 12345 * UserHandle.PER_USER_RANGE; 97 private static final int TEST_PID = 67890; 98 private static final String TEST_PACKAGE = "com.test.package"; 99 100 private TestDragDropController mTarget; 101 private WindowState mWindow; 102 private IBinder mToken; 103 104 static class TestDragDropController extends DragDropController { 105 private Runnable mCloseCallback; 106 boolean mDeferDragStateClosed; 107 boolean mIsAccessibilityDrag; 108 TestDragDropController(WindowManagerService service, Looper looper)109 TestDragDropController(WindowManagerService service, Looper looper) { 110 super(service, looper); 111 } 112 setOnClosedCallbackLocked(Runnable runnable)113 void setOnClosedCallbackLocked(Runnable runnable) { 114 if (mIsAccessibilityDrag) { 115 // Accessibility does not use animation 116 assertTrue(!dragDropActiveLocked()); 117 } else { 118 assertTrue(dragDropActiveLocked()); 119 mCloseCallback = runnable; 120 } 121 } 122 123 @Override onDragStateClosedLocked(DragState dragState)124 void onDragStateClosedLocked(DragState dragState) { 125 if (mDeferDragStateClosed) { 126 return; 127 } 128 super.onDragStateClosedLocked(dragState); 129 if (mCloseCallback != null) { 130 mCloseCallback.run(); 131 mCloseCallback = null; 132 } 133 } 134 } 135 136 /** 137 * Creates a window state which can be used as a drop target. 138 */ createDropTargetWindow(String name, int ownerId)139 private WindowState createDropTargetWindow(String name, int ownerId) { 140 final ActivityRecord activity = createNonAttachedActivityRecord(mDisplayContent); 141 final Task rootTask = createTask(mDisplayContent); 142 final Task task = createTaskInRootTask(rootTask, ownerId); 143 task.addChild(activity, 0); 144 145 // Use a new TestIWindow so we don't collect events for other windows 146 final WindowState window = createWindow( 147 null, TYPE_BASE_APPLICATION, activity, name, ownerId, false, new TestIWindow()); 148 window.mInputChannel = new InputChannel(); 149 window.mInputChannelToken = window.mInputChannel.getToken(); 150 window.mHasSurface = true; 151 mWm.mWindowMap.put(window.mClient.asBinder(), window); 152 mWm.mInputToWindowMap.put(window.mInputChannelToken, window); 153 return window; 154 } 155 156 @BeforeClass setUpOnce()157 public static void setUpOnce() { 158 final UserManagerInternal userManager = mock(UserManagerInternal.class); 159 LocalServices.addService(UserManagerInternal.class, userManager); 160 } 161 162 @AfterClass tearDownOnce()163 public static void tearDownOnce() { 164 LocalServices.removeServiceForTest(UserManagerInternal.class); 165 } 166 167 @Before setUp()168 public void setUp() throws Exception { 169 mTarget = new TestDragDropController(mWm, mWm.mH.getLooper()); 170 mWindow = createDropTargetWindow("Drag test window", 0); 171 doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0); 172 when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class), 173 any(InputChannel.class), any(boolean.class))).thenReturn(true); 174 175 mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow); 176 } 177 178 @After tearDown()179 public void tearDown() throws Exception { 180 final CountDownLatch latch; 181 if (!mTarget.dragDropActiveLocked()) { 182 return; 183 } 184 if (mToken != null) { 185 mTarget.cancelDragAndDrop(mToken, false); 186 } 187 latch = new CountDownLatch(1); 188 mTarget.setOnClosedCallbackLocked(latch::countDown); 189 if (mTarget.mIsAccessibilityDrag) { 190 mTarget.mIsAccessibilityDrag = false; 191 return; 192 } 193 assertTrue(awaitInWmLock(() -> latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS))); 194 } 195 196 @Test testDragFlow()197 public void testDragFlow() { 198 doDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0); 199 } 200 201 @Test testA11yDragFlow()202 public void testA11yDragFlow() { 203 mTarget.mIsAccessibilityDrag = true; 204 doA11yDragAndDrop(0, ClipData.newPlainText("label", "Test"), 0, 0); 205 } 206 207 @Test testPerformDrag_NullDataWithGrantUri()208 public void testPerformDrag_NullDataWithGrantUri() { 209 doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0); 210 } 211 212 @Test testPerformDrag_NullDataToOtherUser()213 public void testPerformDrag_NullDataToOtherUser() { 214 final WindowState otherUsersWindow = 215 createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE); 216 doReturn(otherUsersWindow).when(mDisplayContent).getTouchableWinAtPointLocked(10, 10); 217 218 doDragAndDrop(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 10, 10); 219 mToken = otherUsersWindow.mClient.asBinder(); 220 } 221 222 @Test testPrivateInterceptGlobalDragDropFlagChecksPermission()223 public void testPrivateInterceptGlobalDragDropFlagChecksPermission() { 224 spyOn(mWm.mContext); 225 226 DisplayPolicy policy = mDisplayContent.getDisplayPolicy(); 227 WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(); 228 attrs.privateFlags |= PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; 229 policy.validateAddingWindowLw(attrs, Binder.getCallingPid(), Binder.getCallingUid()); 230 231 verify(mWm.mAtmService).enforceTaskPermission(any()); 232 } 233 234 @Test testPrivateInterceptGlobalDragDropFlagBehaviour()235 public void testPrivateInterceptGlobalDragDropFlagBehaviour() { 236 mWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; 237 mWindow.setViewVisibility(View.GONE); 238 239 // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events 240 // immediately after dispatching, which is a problem when using mockito arguments captor 241 // because it returns and modifies the same drag event 242 TestIWindow iwindow = (TestIWindow) mWindow.mClient; 243 final ArrayList<DragEvent> dragEvents = new ArrayList<>(); 244 iwindow.setDragEventJournal(dragEvents); 245 246 startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, 247 ClipData.newPlainText("label", "text"), () -> { 248 // Verify the start-drag event is sent for invisible windows 249 final DragEvent dragEvent = dragEvents.get(0); 250 assertTrue(dragEvent.getAction() == ACTION_DRAG_STARTED); 251 252 // Verify after consuming that the drag surface is relinquished 253 try { 254 mTarget.mDeferDragStateClosed = true; 255 mTarget.reportDropWindow(mWindow.mInputChannelToken, 0, 0); 256 // Verify the drop event includes the drag surface 257 mTarget.handleMotionEvent(false, 0, 0); 258 final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1); 259 assertTrue(dropEvent.getDragSurface() != null); 260 261 mTarget.reportDropResult(iwindow, true); 262 } finally { 263 mTarget.mDeferDragStateClosed = false; 264 } 265 assertTrue(mTarget.dragSurfaceRelinquishedToDropTarget()); 266 }); 267 } 268 269 @Test testPrivateInterceptGlobalDragDropIgnoresNonLocalWindows()270 public void testPrivateInterceptGlobalDragDropIgnoresNonLocalWindows() { 271 WindowState nonLocalWindow = createDropTargetWindow("App drag test window", 0); 272 WindowState globalInterceptWindow = createDropTargetWindow("Global drag test window", 0); 273 globalInterceptWindow.mAttrs.privateFlags |= PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; 274 275 // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events 276 // immediately after dispatching, which is a problem when using mockito arguments captor 277 // because it returns and modifies the same drag event 278 TestIWindow localIWindow = (TestIWindow) mWindow.mClient; 279 final ArrayList<DragEvent> localWindowDragEvents = new ArrayList<>(); 280 localIWindow.setDragEventJournal(localWindowDragEvents); 281 TestIWindow nonLocalIWindow = (TestIWindow) nonLocalWindow.mClient; 282 final ArrayList<DragEvent> nonLocalWindowDragEvents = new ArrayList<>(); 283 nonLocalIWindow.setDragEventJournal(nonLocalWindowDragEvents); 284 TestIWindow globalInterceptIWindow = (TestIWindow) globalInterceptWindow.mClient; 285 final ArrayList<DragEvent> globalInterceptWindowDragEvents = new ArrayList<>(); 286 globalInterceptIWindow.setDragEventJournal(globalInterceptWindowDragEvents); 287 288 startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, 289 createClipDataForActivity(null, mock(UserHandle.class)), () -> { 290 // Verify the start-drag event is sent for the local and global intercept window 291 // but not the other window 292 assertTrue(nonLocalWindowDragEvents.isEmpty()); 293 assertTrue(localWindowDragEvents.get(0).getAction() 294 == ACTION_DRAG_STARTED); 295 assertTrue(globalInterceptWindowDragEvents.get(0).getAction() 296 == ACTION_DRAG_STARTED); 297 298 // Verify that only the global intercept window receives the clip data with the 299 // resolved activity info for the drag 300 assertNull(localWindowDragEvents.get(0).getClipData()); 301 assertTrue(globalInterceptWindowDragEvents.get(0).getClipData() 302 .willParcelWithActivityInfo()); 303 304 mTarget.reportDropWindow(globalInterceptWindow.mInputChannelToken, 0, 0); 305 mTarget.handleMotionEvent(false, 0, 0); 306 mToken = globalInterceptWindow.mClient.asBinder(); 307 308 // Verify the drop event is only sent for the global intercept window 309 assertTrue(nonLocalWindowDragEvents.isEmpty()); 310 assertTrue(last(localWindowDragEvents).getAction() != ACTION_DROP); 311 assertTrue(last(globalInterceptWindowDragEvents).getAction() == ACTION_DROP); 312 313 // Verify that item extras were not sent with the drop event 314 assertNull(last(localWindowDragEvents).getClipData()); 315 assertFalse(last(globalInterceptWindowDragEvents).getClipData() 316 .willParcelWithActivityInfo()); 317 }); 318 } 319 last(ArrayList<DragEvent> list)320 private DragEvent last(ArrayList<DragEvent> list) { 321 return list.get(list.size() - 1); 322 } 323 324 @Test testValidateAppActivityArguments()325 public void testValidateAppActivityArguments() { 326 final Session session = new Session(mWm, new IWindowSessionCallback.Stub() { 327 @Override 328 public void onAnimatorScaleChanged(float scale) {} 329 }); 330 try { 331 session.validateAndResolveDragMimeTypeExtras( 332 createClipDataForActivity(mock(PendingIntent.class), null), TEST_UID, TEST_PID, 333 TEST_PACKAGE); 334 fail("Expected failure without user"); 335 } catch (IllegalArgumentException e) { 336 // Expected failure 337 } 338 try { 339 session.validateAndResolveDragMimeTypeExtras( 340 createClipDataForActivity(null, mock(UserHandle.class)), TEST_UID, TEST_PID, 341 TEST_PACKAGE); 342 fail("Expected failure without pending intent"); 343 } catch (IllegalArgumentException e) { 344 // Expected failure 345 } 346 } 347 createClipDataForActivity(PendingIntent pi, UserHandle user)348 private ClipData createClipDataForActivity(PendingIntent pi, UserHandle user) { 349 final Intent data = new Intent(); 350 if (pi != null) { 351 data.putExtra(ClipDescription.EXTRA_PENDING_INTENT, (Parcelable) pi); 352 } 353 if (user != null) { 354 data.putExtra(Intent.EXTRA_USER, user); 355 } 356 final ClipData clipData = new ClipData( 357 new ClipDescription("drag", new String[] { 358 MIMETYPE_APPLICATION_ACTIVITY}), 359 new ClipData.Item(data)); 360 return clipData; 361 } 362 363 @Test testValidateAppShortcutArguments()364 public void testValidateAppShortcutArguments() { 365 doReturn(PERMISSION_GRANTED).when(mWm.mContext) 366 .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS)); 367 final Session session = new Session(mWm, new IWindowSessionCallback.Stub() { 368 @Override 369 public void onAnimatorScaleChanged(float scale) {} 370 }); 371 try { 372 session.validateAndResolveDragMimeTypeExtras( 373 createClipDataForShortcut(null, "test_shortcut_id", mock(UserHandle.class)), 374 TEST_UID, TEST_PID, TEST_PACKAGE); 375 fail("Expected failure without package name"); 376 } catch (IllegalArgumentException e) { 377 // Expected failure 378 } 379 try { 380 session.validateAndResolveDragMimeTypeExtras( 381 createClipDataForShortcut("test_package", null, mock(UserHandle.class)), 382 TEST_UID, TEST_PID, TEST_PACKAGE); 383 fail("Expected failure without shortcut id"); 384 } catch (IllegalArgumentException e) { 385 // Expected failure 386 } 387 try { 388 session.validateAndResolveDragMimeTypeExtras( 389 createClipDataForShortcut("test_package", "test_shortcut_id", null), 390 TEST_UID, TEST_PID, TEST_PACKAGE); 391 fail("Expected failure without package name"); 392 } catch (IllegalArgumentException e) { 393 // Expected failure 394 } 395 } 396 397 @Test testValidateProfileAppShortcutArguments_notCallingUid()398 public void testValidateProfileAppShortcutArguments_notCallingUid() { 399 doReturn(PERMISSION_GRANTED).when(mWm.mContext) 400 .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS)); 401 final Session session = Mockito.spy(new Session(mWm, new IWindowSessionCallback.Stub() { 402 @Override 403 public void onAnimatorScaleChanged(float scale) {} 404 })); 405 final ShortcutServiceInternal shortcutService = mock(ShortcutServiceInternal.class); 406 final Intent[] shortcutIntents = new Intent[1]; 407 shortcutIntents[0] = new Intent(); 408 doReturn(shortcutIntents).when(shortcutService).createShortcutIntents(anyInt(), any(), 409 any(), any(), anyInt(), anyInt(), anyInt()); 410 LocalServices.removeServiceForTest(ShortcutServiceInternal.class); 411 LocalServices.addService(ShortcutServiceInternal.class, shortcutService); 412 413 ArgumentCaptor<Integer> callingUser = ArgumentCaptor.forClass(Integer.class); 414 session.validateAndResolveDragMimeTypeExtras( 415 createClipDataForShortcut("test_package", "test_shortcut_id", 416 mock(UserHandle.class)), 417 TEST_PROFILE_UID, TEST_PID, TEST_PACKAGE); 418 verify(shortcutService).createShortcutIntents(callingUser.capture(), any(), 419 any(), any(), anyInt(), anyInt(), anyInt()); 420 assertTrue(callingUser.getValue() == UserHandle.getUserId(TEST_PROFILE_UID)); 421 } 422 createClipDataForShortcut(String packageName, String shortcutId, UserHandle user)423 private ClipData createClipDataForShortcut(String packageName, String shortcutId, 424 UserHandle user) { 425 final Intent data = new Intent(); 426 if (packageName != null) { 427 data.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); 428 } 429 if (shortcutId != null) { 430 data.putExtra(Intent.EXTRA_SHORTCUT_ID, shortcutId); 431 } 432 if (user != null) { 433 data.putExtra(Intent.EXTRA_USER, user); 434 } 435 final ClipData clipData = new ClipData( 436 new ClipDescription("drag", new String[] { 437 MIMETYPE_APPLICATION_SHORTCUT}), 438 new ClipData.Item(data)); 439 return clipData; 440 } 441 442 @Test testValidateAppTaskArguments()443 public void testValidateAppTaskArguments() { 444 doReturn(PERMISSION_GRANTED).when(mWm.mContext) 445 .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS)); 446 final Session session = new Session(mWm, new IWindowSessionCallback.Stub() { 447 @Override 448 public void onAnimatorScaleChanged(float scale) {} 449 }); 450 try { 451 final ClipData clipData = new ClipData( 452 new ClipDescription("drag", new String[] { MIMETYPE_APPLICATION_TASK }), 453 new ClipData.Item(new Intent())); 454 455 session.validateAndResolveDragMimeTypeExtras(clipData, TEST_UID, TEST_PID, 456 TEST_PACKAGE); 457 fail("Expected failure without task id"); 458 } catch (IllegalArgumentException e) { 459 // Expected failure 460 } 461 } 462 463 @Test testValidateFlags()464 public void testValidateFlags() { 465 final Session session = new Session(mWm, new IWindowSessionCallback.Stub() { 466 @Override 467 public void onAnimatorScaleChanged(float scale) {} 468 }); 469 try { 470 session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION); 471 fail("Expected failure without permission"); 472 } catch (SecurityException e) { 473 // Expected failure 474 } 475 } 476 477 @Test testValidateFlagsWithPermission()478 public void testValidateFlagsWithPermission() { 479 doReturn(PERMISSION_GRANTED).when(mWm.mContext) 480 .checkCallingOrSelfPermission(eq(START_TASKS_FROM_RECENTS)); 481 final Session session = new Session(mWm, new IWindowSessionCallback.Stub() { 482 @Override 483 public void onAnimatorScaleChanged(float scale) {} 484 }); 485 try { 486 session.validateDragFlags(View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION); 487 // Expected pass 488 } catch (SecurityException e) { 489 fail("Expected no failure with permission"); 490 } 491 } 492 493 @Test testRequestSurfaceForReturnAnimationFlag_dropSuccessful()494 public void testRequestSurfaceForReturnAnimationFlag_dropSuccessful() { 495 WindowState otherWindow = createDropTargetWindow("App drag test window", 0); 496 TestIWindow otherIWindow = (TestIWindow) otherWindow.mClient; 497 498 // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events 499 // immediately after dispatching, which is a problem when using mockito arguments captor 500 // because it returns and modifies the same drag event 501 TestIWindow iwindow = (TestIWindow) mWindow.mClient; 502 final ArrayList<DragEvent> dragEvents = new ArrayList<>(); 503 iwindow.setDragEventJournal(dragEvents); 504 505 startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ 506 | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION, 507 ClipData.newPlainText("label", "text"), () -> { 508 assertTrue(dragEvents.get(0).getAction() == ACTION_DRAG_STARTED); 509 510 // Verify after consuming that the drag surface is relinquished 511 mTarget.reportDropWindow(otherWindow.mInputChannelToken, 0, 0); 512 mTarget.handleMotionEvent(false, 0, 0); 513 mToken = otherWindow.mClient.asBinder(); 514 mTarget.reportDropResult(otherIWindow, true); 515 516 // Verify the DRAG_ENDED event does NOT include the drag surface 517 final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1); 518 assertTrue(dragEvents.get(dragEvents.size() - 1).getAction() 519 == ACTION_DRAG_ENDED); 520 assertTrue(dropEvent.getDragSurface() == null); 521 }); 522 } 523 524 @Test testRequestSurfaceForReturnAnimationFlag_dropUnsuccessful()525 public void testRequestSurfaceForReturnAnimationFlag_dropUnsuccessful() { 526 WindowState otherWindow = createDropTargetWindow("App drag test window", 0); 527 TestIWindow otherIWindow = (TestIWindow) otherWindow.mClient; 528 529 // Necessary for now since DragState.sendDragStartedLocked() will recycle drag events 530 // immediately after dispatching, which is a problem when using mockito arguments captor 531 // because it returns and modifies the same drag event 532 TestIWindow iwindow = (TestIWindow) mWindow.mClient; 533 final ArrayList<DragEvent> dragEvents = new ArrayList<>(); 534 iwindow.setDragEventJournal(dragEvents); 535 536 startDrag(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ 537 | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION, 538 ClipData.newPlainText("label", "text"), () -> { 539 assertTrue(dragEvents.get(0).getAction() == ACTION_DRAG_STARTED); 540 541 // Verify after consuming that the drag surface is relinquished 542 mTarget.reportDropWindow(otherWindow.mInputChannelToken, 0, 0); 543 mTarget.handleMotionEvent(false, 0, 0); 544 mToken = otherWindow.mClient.asBinder(); 545 mTarget.reportDropResult(otherIWindow, false); 546 547 // Verify the DRAG_ENDED event includes the drag surface 548 final DragEvent dropEvent = dragEvents.get(dragEvents.size() - 1); 549 assertTrue(dragEvents.get(dragEvents.size() - 1).getAction() 550 == ACTION_DRAG_ENDED); 551 assertTrue(dropEvent.getDragSurface() != null); 552 }); 553 } 554 doDragAndDrop(int flags, ClipData data, float dropX, float dropY)555 private void doDragAndDrop(int flags, ClipData data, float dropX, float dropY) { 556 startDrag(flags, data, () -> { 557 mTarget.reportDropWindow(mWindow.mInputChannelToken, dropX, dropY); 558 mTarget.handleMotionEvent(false, dropX, dropY); 559 mToken = mWindow.mClient.asBinder(); 560 }); 561 } 562 startDrag(int flag, ClipData data, Runnable r)563 private void startDrag(int flag, ClipData data, Runnable r) { 564 final SurfaceSession appSession = new SurfaceSession(); 565 try { 566 final SurfaceControl surface = new SurfaceControl.Builder(appSession) 567 .setName("drag surface") 568 .setBufferSize(100, 100) 569 .setFormat(PixelFormat.TRANSLUCENT) 570 .build(); 571 572 assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(), 573 new InputChannel(), true /* isDragDrop */)); 574 mToken = mTarget.performDrag(0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0, data); 575 assertNotNull(mToken); 576 577 r.run(); 578 } finally { 579 appSession.kill(); 580 } 581 } 582 doA11yDragAndDrop(int flags, ClipData data, float dropX, float dropY)583 private void doA11yDragAndDrop(int flags, ClipData data, float dropX, float dropY) { 584 spyOn(mTarget); 585 AccessibilityManager accessibilityManager = Mockito.mock(AccessibilityManager.class); 586 when(accessibilityManager.isEnabled()).thenReturn(true); 587 doReturn(accessibilityManager).when(mTarget).getAccessibilityManager(); 588 startA11yDrag(flags, data, () -> { 589 boolean dropped = mTarget.dropForAccessibility(mWindow.mClient, dropX, dropY); 590 mToken = mWindow.mClient.asBinder(); 591 }); 592 } 593 startA11yDrag(int flags, ClipData data, Runnable r)594 private void startA11yDrag(int flags, ClipData data, Runnable r) { 595 mToken = mTarget.performDrag(0, 0, mWindow.mClient, 596 flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, data); 597 assertNotNull(mToken); 598 r.run(); 599 } 600 } 601