Home | History | Annotate | Download | only in cts
      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.wm.cts;
     18 
     19 import com.android.tradefed.device.CollectingOutputReceiver;
     20 import com.android.tradefed.device.DeviceNotAvailableException;
     21 import com.android.tradefed.device.ITestDevice;
     22 import com.android.tradefed.testtype.DeviceTestCase;
     23 
     24 import java.util.HashMap;
     25 import java.util.Map;
     26 
     27 public class CrossAppDragAndDropTests extends DeviceTestCase {
     28     // Constants copied from ActivityManager.StackId. If they are changed there, these must be
     29     // updated.
     30     /** ID of stack where fullscreen activities are normally launched into. */
     31     private static final int FULLSCREEN_WORKSPACE_STACK_ID = 1;
     32 
     33     /** ID of stack where freeform/resized activities are normally launched into. */
     34     private static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1;
     35 
     36     /** ID of stack that occupies a dedicated region of the screen. */
     37     private static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1;
     38 
     39     /** ID of stack that always on top (always visible) when it exists. */
     40     private static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1;
     41 
     42     private static final String AM_FORCE_STOP = "am force-stop ";
     43     private static final String AM_MOVE_TASK = "am stack movetask ";
     44     private static final String AM_REMOVE_STACK = "am stack remove ";
     45     private static final String AM_START_N = "am start -n ";
     46     private static final String AM_STACK_LIST = "am stack list";
     47     private static final String INPUT_MOUSE_SWIPE = "input mouse swipe ";
     48     private static final String TASK_ID_PREFIX = "taskId";
     49 
     50     private static final int SWIPE_DURATION_MS = 500;
     51 
     52     private static final String SOURCE_PACKAGE_NAME = "android.wm.cts.dndsourceapp";
     53     private static final String TARGET_PACKAGE_NAME = "android.wm.cts.dndtargetapp";
     54     private static final String TARGET_23_PACKAGE_NAME = "android.wm.cts.dndtargetappsdk23";
     55 
     56 
     57     private static final String SOURCE_ACTIVITY_NAME = "DragSource";
     58     private static final String TARGET_ACTIVITY_NAME = "DropTarget";
     59 
     60     private static final String DISALLOW_GLOBAL = "disallow_global";
     61     private static final String CANCEL_SOON = "cancel_soon";
     62     private static final String GRANT_NONE = "grant_none";
     63     private static final String GRANT_READ = "grant_read";
     64     private static final String GRANT_WRITE = "grant_write";
     65     private static final String GRANT_READ_PREFIX = "grant_read_prefix";
     66     private static final String GRANT_READ_NOPREFIX = "grant_read_noprefix";
     67     private static final String GRANT_READ_PERSISTABLE = "grant_read_persistable";
     68 
     69     private static final String REQUEST_NONE = "request_none";
     70     private static final String REQUEST_READ = "request_read";
     71     private static final String REQUEST_READ_NESTED = "request_read_nested";
     72     private static final String REQUEST_TAKE_PERSISTABLE = "request_take_persistable";
     73     private static final String REQUEST_WRITE = "request_write";
     74 
     75     private static final String TARGET_LOG_TAG = "DropTarget";
     76 
     77     private static final String RESULT_KEY_DRAG_STARTED = "DRAG_STARTED";
     78     private static final String RESULT_KEY_EXTRAS = "EXTRAS";
     79     private static final String RESULT_KEY_DROP_RESULT = "DROP";
     80 
     81     private static final String RESULT_OK = "OK";
     82     private static final String RESULT_EXCEPTION = "Exception";
     83     private static final String RESULT_NULL_DROP_PERMISSIONS = "Null DragAndDropPermissions";
     84 
     85     private ITestDevice mDevice;
     86 
     87     private Map<String, String> mResults;
     88 
     89     private String mSourcePackageName;
     90     private String mTargetPackageName;
     91 
     92     @Override
     93     protected void setUp() throws Exception {
     94         super.setUp();
     95         mDevice = getDevice();
     96         mSourcePackageName = SOURCE_PACKAGE_NAME;
     97         mTargetPackageName = TARGET_PACKAGE_NAME;
     98         cleanupState();
     99     }
    100 
    101     @Override
    102     protected void tearDown() throws Exception {
    103         super.tearDown();
    104         mDevice.executeShellCommand(AM_FORCE_STOP + mSourcePackageName);
    105         mDevice.executeShellCommand(AM_FORCE_STOP + mTargetPackageName);
    106     }
    107 
    108     private String executeShellCommand(String command) throws DeviceNotAvailableException {
    109         return mDevice.executeShellCommand(command);
    110     }
    111 
    112     private void clearLogs() throws DeviceNotAvailableException {
    113         executeShellCommand("logcat -c");
    114     }
    115 
    116     private String getStartCommand(String componentName, String modeExtra) {
    117         return AM_START_N + componentName + " -e mode " + modeExtra;
    118     }
    119 
    120     private String getMoveTaskCommand(int taskId, int stackId) throws Exception {
    121         return AM_MOVE_TASK + taskId + " " + stackId + " true";
    122     }
    123 
    124     private String getComponentName(String packageName, String activityName) {
    125         return packageName + "/" + packageName + "." + activityName;
    126     }
    127 
    128     /**
    129      * Make sure that the special activity stacks are removed and the ActivityManager/WindowManager
    130      * is in a good state.
    131      */
    132     private void cleanupState() throws Exception {
    133         executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
    134         executeShellCommand(AM_FORCE_STOP + TARGET_PACKAGE_NAME);
    135         executeShellCommand(AM_FORCE_STOP + TARGET_23_PACKAGE_NAME);
    136         unlockDevice();
    137 
    138         // Reinitialize the docked stack to force the window manager to reset its default bounds.
    139         // See b/29068935.
    140         clearLogs();
    141         final String componentName = getComponentName(mSourcePackageName, SOURCE_ACTIVITY_NAME);
    142         executeShellCommand(getStartCommand(componentName, null) + " --stack " +
    143                 FULLSCREEN_WORKSPACE_STACK_ID);
    144         final int taskId = getActivityTaskId(componentName);
    145         // Moving a task from the full screen stack to the docked stack resets
    146         // WindowManagerService#mDockedStackCreateBounds.
    147         executeShellCommand(getMoveTaskCommand(taskId, DOCKED_STACK_ID));
    148         waitForResume(mSourcePackageName, SOURCE_ACTIVITY_NAME);
    149         executeShellCommand(AM_FORCE_STOP + SOURCE_PACKAGE_NAME);
    150 
    151         // Remove special stacks.
    152         executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID);
    153         executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID);
    154         executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID);
    155     }
    156 
    157     private void launchDockedActivity(String packageName, String activityName, String mode)
    158             throws Exception {
    159         clearLogs();
    160         final String componentName = getComponentName(packageName, activityName);
    161         executeShellCommand(getStartCommand(componentName, mode) + " --stack " + DOCKED_STACK_ID);
    162         waitForResume(packageName, activityName);
    163     }
    164 
    165     private void launchFullscreenActivity(String packageName, String activityName, String mode)
    166             throws Exception {
    167         clearLogs();
    168         final String componentName = getComponentName(packageName, activityName);
    169         executeShellCommand(getStartCommand(componentName, mode) + " --stack "
    170                 + FULLSCREEN_WORKSPACE_STACK_ID);
    171         waitForResume(packageName, activityName);
    172     }
    173 
    174     private void waitForResume(String packageName, String activityName) throws Exception {
    175         final String fullActivityName = packageName + "." + activityName;
    176         int retryCount = 3;
    177         do {
    178             Thread.sleep(500);
    179             String logs = executeShellCommand("logcat -d -b events");
    180             for (String line : logs.split("\\n")) {
    181                 if(line.contains("am_on_resume_called") && line.contains(fullActivityName)) {
    182                     return;
    183                 }
    184             }
    185         } while (retryCount-- > 0);
    186 
    187         throw new Exception(fullActivityName + " has failed to start");
    188     }
    189 
    190     private void injectInput(Point from, Point to, int durationMs) throws Exception {
    191         executeShellCommand(
    192                 INPUT_MOUSE_SWIPE + from.x + " " + from.y + " " + to.x + " " + to.y + " " +
    193                 durationMs);
    194     }
    195 
    196     static class Point {
    197         public int x, y;
    198 
    199         public Point(int _x, int _y) {
    200             x=_x;
    201             y=_y;
    202         }
    203 
    204         public Point() {}
    205     }
    206 
    207     private String findTaskInfo(String name) throws Exception {
    208         CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver();
    209         mDevice.executeShellCommand(AM_STACK_LIST, outputReceiver);
    210         final String output = outputReceiver.getOutput();
    211         for (String line : output.split("\\n")) {
    212             if (line.contains(name)) {
    213                 return line;
    214             }
    215         }
    216         return "";
    217     }
    218 
    219     private boolean getWindowBounds(String name, Point from, Point to) throws Exception {
    220         final String taskInfo = findTaskInfo(name);
    221         final String[] sections = taskInfo.split("\\[");
    222         if (sections.length > 2) {
    223             try {
    224                 parsePoint(sections[1], from);
    225                 parsePoint(sections[2], to);
    226                 return true;
    227             } catch (Exception e) {
    228                 return false;
    229             }
    230         }
    231         return false;
    232     }
    233 
    234     private int getActivityTaskId(String name) throws Exception {
    235         final String taskInfo = findTaskInfo(name);
    236         for (String word : taskInfo.split("\\s+")) {
    237             if (word.startsWith(TASK_ID_PREFIX)) {
    238                 final String withColon = word.split("=")[1];
    239                 return Integer.parseInt(withColon.substring(0, withColon.length() - 1));
    240             }
    241         }
    242         return -1;
    243     }
    244 
    245     private Point getWindowCenter(String name) throws Exception {
    246         Point p1 = new Point();
    247         Point p2 = new Point();
    248         if (getWindowBounds(name, p1, p2)) {
    249             return new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
    250         }
    251         return null;
    252     }
    253 
    254     private void parsePoint(String string, Point point) {
    255         final String[] parts = string.split("[,|\\]]");
    256         point.x = Integer.parseInt(parts[0]);
    257         point.y = Integer.parseInt(parts[1]);
    258     }
    259 
    260     private void unlockDevice() throws DeviceNotAvailableException {
    261         // Wake up the device, if necessary.
    262         executeShellCommand("input keyevent 224");
    263         // Unlock the screen.
    264         executeShellCommand("input keyevent 82");
    265     }
    266 
    267     private Map<String, String> getLogResults(String className) throws Exception {
    268         int retryCount = 3;
    269         Map<String, String> output = new HashMap<String, String>();
    270         do {
    271 
    272             String logs = executeShellCommand("logcat -v brief -d " + className + ":I" + " *:S");
    273             for (String line : logs.split("\\n")) {
    274                 if (line.startsWith("I/" + className)) {
    275                     String payload = line.split(":")[1].trim();
    276                     final String[] split = payload.split("=");
    277                     if (split.length > 1) {
    278                         output.put(split[0], split[1]);
    279                     }
    280                 }
    281             }
    282             if (output.containsKey(RESULT_KEY_DROP_RESULT)) {
    283                 return output;
    284             }
    285         } while (retryCount-- > 0);
    286         return output;
    287     }
    288 
    289     private void doTestDragAndDrop(String sourceMode, String targetMode, String expectedDropResult)
    290             throws Exception {
    291 
    292         launchDockedActivity(mSourcePackageName, SOURCE_ACTIVITY_NAME, sourceMode);
    293         launchFullscreenActivity(mTargetPackageName, TARGET_ACTIVITY_NAME, targetMode);
    294 
    295         clearLogs();
    296 
    297         injectInput(
    298                 getWindowCenter(getComponentName(mSourcePackageName, SOURCE_ACTIVITY_NAME)),
    299                 getWindowCenter(getComponentName(mTargetPackageName, TARGET_ACTIVITY_NAME)),
    300                 SWIPE_DURATION_MS);
    301 
    302         mResults = getLogResults(TARGET_LOG_TAG);
    303         assertResult(RESULT_KEY_DROP_RESULT, expectedDropResult);
    304     }
    305 
    306 
    307     private void assertResult(String resultKey, String expectedResult) {
    308         if (expectedResult == null) {
    309             if (mResults.containsKey(resultKey)) {
    310                 fail("Unexpected " + resultKey + "=" + mResults.get(resultKey));
    311             }
    312         } else {
    313             assertTrue("Missing " + resultKey, mResults.containsKey(resultKey));
    314             assertEquals(expectedResult, mResults.get(resultKey));
    315         }
    316     }
    317 
    318     public void testCancelSoon() throws Exception {
    319         doTestDragAndDrop(CANCEL_SOON, REQUEST_NONE, null);
    320         assertResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
    321         assertResult(RESULT_KEY_EXTRAS, RESULT_OK);
    322     }
    323 
    324     public void testDisallowGlobal() throws Exception {
    325         doTestDragAndDrop(DISALLOW_GLOBAL, REQUEST_NONE, null);
    326         assertResult(RESULT_KEY_DRAG_STARTED, null);
    327     }
    328 
    329     public void testDisallowGlobalBelowSdk24() throws Exception {
    330         mTargetPackageName = TARGET_23_PACKAGE_NAME;
    331         doTestDragAndDrop(GRANT_NONE, REQUEST_NONE, null);
    332         assertResult(RESULT_KEY_DRAG_STARTED, null);
    333     }
    334 
    335     public void testGrantNoneRequestNone() throws Exception {
    336         doTestDragAndDrop(GRANT_NONE, REQUEST_NONE, RESULT_EXCEPTION);
    337         assertResult(RESULT_KEY_DRAG_STARTED, RESULT_OK);
    338         assertResult(RESULT_KEY_EXTRAS, RESULT_OK);
    339     }
    340 
    341     public void testGrantNoneRequestRead() throws Exception {
    342         doTestDragAndDrop(GRANT_NONE, REQUEST_READ, RESULT_NULL_DROP_PERMISSIONS);
    343     }
    344 
    345     public void testGrantNoneRequestWrite() throws Exception {
    346         doTestDragAndDrop(GRANT_NONE, REQUEST_WRITE, RESULT_NULL_DROP_PERMISSIONS);
    347     }
    348 
    349     public void testGrantReadRequestNone() throws Exception {
    350         doTestDragAndDrop(GRANT_READ, REQUEST_NONE, RESULT_EXCEPTION);
    351     }
    352 
    353     public void testGrantReadRequestRead() throws Exception {
    354         doTestDragAndDrop(GRANT_READ, REQUEST_READ, RESULT_OK);
    355     }
    356 
    357     public void testGrantReadRequestWrite() throws Exception {
    358         doTestDragAndDrop(GRANT_READ, REQUEST_WRITE, RESULT_EXCEPTION);
    359     }
    360 
    361     public void testGrantReadNoPrefixRequestReadNested() throws Exception {
    362         doTestDragAndDrop(GRANT_READ_NOPREFIX, REQUEST_READ_NESTED, RESULT_EXCEPTION);
    363     }
    364 
    365     public void testGrantReadPrefixRequestReadNested() throws Exception {
    366         doTestDragAndDrop(GRANT_READ_PREFIX, REQUEST_READ_NESTED, RESULT_OK);
    367     }
    368 
    369     public void testGrantPersistableRequestTakePersistable() throws Exception {
    370         doTestDragAndDrop(GRANT_READ_PERSISTABLE, REQUEST_TAKE_PERSISTABLE, RESULT_OK);
    371     }
    372 
    373     public void testGrantReadRequestTakePersistable() throws Exception {
    374         doTestDragAndDrop(GRANT_READ, REQUEST_TAKE_PERSISTABLE, RESULT_EXCEPTION);
    375     }
    376 
    377     public void testGrantWriteRequestNone() throws Exception {
    378         doTestDragAndDrop(GRANT_WRITE, REQUEST_NONE, RESULT_EXCEPTION);
    379     }
    380 
    381     public void testGrantWriteRequestRead() throws Exception {
    382         doTestDragAndDrop(GRANT_WRITE, REQUEST_READ, RESULT_EXCEPTION);
    383     }
    384 
    385     public void testGrantWriteRequestWrite() throws Exception {
    386         doTestDragAndDrop(GRANT_WRITE, REQUEST_WRITE, RESULT_OK);
    387     }
    388 }
    389