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.cts; 18 19 import com.android.ddmlib.Log.LogLevel; 20 import com.android.tradefed.device.CollectingOutputReceiver; 21 import com.android.tradefed.device.DeviceNotAvailableException; 22 import com.android.tradefed.device.ITestDevice; 23 import com.android.tradefed.log.LogUtil.CLog; 24 import com.android.tradefed.testtype.DeviceTestCase; 25 26 import java.lang.Exception; 27 import java.lang.Integer; 28 import java.lang.String; 29 import java.util.HashSet; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 33 import static android.server.cts.StateLogger.log; 34 35 public abstract class ActivityManagerTestBase extends DeviceTestCase { 36 private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false; 37 private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false; 38 39 // Constants copied from ActivityManager.StackId. If they are changed there, these must be 40 // updated. 41 /** First static stack ID. */ 42 public static final int FIRST_STATIC_STACK_ID = 0; 43 44 /** Home activity stack ID. */ 45 public static final int HOME_STACK_ID = FIRST_STATIC_STACK_ID; 46 47 /** ID of stack where fullscreen activities are normally launched into. */ 48 public static final int FULLSCREEN_WORKSPACE_STACK_ID = 1; 49 50 /** ID of stack where freeform/resized activities are normally launched into. */ 51 public static final int FREEFORM_WORKSPACE_STACK_ID = FULLSCREEN_WORKSPACE_STACK_ID + 1; 52 53 /** ID of stack that occupies a dedicated region of the screen. */ 54 public static final int DOCKED_STACK_ID = FREEFORM_WORKSPACE_STACK_ID + 1; 55 56 /** ID of stack that always on top (always visible) when it exist. */ 57 public static final int PINNED_STACK_ID = DOCKED_STACK_ID + 1; 58 59 private static final String TASK_ID_PREFIX = "taskId"; 60 61 private static final String AM_STACK_LIST = "am stack list"; 62 63 private static final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.server.app"; 64 65 private static final String AM_REMOVE_STACK = "am stack remove "; 66 67 protected static final String AM_START_HOME_ACTIVITY_COMMAND = 68 "am start -a android.intent.action.MAIN -c android.intent.category.HOME"; 69 70 protected static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND = 71 "am stack move-top-activity-to-pinned-stack 1 0 0 500 500"; 72 73 protected static final String LAUNCHING_ACTIVITY = "LaunchingActivity"; 74 75 private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack "; 76 77 private static final String AM_MOVE_TASK = "am stack movetask "; 78 79 private static final String INPUT_KEYEVENT_HOME = "input keyevent 3"; 80 81 /** A reference to the device under test. */ 82 protected ITestDevice mDevice; 83 84 private HashSet<String> mAvailableFeatures; 85 86 protected static String getAmStartCmd(final String activityName) { 87 return "am start -n " + getActivityComponentName(activityName); 88 } 89 90 protected static String getAmStartCmdOverHome(final String activityName) { 91 return "am start --activity-task-on-home -n " + getActivityComponentName(activityName); 92 } 93 94 static String getActivityComponentName(final String activityName) { 95 return "android.server.app/." + activityName; 96 } 97 98 static String getWindowName(final String activityName) { 99 return "android.server.app/android.server.app." + activityName; 100 } 101 102 protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState(); 103 104 private int mInitialAccelerometerRotation; 105 private int mUserRotation; 106 private float mFontScale; 107 108 @Override 109 protected void setUp() throws Exception { 110 super.setUp(); 111 112 // Get the device, this gives a handle to run commands and install APKs. 113 mDevice = getDevice(); 114 unlockDevice(); 115 // Remove special stacks. 116 executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID); 117 executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID); 118 executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID); 119 // Store rotation settings. 120 mInitialAccelerometerRotation = getAccelerometerRotation(); 121 mUserRotation = getUserRotation(); 122 mFontScale = getFontScale(); 123 } 124 125 @Override 126 protected void tearDown() throws Exception { 127 super.tearDown(); 128 try { 129 unlockDevice(); 130 executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE); 131 // Restore rotation settings to the state they were before test. 132 setAccelerometerRotation(mInitialAccelerometerRotation); 133 setUserRotation(mUserRotation); 134 setFontScale(mFontScale); 135 // Remove special stacks. 136 executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID); 137 executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID); 138 executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID); 139 } catch (DeviceNotAvailableException e) { 140 } 141 } 142 143 protected String executeShellCommand(String command) throws DeviceNotAvailableException { 144 log("adb shell " + command); 145 return mDevice.executeShellCommand(command); 146 } 147 148 protected void executeShellCommand(String command, CollectingOutputReceiver outputReceiver) 149 throws DeviceNotAvailableException { 150 log("adb shell " + command); 151 mDevice.executeShellCommand(command, outputReceiver); 152 } 153 154 /** 155 * Launch specific target activity. It uses existing instance of {@link #LAUNCHING_ACTIVITY}, so 156 * that one should be started first. 157 * @param toSide Launch to side in split-screen. 158 * @param randomData Make intent URI random by generating random data. 159 * @param multipleTask Allow multiple task launch. 160 * @param targetActivityName Target activity to be launched. Only class name should be provided, 161 * package name of {@link #LAUNCHING_ACTIVITY} will be added 162 * automatically. 163 * @throws Exception 164 */ 165 protected void launchActivity(boolean toSide, boolean randomData, boolean multipleTask, 166 String targetActivityName) throws Exception { 167 StringBuilder commandBuilder = new StringBuilder(getAmStartCmd(LAUNCHING_ACTIVITY)); 168 commandBuilder.append(" -f 0x20000000"); 169 if (toSide) { 170 commandBuilder.append(" --ez launch_to_the_side true"); 171 } 172 if (randomData) { 173 commandBuilder.append(" --ez random_data true"); 174 } 175 if (multipleTask) { 176 commandBuilder.append(" --ez multiple_task true"); 177 } 178 if (targetActivityName != null) { 179 commandBuilder.append(" --es target_activity ").append(targetActivityName); 180 } 181 executeShellCommand(commandBuilder.toString()); 182 } 183 184 protected void launchActivityInStack(String activityName, int stackId) throws Exception { 185 executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId); 186 } 187 188 protected void launchActivityInDockStack(String activityName) throws Exception { 189 executeShellCommand(getAmStartCmd(activityName)); 190 moveActivityToDockStack(activityName); 191 } 192 193 protected void moveActivityToDockStack(String activityName) throws Exception { 194 moveActivityToStack(activityName, DOCKED_STACK_ID); 195 } 196 197 protected void moveActivityToStack(String activityName, int stackId) throws Exception { 198 final int taskId = getActivityTaskId(activityName); 199 final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true"; 200 executeShellCommand(cmd); 201 } 202 203 protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom) 204 throws Exception { 205 final int taskId = getActivityTaskId(activityName); 206 final String cmd = "am task resize " 207 + taskId + " " + left + " " + top + " " + right + " " + bottom; 208 executeShellCommand(cmd); 209 } 210 211 protected void resizeDockedStack( 212 int stackWidth, int stackHeight, int taskWidth, int taskHeight) 213 throws DeviceNotAvailableException { 214 executeShellCommand(AM_RESIZE_DOCKED_STACK 215 + "0 0 " + stackWidth + " " + stackHeight 216 + " 0 0 " + taskWidth + " " + taskHeight); 217 } 218 219 protected void pressHomeButton() throws DeviceNotAvailableException { 220 executeShellCommand(INPUT_KEYEVENT_HOME); 221 } 222 223 // Utility method for debugging, not used directly here, but useful, so kept around. 224 protected void printStacksAndTasks() throws DeviceNotAvailableException { 225 CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver(); 226 executeShellCommand(AM_STACK_LIST, outputReceiver); 227 String output = outputReceiver.getOutput(); 228 for (String line : output.split("\\n")) { 229 CLog.logAndDisplay(LogLevel.INFO, line); 230 } 231 } 232 233 protected int getActivityTaskId(String name) throws DeviceNotAvailableException { 234 CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver(); 235 executeShellCommand(AM_STACK_LIST, outputReceiver); 236 final String output = outputReceiver.getOutput(); 237 final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)"); 238 for (String line : output.split("\\n")) { 239 Matcher matcher = activityPattern.matcher(line); 240 if (matcher.matches()) { 241 for (String word : line.split("\\s+")) { 242 if (word.startsWith(TASK_ID_PREFIX)) { 243 final String withColon = word.split("=")[1]; 244 return Integer.parseInt(withColon.substring(0, withColon.length() - 1)); 245 } 246 } 247 } 248 } 249 return -1; 250 } 251 252 protected boolean supportsPip() throws DeviceNotAvailableException { 253 return hasDeviceFeature("android.software.picture_in_picture") 254 || PRETEND_DEVICE_SUPPORTS_PIP; 255 } 256 257 protected boolean supportsFreeform() throws DeviceNotAvailableException { 258 return hasDeviceFeature("android.software.freeform_window_management") 259 || PRETEND_DEVICE_SUPPORTS_FREEFORM; 260 } 261 262 protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException { 263 if (mAvailableFeatures == null) { 264 // TODO: Move this logic to ITestDevice. 265 final String output = runCommandAndPrintOutput("pm list features"); 266 267 // Extract the id of the new user. 268 mAvailableFeatures = new HashSet<>(); 269 for (String feature: output.split("\\s+")) { 270 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}". 271 String[] tokens = feature.split(":"); 272 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}", 273 tokens.length > 1); 274 assertEquals(feature, "feature", tokens[0]); 275 mAvailableFeatures.add(tokens[1]); 276 } 277 } 278 boolean result = mAvailableFeatures.contains(requiredFeature); 279 if (!result) { 280 CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support " + requiredFeature); 281 } 282 return result; 283 } 284 285 private boolean isDisplayOn() throws DeviceNotAvailableException { 286 final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver(); 287 mDevice.executeShellCommand("dumpsys power", outputReceiver); 288 289 for (String line : outputReceiver.getOutput().split("\\n")) { 290 line = line.trim(); 291 292 final Matcher matcher = sDisplayStatePattern.matcher(line); 293 if (matcher.matches()) { 294 final String state = matcher.group(1); 295 log("power state=" + state); 296 return "ON".equals(state); 297 } 298 } 299 log("power state :("); 300 return false; 301 } 302 303 protected void lockDevice() throws DeviceNotAvailableException { 304 int retriesLeft = 5; 305 runCommandAndPrintOutput("input keyevent 26"); 306 do { 307 if (isDisplayOn()) { 308 log("***Waiting for display to turn off..."); 309 try { 310 Thread.sleep(1000); 311 } catch (InterruptedException e) { 312 log(e.toString()); 313 // Well I guess we are not waiting... 314 } 315 } else { 316 break; 317 } 318 } while (retriesLeft-- > 0); 319 } 320 321 protected void unlockDevice() throws DeviceNotAvailableException { 322 if (!isDisplayOn()) { 323 runCommandAndPrintOutput("input keyevent 224"); 324 runCommandAndPrintOutput("input keyevent 82"); 325 } 326 } 327 328 protected void setDeviceRotation(int rotation) throws DeviceNotAvailableException { 329 setAccelerometerRotation(0); 330 setUserRotation(rotation); 331 } 332 333 private int getAccelerometerRotation() throws DeviceNotAvailableException { 334 final String rotation = 335 runCommandAndPrintOutput("settings get system accelerometer_rotation"); 336 return Integer.parseInt(rotation.trim()); 337 } 338 339 private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException { 340 runCommandAndPrintOutput( 341 "settings put system accelerometer_rotation " + rotation); 342 } 343 344 private int getUserRotation() throws DeviceNotAvailableException { 345 final String rotation = 346 runCommandAndPrintOutput("settings get system user_rotation").trim(); 347 if ("null".equals(rotation)) { 348 return -1; 349 } 350 return Integer.parseInt(rotation); 351 } 352 353 private void setUserRotation(int rotation) throws DeviceNotAvailableException { 354 if (rotation == -1) { 355 runCommandAndPrintOutput( 356 "settings delete system user_rotation"); 357 } else { 358 runCommandAndPrintOutput( 359 "settings put system user_rotation " + rotation); 360 } 361 } 362 363 protected void setFontScale(float fontScale) throws DeviceNotAvailableException { 364 if (fontScale == 0.0f) { 365 runCommandAndPrintOutput( 366 "settings delete system font_scale"); 367 } else { 368 runCommandAndPrintOutput( 369 "settings put system font_scale " + fontScale); 370 } 371 } 372 373 protected float getFontScale() throws DeviceNotAvailableException { 374 try { 375 final String fontScale = 376 runCommandAndPrintOutput("settings get system font_scale").trim(); 377 return Float.parseFloat(fontScale); 378 } catch (NumberFormatException e) { 379 // If we don't have a valid font scale key, return 0.0f now so 380 // that we delete the key in tearDown(). 381 return 0.0f; 382 } 383 } 384 385 protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException { 386 final String output = executeShellCommand(command); 387 log(output); 388 return output; 389 } 390 391 protected void clearLogcat() throws DeviceNotAvailableException { 392 mDevice.executeAdbCommand("logcat", "-c"); 393 } 394 395 protected void assertActivityLifecycle(String activityName, boolean relaunched) 396 throws DeviceNotAvailableException { 397 final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName); 398 399 if (relaunched) { 400 if (lifecycleCounts.mDestroyCount < 1) { 401 fail(activityName + " must have been destroyed. mDestroyCount=" 402 + lifecycleCounts.mDestroyCount); 403 } 404 if (lifecycleCounts.mCreateCount < 1) { 405 fail(activityName + " must have been (re)created. mCreateCount=" 406 + lifecycleCounts.mCreateCount); 407 } 408 } else { 409 if (lifecycleCounts.mDestroyCount > 0) { 410 fail(activityName + " must *NOT* have been destroyed. mDestroyCount=" 411 + lifecycleCounts.mDestroyCount); 412 } 413 if (lifecycleCounts.mCreateCount > 0) { 414 fail(activityName + " must *NOT* have been (re)created. mCreateCount=" 415 + lifecycleCounts.mCreateCount); 416 } 417 if (lifecycleCounts.mConfigurationChangedCount < 1) { 418 fail(activityName + " must have received configuration changed. " 419 + "mConfigurationChangedCount=" 420 + lifecycleCounts.mConfigurationChangedCount); 421 } 422 } 423 } 424 425 protected void assertRelaunchOrConfigChanged( 426 String activityName, int numRelaunch, int numConfigChange) 427 throws DeviceNotAvailableException { 428 final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName); 429 430 if (lifecycleCounts.mDestroyCount != numRelaunch) { 431 fail(activityName + " has been destroyed " + lifecycleCounts.mDestroyCount 432 + " time(s), expecting " + numRelaunch); 433 } else if (lifecycleCounts.mCreateCount != numRelaunch) { 434 fail(activityName + " has been (re)created " + lifecycleCounts.mCreateCount 435 + " time(s), expecting " + numRelaunch); 436 } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) { 437 fail(activityName + " has received " + lifecycleCounts.mConfigurationChangedCount 438 + " onConfigurationChanged() calls, expecting " + numConfigChange); 439 } 440 } 441 442 protected String[] getDeviceLogsForComponent(String componentName) 443 throws DeviceNotAvailableException { 444 return mDevice.executeAdbCommand( 445 "logcat", "-v", "brief", "-d", componentName + ":I", "*:S").split("\\n"); 446 } 447 448 protected String[] getDeviceLogsForComponents(final String[] componentNames) 449 throws DeviceNotAvailableException { 450 String filters = ""; 451 for (int i = 0; i < componentNames.length; i++) { 452 filters += componentNames[i] + ":I "; 453 } 454 return mDevice.executeAdbCommand( 455 "logcat", "-v", "brief", "-d", filters, "*:S").split("\\n"); 456 } 457 458 private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate"); 459 private static final Pattern sConfigurationChangedPattern = 460 Pattern.compile("(.+): onConfigurationChanged"); 461 private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy"); 462 private static final Pattern sNewConfigPattern = Pattern.compile( 463 "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)" + 464 " metricsSize=\\((\\d+),(\\d+)\\)"); 465 private static final Pattern sDisplayStatePattern = 466 Pattern.compile("Display Power: state=(.+)"); 467 468 protected class ReportedSizes { 469 int widthDp; 470 int heightDp; 471 int displayWidth; 472 int displayHeight; 473 int metricsWidth; 474 int metricsHeight; 475 } 476 477 protected ReportedSizes getLastReportedSizesForActivity(String activityName) 478 throws DeviceNotAvailableException { 479 final String[] lines = getDeviceLogsForComponent(activityName); 480 for (int i = lines.length - 1; i >= 0; i--) { 481 final String line = lines[i].trim(); 482 final Matcher matcher = sNewConfigPattern.matcher(line); 483 if (matcher.matches()) { 484 ReportedSizes details = new ReportedSizes(); 485 details.widthDp = Integer.parseInt(matcher.group(2)); 486 details.heightDp = Integer.parseInt(matcher.group(3)); 487 details.displayWidth = Integer.parseInt(matcher.group(4)); 488 details.displayHeight = Integer.parseInt(matcher.group(5)); 489 details.metricsWidth = Integer.parseInt(matcher.group(6)); 490 details.metricsHeight = Integer.parseInt(matcher.group(7)); 491 return details; 492 } 493 } 494 return null; 495 } 496 497 private class ActivityLifecycleCounts { 498 int mCreateCount; 499 int mConfigurationChangedCount; 500 int mDestroyCount; 501 502 public ActivityLifecycleCounts(String activityName) throws DeviceNotAvailableException { 503 for (String line : getDeviceLogsForComponent(activityName)) { 504 line = line.trim(); 505 506 Matcher matcher = sCreatePattern.matcher(line); 507 if (matcher.matches()) { 508 mCreateCount++; 509 continue; 510 } 511 512 matcher = sConfigurationChangedPattern.matcher(line); 513 if (matcher.matches()) { 514 mConfigurationChangedCount++; 515 continue; 516 } 517 518 matcher = sDestroyPattern.matcher(line); 519 if (matcher.matches()) { 520 mDestroyCount++; 521 continue; 522 } 523 } 524 } 525 } 526 } 527