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 private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack "; 74 75 private static final String AM_MOVE_TASK = "am stack movetask "; 76 77 /** A reference to the device under test. */ 78 protected ITestDevice mDevice; 79 80 private HashSet<String> mAvailableFeatures; 81 getAmStartCmd(final String activityName)82 protected static String getAmStartCmd(final String activityName) { 83 return "am start -n " + getActivityComponentName(activityName); 84 } 85 getAmStartCmdOverHome(final String activityName)86 protected static String getAmStartCmdOverHome(final String activityName) { 87 return "am start --activity-task-on-home -n " + getActivityComponentName(activityName); 88 } 89 getActivityComponentName(final String activityName)90 static String getActivityComponentName(final String activityName) { 91 return "android.server.app/." + activityName; 92 } 93 getWindowName(final String activityName)94 static String getWindowName(final String activityName) { 95 return "android.server.app/android.server.app." + activityName; 96 } 97 98 protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState(); 99 100 private int mInitialAccelerometerRotation; 101 private int mUserRotation; 102 103 @Override setUp()104 protected void setUp() throws Exception { 105 super.setUp(); 106 107 // Get the device, this gives a handle to run commands and install APKs. 108 mDevice = getDevice(); 109 unlockDevice(); 110 // Remove special stacks. 111 executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID); 112 executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID); 113 executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID); 114 // Store rotation settings. 115 mInitialAccelerometerRotation = getAccelerometerRotation(); 116 mUserRotation = getUserRotation(); 117 } 118 119 @Override tearDown()120 protected void tearDown() throws Exception { 121 super.tearDown(); 122 try { 123 unlockDevice(); 124 executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE); 125 // Restore rotation settings to the state they were before test. 126 setAccelerometerRotation(mInitialAccelerometerRotation); 127 setUserRotation(mUserRotation); 128 // Remove special stacks. 129 executeShellCommand(AM_REMOVE_STACK + PINNED_STACK_ID); 130 executeShellCommand(AM_REMOVE_STACK + DOCKED_STACK_ID); 131 executeShellCommand(AM_REMOVE_STACK + FREEFORM_WORKSPACE_STACK_ID); 132 } catch (DeviceNotAvailableException e) { 133 } 134 } 135 executeShellCommand(String command)136 protected String executeShellCommand(String command) throws DeviceNotAvailableException { 137 log("adb shell " + command); 138 return mDevice.executeShellCommand(command); 139 } 140 executeShellCommand(String command, CollectingOutputReceiver outputReceiver)141 protected void executeShellCommand(String command, CollectingOutputReceiver outputReceiver) 142 throws DeviceNotAvailableException { 143 log("adb shell " + command); 144 mDevice.executeShellCommand(command, outputReceiver); 145 } 146 launchActivityInStack(String activityName, int stackId)147 protected void launchActivityInStack(String activityName, int stackId) throws Exception { 148 executeShellCommand(getAmStartCmd(activityName) + " --stack " + stackId); 149 } 150 launchActivityInDockStack(String activityName)151 protected void launchActivityInDockStack(String activityName) throws Exception { 152 executeShellCommand(getAmStartCmd(activityName)); 153 moveActivityToDockStack(activityName); 154 } 155 moveActivityToDockStack(String activityName)156 protected void moveActivityToDockStack(String activityName) throws Exception { 157 moveActivityToStack(activityName, DOCKED_STACK_ID); 158 } 159 moveActivityToStack(String activityName, int stackId)160 protected void moveActivityToStack(String activityName, int stackId) throws Exception { 161 final int taskId = getActivityTaskId(activityName); 162 final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true"; 163 executeShellCommand(cmd); 164 } 165 resizeActivityTask(String activityName, int left, int top, int right, int bottom)166 protected void resizeActivityTask(String activityName, int left, int top, int right, int bottom) 167 throws Exception { 168 final int taskId = getActivityTaskId(activityName); 169 final String cmd = "am task resize " 170 + taskId + " " + left + " " + top + " " + right + " " + bottom; 171 executeShellCommand(cmd); 172 } 173 resizeDockedStack( int stackWidth, int stackHeight, int taskWidth, int taskHeight)174 protected void resizeDockedStack( 175 int stackWidth, int stackHeight, int taskWidth, int taskHeight) 176 throws DeviceNotAvailableException { 177 executeShellCommand(AM_RESIZE_DOCKED_STACK 178 + "0 0 " + stackWidth + " " + stackHeight 179 + " 0 0 " + taskWidth + " " + taskHeight); 180 } 181 182 // Utility method for debugging, not used directly here, but useful, so kept around. printStacksAndTasks()183 protected void printStacksAndTasks() throws DeviceNotAvailableException { 184 CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver(); 185 executeShellCommand(AM_STACK_LIST, outputReceiver); 186 String output = outputReceiver.getOutput(); 187 for (String line : output.split("\\n")) { 188 CLog.logAndDisplay(LogLevel.INFO, line); 189 } 190 } 191 getActivityTaskId(String name)192 protected int getActivityTaskId(String name) throws DeviceNotAvailableException { 193 CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver(); 194 executeShellCommand(AM_STACK_LIST, outputReceiver); 195 final String output = outputReceiver.getOutput(); 196 final Pattern activityPattern = Pattern.compile("(.*) " + getWindowName(name) + " (.*)"); 197 for (String line : output.split("\\n")) { 198 Matcher matcher = activityPattern.matcher(line); 199 if (matcher.matches()) { 200 for (String word : line.split("\\s+")) { 201 if (word.startsWith(TASK_ID_PREFIX)) { 202 final String withColon = word.split("=")[1]; 203 return Integer.parseInt(withColon.substring(0, withColon.length() - 1)); 204 } 205 } 206 } 207 } 208 return -1; 209 } 210 supportsPip()211 protected boolean supportsPip() throws DeviceNotAvailableException { 212 return hasDeviceFeature("android.software.picture_in_picture") 213 || PRETEND_DEVICE_SUPPORTS_PIP; 214 } 215 supportsFreeform()216 protected boolean supportsFreeform() throws DeviceNotAvailableException { 217 return hasDeviceFeature("android.software.freeform_window_management") 218 || PRETEND_DEVICE_SUPPORTS_FREEFORM; 219 } 220 hasDeviceFeature(String requiredFeature)221 protected boolean hasDeviceFeature(String requiredFeature) throws DeviceNotAvailableException { 222 if (mAvailableFeatures == null) { 223 // TODO: Move this logic to ITestDevice. 224 final String output = runCommandAndPrintOutput("pm list features"); 225 226 // Extract the id of the new user. 227 mAvailableFeatures = new HashSet<>(); 228 for (String feature: output.split("\\s+")) { 229 // Each line in the output of the command has the format "feature:{FEATURE_VALUE}". 230 String[] tokens = feature.split(":"); 231 assertTrue("\"" + feature + "\" expected to have format feature:{FEATURE_VALUE}", 232 tokens.length > 1); 233 assertEquals(feature, "feature", tokens[0]); 234 mAvailableFeatures.add(tokens[1]); 235 } 236 } 237 boolean result = mAvailableFeatures.contains(requiredFeature); 238 if (!result) { 239 CLog.logAndDisplay(LogLevel.INFO, "Device doesn't support " + requiredFeature); 240 } 241 return result; 242 } 243 isDisplayOn()244 private boolean isDisplayOn() throws DeviceNotAvailableException { 245 final CollectingOutputReceiver outputReceiver = new CollectingOutputReceiver(); 246 mDevice.executeShellCommand("dumpsys power", outputReceiver); 247 248 for (String line : outputReceiver.getOutput().split("\\n")) { 249 line = line.trim(); 250 251 final Matcher matcher = sDisplayStatePattern.matcher(line); 252 if (matcher.matches()) { 253 final String state = matcher.group(1); 254 log("power state=" + state); 255 return "ON".equals(state); 256 } 257 } 258 log("power state :("); 259 return false; 260 } 261 lockDevice()262 protected void lockDevice() throws DeviceNotAvailableException { 263 int retriesLeft = 5; 264 runCommandAndPrintOutput("input keyevent 26"); 265 do { 266 if (isDisplayOn()) { 267 log("***Waiting for display to turn off..."); 268 try { 269 Thread.sleep(1000); 270 } catch (InterruptedException e) { 271 log(e.toString()); 272 // Well I guess we are not waiting... 273 } 274 } else { 275 break; 276 } 277 } while (retriesLeft-- > 0); 278 } 279 unlockDevice()280 protected void unlockDevice() throws DeviceNotAvailableException { 281 if (!isDisplayOn()) { 282 runCommandAndPrintOutput("input keyevent 224"); 283 runCommandAndPrintOutput("input keyevent 82"); 284 } 285 } 286 setDeviceRotation(int rotation)287 protected void setDeviceRotation(int rotation) throws DeviceNotAvailableException { 288 setAccelerometerRotation(0); 289 setUserRotation(rotation); 290 } 291 getAccelerometerRotation()292 private int getAccelerometerRotation() throws DeviceNotAvailableException { 293 final String rotation = 294 runCommandAndPrintOutput("settings get system accelerometer_rotation"); 295 return Integer.parseInt(rotation.trim()); 296 } 297 setAccelerometerRotation(int rotation)298 private void setAccelerometerRotation(int rotation) throws DeviceNotAvailableException { 299 runCommandAndPrintOutput( 300 "settings put system accelerometer_rotation " + rotation); 301 } 302 getUserRotation()303 private int getUserRotation() throws DeviceNotAvailableException { 304 final String rotation = 305 runCommandAndPrintOutput("settings get system user_rotation").trim(); 306 if ("null".equals(rotation)) { 307 return -1; 308 } 309 return Integer.parseInt(rotation); 310 } 311 setUserRotation(int rotation)312 private void setUserRotation(int rotation) throws DeviceNotAvailableException { 313 if (rotation == -1) { 314 runCommandAndPrintOutput( 315 "settings delete system user_rotation"); 316 } else { 317 runCommandAndPrintOutput( 318 "settings put system user_rotation " + rotation); 319 } 320 } 321 runCommandAndPrintOutput(String command)322 protected String runCommandAndPrintOutput(String command) throws DeviceNotAvailableException { 323 final String output = executeShellCommand(command); 324 log(output); 325 return output; 326 } 327 clearLogcat()328 protected void clearLogcat() throws DeviceNotAvailableException { 329 mDevice.executeAdbCommand("logcat", "-c"); 330 } 331 assertActivityLifecycle(String activityName, boolean relaunched)332 protected void assertActivityLifecycle(String activityName, boolean relaunched) 333 throws DeviceNotAvailableException { 334 final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts(activityName); 335 336 if (relaunched) { 337 if (lifecycleCounts.mDestroyCount < 1) { 338 fail(activityName + " must have been destroyed. mDestroyCount=" 339 + lifecycleCounts.mDestroyCount); 340 } 341 if (lifecycleCounts.mCreateCount < 1) { 342 fail(activityName + " must have been (re)created. mCreateCount=" 343 + lifecycleCounts.mCreateCount); 344 } 345 } else { 346 if (lifecycleCounts.mDestroyCount > 0) { 347 fail(activityName + " must *NOT* have been destroyed. mDestroyCount=" 348 + lifecycleCounts.mDestroyCount); 349 } 350 if (lifecycleCounts.mCreateCount > 0) { 351 fail(activityName + " must *NOT* have been (re)created. mCreateCount=" 352 + lifecycleCounts.mCreateCount); 353 } 354 if (lifecycleCounts.mConfigurationChangedCount < 1) { 355 fail(activityName + " must have received configuration changed. " 356 + "mConfigurationChangedCount=" 357 + lifecycleCounts.mConfigurationChangedCount); 358 } 359 } 360 } 361 getDeviceLogsForActivity(String activityName)362 private String[] getDeviceLogsForActivity(String activityName) 363 throws DeviceNotAvailableException { 364 return mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", activityName + ":I", "*:S") 365 .split("\\n"); 366 } 367 368 private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate"); 369 private static final Pattern sConfigurationChangedPattern = 370 Pattern.compile("(.+): onConfigurationChanged"); 371 private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy"); 372 private static final Pattern sNewConfigPattern = Pattern.compile( 373 "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)" + 374 " metricsSize=\\((\\d+),(\\d+)\\)"); 375 private static final Pattern sDisplayStatePattern = 376 Pattern.compile("Display Power: state=(.+)"); 377 378 protected class ReportedSizes { 379 int widthDp; 380 int heightDp; 381 int displayWidth; 382 int displayHeight; 383 int metricsWidth; 384 int metricsHeight; 385 } 386 getLastReportedSizesForActivity(String activityName)387 protected ReportedSizes getLastReportedSizesForActivity(String activityName) 388 throws DeviceNotAvailableException { 389 final String[] lines = getDeviceLogsForActivity(activityName); 390 for (int i = lines.length - 1; i >= 0; i--) { 391 final String line = lines[i].trim(); 392 final Matcher matcher = sNewConfigPattern.matcher(line); 393 if (matcher.matches()) { 394 ReportedSizes details = new ReportedSizes(); 395 details.widthDp = Integer.parseInt(matcher.group(2)); 396 details.heightDp = Integer.parseInt(matcher.group(3)); 397 details.displayWidth = Integer.parseInt(matcher.group(4)); 398 details.displayHeight = Integer.parseInt(matcher.group(5)); 399 details.metricsWidth = Integer.parseInt(matcher.group(6)); 400 details.metricsHeight = Integer.parseInt(matcher.group(7)); 401 return details; 402 } 403 } 404 return null; 405 } 406 407 private class ActivityLifecycleCounts { 408 int mCreateCount; 409 int mConfigurationChangedCount; 410 int mDestroyCount; 411 ActivityLifecycleCounts(String activityName)412 public ActivityLifecycleCounts(String activityName) throws DeviceNotAvailableException { 413 for (String line : getDeviceLogsForActivity(activityName)) { 414 line = line.trim(); 415 416 Matcher matcher = sCreatePattern.matcher(line); 417 if (matcher.matches()) { 418 mCreateCount++; 419 continue; 420 } 421 422 matcher = sConfigurationChangedPattern.matcher(line); 423 if (matcher.matches()) { 424 mConfigurationChangedCount++; 425 continue; 426 } 427 428 matcher = sDestroyPattern.matcher(line); 429 if (matcher.matches()) { 430 mDestroyCount++; 431 continue; 432 } 433 } 434 } 435 } 436 } 437