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