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