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.am; 18 19 import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; 20 import static android.app.ActivityManager.StackId.INVALID_STACK_ID; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 24 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 25 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 26 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; 27 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 28 import static android.content.pm.PackageManager.FEATURE_EMBEDDED; 29 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; 30 import static android.content.pm.PackageManager.FEATURE_LEANBACK; 31 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; 32 import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE; 33 import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT; 34 import static android.content.pm.PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE; 35 import static android.content.pm.PackageManager.FEATURE_WATCH; 36 import static android.server.am.ActivityLauncher.KEY_DISPLAY_ID; 37 import static android.server.am.ActivityLauncher.KEY_LAUNCH_ACTIVITY; 38 import static android.server.am.ActivityLauncher.KEY_LAUNCH_TO_SIDE; 39 import static android.server.am.ActivityLauncher.KEY_MULTIPLE_TASK; 40 import static android.server.am.ActivityLauncher.KEY_NEW_TASK; 41 import static android.server.am.ActivityLauncher.KEY_RANDOM_DATA; 42 import static android.server.am.ActivityLauncher.KEY_REORDER_TO_FRONT; 43 import static android.server.am.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS; 44 import static android.server.am.ActivityLauncher.KEY_TARGET_COMPONENT; 45 import static android.server.am.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT; 46 import static android.server.am.ActivityLauncher.KEY_USE_INSTRUMENTATION; 47 import static android.server.am.ActivityLauncher.launchActivityFromExtras; 48 import static android.server.am.ActivityManagerState.STATE_RESUMED; 49 import static android.server.am.ComponentNameUtils.getActivityName; 50 import static android.server.am.ComponentNameUtils.getLogTag; 51 import static android.server.am.Components.LAUNCHING_ACTIVITY; 52 import static android.server.am.Components.TEST_ACTIVITY; 53 import static android.server.am.StateLogger.log; 54 import static android.server.am.StateLogger.logAlways; 55 import static android.server.am.StateLogger.logE; 56 import static android.server.am.UiDeviceUtils.pressAppSwitchButton; 57 import static android.server.am.UiDeviceUtils.pressBackButton; 58 import static android.server.am.UiDeviceUtils.pressEnterButton; 59 import static android.server.am.UiDeviceUtils.pressHomeButton; 60 import static android.server.am.UiDeviceUtils.pressSleepButton; 61 import static android.server.am.UiDeviceUtils.pressUnlockButton; 62 import static android.server.am.UiDeviceUtils.pressWakeupButton; 63 import static android.server.am.UiDeviceUtils.waitForDeviceIdle; 64 import static android.view.Display.DEFAULT_DISPLAY; 65 import static android.view.Display.INVALID_DISPLAY; 66 67 import static org.junit.Assert.assertTrue; 68 import static org.junit.Assert.fail; 69 70 import static java.lang.Integer.toHexString; 71 72 import android.accessibilityservice.AccessibilityService; 73 import android.app.Activity; 74 import android.app.ActivityManager; 75 import android.content.ComponentName; 76 import android.content.Context; 77 import android.graphics.Bitmap; 78 import android.content.Intent; 79 import android.os.Bundle; 80 import android.os.SystemClock; 81 import android.provider.Settings; 82 import android.server.am.settings.SettingsSession; 83 import android.support.test.InstrumentationRegistry; 84 85 import android.support.test.rule.ActivityTestRule; 86 87 import com.android.compatibility.common.util.SystemUtil; 88 89 import org.junit.After; 90 import org.junit.Before; 91 import org.junit.Rule; 92 import org.junit.rules.TestRule; 93 import org.junit.runner.Description; 94 import org.junit.runners.model.Statement; 95 96 import java.io.IOException; 97 import java.util.Arrays; 98 import java.util.HashMap; 99 import java.util.HashSet; 100 import java.util.List; 101 import java.util.Map; 102 import java.util.UUID; 103 import java.util.concurrent.TimeUnit; 104 import java.util.regex.Matcher; 105 import java.util.regex.Pattern; 106 import java.util.stream.Collectors; 107 import java.util.stream.IntStream; 108 109 import androidx.annotation.NonNull; 110 import androidx.annotation.Nullable; 111 112 public abstract class ActivityManagerTestBase { 113 private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false; 114 private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false; 115 private static final String LOG_SEPARATOR = "LOG_SEPARATOR"; 116 117 protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = { 118 ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, 119 ACTIVITY_TYPE_UNDEFINED 120 }; 121 122 private static final String TASK_ID_PREFIX = "taskId"; 123 124 private static final String AM_STACK_LIST = "am stack list"; 125 126 private static final String AM_FORCE_STOP_TEST_PACKAGE = "am force-stop android.server.am"; 127 private static final String AM_FORCE_STOP_SECOND_TEST_PACKAGE 128 = "am force-stop android.server.am.second"; 129 private static final String AM_FORCE_STOP_THIRD_TEST_PACKAGE 130 = "am force-stop android.server.am.third"; 131 132 protected static final String AM_START_HOME_ACTIVITY_COMMAND = 133 "am start -a android.intent.action.MAIN -c android.intent.category.HOME"; 134 135 private static final String AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND_FORMAT = 136 "am stack move-top-activity-to-pinned-stack %1d 0 0 500 500"; 137 138 private static final String AM_RESIZE_DOCKED_STACK = "am stack resize-docked-stack "; 139 private static final String AM_RESIZE_STACK = "am stack resize "; 140 141 static final String AM_MOVE_TASK = "am stack move-task "; 142 143 private static final String AM_NO_HOME_SCREEN = "am no-home-screen"; 144 145 private static final String LOCK_CREDENTIAL = "1234"; 146 147 private static final int UI_MODE_TYPE_MASK = 0x0f; 148 private static final int UI_MODE_TYPE_VR_HEADSET = 0x07; 149 150 private static Boolean sHasHomeScreen = null; 151 152 protected static final int INVALID_DEVICE_ROTATION = -1; 153 154 protected Context mContext; 155 protected ActivityManager mAm; 156 157 @Rule 158 public final ActivityTestRule<SideActivity> mSideActivityRule = 159 new ActivityTestRule<>(SideActivity.class, true /* initialTouchMode */, 160 false /* launchActivity */); 161 162 /** 163 * @return the am command to start the given activity with the following extra key/value pairs. 164 * {@param keyValuePairs} must be a list of arguments defining each key/value extra. 165 */ 166 // TODO: Make this more generic, for instance accepting flags or extras of other types. getAmStartCmd(final ComponentName activityName, final String... keyValuePairs)167 protected static String getAmStartCmd(final ComponentName activityName, 168 final String... keyValuePairs) { 169 return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs); 170 } 171 getAmStartCmdInternal(final String activityName, final String... keyValuePairs)172 private static String getAmStartCmdInternal(final String activityName, 173 final String... keyValuePairs) { 174 return appendKeyValuePairs( 175 new StringBuilder("am start -n ").append(activityName), 176 keyValuePairs); 177 } 178 appendKeyValuePairs( final StringBuilder cmd, final String... keyValuePairs)179 private static String appendKeyValuePairs( 180 final StringBuilder cmd, final String... keyValuePairs) { 181 if (keyValuePairs.length % 2 != 0) { 182 throw new RuntimeException("keyValuePairs must be pairs of key/value arguments"); 183 } 184 for (int i = 0; i < keyValuePairs.length; i += 2) { 185 final String key = keyValuePairs[i]; 186 final String value = keyValuePairs[i + 1]; 187 cmd.append(" --es ") 188 .append(key) 189 .append(" ") 190 .append(value); 191 } 192 return cmd.toString(); 193 } 194 getAmStartCmd(final ComponentName activityName, final int displayId, final String... keyValuePair)195 protected static String getAmStartCmd(final ComponentName activityName, final int displayId, 196 final String... keyValuePair) { 197 return getAmStartCmdInternal(getActivityName(activityName), displayId, keyValuePair); 198 } 199 getAmStartCmdInternal(final String activityName, final int displayId, final String... keyValuePairs)200 private static String getAmStartCmdInternal(final String activityName, final int displayId, 201 final String... keyValuePairs) { 202 return appendKeyValuePairs( 203 new StringBuilder("am start -n ") 204 .append(activityName) 205 .append(" -f 0x") 206 .append(toHexString(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)) 207 .append(" --display ") 208 .append(displayId), 209 keyValuePairs); 210 } 211 getAmStartCmdInNewTask(final ComponentName activityName)212 protected static String getAmStartCmdInNewTask(final ComponentName activityName) { 213 return "am start -n " + getActivityName(activityName) + " -f 0x18000000"; 214 } 215 getAmStartCmdOverHome(final ComponentName activityName)216 protected static String getAmStartCmdOverHome(final ComponentName activityName) { 217 return "am start --activity-task-on-home -n " + getActivityName(activityName); 218 } 219 getMoveToPinnedStackCommand(int stackId)220 protected static String getMoveToPinnedStackCommand(int stackId) { 221 return String.format(AM_MOVE_TOP_ACTIVITY_TO_PINNED_STACK_COMMAND_FORMAT, stackId); 222 } 223 224 protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState(); 225 226 @Before setUp()227 public void setUp() throws Exception { 228 mContext = InstrumentationRegistry.getContext(); 229 mAm = mContext.getSystemService(ActivityManager.class); 230 231 InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission( 232 mContext.getPackageName(), android.Manifest.permission.MANAGE_ACTIVITY_STACKS); 233 InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission( 234 mContext.getPackageName(), android.Manifest.permission.ACTIVITY_EMBEDDING); 235 236 pressWakeupButton(); 237 pressUnlockButton(); 238 pressHomeButton(); 239 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 240 } 241 242 @After tearDown()243 public void tearDown() throws Exception { 244 // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but 245 // home are cleaned up from the stack at the end of each test. Am force stop shell commands 246 // might be asynchronous and could interrupt the stack cleanup process if executed first. 247 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 248 executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE); 249 executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE); 250 executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE); 251 pressHomeButton(); 252 } 253 removeStacksWithActivityTypes(int... activityTypes)254 protected void removeStacksWithActivityTypes(int... activityTypes) { 255 mAm.removeStacksWithActivityTypes(activityTypes); 256 waitForIdle(); 257 } 258 removeStacksInWindowingModes(int... windowingModes)259 protected void removeStacksInWindowingModes(int... windowingModes) { 260 mAm.removeStacksInWindowingModes(windowingModes); 261 waitForIdle(); 262 } 263 executeShellCommand(String command)264 public static String executeShellCommand(String command) { 265 log("Shell command: " + command); 266 try { 267 return SystemUtil 268 .runShellCommand(InstrumentationRegistry.getInstrumentation(), command); 269 } catch (IOException e) { 270 //bubble it up 271 logE("Error running shell command: " + command); 272 throw new RuntimeException(e); 273 } 274 } 275 takeScreenshot()276 protected Bitmap takeScreenshot() { 277 return InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot(); 278 } 279 launchActivity(final ComponentName activityName, final String... keyValuePairs)280 protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) { 281 launchActivityNoWait(activityName, keyValuePairs); 282 mAmWmState.waitForValidState(activityName); 283 } 284 launchActivityNoWait(final ComponentName activityName, final String... keyValuePairs)285 protected void launchActivityNoWait(final ComponentName activityName, 286 final String... keyValuePairs) { 287 executeShellCommand(getAmStartCmd(activityName, keyValuePairs)); 288 } 289 launchActivityInNewTask(final ComponentName activityName)290 protected void launchActivityInNewTask(final ComponentName activityName) { 291 executeShellCommand(getAmStartCmdInNewTask(activityName)); 292 mAmWmState.waitForValidState(activityName); 293 } 294 295 /** 296 * Starts an activity in a new stack. 297 * @return the stack id of the newly created stack. 298 */ 299 @Deprecated launchActivityInNewDynamicStack(ComponentName activityName)300 protected int launchActivityInNewDynamicStack(ComponentName activityName) { 301 HashSet<Integer> stackIds = getStackIds(); 302 executeShellCommand("am stack start " + DEFAULT_DISPLAY + " " 303 + getActivityName(activityName)); 304 HashSet<Integer> newStackIds = getStackIds(); 305 newStackIds.removeAll(stackIds); 306 if (newStackIds.isEmpty()) { 307 return INVALID_STACK_ID; 308 } else { 309 assertTrue(newStackIds.size() == 1); 310 return newStackIds.iterator().next(); 311 } 312 } 313 waitForIdle()314 private static void waitForIdle() { 315 InstrumentationRegistry.getInstrumentation().waitForIdleSync(); 316 } 317 318 /** Returns the set of stack ids. */ getStackIds()319 private HashSet<Integer> getStackIds() { 320 mAmWmState.computeState(true); 321 final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks(); 322 final HashSet<Integer> stackIds = new HashSet<>(); 323 for (ActivityManagerState.ActivityStack s : stacks) { 324 stackIds.add(s.mStackId); 325 } 326 return stackIds; 327 } 328 launchHomeActivity()329 protected void launchHomeActivity() { 330 executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND); 331 mAmWmState.waitForHomeActivityVisible(); 332 } 333 launchActivity(ComponentName activityName, int windowingMode, final String... keyValuePairs)334 protected void launchActivity(ComponentName activityName, int windowingMode, 335 final String... keyValuePairs) { 336 executeShellCommand(getAmStartCmd(activityName, keyValuePairs) 337 + " --windowingMode " + windowingMode); 338 mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 339 .setWindowingMode(windowingMode) 340 .build()); 341 } 342 launchActivityOnDisplay(ComponentName activityName, int displayId, String... keyValuePairs)343 protected void launchActivityOnDisplay(ComponentName activityName, int displayId, 344 String... keyValuePairs) { 345 launchActivityOnDisplayNoWait(activityName, displayId, keyValuePairs); 346 mAmWmState.waitForValidState(activityName); 347 } 348 launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, String... keyValuePairs)349 protected void launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, 350 String... keyValuePairs) { 351 executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs)); 352 } 353 354 /** 355 * Launches {@param activityName} into split-screen primary windowing mode and also makes 356 * the recents activity visible to the side of it. 357 * NOTE: Recents view may be combined with home screen on some devices, so using this to wait 358 * for Recents only makes sense when {@link ActivityManagerState#isHomeRecentsComponent()} is 359 * {@code false}. 360 */ launchActivityInSplitScreenWithRecents(ComponentName activityName)361 protected void launchActivityInSplitScreenWithRecents(ComponentName activityName) { 362 launchActivityInSplitScreenWithRecents(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); 363 } 364 launchActivityInSplitScreenWithRecents(ComponentName activityName, int createMode)365 protected void launchActivityInSplitScreenWithRecents(ComponentName activityName, 366 int createMode) { 367 launchActivity(activityName); 368 final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId; 369 mAm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */, 370 false /* animate */, null /* initialBounds */, true /* showRecents */); 371 372 mAmWmState.waitForValidState( 373 new WaitForValidActivityState.Builder(activityName) 374 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) 375 .setActivityType(ACTIVITY_TYPE_STANDARD) 376 .build()); 377 mAmWmState.waitForRecentsActivityVisible(); 378 } 379 moveTaskToPrimarySplitScreen(int taskId)380 public void moveTaskToPrimarySplitScreen(int taskId) { 381 moveTaskToPrimarySplitScreen(taskId, false /* launchSideActivityIfNeeded */); 382 } 383 384 /** 385 * Moves the device into split-screen with the specified task into the primary stack. 386 * @param taskId The id of the task to move into the primary stack. 387 * @param launchSideActivityIfNeeded Whether a placeholder activity should be launched if no 388 * recents activity is available. 389 */ moveTaskToPrimarySplitScreen(int taskId, boolean launchSideActivityIfNeeded)390 public void moveTaskToPrimarySplitScreen(int taskId, boolean launchSideActivityIfNeeded) { 391 mAm.setTaskWindowingModeSplitScreenPrimary(taskId, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, 392 true /* onTop */, false /* animate */, null /* initialBounds */, 393 true /* showRecents */); 394 mAmWmState.waitForRecentsActivityVisible(); 395 396 if (mAmWmState.getAmState().isHomeRecentsComponent() && launchSideActivityIfNeeded) { 397 // Launch Placeholder Recents 398 final Activity recentsActivity = mSideActivityRule.launchActivity(new Intent()); 399 mAmWmState.waitForActivityState(recentsActivity.getComponentName(), STATE_RESUMED); 400 } 401 } 402 403 /** 404 * Launches {@param primaryActivity} into split-screen primary windowing mode 405 * and {@param secondaryActivity} to the side in split-screen secondary windowing mode. 406 */ launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, LaunchActivityBuilder secondaryActivity)407 protected void launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, 408 LaunchActivityBuilder secondaryActivity) { 409 // Launch split-screen primary. 410 primaryActivity 411 .setUseInstrumentation() 412 .setWaitForLaunched(true) 413 .execute(); 414 415 final int taskId = mAmWmState.getAmState().getTaskByActivity( 416 primaryActivity.mTargetActivity).mTaskId; 417 moveTaskToPrimarySplitScreen(taskId); 418 419 // Launch split-screen secondary 420 // Recents become focused, so we can just launch new task in focused stack 421 secondaryActivity 422 .setUseInstrumentation() 423 .setWaitForLaunched(true) 424 .setNewTask(true) 425 .setMultipleTask(true) 426 .execute(); 427 } 428 setActivityTaskWindowingMode(ComponentName activityName, int windowingMode)429 protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) { 430 mAmWmState.computeState(activityName); 431 final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId; 432 mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */); 433 mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 434 .setActivityType(ACTIVITY_TYPE_STANDARD) 435 .setWindowingMode(windowingMode) 436 .build()); 437 } 438 moveActivityToStack(ComponentName activityName, int stackId)439 protected void moveActivityToStack(ComponentName activityName, int stackId) { 440 mAmWmState.computeState(activityName); 441 final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId; 442 final String cmd = AM_MOVE_TASK + taskId + " " + stackId + " true"; 443 executeShellCommand(cmd); 444 445 mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 446 .setStackId(stackId) 447 .build()); 448 } 449 resizeActivityTask( ComponentName activityName, int left, int top, int right, int bottom)450 protected void resizeActivityTask( 451 ComponentName activityName, int left, int top, int right, int bottom) { 452 mAmWmState.computeState(activityName); 453 final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId; 454 final String cmd = "am task resize " 455 + taskId + " " + left + " " + top + " " + right + " " + bottom; 456 executeShellCommand(cmd); 457 } 458 resizeDockedStack( int stackWidth, int stackHeight, int taskWidth, int taskHeight)459 protected void resizeDockedStack( 460 int stackWidth, int stackHeight, int taskWidth, int taskHeight) { 461 executeShellCommand(AM_RESIZE_DOCKED_STACK 462 + "0 0 " + stackWidth + " " + stackHeight 463 + " 0 0 " + taskWidth + " " + taskHeight); 464 } 465 resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth, int stackHeight)466 protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth, 467 int stackHeight) { 468 executeShellCommand(AM_RESIZE_STACK + String.format("%d %d %d %d %d", stackId, stackLeft, 469 stackTop, stackWidth, stackHeight)); 470 } 471 pressAppSwitchButtonAndWaitForRecents()472 protected void pressAppSwitchButtonAndWaitForRecents() { 473 pressAppSwitchButton(); 474 mAmWmState.waitForRecentsActivityVisible(); 475 mAmWmState.waitForAppTransitionIdle(); 476 } 477 478 // Utility method for debugging, not used directly here, but useful, so kept around. printStacksAndTasks()479 protected void printStacksAndTasks() { 480 String output = executeShellCommand(AM_STACK_LIST); 481 for (String line : output.split("\\n")) { 482 log(line); 483 } 484 } 485 supportsVrMode()486 protected boolean supportsVrMode() { 487 return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE); 488 } 489 supportsPip()490 protected boolean supportsPip() { 491 return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE) 492 || PRETEND_DEVICE_SUPPORTS_PIP; 493 } 494 supportsFreeform()495 protected boolean supportsFreeform() { 496 return hasDeviceFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT) 497 || PRETEND_DEVICE_SUPPORTS_FREEFORM; 498 } 499 500 /** Whether or not the device pin/pattern/password lock. */ supportsSecureLock()501 protected boolean supportsSecureLock() { 502 return !hasDeviceFeature(FEATURE_LEANBACK) 503 && !hasDeviceFeature(FEATURE_EMBEDDED); 504 } 505 506 /** Whether or not the device supports "swipe" lock. */ supportsInsecureLock()507 protected boolean supportsInsecureLock() { 508 return !hasDeviceFeature(FEATURE_LEANBACK) 509 && !hasDeviceFeature(FEATURE_WATCH) 510 && !hasDeviceFeature(FEATURE_EMBEDDED); 511 } 512 isWatch()513 protected boolean isWatch() { 514 return hasDeviceFeature(FEATURE_WATCH); 515 } 516 isTablet()517 protected boolean isTablet() { 518 // Larger than approx 7" tablets 519 return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600; 520 } 521 522 // TODO: Switch to using a feature flag, when available. isUiModeLockedToVrHeadset()523 protected static boolean isUiModeLockedToVrHeadset() { 524 final String output = runCommandAndPrintOutput("dumpsys uimode"); 525 526 Integer curUiMode = null; 527 Boolean uiModeLocked = null; 528 for (String line : output.split("\\n")) { 529 line = line.trim(); 530 Matcher matcher = sCurrentUiModePattern.matcher(line); 531 if (matcher.find()) { 532 curUiMode = Integer.parseInt(matcher.group(1), 16); 533 } 534 matcher = sUiModeLockedPattern.matcher(line); 535 if (matcher.find()) { 536 uiModeLocked = matcher.group(1).equals("true"); 537 } 538 } 539 540 boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null) 541 && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked; 542 543 if (uiModeLockedToVrHeadset) { 544 log("UI mode is locked to VR headset"); 545 } 546 547 return uiModeLockedToVrHeadset; 548 } 549 supportsSplitScreenMultiWindow()550 protected boolean supportsSplitScreenMultiWindow() { 551 return ActivityManager.supportsSplitScreenMultiWindow(mContext); 552 } 553 hasHomeScreen()554 protected boolean hasHomeScreen() { 555 if (sHasHomeScreen == null) { 556 sHasHomeScreen = !executeShellCommand(AM_NO_HOME_SCREEN).startsWith("true"); 557 } 558 return sHasHomeScreen; 559 } 560 561 /** 562 * Rotation support is indicated by explicitly having both landscape and portrait 563 * features or not listing either at all. 564 */ supportsRotation()565 protected boolean supportsRotation() { 566 final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE); 567 final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT); 568 return (supportsLandscape && supportsPortrait) 569 || (!supportsLandscape && !supportsPortrait); 570 } 571 hasDeviceFeature(final String requiredFeature)572 protected boolean hasDeviceFeature(final String requiredFeature) { 573 return InstrumentationRegistry.getContext() 574 .getPackageManager() 575 .hasSystemFeature(requiredFeature); 576 } 577 isDisplayOn()578 protected boolean isDisplayOn() { 579 final String output = executeShellCommand("dumpsys power"); 580 final Matcher matcher = sDisplayStatePattern.matcher(output); 581 if (matcher.find()) { 582 final String state = matcher.group(1); 583 log("power state=" + state); 584 return "ON".equals(state); 585 } 586 logAlways("power state :("); 587 return false; 588 } 589 590 /** 591 * Test @Rule class that disables screen doze settings before each test method running and 592 * restoring to initial values after test method finished. 593 */ 594 protected static class DisableScreenDozeRule implements TestRule { 595 596 /** Copied from android.provider.Settings.Secure since these keys are hiden. */ 597 private static final String[] DOZE_SETTINGS = { 598 "doze_enabled", 599 "doze_always_on", 600 "doze_pulse_on_pick_up", 601 "doze_pulse_on_long_press", 602 "doze_pulse_on_double_tap" 603 }; 604 get(String key)605 private String get(String key) { 606 return executeShellCommand("settings get secure " + key).trim(); 607 } 608 put(String key, String value)609 private void put(String key, String value) { 610 executeShellCommand("settings put secure " + key + " " + value); 611 } 612 613 @Override apply(final Statement base, final Description description)614 public Statement apply(final Statement base, final Description description) { 615 return new Statement() { 616 @Override 617 public void evaluate() throws Throwable { 618 final Map<String, String> initialValues = new HashMap<>(); 619 Arrays.stream(DOZE_SETTINGS).forEach(k -> initialValues.put(k, get(k))); 620 try { 621 Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, "0")); 622 base.evaluate(); 623 } finally { 624 Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, initialValues.get(k))); 625 } 626 } 627 }; 628 } 629 } 630 631 protected class LockScreenSession implements AutoCloseable { 632 private static final boolean DEBUG = false; 633 634 private final boolean mIsLockDisabled; 635 private boolean mLockCredentialSet; 636 637 public LockScreenSession() { 638 mIsLockDisabled = isLockDisabled(); 639 mLockCredentialSet = false; 640 // Enable lock screen (swipe) by default. 641 setLockDisabled(false); 642 } 643 644 public LockScreenSession setLockCredential() { 645 mLockCredentialSet = true; 646 runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL); 647 return this; 648 } 649 650 public LockScreenSession enterAndConfirmLockCredential() { 651 waitForDeviceIdle(3000); 652 653 runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL); 654 pressEnterButton(); 655 return this; 656 } 657 658 private void removeLockCredential() { 659 runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL); 660 mLockCredentialSet = false; 661 } 662 663 LockScreenSession disableLockScreen() { 664 setLockDisabled(true); 665 return this; 666 } 667 668 LockScreenSession sleepDevice() { 669 pressSleepButton(); 670 // Not all device variants lock when we go to sleep, so we need to explicitly lock the 671 // device. Note that pressSleepButton() above is redundant because the action also 672 // puts the device to sleep, but kept around for clarity. 673 InstrumentationRegistry.getInstrumentation().getUiAutomation().performGlobalAction( 674 AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN); 675 for (int retry = 1; isDisplayOn() && retry <= 5; retry++) { 676 logAlways("***Waiting for display to turn off... retry=" + retry); 677 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); 678 } 679 return this; 680 } 681 682 LockScreenSession wakeUpDevice() { 683 pressWakeupButton(); 684 return this; 685 } 686 687 LockScreenSession unlockDevice() { 688 pressUnlockButton(); 689 return this; 690 } 691 692 public LockScreenSession gotoKeyguard() { 693 if (DEBUG && isLockDisabled()) { 694 logE("LockScreenSession.gotoKeyguard() is called without lock enabled."); 695 } 696 sleepDevice(); 697 wakeUpDevice(); 698 mAmWmState.waitForKeyguardShowingAndNotOccluded(); 699 return this; 700 } 701 702 @Override 703 public void close() throws Exception { 704 setLockDisabled(mIsLockDisabled); 705 if (mLockCredentialSet) { 706 removeLockCredential(); 707 } 708 709 // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for 710 // the stale credential. 711 pressBackButton(); 712 sleepDevice(); 713 wakeUpDevice(); 714 unlockDevice(); 715 } 716 717 /** 718 * Returns whether the lock screen is disabled. 719 * 720 * @return true if the lock screen is disabled, false otherwise. 721 */ 722 private boolean isLockDisabled() { 723 final String isLockDisabled = runCommandAndPrintOutput( 724 "locksettings get-disabled").trim(); 725 return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled); 726 } 727 728 /** 729 * Disable the lock screen. 730 * 731 * @param lockDisabled true if should disable, false otherwise. 732 */ 733 protected void setLockDisabled(boolean lockDisabled) { 734 runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled); 735 } 736 } 737 738 /** Helper class to save, set & wait, and restore rotation related preferences. */ 739 protected class RotationSession extends SettingsSession<Integer> { 740 private final SettingsSession<Integer> mUserRotation; 741 742 public RotationSession() throws Exception { 743 // Save accelerometer_rotation preference. 744 super(Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), 745 Settings.System::getInt, Settings.System::putInt); 746 mUserRotation = new SettingsSession<>( 747 Settings.System.getUriFor(Settings.System.USER_ROTATION), 748 Settings.System::getInt, Settings.System::putInt); 749 // Disable accelerometer_rotation. 750 super.set(0); 751 } 752 753 @Override 754 public void set(@NonNull Integer value) throws Exception { 755 mUserRotation.set(value); 756 // Wait for settling rotation. 757 mAmWmState.waitForRotation(value); 758 } 759 760 @Override 761 public void close() throws Exception { 762 mUserRotation.close(); 763 // Restore accelerometer_rotation preference. 764 super.close(); 765 } 766 } 767 768 protected int getDeviceRotation(int displayId) { 769 final String displays = runCommandAndPrintOutput("dumpsys display displays").trim(); 770 Pattern pattern = Pattern.compile( 771 "(mDisplayId=" + displayId + ")([\\s\\S]*)(mOverrideDisplayInfo)(.*)" 772 + "(rotation)(\\s+)(\\d+)"); 773 Matcher matcher = pattern.matcher(displays); 774 if (matcher.find()) { 775 final String match = matcher.group(7); 776 return Integer.parseInt(match); 777 } 778 779 return INVALID_DEVICE_ROTATION; 780 } 781 782 protected static String runCommandAndPrintOutput(String command) { 783 final String output = executeShellCommand(command); 784 log(output); 785 return output; 786 } 787 788 protected static class LogSeparator { 789 private final String mUniqueString; 790 791 private LogSeparator() { 792 mUniqueString = UUID.randomUUID().toString(); 793 } 794 795 @Override 796 public String toString() { 797 return mUniqueString; 798 } 799 } 800 801 /** 802 * Inserts a log separator so we can always find the starting point from where to evaluate 803 * following logs. 804 * @return Unique log separator. 805 */ 806 protected LogSeparator separateLogs() { 807 final LogSeparator logSeparator = new LogSeparator(); 808 executeShellCommand("log -t " + LOG_SEPARATOR + " " + logSeparator); 809 return logSeparator; 810 } 811 812 protected static String[] getDeviceLogsForComponents( 813 LogSeparator logSeparator, String... logTags) { 814 String filters = LOG_SEPARATOR + ":I "; 815 for (String component : logTags) { 816 filters += component + ":I "; 817 } 818 final String[] result = executeShellCommand("logcat -v brief -d " + filters + " *:S") 819 .split("\\n"); 820 if (logSeparator == null) { 821 return result; 822 } 823 824 // Make sure that we only check logs after the separator. 825 int i = 0; 826 boolean lookingForSeparator = true; 827 while (i < result.length && lookingForSeparator) { 828 if (result[i].contains(logSeparator.toString())) { 829 lookingForSeparator = false; 830 } 831 i++; 832 } 833 final String[] filteredResult = new String[result.length - i]; 834 for (int curPos = 0; i < result.length; curPos++, i++) { 835 filteredResult[curPos] = result[i]; 836 } 837 return filteredResult; 838 } 839 840 /** 841 * Base helper class for retrying validator success. 842 */ 843 private abstract static class RetryValidator { 844 845 private static final int RETRY_LIMIT = 5; 846 private static final long RETRY_INTERVAL = TimeUnit.SECONDS.toMillis(1); 847 848 /** 849 * @return Error string if validation is failed, null if everything is fine. 850 **/ 851 @Nullable 852 protected abstract String validate(); 853 854 /** 855 * Executes {@link #validate()}. Retries {@link #RETRY_LIMIT} times with 856 * {@link #RETRY_INTERVAL} interval. 857 * 858 * @param waitingMessage logging message while waiting validation. 859 */ 860 void assertValidator(String waitingMessage) { 861 String resultString = null; 862 for (int retry = 1; retry <= RETRY_LIMIT; retry++) { 863 resultString = validate(); 864 if (resultString == null) { 865 return; 866 } 867 logAlways(waitingMessage + ": " + resultString); 868 SystemClock.sleep(RETRY_INTERVAL); 869 } 870 fail(resultString); 871 } 872 } 873 874 private static class ActivityLifecycleCountsValidator extends RetryValidator { 875 private final ComponentName mActivityName; 876 private final LogSeparator mLogSeparator; 877 private final int mCreateCount; 878 private final int mStartCount; 879 private final int mResumeCount; 880 private final int mPauseCount; 881 private final int mStopCount; 882 private final int mDestroyCount; 883 884 ActivityLifecycleCountsValidator(ComponentName activityName, LogSeparator logSeparator, 885 int createCount, int startCount, int resumeCount, int pauseCount, int stopCount, 886 int destroyCount) { 887 mActivityName = activityName; 888 mLogSeparator = logSeparator; 889 mCreateCount = createCount; 890 mStartCount = startCount; 891 mResumeCount = resumeCount; 892 mPauseCount = pauseCount; 893 mStopCount = stopCount; 894 mDestroyCount = destroyCount; 895 } 896 897 @Override 898 @Nullable 899 protected String validate() { 900 final ActivityLifecycleCounts lifecycleCounts = new ActivityLifecycleCounts( 901 mActivityName, mLogSeparator); 902 if (lifecycleCounts.mCreateCount == mCreateCount 903 && lifecycleCounts.mStartCount == mStartCount 904 && lifecycleCounts.mResumeCount == mResumeCount 905 && lifecycleCounts.mPauseCount == mPauseCount 906 && lifecycleCounts.mStopCount == mStopCount 907 && lifecycleCounts.mDestroyCount == mDestroyCount) { 908 return null; 909 } 910 final String expected = IntStream.of(mCreateCount, mStopCount, mResumeCount, 911 mPauseCount, mStopCount, mDestroyCount) 912 .mapToObj(Integer::toString) 913 .collect(Collectors.joining("/")); 914 return getActivityName(mActivityName) + " lifecycle count mismatched:" 915 + " expected=" + expected 916 + " actual=" + lifecycleCounts.counters(); 917 } 918 } 919 920 void assertActivityLifecycle(ComponentName activityName, boolean relaunched, 921 LogSeparator logSeparator) { 922 new RetryValidator() { 923 924 @Nullable 925 @Override 926 protected String validate() { 927 final ActivityLifecycleCounts lifecycleCounts = 928 new ActivityLifecycleCounts(activityName, logSeparator); 929 final String logTag = getLogTag(activityName); 930 if (relaunched) { 931 if (lifecycleCounts.mDestroyCount < 1) { 932 return logTag + " must have been destroyed. mDestroyCount=" 933 + lifecycleCounts.mDestroyCount; 934 } 935 if (lifecycleCounts.mCreateCount < 1) { 936 return logTag + " must have been (re)created. mCreateCount=" 937 + lifecycleCounts.mCreateCount; 938 } 939 return null; 940 } 941 if (lifecycleCounts.mDestroyCount > 0) { 942 return logTag + " must *NOT* have been destroyed. mDestroyCount=" 943 + lifecycleCounts.mDestroyCount; 944 } 945 if (lifecycleCounts.mCreateCount > 0) { 946 return logTag + " must *NOT* have been (re)created. mCreateCount=" 947 + lifecycleCounts.mCreateCount; 948 } 949 if (lifecycleCounts.mConfigurationChangedCount < 1) { 950 return logTag + " must have received configuration changed. " 951 + "mConfigurationChangedCount=" 952 + lifecycleCounts.mConfigurationChangedCount; 953 } 954 return null; 955 } 956 }.assertValidator("***Waiting for valid lifecycle state"); 957 } 958 959 protected void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch, 960 int numConfigChange, LogSeparator logSeparator) { 961 new RetryValidator() { 962 963 @Nullable 964 @Override 965 protected String validate() { 966 final ActivityLifecycleCounts lifecycleCounts = 967 new ActivityLifecycleCounts(activityName, logSeparator); 968 final String logTag = getLogTag(activityName); 969 if (lifecycleCounts.mDestroyCount != numRelaunch) { 970 return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount 971 + " time(s), expecting " + numRelaunch; 972 } else if (lifecycleCounts.mCreateCount != numRelaunch) { 973 return logTag + " has been (re)created " + lifecycleCounts.mCreateCount 974 + " time(s), expecting " + numRelaunch; 975 } else if (lifecycleCounts.mConfigurationChangedCount != numConfigChange) { 976 return logTag + " has received " 977 + lifecycleCounts.mConfigurationChangedCount 978 + " onConfigurationChanged() calls, expecting " + numConfigChange; 979 } 980 return null; 981 } 982 }.assertValidator("***Waiting for relaunch or config changed"); 983 } 984 985 protected void assertActivityDestroyed(ComponentName activityName, LogSeparator logSeparator) { 986 new RetryValidator() { 987 988 @Nullable 989 @Override 990 protected String validate() { 991 final ActivityLifecycleCounts lifecycleCounts = 992 new ActivityLifecycleCounts(activityName, logSeparator); 993 final String logTag = getLogTag(activityName); 994 if (lifecycleCounts.mDestroyCount != 1) { 995 return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount 996 + " time(s), expecting single destruction."; 997 } 998 if (lifecycleCounts.mCreateCount != 0) { 999 return logTag + " has been (re)created " + lifecycleCounts.mCreateCount 1000 + " time(s), not expecting any."; 1001 } 1002 if (lifecycleCounts.mConfigurationChangedCount != 0) { 1003 return logTag + " has received " + lifecycleCounts.mConfigurationChangedCount 1004 + " onConfigurationChanged() calls, not expecting any."; 1005 } 1006 return null; 1007 } 1008 }.assertValidator("***Waiting for activity destroyed"); 1009 } 1010 1011 void assertLifecycleCounts(ComponentName activityName, LogSeparator logSeparator, 1012 int createCount, int startCount, int resumeCount, int pauseCount, int stopCount, 1013 int destroyCount, int configurationChangeCount) { 1014 new RetryValidator() { 1015 @Override 1016 protected String validate() { 1017 final ActivityLifecycleCounts lifecycleCounts = 1018 new ActivityLifecycleCounts(activityName, logSeparator); 1019 final String logTag = getLogTag(activityName); 1020 if (createCount != lifecycleCounts.mCreateCount) { 1021 return logTag + " has been created " + lifecycleCounts.mCreateCount 1022 + " time(s), expecting " + createCount; 1023 } 1024 if (startCount != lifecycleCounts.mStartCount) { 1025 return logTag + " has been started " + lifecycleCounts.mStartCount 1026 + " time(s), expecting " + startCount; 1027 } 1028 if (resumeCount != lifecycleCounts.mResumeCount) { 1029 return logTag + " has been resumed " + lifecycleCounts.mResumeCount 1030 + " time(s), expecting " + resumeCount; 1031 } 1032 if (pauseCount != lifecycleCounts.mPauseCount) { 1033 return logTag + " has been paused " + lifecycleCounts.mPauseCount 1034 + " time(s), expecting " + pauseCount; 1035 } 1036 if (stopCount != lifecycleCounts.mStopCount) { 1037 return logTag + " has been stopped " + lifecycleCounts.mStopCount 1038 + " time(s), expecting " + stopCount; 1039 } 1040 if (destroyCount != lifecycleCounts.mDestroyCount) { 1041 return logTag + " has been destroyed " + lifecycleCounts.mDestroyCount 1042 + " time(s), expecting " + destroyCount; 1043 } 1044 if (configurationChangeCount != lifecycleCounts.mConfigurationChangedCount) { 1045 return logTag + " has received config changes " 1046 + lifecycleCounts.mConfigurationChangedCount 1047 + " time(s), expecting " + configurationChangeCount; 1048 } 1049 return null; 1050 } 1051 }.assertValidator("***Waiting for activity lifecycle counts"); 1052 } 1053 1054 void assertSingleLaunch(ComponentName activityName, LogSeparator logSeparator) { 1055 new ActivityLifecycleCountsValidator(activityName, logSeparator, 1 /* createCount */, 1056 1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */, 1057 0 /* destroyCount */) 1058 .assertValidator("***Waiting for activity create, start, and resume"); 1059 } 1060 1061 void assertSingleLaunchAndStop(ComponentName activityName, LogSeparator logSeparator) { 1062 new ActivityLifecycleCountsValidator(activityName, logSeparator, 1 /* createCount */, 1063 1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */, 1064 0 /* destroyCount */) 1065 .assertValidator("***Waiting for activity create, start, resume, pause, and stop"); 1066 } 1067 1068 void assertSingleStartAndStop(ComponentName activityName, LogSeparator logSeparator) { 1069 new ActivityLifecycleCountsValidator(activityName, logSeparator, 0 /* createCount */, 1070 1 /* startCount */, 1 /* resumeCount */, 1 /* pauseCount */, 1 /* stopCount */, 1071 0 /* destroyCount */) 1072 .assertValidator("***Waiting for activity start, resume, pause, and stop"); 1073 } 1074 1075 void assertSingleStart(ComponentName activityName, LogSeparator logSeparator) { 1076 new ActivityLifecycleCountsValidator(activityName, logSeparator, 0 /* createCount */, 1077 1 /* startCount */, 1 /* resumeCount */, 0 /* pauseCount */, 0 /* stopCount */, 1078 0 /* destroyCount */) 1079 .assertValidator("***Waiting for activity start and resume"); 1080 } 1081 1082 // TODO: Now that our test are device side, we can convert these to a more direct communication 1083 // channel vs. depending on logs. 1084 private static final Pattern sCreatePattern = Pattern.compile("(.+): onCreate"); 1085 private static final Pattern sStartPattern = Pattern.compile("(.+): onStart"); 1086 private static final Pattern sResumePattern = Pattern.compile("(.+): onResume"); 1087 private static final Pattern sPausePattern = Pattern.compile("(.+): onPause"); 1088 private static final Pattern sConfigurationChangedPattern = 1089 Pattern.compile("(.+): onConfigurationChanged"); 1090 private static final Pattern sMovedToDisplayPattern = 1091 Pattern.compile("(.+): onMovedToDisplay"); 1092 private static final Pattern sStopPattern = Pattern.compile("(.+): onStop"); 1093 private static final Pattern sDestroyPattern = Pattern.compile("(.+): onDestroy"); 1094 private static final Pattern sMultiWindowModeChangedPattern = 1095 Pattern.compile("(.+): onMultiWindowModeChanged"); 1096 private static final Pattern sPictureInPictureModeChangedPattern = 1097 Pattern.compile("(.+): onPictureInPictureModeChanged"); 1098 private static final Pattern sUserLeaveHintPattern = Pattern.compile("(.+): onUserLeaveHint"); 1099 private static final Pattern sNewConfigPattern = Pattern.compile( 1100 "(.+): config size=\\((\\d+),(\\d+)\\) displaySize=\\((\\d+),(\\d+)\\)" 1101 + " metricsSize=\\((\\d+),(\\d+)\\) smallestScreenWidth=(\\d+) densityDpi=(\\d+)" 1102 + " orientation=(\\d+)"); 1103 private static final Pattern sDisplayStatePattern = 1104 Pattern.compile("Display Power: state=(.+)"); 1105 private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)"); 1106 private static final Pattern sUiModeLockedPattern = 1107 Pattern.compile("mUiModeLocked=(true|false)"); 1108 1109 static class ReportedSizes { 1110 int widthDp; 1111 int heightDp; 1112 int displayWidth; 1113 int displayHeight; 1114 int metricsWidth; 1115 int metricsHeight; 1116 int smallestWidthDp; 1117 int densityDpi; 1118 int orientation; 1119 1120 @Override 1121 public String toString() { 1122 return "ReportedSizes: {widthDp=" + widthDp + " heightDp=" + heightDp 1123 + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight 1124 + " metricsWidth=" + metricsWidth + " metricsHeight=" + metricsHeight 1125 + " smallestWidthDp=" + smallestWidthDp + " densityDpi=" + densityDpi 1126 + " orientation=" + orientation + "}"; 1127 } 1128 1129 @Override 1130 public boolean equals(Object obj) { 1131 if ( this == obj ) return true; 1132 if ( !(obj instanceof ReportedSizes) ) return false; 1133 ReportedSizes that = (ReportedSizes) obj; 1134 return widthDp == that.widthDp 1135 && heightDp == that.heightDp 1136 && displayWidth == that.displayWidth 1137 && displayHeight == that.displayHeight 1138 && metricsWidth == that.metricsWidth 1139 && metricsHeight == that.metricsHeight 1140 && smallestWidthDp == that.smallestWidthDp 1141 && densityDpi == that.densityDpi 1142 && orientation == that.orientation; 1143 } 1144 } 1145 1146 @Nullable 1147 ReportedSizes getLastReportedSizesForActivity( 1148 ComponentName activityName, LogSeparator logSeparator) { 1149 final String logTag = getLogTag(activityName); 1150 for (int retry = 1; retry <= 5; retry++ ) { 1151 final ReportedSizes result = readLastReportedSizes(logSeparator, logTag); 1152 if (result != null) { 1153 return result; 1154 } 1155 logAlways("***Waiting for sizes to be reported... retry=" + retry); 1156 SystemClock.sleep(1000); 1157 } 1158 logE("***Waiting for activity size failed: activityName=" + logTag); 1159 return null; 1160 } 1161 1162 private ReportedSizes readLastReportedSizes(LogSeparator logSeparator, String logTag) { 1163 final String[] lines = getDeviceLogsForComponents(logSeparator, logTag); 1164 for (int i = lines.length - 1; i >= 0; i--) { 1165 final String line = lines[i].trim(); 1166 final Matcher matcher = sNewConfigPattern.matcher(line); 1167 if (matcher.matches()) { 1168 ReportedSizes details = new ReportedSizes(); 1169 details.widthDp = Integer.parseInt(matcher.group(2)); 1170 details.heightDp = Integer.parseInt(matcher.group(3)); 1171 details.displayWidth = Integer.parseInt(matcher.group(4)); 1172 details.displayHeight = Integer.parseInt(matcher.group(5)); 1173 details.metricsWidth = Integer.parseInt(matcher.group(6)); 1174 details.metricsHeight = Integer.parseInt(matcher.group(7)); 1175 details.smallestWidthDp = Integer.parseInt(matcher.group(8)); 1176 details.densityDpi = Integer.parseInt(matcher.group(9)); 1177 details.orientation = Integer.parseInt(matcher.group(10)); 1178 return details; 1179 } 1180 } 1181 return null; 1182 } 1183 1184 /** Waits for at least one onMultiWindowModeChanged event. */ 1185 ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName, 1186 LogSeparator logSeparator) { 1187 int retry = 1; 1188 ActivityLifecycleCounts result; 1189 do { 1190 result = new ActivityLifecycleCounts(activityName, logSeparator); 1191 if (result.mMultiWindowModeChangedCount >= 1) { 1192 return result; 1193 } 1194 logAlways("***waitForOnMultiWindowModeChanged... retry=" + retry); 1195 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); 1196 } while (retry++ <= 5); 1197 return result; 1198 } 1199 1200 // TODO: Now that our test are device side, we can convert these to a more direct communication 1201 // channel vs. depending on logs. 1202 static class ActivityLifecycleCounts { 1203 int mCreateCount; 1204 int mStartCount; 1205 int mResumeCount; 1206 int mConfigurationChangedCount; 1207 int mLastConfigurationChangedLineIndex; 1208 int mMovedToDisplayCount; 1209 int mMultiWindowModeChangedCount; 1210 int mLastMultiWindowModeChangedLineIndex; 1211 int mPictureInPictureModeChangedCount; 1212 int mLastPictureInPictureModeChangedLineIndex; 1213 int mUserLeaveHintCount; 1214 int mPauseCount; 1215 int mStopCount; 1216 int mLastStopLineIndex; 1217 int mDestroyCount; 1218 1219 ActivityLifecycleCounts(ComponentName componentName, LogSeparator logSeparator) { 1220 int lineIndex = 0; 1221 waitForIdle(); 1222 for (String line : getDeviceLogsForComponents(logSeparator, getLogTag(componentName))) { 1223 line = line.trim(); 1224 lineIndex++; 1225 1226 Matcher matcher = sCreatePattern.matcher(line); 1227 if (matcher.matches()) { 1228 mCreateCount++; 1229 continue; 1230 } 1231 1232 matcher = sStartPattern.matcher(line); 1233 if (matcher.matches()) { 1234 mStartCount++; 1235 continue; 1236 } 1237 1238 matcher = sResumePattern.matcher(line); 1239 if (matcher.matches()) { 1240 mResumeCount++; 1241 continue; 1242 } 1243 1244 matcher = sConfigurationChangedPattern.matcher(line); 1245 if (matcher.matches()) { 1246 mConfigurationChangedCount++; 1247 mLastConfigurationChangedLineIndex = lineIndex; 1248 continue; 1249 } 1250 1251 matcher = sMovedToDisplayPattern.matcher(line); 1252 if (matcher.matches()) { 1253 mMovedToDisplayCount++; 1254 continue; 1255 } 1256 1257 matcher = sMultiWindowModeChangedPattern.matcher(line); 1258 if (matcher.matches()) { 1259 mMultiWindowModeChangedCount++; 1260 mLastMultiWindowModeChangedLineIndex = lineIndex; 1261 continue; 1262 } 1263 1264 matcher = sPictureInPictureModeChangedPattern.matcher(line); 1265 if (matcher.matches()) { 1266 mPictureInPictureModeChangedCount++; 1267 mLastPictureInPictureModeChangedLineIndex = lineIndex; 1268 continue; 1269 } 1270 1271 matcher = sUserLeaveHintPattern.matcher(line); 1272 if (matcher.matches()) { 1273 mUserLeaveHintCount++; 1274 continue; 1275 } 1276 1277 matcher = sPausePattern.matcher(line); 1278 if (matcher.matches()) { 1279 mPauseCount++; 1280 continue; 1281 } 1282 1283 matcher = sStopPattern.matcher(line); 1284 if (matcher.matches()) { 1285 mStopCount++; 1286 mLastStopLineIndex = lineIndex; 1287 continue; 1288 } 1289 1290 matcher = sDestroyPattern.matcher(line); 1291 if (matcher.matches()) { 1292 mDestroyCount++; 1293 continue; 1294 } 1295 } 1296 } 1297 1298 String counters() { 1299 return IntStream.of(mCreateCount, mStartCount, mResumeCount, mPauseCount, mStopCount, 1300 mDestroyCount) 1301 .mapToObj(Integer::toString) 1302 .collect(Collectors.joining("/")); 1303 } 1304 } 1305 1306 protected void stopTestPackage(final ComponentName activityName) { 1307 executeShellCommand("am force-stop " + activityName.getPackageName()); 1308 } 1309 1310 protected LaunchActivityBuilder getLaunchActivityBuilder() { 1311 return new LaunchActivityBuilder(mAmWmState); 1312 } 1313 1314 protected static class LaunchActivityBuilder { 1315 private final ActivityAndWindowManagersState mAmWmState; 1316 1317 // The activity to be launched 1318 private ComponentName mTargetActivity = TEST_ACTIVITY; 1319 private boolean mUseApplicationContext; 1320 private boolean mToSide; 1321 private boolean mRandomData; 1322 private boolean mNewTask; 1323 private boolean mMultipleTask; 1324 private int mDisplayId = INVALID_DISPLAY; 1325 // A proxy activity that launches other activities including mTargetActivityName 1326 private ComponentName mLaunchingActivity = LAUNCHING_ACTIVITY; 1327 private boolean mReorderToFront; 1328 private boolean mWaitForLaunched; 1329 private boolean mSuppressExceptions; 1330 // Use of the following variables indicates that a broadcast receiver should be used instead 1331 // of a launching activity; 1332 private ComponentName mBroadcastReceiver; 1333 private String mBroadcastReceiverAction; 1334 1335 private enum LauncherType { 1336 INSTRUMENTATION, LAUNCHING_ACTIVITY, BROADCAST_RECEIVER 1337 } 1338 private LauncherType mLauncherType = LauncherType.LAUNCHING_ACTIVITY; 1339 1340 public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState) { 1341 mAmWmState = amWmState; 1342 mWaitForLaunched = true; 1343 } 1344 1345 public LaunchActivityBuilder setToSide(boolean toSide) { 1346 mToSide = toSide; 1347 return this; 1348 } 1349 1350 public LaunchActivityBuilder setRandomData(boolean randomData) { 1351 mRandomData = randomData; 1352 return this; 1353 } 1354 1355 public LaunchActivityBuilder setNewTask(boolean newTask) { 1356 mNewTask = newTask; 1357 return this; 1358 } 1359 1360 public LaunchActivityBuilder setMultipleTask(boolean multipleTask) { 1361 mMultipleTask = multipleTask; 1362 return this; 1363 } 1364 1365 public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) { 1366 mReorderToFront = reorderToFront; 1367 return this; 1368 } 1369 1370 public LaunchActivityBuilder setUseApplicationContext(boolean useApplicationContext) { 1371 mUseApplicationContext = useApplicationContext; 1372 return this; 1373 } 1374 1375 public ComponentName getTargetActivity() { 1376 return mTargetActivity; 1377 } 1378 1379 public boolean isTargetActivityTranslucent() { 1380 return mAmWmState.getAmState().isActivityTranslucent(mTargetActivity); 1381 } 1382 1383 public LaunchActivityBuilder setTargetActivity(ComponentName targetActivity) { 1384 mTargetActivity = targetActivity; 1385 return this; 1386 } 1387 1388 public LaunchActivityBuilder setDisplayId(int id) { 1389 mDisplayId = id; 1390 return this; 1391 } 1392 1393 public LaunchActivityBuilder setLaunchingActivity(ComponentName launchingActivity) { 1394 mLaunchingActivity = launchingActivity; 1395 mLauncherType = LauncherType.LAUNCHING_ACTIVITY; 1396 return this; 1397 } 1398 1399 public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) { 1400 mWaitForLaunched = shouldWait; 1401 return this; 1402 } 1403 1404 /** Use broadcast receiver as a launchpad for activities. */ 1405 public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver, 1406 final String broadcastAction) { 1407 mBroadcastReceiver = broadcastReceiver; 1408 mBroadcastReceiverAction = broadcastAction; 1409 mLauncherType = LauncherType.BROADCAST_RECEIVER; 1410 return this; 1411 } 1412 1413 /** Use {@link android.app.Instrumentation} as a launchpad for activities. */ 1414 public LaunchActivityBuilder setUseInstrumentation() { 1415 mLauncherType = LauncherType.INSTRUMENTATION; 1416 // Calling startActivity() from outside of an Activity context requires the 1417 // FLAG_ACTIVITY_NEW_TASK flag. 1418 setNewTask(true); 1419 return this; 1420 } 1421 1422 public LaunchActivityBuilder setSuppressExceptions(boolean suppress) { 1423 mSuppressExceptions = suppress; 1424 return this; 1425 } 1426 1427 public void execute() { 1428 switch (mLauncherType) { 1429 case INSTRUMENTATION: 1430 launchUsingInstrumentation(); 1431 break; 1432 case LAUNCHING_ACTIVITY: 1433 case BROADCAST_RECEIVER: 1434 launchUsingShellCommand(); 1435 } 1436 1437 if (mWaitForLaunched) { 1438 mAmWmState.waitForValidState(mTargetActivity); 1439 } 1440 } 1441 1442 /** Launch an activity using instrumentation. */ 1443 private void launchUsingInstrumentation() { 1444 final Bundle b = new Bundle(); 1445 b.putBoolean(KEY_USE_INSTRUMENTATION, true); 1446 b.putBoolean(KEY_LAUNCH_ACTIVITY, true); 1447 b.putBoolean(KEY_LAUNCH_TO_SIDE, mToSide); 1448 b.putBoolean(KEY_RANDOM_DATA, mRandomData); 1449 b.putBoolean(KEY_NEW_TASK, mNewTask); 1450 b.putBoolean(KEY_MULTIPLE_TASK, mMultipleTask); 1451 b.putBoolean(KEY_REORDER_TO_FRONT, mReorderToFront); 1452 b.putInt(KEY_DISPLAY_ID, mDisplayId); 1453 b.putBoolean(KEY_USE_APPLICATION_CONTEXT, mUseApplicationContext); 1454 b.putString(KEY_TARGET_COMPONENT, getActivityName(mTargetActivity)); 1455 b.putBoolean(KEY_SUPPRESS_EXCEPTIONS, mSuppressExceptions); 1456 final Context context = InstrumentationRegistry.getContext(); 1457 launchActivityFromExtras(context, b); 1458 } 1459 1460 /** Build and execute a shell command to launch an activity. */ 1461 private void launchUsingShellCommand() { 1462 StringBuilder commandBuilder = new StringBuilder(); 1463 if (mBroadcastReceiver != null && mBroadcastReceiverAction != null) { 1464 // Use broadcast receiver to launch the target. 1465 commandBuilder.append("am broadcast -a ").append(mBroadcastReceiverAction) 1466 .append(" -p ").append(mBroadcastReceiver.getPackageName()) 1467 // Include stopped packages 1468 .append(" -f 0x00000020"); 1469 } else { 1470 // Use launching activity to launch the target. 1471 commandBuilder.append(getAmStartCmd(mLaunchingActivity)) 1472 .append(" -f 0x20000020"); 1473 } 1474 1475 // Add a flag to ensure we actually mean to launch an activity. 1476 commandBuilder.append(" --ez " + KEY_LAUNCH_ACTIVITY + " true"); 1477 1478 if (mToSide) { 1479 commandBuilder.append(" --ez " + KEY_LAUNCH_TO_SIDE + " true"); 1480 } 1481 if (mRandomData) { 1482 commandBuilder.append(" --ez " + KEY_RANDOM_DATA + " true"); 1483 } 1484 if (mNewTask) { 1485 commandBuilder.append(" --ez " + KEY_NEW_TASK + " true"); 1486 } 1487 if (mMultipleTask) { 1488 commandBuilder.append(" --ez " + KEY_MULTIPLE_TASK + " true"); 1489 } 1490 if (mReorderToFront) { 1491 commandBuilder.append(" --ez " + KEY_REORDER_TO_FRONT + " true"); 1492 } 1493 if (mDisplayId != INVALID_DISPLAY) { 1494 commandBuilder.append(" --ei " + KEY_DISPLAY_ID + " ").append(mDisplayId); 1495 } 1496 1497 if (mUseApplicationContext) { 1498 commandBuilder.append(" --ez " + KEY_USE_APPLICATION_CONTEXT + " true"); 1499 } 1500 1501 if (mTargetActivity != null) { 1502 // {@link ActivityLauncher} parses this extra string by 1503 // {@link ComponentName#unflattenFromString(String)}. 1504 commandBuilder.append(" --es " + KEY_TARGET_COMPONENT + " ") 1505 .append(getActivityName(mTargetActivity)); 1506 } 1507 1508 if (mSuppressExceptions) { 1509 commandBuilder.append(" --ez " + KEY_SUPPRESS_EXCEPTIONS + " true"); 1510 } 1511 executeShellCommand(commandBuilder.toString()); 1512 } 1513 } 1514 1515 // Activity used in place of recents when home is the recents component. 1516 public static class SideActivity extends Activity { 1517 } 1518 } 1519