• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 android.server.wm;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 
21 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
22 
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertTrue;
25 import static org.junit.Assert.fail;
26 import static org.junit.Assume.assumeFalse;
27 
28 import android.app.Instrumentation;
29 import android.app.UiAutomation;
30 import android.content.ClipData;
31 import android.content.ClipDescription;
32 import android.content.pm.PackageManager;
33 import android.graphics.Bitmap;
34 import android.graphics.Canvas;
35 import android.graphics.Color;
36 import android.graphics.Point;
37 import android.os.Bundle;
38 import android.os.Parcel;
39 import android.os.Parcelable;
40 import android.os.SystemClock;
41 import android.platform.test.annotations.Presubmit;
42 import android.server.wm.cts.R;
43 import android.util.Size;
44 import android.view.Display;
45 import android.view.DragEvent;
46 import android.view.InputDevice;
47 import android.view.MotionEvent;
48 import android.view.View;
49 import android.view.ViewGroup;
50 
51 import androidx.test.InstrumentationRegistry;
52 import androidx.test.runner.AndroidJUnit4;
53 
54 import org.junit.After;
55 import org.junit.AfterClass;
56 import org.junit.Before;
57 import org.junit.BeforeClass;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 
61 import java.util.ArrayList;
62 import java.util.Arrays;
63 import java.util.concurrent.CountDownLatch;
64 import java.util.concurrent.TimeUnit;
65 import java.util.concurrent.atomic.AtomicBoolean;
66 import java.util.stream.IntStream;
67 
68 @Presubmit
69 @RunWith(AndroidJUnit4.class)
70 public class DragDropTest extends WindowManagerTestBase {
71     static final String TAG = "DragDropTest";
72 
73     final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
74     final UiAutomation mAutomation = mInstrumentation.getUiAutomation();
75 
76     private DragDropActivity mActivity;
77 
78     private CountDownLatch mStartReceived;
79     private CountDownLatch mEndReceived;
80 
81     private AssertionError mMainThreadAssertionError;
82 
83     private static final ReportedDisplayMetrics sReportedDisplayMetrics =
84             ReportedDisplayMetrics.getDisplayMetrics(Display.DEFAULT_DISPLAY);
85 
86     /**
87      * Check whether two objects have the same binary data when dumped into Parcels
88      * @return True if the objects are equal
89      */
compareParcelables(Parcelable obj1, Parcelable obj2)90     private static boolean compareParcelables(Parcelable obj1, Parcelable obj2) {
91         if (obj1 == null && obj2 == null) {
92             return true;
93         }
94         if (obj1 == null || obj2 == null) {
95             return false;
96         }
97         Parcel p1 = Parcel.obtain();
98         obj1.writeToParcel(p1, 0);
99         Parcel p2 = Parcel.obtain();
100         obj2.writeToParcel(p2, 0);
101         boolean result = Arrays.equals(p1.marshall(), p2.marshall());
102         p1.recycle();
103         p2.recycle();
104         return result;
105     }
106 
107     private static final ClipDescription sClipDescription =
108             new ClipDescription("TestLabel", new String[]{"text/plain"});
109     private static final ClipData sClipData =
110             new ClipData(sClipDescription, new ClipData.Item("TestText"));
111     private static final Object sLocalState = new Object(); // just check if null or not
112 
113     class LogEntry {
114         public View view;
115 
116         // Public DragEvent fields
117         public int action; // DragEvent.getAction()
118         public float x; // DragEvent.getX()
119         public float y; // DragEvent.getY()
120         public ClipData clipData; // DragEvent.getClipData()
121         public ClipDescription clipDescription; // DragEvent.getClipDescription()
122         public Object localState; // DragEvent.getLocalState()
123         public boolean result; // DragEvent.getResult()
124 
LogEntry(View v, int action, float x, float y, ClipData clipData, ClipDescription clipDescription, Object localState, boolean result)125         LogEntry(View v, int action, float x, float y, ClipData clipData,
126                 ClipDescription clipDescription, Object localState, boolean result) {
127             this.view = v;
128             this.action = action;
129             this.x = x;
130             this.y = y;
131             this.clipData = clipData;
132             this.clipDescription = clipDescription;
133             this.localState = localState;
134             this.result = result;
135         }
136 
137         @Override
equals(Object obj)138         public boolean equals(Object obj) {
139             if (this == obj) {
140                 return true;
141             }
142             if (!(obj instanceof LogEntry)) {
143                 return false;
144             }
145             final LogEntry other = (LogEntry) obj;
146             return view == other.view && action == other.action
147                     && x == other.x && y == other.y
148                     && compareParcelables(clipData, other.clipData)
149                     && compareParcelables(clipDescription, other.clipDescription)
150                     && localState == other.localState
151                     && result == other.result;
152         }
153 
154         @Override
toString()155         public String toString() {
156             StringBuilder sb = new StringBuilder();
157             sb.append("DragEvent {action=").append(action).append(" x=").append(x).append(" y=")
158                     .append(y).append(" result=").append(result).append("}")
159                     .append(" @ ").append(view);
160             return sb.toString();
161         }
162     }
163 
164     // Actual and expected sequences of events.
165     // While the test is running, logs should be accessed only from the main thread.
166     final private ArrayList<LogEntry> mActual = new ArrayList<LogEntry> ();
167     final private ArrayList<LogEntry> mExpected = new ArrayList<LogEntry> ();
168 
obtainClipData(int action)169     private static ClipData obtainClipData(int action) {
170         if (action == DragEvent.ACTION_DROP) {
171             return sClipData;
172         }
173         return null;
174     }
175 
obtainClipDescription(int action)176     private static ClipDescription obtainClipDescription(int action) {
177         if (action == DragEvent.ACTION_DRAG_ENDED) {
178             return null;
179         }
180         return sClipDescription;
181     }
182 
logEvent(View v, DragEvent ev)183     private void logEvent(View v, DragEvent ev) {
184         if (ev.getAction() == DragEvent.ACTION_DRAG_STARTED) {
185             mStartReceived.countDown();
186         }
187         if (ev.getAction() == DragEvent.ACTION_DRAG_ENDED) {
188             mEndReceived.countDown();
189         }
190         mActual.add(new LogEntry(v, ev.getAction(), ev.getX(), ev.getY(), ev.getClipData(),
191                 ev.getClipDescription(), ev.getLocalState(), ev.getResult()));
192     }
193 
194     // Add expected event for a view, with zero coordinates.
expectEvent5(int action, int viewId)195     private void expectEvent5(int action, int viewId) {
196         View v = mActivity.findViewById(viewId);
197         mExpected.add(new LogEntry(v, action, 0, 0, obtainClipData(action),
198                 obtainClipDescription(action), sLocalState, false));
199     }
200 
201     // Add expected event for a view.
expectEndEvent(int viewId, float x, float y, boolean result)202     private void expectEndEvent(int viewId, float x, float y, boolean result) {
203         View v = mActivity.findViewById(viewId);
204         int action = DragEvent.ACTION_DRAG_ENDED;
205         mExpected.add(new LogEntry(v, action, x, y, obtainClipData(action),
206                 obtainClipDescription(action), sLocalState, result));
207     }
208 
209     // Add expected successful-end event for a view.
expectEndEventSuccess(int viewId)210     private void expectEndEventSuccess(int viewId) {
211         expectEndEvent(viewId, 0, 0, true);
212     }
213 
214     // Add expected failed-end event for a view, with the release coordinates shifted by 6 relative
215     // to the left-upper corner of a view with id releaseViewId.
expectEndEventFailure6(int viewId, int releaseViewId)216     private void expectEndEventFailure6(int viewId, int releaseViewId) {
217         View v = mActivity.findViewById(viewId);
218         View release = mActivity.findViewById(releaseViewId);
219         int [] releaseLoc = new int[2];
220         release.getLocationOnScreen(releaseLoc);
221         int action = DragEvent.ACTION_DRAG_ENDED;
222         mExpected.add(new LogEntry(v, action,
223                 releaseLoc[0] + 6, releaseLoc[1] + 6, obtainClipData(action),
224                 obtainClipDescription(action), sLocalState, false));
225     }
226 
227     // Add expected event for a view, with coordinates over view locationViewId, with the specified
228     // offset from the location view's upper-left corner.
expectEventWithOffset(int action, int viewId, int locationViewId, int offset)229     private void expectEventWithOffset(int action, int viewId, int locationViewId, int offset) {
230         View v = mActivity.findViewById(viewId);
231         View locationView = mActivity.findViewById(locationViewId);
232         int [] viewLocation = new int[2];
233         v.getLocationOnScreen(viewLocation);
234         int [] locationViewLocation = new int[2];
235         locationView.getLocationOnScreen(locationViewLocation);
236         mExpected.add(new LogEntry(v, action,
237                 locationViewLocation[0] - viewLocation[0] + offset,
238                 locationViewLocation[1] - viewLocation[1] + offset, obtainClipData(action),
239                 obtainClipDescription(action), sLocalState, false));
240     }
241 
expectEvent5(int action, int viewId, int locationViewId)242     private void expectEvent5(int action, int viewId, int locationViewId) {
243         expectEventWithOffset(action, viewId, locationViewId, 5);
244     }
245 
246     // See comment for injectMouse6 on why we need both *5 and *6 methods.
expectEvent6(int action, int viewId, int locationViewId)247     private void expectEvent6(int action, int viewId, int locationViewId) {
248         expectEventWithOffset(action, viewId, locationViewId, 6);
249     }
250 
251     // Inject mouse event over a given view, with specified offset from its left-upper corner.
injectMouseWithOffset(int viewId, int action, int offset)252     private void injectMouseWithOffset(int viewId, int action, int offset) {
253         runOnMain(() -> {
254             View v = mActivity.findViewById(viewId);
255             int [] destLoc = new int [2];
256             v.getLocationOnScreen(destLoc);
257             long downTime = SystemClock.uptimeMillis();
258             MotionEvent event = MotionEvent.obtain(downTime, downTime, action,
259                     destLoc[0] + offset, destLoc[1] + offset, 1);
260             event.setSource(InputDevice.SOURCE_MOUSE);
261             mAutomation.injectInputEvent(event, false);
262         });
263 
264         // Wait till the mouse event generates drag events. Also, some waiting needed because the
265         // system seems to collapse too frequent mouse events.
266         try {
267             Thread.sleep(100);
268         } catch (Exception e) {
269             fail("Exception while wait: " + e);
270         }
271     }
272 
273     // Inject mouse event over a given view, with offset 5 from its left-upper corner.
injectMouse5(int viewId, int action)274     private void injectMouse5(int viewId, int action) {
275         injectMouseWithOffset(viewId, action, 5);
276     }
277 
278     // Inject mouse event over a given view, with offset 6 from its left-upper corner.
279     // We need both injectMouse5 and injectMouse6 if we want to inject 2 events in a row in the same
280     // view, and want them to produce distinct drag events or simply drag events with different
281     // coordinates.
injectMouse6(int viewId, int action)282     private void injectMouse6(int viewId, int action) {
283         injectMouseWithOffset(viewId, action, 6);
284     }
285 
logToString(ArrayList<LogEntry> log)286     private String logToString(ArrayList<LogEntry> log) {
287         StringBuilder sb = new StringBuilder();
288         for (int i = 0; i < log.size(); ++i) {
289             LogEntry e = log.get(i);
290             sb.append("#").append(i + 1).append(": ").append(e).append('\n');
291         }
292         return sb.toString();
293     }
294 
failWithLogs(String message)295     private void failWithLogs(String message) {
296         fail(message + ":\nExpected event sequence:\n" + logToString(mExpected) +
297                 "\nActual event sequence:\n" + logToString(mActual));
298     }
299 
verifyEventLog()300     private void verifyEventLog() {
301         try {
302             assertTrue("Timeout while waiting for END event",
303                     mEndReceived.await(1, TimeUnit.SECONDS));
304         } catch (InterruptedException e) {
305             fail("Got InterruptedException while waiting for END event");
306         }
307 
308         // Verify the log.
309         runOnMain(() -> {
310             if (mExpected.size() != mActual.size()) {
311                 failWithLogs("Actual log has different size than expected");
312             }
313 
314             for (int i = 0; i < mActual.size(); ++i) {
315                 if (!mActual.get(i).equals(mExpected.get(i))) {
316                     failWithLogs("Actual event #" + (i + 1) + " is different from expected");
317                 }
318             }
319         });
320     }
321 
322     /** Checks if device type is watch. */
isWatchDevice()323     private boolean isWatchDevice() {
324         return mInstrumentation.getTargetContext().getPackageManager()
325                 .hasSystemFeature(PackageManager.FEATURE_WATCH);
326     }
327 
328     @Before
setUp()329     public void setUp() throws InterruptedException {
330         assumeFalse(isWatchDevice());
331         mActivity = startActivityInWindowingMode(DragDropActivity.class, WINDOWING_MODE_FULLSCREEN);
332 
333         mStartReceived = new CountDownLatch(1);
334         mEndReceived = new CountDownLatch(1);
335     }
336 
337     @After
tearDown()338     public void tearDown() throws Exception {
339         mActual.clear();
340         mExpected.clear();
341     }
342 
343     @BeforeClass
resetToPhysicalDisplayMetrics()344     public static void resetToPhysicalDisplayMetrics() {
345         if (sReportedDisplayMetrics.overrideSize != null) {
346             final Size realSize = new Size(
347                     sReportedDisplayMetrics.physicalSize.getWidth(),
348                     sReportedDisplayMetrics.physicalSize.getHeight());
349             sReportedDisplayMetrics.setSize(realSize);
350         }
351 
352         if (sReportedDisplayMetrics.overrideDensity != null) {
353             final Integer realDensity = sReportedDisplayMetrics.physicalDensity;
354             sReportedDisplayMetrics.setDensity(realDensity);
355         }
356     }
357 
358     @AfterClass
restoreDisplayMetrics()359     public static void restoreDisplayMetrics() {
360         sReportedDisplayMetrics.restoreDisplayMetrics();
361     }
362 
363     // Sets handlers on all views in a tree, which log the event and return false.
setRejectingHandlersOnTree(View v)364     private void setRejectingHandlersOnTree(View v) {
365         v.setOnDragListener((_v, ev) -> {
366             logEvent(_v, ev);
367             return false;
368         });
369 
370         if (v instanceof ViewGroup) {
371             ViewGroup group = (ViewGroup) v;
372             for (int i = 0; i < group.getChildCount(); ++i) {
373                 setRejectingHandlersOnTree(group.getChildAt(i));
374             }
375         }
376     }
377 
runOnMain(Runnable runner)378     private void runOnMain(Runnable runner) throws AssertionError {
379         mMainThreadAssertionError = null;
380         mInstrumentation.runOnMainSync(() -> {
381             try {
382                 runner.run();
383             } catch (AssertionError error) {
384                 mMainThreadAssertionError = error;
385             }
386         });
387         if (mMainThreadAssertionError != null) {
388             throw mMainThreadAssertionError;
389         }
390     }
391 
startDrag()392     private void startDrag() {
393         // Mouse down. Required for the drag to start.
394         injectMouse5(R.id.draggable, MotionEvent.ACTION_DOWN);
395 
396         runOnMain(() -> {
397             // Start drag.
398             View v = mActivity.findViewById(R.id.draggable);
399             assertTrue("Couldn't start drag",
400                     v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v), sLocalState, 0));
401         });
402 
403         try {
404             assertTrue("Timeout while waiting for START event",
405                     mStartReceived.await(1, TimeUnit.SECONDS));
406         } catch (InterruptedException e) {
407             fail("Got InterruptedException while waiting for START event");
408         }
409 
410         // This is needed after startDragAndDrop to ensure the drag window is ready.
411         getInstrumentation().getUiAutomation().syncInputTransactions();
412     }
413 
414     /**
415      * Tests that no drag-drop events are sent to views that aren't supposed to receive them.
416      */
417     @Test
testNoExtraEvents()418     public void testNoExtraEvents() throws Exception {
419         runOnMain(() -> {
420             // Tell all views in layout to return false to all events, and log them.
421             setRejectingHandlersOnTree(mActivity.findViewById(R.id.drag_drop_activity_main));
422 
423             // Override handlers for the inner view and its parent to return true.
424             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
425                 logEvent(v, ev);
426                 return true;
427             });
428             mActivity.findViewById(R.id.subcontainer).setOnDragListener((v, ev) -> {
429                 logEvent(v, ev);
430                 return true;
431             });
432         });
433 
434         startDrag();
435 
436         // Move mouse to the outmost view. This shouldn't generate any events since it returned
437         // false to STARTED.
438         injectMouse5(R.id.container, MotionEvent.ACTION_MOVE);
439         // Release mouse over the inner view. This produces DROP there.
440         injectMouse5(R.id.inner, MotionEvent.ACTION_UP);
441 
442         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.inner, R.id.draggable);
443         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.subcontainer, R.id.draggable);
444         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.container, R.id.draggable);
445         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.draggable, R.id.draggable);
446         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.drag_drop_activity_main, R.id.draggable);
447 
448         expectEvent5(DragEvent.ACTION_DRAG_ENTERED, R.id.inner);
449         expectEvent5(DragEvent.ACTION_DROP, R.id.inner, R.id.inner);
450 
451         expectEndEventSuccess(R.id.inner);
452         expectEndEventSuccess(R.id.subcontainer);
453 
454         verifyEventLog();
455     }
456 
457     /**
458      * Tests events over a non-accepting view with an accepting child get delivered to that view's
459      * parent.
460      */
461     @Test
testBlackHole()462     public void testBlackHole() throws Exception {
463         runOnMain(() -> {
464             // Accepting child.
465             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
466                 logEvent(v, ev);
467                 return true;
468             });
469             // Non-accepting parent of that child.
470             mActivity.findViewById(R.id.subcontainer).setOnDragListener((v, ev) -> {
471                 logEvent(v, ev);
472                 return false;
473             });
474             // Accepting parent of the previous view.
475             mActivity.findViewById(R.id.container).setOnDragListener((v, ev) -> {
476                 logEvent(v, ev);
477                 return true;
478             });
479         });
480 
481         startDrag();
482 
483         // Move mouse to the non-accepting view.
484         injectMouse5(R.id.subcontainer, MotionEvent.ACTION_MOVE);
485         // Release mouse over the non-accepting view, with different coordinates.
486         injectMouse6(R.id.subcontainer, MotionEvent.ACTION_UP);
487 
488         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.inner, R.id.draggable);
489         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.subcontainer, R.id.draggable);
490         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.container, R.id.draggable);
491 
492         expectEvent5(DragEvent.ACTION_DRAG_ENTERED, R.id.container);
493         expectEvent5(DragEvent.ACTION_DRAG_LOCATION, R.id.container, R.id.subcontainer);
494         expectEvent6(DragEvent.ACTION_DROP, R.id.container, R.id.subcontainer);
495 
496         expectEndEventSuccess(R.id.inner);
497         expectEndEventSuccess(R.id.container);
498 
499         verifyEventLog();
500     }
501 
502     /**
503      * Tests generation of ENTER/EXIT events.
504      */
505     @Test
testEnterExit()506     public void testEnterExit() throws Exception {
507         runOnMain(() -> {
508             // The setup is same as for testBlackHole.
509 
510             // Accepting child.
511             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
512                 logEvent(v, ev);
513                 return true;
514             });
515             // Non-accepting parent of that child.
516             mActivity.findViewById(R.id.subcontainer).setOnDragListener((v, ev) -> {
517                 logEvent(v, ev);
518                 return false;
519             });
520             // Accepting parent of the previous view.
521             mActivity.findViewById(R.id.container).setOnDragListener((v, ev) -> {
522                 logEvent(v, ev);
523                 return true;
524             });
525 
526         });
527 
528         startDrag();
529 
530         // Move mouse to the non-accepting view, then to the inner one, then back to the
531         // non-accepting view, then release over the inner.
532         injectMouse5(R.id.subcontainer, MotionEvent.ACTION_MOVE);
533         injectMouse5(R.id.inner, MotionEvent.ACTION_MOVE);
534         injectMouse5(R.id.subcontainer, MotionEvent.ACTION_MOVE);
535         injectMouse5(R.id.inner, MotionEvent.ACTION_UP);
536 
537         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.inner, R.id.draggable);
538         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.subcontainer, R.id.draggable);
539         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.container, R.id.draggable);
540 
541         expectEvent5(DragEvent.ACTION_DRAG_ENTERED, R.id.container);
542         expectEvent5(DragEvent.ACTION_DRAG_LOCATION, R.id.container, R.id.subcontainer);
543         expectEvent5(DragEvent.ACTION_DRAG_EXITED, R.id.container);
544 
545         expectEvent5(DragEvent.ACTION_DRAG_ENTERED, R.id.inner);
546         expectEvent5(DragEvent.ACTION_DRAG_LOCATION, R.id.inner, R.id.inner);
547         expectEvent5(DragEvent.ACTION_DRAG_EXITED, R.id.inner);
548 
549         expectEvent5(DragEvent.ACTION_DRAG_ENTERED, R.id.container);
550         expectEvent5(DragEvent.ACTION_DRAG_LOCATION, R.id.container, R.id.subcontainer);
551         expectEvent5(DragEvent.ACTION_DRAG_EXITED, R.id.container);
552 
553         expectEvent5(DragEvent.ACTION_DRAG_ENTERED, R.id.inner);
554         expectEvent5(DragEvent.ACTION_DROP, R.id.inner, R.id.inner);
555 
556         expectEndEventSuccess(R.id.inner);
557         expectEndEventSuccess(R.id.container);
558 
559         verifyEventLog();
560     }
561     /**
562      * Tests events over a non-accepting view that has no accepting ancestors.
563      */
564     @Test
testOverNowhere()565     public void testOverNowhere() throws Exception {
566         runOnMain(() -> {
567             // Accepting child.
568             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
569                 logEvent(v, ev);
570                 return true;
571             });
572             // Non-accepting parent of that child.
573             mActivity.findViewById(R.id.subcontainer).setOnDragListener((v, ev) -> {
574                 logEvent(v, ev);
575                 return false;
576             });
577         });
578 
579         startDrag();
580 
581         // Move mouse to the non-accepting view, then to accepting view, and back, and drop there.
582         injectMouse5(R.id.subcontainer, MotionEvent.ACTION_MOVE);
583         injectMouse5(R.id.inner, MotionEvent.ACTION_MOVE);
584         injectMouse5(R.id.subcontainer, MotionEvent.ACTION_MOVE);
585         injectMouse6(R.id.subcontainer, MotionEvent.ACTION_UP);
586 
587         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.inner, R.id.draggable);
588         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.subcontainer, R.id.draggable);
589 
590         expectEvent5(DragEvent.ACTION_DRAG_ENTERED, R.id.inner);
591         expectEvent5(DragEvent.ACTION_DRAG_LOCATION, R.id.inner, R.id.inner);
592         expectEvent5(DragEvent.ACTION_DRAG_EXITED, R.id.inner);
593 
594         expectEndEventFailure6(R.id.inner, R.id.subcontainer);
595 
596         verifyEventLog();
597     }
598 
599     /**
600      * Tests that events are properly delivered to a view that is in the middle of the accepting
601      * hierarchy.
602      */
603     @Test
testAcceptingGroupInTheMiddle()604     public void testAcceptingGroupInTheMiddle() throws Exception {
605         runOnMain(() -> {
606             // Set accepting handlers to the inner view and its 2 ancestors.
607             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
608                 logEvent(v, ev);
609                 return true;
610             });
611             mActivity.findViewById(R.id.subcontainer).setOnDragListener((v, ev) -> {
612                 logEvent(v, ev);
613                 return true;
614             });
615             mActivity.findViewById(R.id.container).setOnDragListener((v, ev) -> {
616                 logEvent(v, ev);
617                 return true;
618             });
619         });
620 
621         startDrag();
622 
623         // Move mouse to the outmost container, then move to the subcontainer and drop there.
624         injectMouse5(R.id.container, MotionEvent.ACTION_MOVE);
625         injectMouse5(R.id.subcontainer, MotionEvent.ACTION_MOVE);
626         injectMouse6(R.id.subcontainer, MotionEvent.ACTION_UP);
627 
628         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.inner, R.id.draggable);
629         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.subcontainer, R.id.draggable);
630         expectEvent5(DragEvent.ACTION_DRAG_STARTED, R.id.container, R.id.draggable);
631 
632         expectEvent5(DragEvent.ACTION_DRAG_ENTERED, R.id.container);
633         expectEvent5(DragEvent.ACTION_DRAG_LOCATION, R.id.container, R.id.container);
634         expectEvent5(DragEvent.ACTION_DRAG_EXITED, R.id.container);
635 
636         expectEvent5(DragEvent.ACTION_DRAG_ENTERED, R.id.subcontainer);
637         expectEvent5(DragEvent.ACTION_DRAG_LOCATION, R.id.subcontainer, R.id.subcontainer);
638         expectEvent6(DragEvent.ACTION_DROP, R.id.subcontainer, R.id.subcontainer);
639 
640         expectEndEventSuccess(R.id.inner);
641         expectEndEventSuccess(R.id.subcontainer);
642         expectEndEventSuccess(R.id.container);
643 
644         verifyEventLog();
645     }
646 
drawableStateContains(int resourceId, int attr)647     private boolean drawableStateContains(int resourceId, int attr) {
648         return IntStream.of(mActivity.findViewById(resourceId).getDrawableState())
649                 .anyMatch(x -> x == attr);
650     }
651 
652     /**
653      * Tests that state_drag_hovered and state_drag_can_accept are set correctly.
654      */
655     @Test
testDrawableState()656     public void testDrawableState() throws Exception {
657         runOnMain(() -> {
658             // Set accepting handler for the inner view.
659             mActivity.findViewById(R.id.inner).setOnDragListener((v, ev) -> {
660                 logEvent(v, ev);
661                 return true;
662             });
663             assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_can_accept));
664         });
665 
666         startDrag();
667 
668         runOnMain(() -> {
669             assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
670             assertTrue(drawableStateContains(R.id.inner, android.R.attr.state_drag_can_accept));
671         });
672 
673         // Move mouse into the view.
674         injectMouse5(R.id.inner, MotionEvent.ACTION_MOVE);
675         runOnMain(() -> {
676             assertTrue(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
677         });
678 
679         // Move out.
680         injectMouse5(R.id.subcontainer, MotionEvent.ACTION_MOVE);
681         runOnMain(() -> {
682             assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
683         });
684 
685         // Move in.
686         injectMouse5(R.id.inner, MotionEvent.ACTION_MOVE);
687         runOnMain(() -> {
688             assertTrue(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
689         });
690 
691         // Release there.
692         injectMouse5(R.id.inner, MotionEvent.ACTION_UP);
693         runOnMain(() -> {
694             assertFalse(drawableStateContains(R.id.inner, android.R.attr.state_drag_hovered));
695         });
696     }
697 
698     /**
699      * Tests if window is removing, it should not perform drag.
700      */
701     @Test
testNoDragIfWindowCantReceiveInput()702     public void testNoDragIfWindowCantReceiveInput() throws InterruptedException {
703         injectMouse5(R.id.draggable, MotionEvent.ACTION_DOWN);
704 
705         runOnMain(() -> {
706             // finish activity and start drag drop.
707             View v = mActivity.findViewById(R.id.draggable);
708             mActivity.finish();
709             assertFalse("Shouldn't start drag",
710                     v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v), sLocalState, 0));
711         });
712 
713         injectMouse5(R.id.draggable, MotionEvent.ACTION_UP);
714     }
715 
716     /**
717      * Tests if there is no touch down, it should not perform drag.
718      */
719     @Test
testNoDragIfNoTouchDown()720     public void testNoDragIfNoTouchDown() throws InterruptedException {
721         // perform a click.
722         injectMouse5(R.id.draggable, MotionEvent.ACTION_DOWN);
723         injectMouse5(R.id.draggable, MotionEvent.ACTION_UP);
724 
725         runOnMain(() -> {
726             View v = mActivity.findViewById(R.id.draggable);
727             assertFalse("Shouldn't start drag",
728                 v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v), sLocalState, 0));
729         });
730     }
731 
732     /**
733      * Tests that the canvas is hardware accelerated when the activity is hardware accelerated.
734      */
735     @Test
testHardwareAcceleratedCanvas()736     public void testHardwareAcceleratedCanvas() throws InterruptedException {
737         assertDragCanvasHwAcceleratedState(mActivity, true);
738     }
739 
740     /**
741      * Tests that the canvas is not hardware accelerated when the activity is not hardware
742      * accelerated.
743      */
744     @Test
testSoftwareCanvas()745     public void testSoftwareCanvas() throws InterruptedException {
746         SoftwareCanvasDragDropActivity activity =
747                 startActivityInWindowingMode(SoftwareCanvasDragDropActivity.class,
748                         WINDOWING_MODE_FULLSCREEN);
749         assertDragCanvasHwAcceleratedState(activity, false);
750     }
751 
assertDragCanvasHwAcceleratedState(DragDropActivity activity, boolean expectedHwAccelerated)752     private void assertDragCanvasHwAcceleratedState(DragDropActivity activity,
753             boolean expectedHwAccelerated) {
754         CountDownLatch latch = new CountDownLatch(1);
755         AtomicBoolean isCanvasHwAccelerated = new AtomicBoolean();
756         runOnMain(() -> {
757             View v = activity.findViewById(R.id.draggable);
758             v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v) {
759                 @Override
760                 public void onDrawShadow(Canvas canvas) {
761                     isCanvasHwAccelerated.set(canvas.isHardwareAccelerated());
762                     latch.countDown();
763                 }
764             }, null, 0);
765         });
766 
767         try {
768             assertTrue("Timeout while waiting for canvas", latch.await(5, TimeUnit.SECONDS));
769             assertTrue("Expected canvas hardware acceleration to be: " + expectedHwAccelerated,
770                     expectedHwAccelerated == isCanvasHwAccelerated.get());
771         } catch (InterruptedException e) {
772             fail("Got InterruptedException while waiting for canvas");
773         }
774     }
775 
776     @Test
testDragShadowWhenPerformDrag()777     public void testDragShadowWhenPerformDrag() {
778         // Mouse down. Required for the drag to start.
779         injectMouseWithOffset(R.id.draggable, MotionEvent.ACTION_DOWN, 0);
780         final View v = mActivity.findViewById(R.id.draggable);
781         runOnMain(() -> {
782             // Start drag.
783             assertTrue("Couldn't start drag",
784                     v.startDragAndDrop(sClipData, new View.DragShadowBuilder(v) {
785                         @Override
786                         public void onProvideShadowMetrics(Point outShadowSize,
787                                 Point outShadowTouchPoint) {
788                             outShadowSize.set(v.getWidth(), v.getHeight());
789                             outShadowTouchPoint.set(0, 0);
790                         }
791 
792                         @Override
793                         public void onDrawShadow(Canvas canvas) {
794                             canvas.drawColor(Color.RED);
795                         }
796                     }, sLocalState, View.DRAG_FLAG_OPAQUE));
797         });
798         getInstrumentation().getUiAutomation().syncInputTransactions();
799 
800         // Verify if the drag shadow present before any move event.
801         final Bitmap screenshot = mInstrumentation.getUiAutomation().takeScreenshot();
802         injectMouseWithOffset(R.id.draggable, MotionEvent.ACTION_UP, 0);
803 
804         int [] viewLoc = new int[2];
805         v.getLocationOnScreen(viewLoc);
806 
807         for (int x = viewLoc[0]; x < viewLoc[0] + v.getWidth(); x++) {
808             for (int y = viewLoc[1]; y < viewLoc[1] + v.getHeight(); y++) {
809                 final Color color = screenshot.getColor(x, y);
810                 assertTrue("Should show drag shadow", color.toArgb() == Color.RED);
811             }
812         }
813     }
814 
815     public static class DragDropActivity extends FocusableActivity {
816         @Override
onCreate(Bundle savedInstanceState)817         protected void onCreate(Bundle savedInstanceState) {
818             super.onCreate(savedInstanceState);
819             setContentView(R.layout.drag_drop_layout);
820         }
821     }
822 
823     public static class SoftwareCanvasDragDropActivity extends DragDropActivity {
824         @Override
onCreate(Bundle savedInstanceState)825         protected void onCreate(Bundle savedInstanceState) {
826             super.onCreate(savedInstanceState);
827             setContentView(R.layout.drag_drop_layout);
828         }
829     }
830 }
831