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_FREEFORM;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
23 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
24 import static android.server.am.ActivityManagerTestBase.executeShellCommand;
25 import static android.server.am.StateLogger.log;
26 import static android.server.am.UiDeviceUtils.dragPointer;
27 import static android.server.am.UiDeviceUtils.pressMenuButton;
28 import static android.server.am.UiDeviceUtils.wakeUpDevice;
29 
30 import static org.junit.Assert.assertEquals;
31 import static org.junit.Assert.assertNotNull;
32 import static org.junit.Assert.assertTrue;
33 import static org.junit.Assert.fail;
34 import static org.junit.Assume.assumeTrue;
35 
36 import android.app.ActivityManager;
37 import android.content.Context;
38 import android.graphics.Point;
39 import android.os.RemoteException;
40 import android.platform.test.annotations.AppModeFull;
41 import android.os.SystemClock;
42 import android.platform.test.annotations.Presubmit;
43 import android.support.test.InstrumentationRegistry;
44 import android.util.Log;
45 
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.Test;
49 
50 import java.util.Map;
51 import java.util.regex.Pattern;
52 
53 /**
54  * Build: mmma -j32 cts/tests/framework/base
55  * Run: cts/tests/framework/base/activitymanager/util/run-test CtsWindowManagerDeviceTestCases android.server.wm.CrossAppDragAndDropTests
56  */
57 @Presubmit
58 @AppModeFull(reason = "Requires android.permission.MANAGE_ACTIVITY_STACKS")
59 public class CrossAppDragAndDropTests {
60     private static final String TAG = "CrossAppDragAndDrop";
61 
62     private static final String AM_FORCE_STOP = "am force-stop ";
63     private static final String AM_MOVE_TASK = "am stack move-task ";
64     private static final String AM_RESIZE_TASK = "am task resize ";
65     private static final String AM_REMOVE_STACK = "am stack remove ";
66     private static final String AM_START_N = "am start -n ";
67     private static final String AM_STACK_LIST = "am stack list";
68     private static final String TASK_ID_PREFIX = "taskId";
69 
70     // Regex pattern to match adb shell am stack list output of the form:
71     // taskId=<TASK_ID>: <componentName> bounds=[LEFT,TOP][RIGHT,BOTTOM]
72     private static final String TASK_REGEX_PATTERN_STRING =
73             "taskId=[0-9]+: %s bounds=\\[[0-9]+,[0-9]+\\]\\[[0-9]+,[0-9]+\\]";
74 
75     private static final int SWIPE_STEPS = 100;
76 
77     private static final String SOURCE_PACKAGE_NAME = "android.server.wm.dndsourceapp";
78     private static final String TARGET_PACKAGE_NAME = "android.server.wm.dndtargetapp";
79     private static final String TARGET_23_PACKAGE_NAME = "android.server.wm.dndtargetappsdk23";
80 
81 
82     private static final String SOURCE_ACTIVITY_NAME = "DragSource";
83     private static final String TARGET_ACTIVITY_NAME = "DropTarget";
84 
85     private static final String FILE_GLOBAL = "file_global";
86     private static final String FILE_LOCAL = "file_local";
87     private static final String DISALLOW_GLOBAL = "disallow_global";
88     private static final String CANCEL_SOON = "cancel_soon";
89     private static final String GRANT_NONE = "grant_none";
90     private static final String GRANT_READ = "grant_read";
91     private static final String GRANT_WRITE = "grant_write";
92     private static final String GRANT_READ_PREFIX = "grant_read_prefix";
93     private static final String GRANT_READ_NOPREFIX = "grant_read_noprefix";
94     private static final String GRANT_READ_PERSISTABLE = "grant_read_persistable";
95 
96     private static final String REQUEST_NONE = "request_none";
97     private static final String REQUEST_READ = "request_read";
98     private static final String REQUEST_READ_NESTED = "request_read_nested";
99     private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
100     private static final String REQUEST_WRITE = "request_write";
101 
102     private static final String SOURCE_LOG_TAG = "DragSource";
103     private static final String TARGET_LOG_TAG = "DropTarget";
104 
105     private static final String RESULT_KEY_START_DRAG = "START_DRAG";
106     private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
107     private static final String RESULT_KEY_DRAG_ENDED = "DRAG_ENDED";
108     private static final String RESULT_KEY_EXTRAS = "EXTRAS";
109     private static final String RESULT_KEY_DROP_RESULT = "DROP";
110     private static final String RESULT_KEY_ACCESS_BEFORE = "BEFORE";
111     private static final String RESULT_KEY_ACCESS_AFTER = "AFTER";
112     private static final String RESULT_KEY_CLIP_DATA_ERROR = "CLIP_DATA_ERROR";
113     private static final String RESULT_KEY_CLIP_DESCR_ERROR = "CLIP_DESCR_ERROR";
114     private static final String RESULT_KEY_LOCAL_STATE_ERROR = "LOCAL_STATE_ERROR";
115 
116     private static final String RESULT_MISSING = "Missing";
117     private static final String RESULT_OK = "OK";
118     private static final String RESULT_EXCEPTION = "Exception";
119     private static final String RESULT_NULL_DROP_PERMISSIONS = "Null DragAndDropPermissions";
120 
121     protected Context mContext;
122     protected ActivityManager mAm;
123 
124     private Map<String, String> mSourceResults;
125     private Map<String, String> mTargetResults;
126 
127     private String mSourcePackageName;
128     private String mTargetPackageName;
129 
130     private String mSessionId;
131     private String mSourceLogTag;
132     private String mTargetLogTag;
133 
134     @Before
setUp()135     public void setUp() throws Exception {
136         assumeTrue(supportsDragAndDrop());
137 
138         // Use uptime in seconds as unique test invocation id.
139         mSessionId = Long.toString(SystemClock.uptimeMillis() / 1000);
140         mSourceLogTag = SOURCE_LOG_TAG + mSessionId;
141         mTargetLogTag = TARGET_LOG_TAG + mSessionId;
142 
143         mContext = InstrumentationRegistry.getContext();
144         mAm = mContext.getSystemService(ActivityManager.class);
145 
146         mSourcePackageName = SOURCE_PACKAGE_NAME;
147         mTargetPackageName = TARGET_PACKAGE_NAME;
148         cleanupState();
149     }
150 
151     @After
tearDown()152     public void tearDown() throws Exception {
153         if (!supportsDragAndDrop()) {
154           return;
155         }
156 
157         executeShellCommand(AM_FORCE_STOP + mSourcePackageName);
158         executeShellCommand(AM_FORCE_STOP + mTargetPackageName);
159     }
160 
clearLogs()161     private void clearLogs() {
162         executeShellCommand("logcat -c");
163     }
164 
getStartCommand(String componentName, String modeExtra, String logtag)165     private String getStartCommand(String componentName, String modeExtra, String logtag) {
166         return AM_START_N + componentName + " -e mode " + modeExtra + " -e logtag " + logtag;
167     }
168 
getMoveTaskCommand(int taskId, int stackId)169     private String getMoveTaskCommand(int taskId, int stackId) {
170         return AM_MOVE_TASK + taskId + " " + stackId + " true";
171     }
172 
getResizeTaskCommand(int taskId, Point topLeft, Point bottomRight)173     private String getResizeTaskCommand(int taskId, Point topLeft, Point bottomRight)
174             throws Exception {
175         return AM_RESIZE_TASK + taskId + " " + topLeft.x + " " + topLeft.y + " " + bottomRight.x
176                 + " " + bottomRight.y;
177     }
178 
getComponentName(String packageName, String activityName)179     private String getComponentName(String packageName, String activityName) {
180         return packageName + "/" + packageName + "." + activityName;
181     }
182 
183     /**
184      * Make sure that the special activity stacks are removed and the ActivityManager/WindowManager
185      * is in a good state.
186      */
cleanupState()187     private void cleanupState() throws Exception {
188         executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
189         executeShellCommand(AM_FORCE_STOP + TARGET_PACKAGE_NAME);
190         executeShellCommand(AM_FORCE_STOP + TARGET_23_PACKAGE_NAME);
191         unlockDevice();
192         clearLogs();
193 
194         // Remove special stacks.
195         mAm.removeStacksInWindowingModes(new int[] {
196                 WINDOWING_MODE_PINNED,
197                 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
198                 WINDOWING_MODE_FREEFORM
199         });
200     }
201 
launchDockedActivity(String packageName, String activityName, String mode, String logtag)202     private void launchDockedActivity(String packageName, String activityName, String mode,
203             String logtag) throws Exception {
204         clearLogs();
205         final String componentName = getComponentName(packageName, activityName);
206         executeShellCommand(getStartCommand(componentName, mode, logtag) + " --windowingMode "
207                 + WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
208         waitForResume(packageName, activityName);
209     }
210 
launchFullscreenActivity(String packageName, String activityName, String mode, String logtag)211     private void launchFullscreenActivity(String packageName, String activityName, String mode,
212             String logtag) throws Exception {
213         clearLogs();
214         final String componentName = getComponentName(packageName, activityName);
215         executeShellCommand(getStartCommand(componentName, mode, logtag) + " --windowingMode "
216                 + WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
217         waitForResume(packageName, activityName);
218     }
219 
220     /**
221      * @param displaySize size of the display
222      * @param leftSide {@code true} to launch the app taking up the left half of the display,
223      *         {@code false} to launch the app taking up the right half of the display.
224      */
launchFreeformActivity(String packageName, String activityName, String mode, String logtag, Point displaySize, boolean leftSide)225     private void launchFreeformActivity(String packageName, String activityName, String mode,
226             String logtag, Point displaySize, boolean leftSide) throws Exception {
227         clearLogs();
228         final String componentName = getComponentName(packageName, activityName);
229         executeShellCommand(getStartCommand(componentName, mode, logtag) + " --windowingMode "
230                 + WINDOWING_MODE_FREEFORM);
231         waitForResume(packageName, activityName);
232         Point topLeft = new Point(leftSide ? 0 : displaySize.x / 2, 0);
233         Point bottomRight = new Point(leftSide ? displaySize.x / 2 : displaySize.x, displaySize.y);
234         executeShellCommand(getResizeTaskCommand(getActivityTaskId(componentName), topLeft,
235                 bottomRight));
236     }
237 
waitForResume(String packageName, String activityName)238     private void waitForResume(String packageName, String activityName) throws Exception {
239         final String fullActivityName = packageName + "." + activityName;
240         int retryCount = 3;
241         do {
242             Thread.sleep(500);
243             String logs = executeShellCommand("logcat -d -b events");
244             for (String line : logs.split("\\n")) {
245                 if (line.contains("am_on_resume_called") && line.contains(fullActivityName)) {
246                     return;
247                 }
248             }
249         } while (retryCount-- > 0);
250 
251         throw new Exception(fullActivityName + " has failed to start");
252     }
253 
injectInput(Point from, Point to, int steps)254     private void injectInput(Point from, Point to, int steps) throws Exception {
255         dragPointer(from, to, steps);
256     }
257 
findTaskInfo(String name)258     private String findTaskInfo(String name) {
259         final String output = executeShellCommand(AM_STACK_LIST);
260         final StringBuilder builder = new StringBuilder();
261         builder.append("Finding task info for task: ");
262         builder.append(name);
263         builder.append("\nParsing adb shell am output: ");
264         builder.append(output);
265         log(builder.toString());
266         final Pattern pattern = Pattern.compile(String.format(TASK_REGEX_PATTERN_STRING, name));
267         for (String line : output.split("\\n")) {
268             final String truncatedLine;
269             // Only look for the activity name before the "topActivity" string.
270             final int pos = line.indexOf("topActivity");
271             if (pos > 0) {
272                 truncatedLine = line.substring(0, pos);
273             } else {
274                 truncatedLine = line;
275             }
276             if (pattern.matcher(truncatedLine).find()) {
277                 return truncatedLine;
278             }
279         }
280         return "";
281     }
282 
getWindowBounds(String name, Point from, Point to)283     private boolean getWindowBounds(String name, Point from, Point to) throws Exception {
284         final String taskInfo = findTaskInfo(name);
285         final String[] sections = taskInfo.split("\\[");
286         if (sections.length > 2) {
287             try {
288                 parsePoint(sections[1], from);
289                 parsePoint(sections[2], to);
290                 return true;
291             } catch (Exception e) {
292                 return false;
293             }
294         }
295         return false;
296     }
297 
getActivityTaskId(String name)298     private int getActivityTaskId(String name) {
299         final String taskInfo = findTaskInfo(name);
300         for (String word : taskInfo.split("\\s+")) {
301             if (word.startsWith(TASK_ID_PREFIX)) {
302                 final String withColon = word.split("=")[1];
303                 return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
304             }
305         }
306         return -1;
307     }
308 
getDisplaySize()309     private Point getDisplaySize() throws Exception {
310         final String output = executeShellCommand("wm size");
311         final String[] sizes = output.split(" ")[2].split("x");
312         return new Point(Integer.valueOf(sizes[0].trim()), Integer.valueOf(sizes[1].trim()));
313     }
314 
getWindowCenter(String name)315     private Point getWindowCenter(String name) throws Exception {
316         Point p1 = new Point();
317         Point p2 = new Point();
318         if (getWindowBounds(name, p1, p2)) {
319             return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
320         }
321         return null;
322     }
323 
parsePoint(String string, Point point)324     private void parsePoint(String string, Point point) {
325         final String[] parts = string.split("[,|\\]]");
326         point.x = Integer.parseInt(parts[0]);
327         point.y = Integer.parseInt(parts[1]);
328     }
329 
unlockDevice()330     private void unlockDevice() {
331         // Wake up the device, if necessary.
332         try {
333             wakeUpDevice();
334         } catch (RemoteException e) {
335             throw new RuntimeException(e);
336         }
337         // Unlock the screen.
338         pressMenuButton();
339     }
340 
assertDropResult(String sourceMode, String targetMode, String expectedDropResult)341     private void assertDropResult(String sourceMode, String targetMode, String expectedDropResult)
342             throws Exception {
343         assertDragAndDropResults(sourceMode, targetMode, RESULT_OK, expectedDropResult, RESULT_OK);
344     }
345 
assertNoGlobalDragEvents(String sourceMode, String expectedStartDragResult)346     private void assertNoGlobalDragEvents(String sourceMode, String expectedStartDragResult)
347             throws Exception {
348         assertDragAndDropResults(
349                 sourceMode, REQUEST_NONE, expectedStartDragResult, RESULT_MISSING, RESULT_MISSING);
350     }
351 
assertDragAndDropResults(String sourceMode, String targetMode, String expectedStartDragResult, String expectedDropResult, String expectedListenerResults)352     private void assertDragAndDropResults(String sourceMode, String targetMode,
353             String expectedStartDragResult, String expectedDropResult,
354             String expectedListenerResults) throws Exception {
355         Log.e(TAG, "session: " + mSessionId + ", source: " + sourceMode
356                 + ", target: " + targetMode);
357 
358         if (supportsSplitScreenMultiWindow()) {
359             launchDockedActivity(
360                     mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode, mSourceLogTag);
361             launchFullscreenActivity(
362                     mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode, mTargetLogTag);
363         } else if (supportsFreeformMultiWindow()) {
364             // Fallback to try to launch two freeform windows side by side.
365             Point displaySize = getDisplaySize();
366             launchFreeformActivity(
367                     mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode, mSourceLogTag,
368                     displaySize, true /* leftSide */);
369             launchFreeformActivity(
370                     mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode, mTargetLogTag,
371                     displaySize, false /* leftSide */);
372         } else {
373             return;
374         }
375 
376         Point p1 = getWindowCenter(getComponentName(mSourcePackageName, SOURCE_ACTIVITY_NAME));
377         assertNotNull(p1);
378         Point p2 = getWindowCenter(getComponentName(mTargetPackageName, TARGET_ACTIVITY_NAME));
379         assertNotNull(p2);
380 
381         TestLogService.registerClient(mSourceLogTag, RESULT_KEY_START_DRAG);
382         TestLogService.registerClient(mTargetLogTag, RESULT_KEY_DRAG_ENDED);
383 
384         injectInput(p1, p2, SWIPE_STEPS);
385 
386         mSourceResults = TestLogService.getResultsForClient(mSourceLogTag, 1000);
387         assertSourceResult(RESULT_KEY_START_DRAG, expectedStartDragResult);
388 
389         mTargetResults = TestLogService.getResultsForClient(mTargetLogTag, 1000);
390         assertTargetResult(RESULT_KEY_DROP_RESULT, expectedDropResult);
391         if (!RESULT_MISSING.equals(expectedDropResult)) {
392             assertTargetResult(RESULT_KEY_ACCESS_BEFORE, RESULT_EXCEPTION);
393             assertTargetResult(RESULT_KEY_ACCESS_AFTER, RESULT_EXCEPTION);
394         }
395         assertListenerResults(expectedListenerResults);
396     }
397 
assertListenerResults(String expectedResult)398     private void assertListenerResults(String expectedResult) throws Exception {
399         assertTargetResult(RESULT_KEY_DRAG_STARTED, expectedResult);
400         assertTargetResult(RESULT_KEY_DRAG_ENDED, expectedResult);
401         assertTargetResult(RESULT_KEY_EXTRAS, expectedResult);
402 
403         assertTargetResult(RESULT_KEY_CLIP_DATA_ERROR, RESULT_MISSING);
404         assertTargetResult(RESULT_KEY_CLIP_DESCR_ERROR, RESULT_MISSING);
405         assertTargetResult(RESULT_KEY_LOCAL_STATE_ERROR, RESULT_MISSING);
406     }
407 
assertSourceResult(String resultKey, String expectedResult)408     private void assertSourceResult(String resultKey, String expectedResult) throws Exception {
409         assertResult(mSourceResults, resultKey, expectedResult);
410     }
411 
assertTargetResult(String resultKey, String expectedResult)412     private void assertTargetResult(String resultKey, String expectedResult) throws Exception {
413         assertResult(mTargetResults, resultKey, expectedResult);
414     }
415 
assertResult(Map<String, String> results, String resultKey, String expectedResult)416     private void assertResult(Map<String, String> results, String resultKey, String expectedResult)
417             throws Exception {
418         if (RESULT_MISSING.equals(expectedResult)) {
419             if (results.containsKey(resultKey)) {
420                 fail("Unexpected " + resultKey + "=" + results.get(resultKey));
421             }
422         } else {
423             assertTrue("Missing " + resultKey, results.containsKey(resultKey));
424             assertEquals(resultKey + " result mismatch,", expectedResult,
425                     results.get(resultKey));
426         }
427     }
428 
supportsDragAndDrop()429     private static boolean supportsDragAndDrop() {
430         return ActivityManager.supportsMultiWindow(InstrumentationRegistry.getContext());
431     }
432 
supportsSplitScreenMultiWindow()433     private static boolean supportsSplitScreenMultiWindow() {
434         return ActivityManager.supportsSplitScreenMultiWindow(InstrumentationRegistry.getContext());
435     }
436 
supportsFreeformMultiWindow()437     private static boolean supportsFreeformMultiWindow() {
438         return InstrumentationRegistry.getContext()
439                 .getPackageManager()
440                 .hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT);
441     }
442 
443     @Test
testCancelSoon()444     public void testCancelSoon() throws Exception {
445         assertDropResult(CANCEL_SOON, REQUEST_NONE, RESULT_MISSING);
446     }
447 
448     @Test
testDisallowGlobal()449     public void testDisallowGlobal() throws Exception {
450         assertNoGlobalDragEvents(DISALLOW_GLOBAL, RESULT_OK);
451     }
452 
453     @Test
testDisallowGlobalBelowSdk24()454     public void testDisallowGlobalBelowSdk24() throws Exception {
455         mTargetPackageName = TARGET_23_PACKAGE_NAME;
456         assertNoGlobalDragEvents(GRANT_NONE, RESULT_OK);
457     }
458 
459     @Test
testFileUriLocal()460     public void testFileUriLocal() throws Exception {
461         assertNoGlobalDragEvents(FILE_LOCAL, RESULT_OK);
462     }
463 
464     @Test
testFileUriGlobal()465     public void testFileUriGlobal() throws Exception {
466         assertNoGlobalDragEvents(FILE_GLOBAL, RESULT_EXCEPTION);
467     }
468 
469     @Test
testGrantNoneRequestNone()470     public void testGrantNoneRequestNone() throws Exception {
471         assertDropResult(GRANT_NONE, REQUEST_NONE, RESULT_EXCEPTION);
472     }
473 
474     @Test
testGrantNoneRequestRead()475     public void testGrantNoneRequestRead() throws Exception {
476         assertDropResult(GRANT_NONE, REQUEST_READ, RESULT_NULL_DROP_PERMISSIONS);
477     }
478 
479     @Test
testGrantNoneRequestWrite()480     public void testGrantNoneRequestWrite() throws Exception {
481         assertDropResult(GRANT_NONE, REQUEST_WRITE, RESULT_NULL_DROP_PERMISSIONS);
482     }
483 
484     @Test
testGrantReadRequestNone()485     public void testGrantReadRequestNone() throws Exception {
486         assertDropResult(GRANT_READ, REQUEST_NONE, RESULT_EXCEPTION);
487     }
488 
489     @Test
testGrantReadRequestRead()490     public void testGrantReadRequestRead() throws Exception {
491         assertDropResult(GRANT_READ, REQUEST_READ, RESULT_OK);
492     }
493 
494     @Test
testGrantReadRequestWrite()495     public void testGrantReadRequestWrite() throws Exception {
496         assertDropResult(GRANT_READ, REQUEST_WRITE, RESULT_EXCEPTION);
497     }
498 
499     @Test
testGrantReadNoPrefixRequestReadNested()500     public void testGrantReadNoPrefixRequestReadNested() throws Exception {
501         assertDropResult(GRANT_READ_NOPREFIX, REQUEST_READ_NESTED, RESULT_EXCEPTION);
502     }
503 
504     @Test
testGrantReadPrefixRequestReadNested()505     public void testGrantReadPrefixRequestReadNested() throws Exception {
506         assertDropResult(GRANT_READ_PREFIX, REQUEST_READ_NESTED, RESULT_OK);
507     }
508 
509     @Test
testGrantPersistableRequestTakePersistable()510     public void testGrantPersistableRequestTakePersistable() throws Exception {
511         assertDropResult(GRANT_READ_PERSISTABLE, REQUEST_TAKE_PERSISTABLE, RESULT_OK);
512     }
513 
514     @Test
testGrantReadRequestTakePersistable()515     public void testGrantReadRequestTakePersistable() throws Exception {
516         assertDropResult(GRANT_READ, REQUEST_TAKE_PERSISTABLE, RESULT_EXCEPTION);
517     }
518 
519     @Test
testGrantWriteRequestNone()520     public void testGrantWriteRequestNone() throws Exception {
521         assertDropResult(GRANT_WRITE, REQUEST_NONE, RESULT_EXCEPTION);
522     }
523 
524     @Test
testGrantWriteRequestRead()525     public void testGrantWriteRequestRead() throws Exception {
526         assertDropResult(GRANT_WRITE, REQUEST_READ, RESULT_EXCEPTION);
527     }
528 
529     @Test
testGrantWriteRequestWrite()530     public void testGrantWriteRequestWrite() throws Exception {
531         assertDropResult(GRANT_WRITE, REQUEST_WRITE, RESULT_OK);
532     }
533 }
534