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.wm; 18 19 import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; 20 import static android.app.Instrumentation.ActivityMonitor; 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.ACTION_MAIN; 27 import static android.content.Intent.CATEGORY_HOME; 28 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; 29 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 30 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 31 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; 32 import static android.content.pm.PackageManager.DONT_KILL_APP; 33 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; 34 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE; 35 import static android.content.pm.PackageManager.FEATURE_EMBEDDED; 36 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; 37 import static android.content.pm.PackageManager.FEATURE_INPUT_METHODS; 38 import static android.content.pm.PackageManager.FEATURE_LEANBACK; 39 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; 40 import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE; 41 import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT; 42 import static android.content.pm.PackageManager.FEATURE_SECURE_LOCK_SCREEN; 43 import static android.content.pm.PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE; 44 import static android.content.pm.PackageManager.FEATURE_WATCH; 45 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; 46 import static android.server.wm.ActivityLauncher.KEY_ACTIVITY_TYPE; 47 import static android.server.wm.ActivityLauncher.KEY_DISPLAY_ID; 48 import static android.server.wm.ActivityLauncher.KEY_INTENT_EXTRAS; 49 import static android.server.wm.ActivityLauncher.KEY_INTENT_FLAGS; 50 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY; 51 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TASK_BEHIND; 52 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TO_SIDE; 53 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_INSTANCES; 54 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_TASK; 55 import static android.server.wm.ActivityLauncher.KEY_NEW_TASK; 56 import static android.server.wm.ActivityLauncher.KEY_RANDOM_DATA; 57 import static android.server.wm.ActivityLauncher.KEY_REORDER_TO_FRONT; 58 import static android.server.wm.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS; 59 import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT; 60 import static android.server.wm.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT; 61 import static android.server.wm.ActivityLauncher.KEY_WINDOWING_MODE; 62 import static android.server.wm.ActivityLauncher.launchActivityFromExtras; 63 import static android.server.wm.CommandSession.KEY_FORWARD; 64 import static android.server.wm.ComponentNameUtils.getActivityName; 65 import static android.server.wm.ComponentNameUtils.getLogTag; 66 import static android.server.wm.StateLogger.log; 67 import static android.server.wm.StateLogger.logE; 68 import static android.server.wm.UiDeviceUtils.pressAppSwitchButton; 69 import static android.server.wm.UiDeviceUtils.pressBackButton; 70 import static android.server.wm.UiDeviceUtils.pressEnterButton; 71 import static android.server.wm.UiDeviceUtils.pressHomeButton; 72 import static android.server.wm.UiDeviceUtils.pressSleepButton; 73 import static android.server.wm.UiDeviceUtils.pressUnlockButton; 74 import static android.server.wm.UiDeviceUtils.pressWakeupButton; 75 import static android.server.wm.UiDeviceUtils.waitForDeviceIdle; 76 import static android.server.wm.WindowManagerState.STATE_RESUMED; 77 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY; 78 import static android.server.wm.app.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST; 79 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION; 80 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_CUTOUT_EXISTS; 81 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD; 82 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD_METHOD; 83 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST; 84 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK; 85 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY; 86 import static android.server.wm.app.Components.LaunchingActivity.KEY_FINISH_BEFORE_LAUNCH; 87 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP; 88 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION; 89 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION; 90 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR; 91 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR; 92 import static android.server.wm.app.Components.TEST_ACTIVITY; 93 import static android.server.wm.second.Components.SECOND_ACTIVITY; 94 import static android.server.wm.third.Components.THIRD_ACTIVITY; 95 import static android.view.Display.DEFAULT_DISPLAY; 96 import static android.view.Display.INVALID_DISPLAY; 97 import static android.view.Surface.ROTATION_0; 98 99 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 100 101 import static org.junit.Assert.assertEquals; 102 import static org.junit.Assert.assertNotNull; 103 import static org.junit.Assert.assertTrue; 104 import static org.junit.Assert.fail; 105 import static org.junit.Assume.assumeTrue; 106 107 import static java.lang.Integer.toHexString; 108 109 import android.accessibilityservice.AccessibilityService; 110 import android.app.Activity; 111 import android.app.ActivityManager; 112 import android.app.ActivityOptions; 113 import android.app.ActivityTaskManager; 114 import android.app.Instrumentation; 115 import android.content.ComponentName; 116 import android.content.ContentResolver; 117 import android.content.Context; 118 import android.content.Intent; 119 import android.content.pm.PackageManager; 120 import android.content.pm.ResolveInfo; 121 import android.content.res.Resources; 122 import android.database.ContentObserver; 123 import android.graphics.Bitmap; 124 import android.graphics.Rect; 125 import android.hardware.display.AmbientDisplayConfiguration; 126 import android.hardware.display.DisplayManager; 127 import android.os.Bundle; 128 import android.os.Handler; 129 import android.os.HandlerThread; 130 import android.os.SystemClock; 131 import android.provider.Settings; 132 import android.server.wm.CommandSession.ActivityCallback; 133 import android.server.wm.CommandSession.ActivitySession; 134 import android.server.wm.CommandSession.ActivitySessionClient; 135 import android.server.wm.CommandSession.ConfigInfo; 136 import android.server.wm.CommandSession.LaunchInjector; 137 import android.server.wm.CommandSession.LaunchProxy; 138 import android.server.wm.CommandSession.SizeInfo; 139 import android.server.wm.TestJournalProvider.TestJournalContainer; 140 import android.server.wm.settings.SettingsSession; 141 import android.util.EventLog; 142 import android.util.EventLog.Event; 143 import android.view.Display; 144 import android.view.InputDevice; 145 import android.view.KeyEvent; 146 import android.view.MotionEvent; 147 import android.view.ViewConfiguration; 148 149 import androidx.annotation.NonNull; 150 import androidx.annotation.Nullable; 151 152 import com.android.compatibility.common.util.SystemUtil; 153 154 import org.junit.Before; 155 import org.junit.Rule; 156 import org.junit.rules.ErrorCollector; 157 import org.junit.rules.RuleChain; 158 import org.junit.rules.TestRule; 159 import org.junit.runner.Description; 160 import org.junit.runners.model.Statement; 161 162 import java.io.IOException; 163 import java.util.ArrayList; 164 import java.util.Arrays; 165 import java.util.Collections; 166 import java.util.HashMap; 167 import java.util.Iterator; 168 import java.util.List; 169 import java.util.Map; 170 import java.util.UUID; 171 import java.util.concurrent.atomic.AtomicBoolean; 172 import java.util.function.BooleanSupplier; 173 import java.util.function.Consumer; 174 import java.util.regex.Matcher; 175 import java.util.regex.Pattern; 176 177 public abstract class ActivityManagerTestBase { 178 private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false; 179 private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false; 180 private static final String LOG_SEPARATOR = "LOG_SEPARATOR"; 181 // Use one of the test tags as a separator 182 private static final int EVENT_LOG_SEPARATOR_TAG = 42; 183 184 protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = { 185 ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, 186 ACTIVITY_TYPE_UNDEFINED 187 }; 188 189 private static final String TEST_PACKAGE = TEST_ACTIVITY.getPackageName(); 190 private static final String SECOND_TEST_PACKAGE = SECOND_ACTIVITY.getPackageName(); 191 private static final String THIRD_TEST_PACKAGE = THIRD_ACTIVITY.getPackageName(); 192 private static final List<String> TEST_PACKAGES; 193 194 static { 195 final List<String> testPackages = new ArrayList<>(); 196 testPackages.add(TEST_PACKAGE); 197 testPackages.add(SECOND_TEST_PACKAGE); 198 testPackages.add(THIRD_TEST_PACKAGE); 199 testPackages.add("android.server.wm.cts"); 200 testPackages.add("android.server.wm.jetpack"); 201 TEST_PACKAGES = Collections.unmodifiableList(testPackages); 202 } 203 204 protected static final String AM_START_HOME_ACTIVITY_COMMAND = 205 "am start -a android.intent.action.MAIN -c android.intent.category.HOME"; 206 207 protected static final String MSG_NO_MOCK_IME = 208 "MockIme cannot be used for devices that do not support installable IMEs"; 209 210 private static final String LOCK_CREDENTIAL = "1234"; 211 212 private static final int UI_MODE_TYPE_MASK = 0x0f; 213 private static final int UI_MODE_TYPE_VR_HEADSET = 0x07; 214 215 private static Boolean sHasHomeScreen = null; 216 private static Boolean sSupportsSystemDecorsOnSecondaryDisplays = null; 217 private static Boolean sSupportsInsecureLockScreen = null; 218 private static Boolean sIsAssistantOnTop = null; 219 private static boolean sStackTaskLeakFound; 220 221 protected static final int INVALID_DEVICE_ROTATION = -1; 222 223 protected final Instrumentation mInstrumentation = getInstrumentation(); 224 protected final Context mContext = getInstrumentation().getContext(); 225 protected final ActivityManager mAm = mContext.getSystemService(ActivityManager.class); 226 protected final ActivityTaskManager mAtm = mContext.getSystemService(ActivityTaskManager.class); 227 protected final DisplayManager mDm = mContext.getSystemService(DisplayManager.class); 228 229 /** The tracker to manage objects (especially {@link AutoCloseable}) in a test method. */ 230 protected final ObjectTracker mObjectTracker = new ObjectTracker(); 231 232 /** The last rule to handle all errors. */ 233 private final ErrorCollector mPostAssertionRule = new PostAssertionRule(); 234 235 /** The necessary procedures of set up and tear down. */ 236 @Rule 237 public final TestRule mBaseRule = RuleChain.outerRule(mPostAssertionRule) 238 .around(new WrapperRule(null /* before */, this::tearDownBase)); 239 240 /** 241 * @return the am command to start the given activity with the following extra key/value pairs. 242 * {@param keyValuePairs} must be a list of arguments defining each key/value extra. 243 */ 244 // TODO: Make this more generic, for instance accepting flags or extras of other types. getAmStartCmd(final ComponentName activityName, final String... keyValuePairs)245 protected static String getAmStartCmd(final ComponentName activityName, 246 final String... keyValuePairs) { 247 return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs); 248 } 249 getAmStartCmdInternal(final String activityName, final String... keyValuePairs)250 private static String getAmStartCmdInternal(final String activityName, 251 final String... keyValuePairs) { 252 return appendKeyValuePairs( 253 new StringBuilder("am start -n ").append(activityName), 254 keyValuePairs); 255 } 256 appendKeyValuePairs( final StringBuilder cmd, final String... keyValuePairs)257 private static String appendKeyValuePairs( 258 final StringBuilder cmd, final String... keyValuePairs) { 259 if (keyValuePairs.length % 2 != 0) { 260 throw new RuntimeException("keyValuePairs must be pairs of key/value arguments"); 261 } 262 for (int i = 0; i < keyValuePairs.length; i += 2) { 263 final String key = keyValuePairs[i]; 264 final String value = keyValuePairs[i + 1]; 265 cmd.append(" --es ") 266 .append(key) 267 .append(" ") 268 .append(value); 269 } 270 return cmd.toString(); 271 } 272 getAmStartCmd(final ComponentName activityName, final int displayId, final String... keyValuePair)273 protected static String getAmStartCmd(final ComponentName activityName, final int displayId, 274 final String... keyValuePair) { 275 return getAmStartCmdInternal(getActivityName(activityName), displayId, keyValuePair); 276 } 277 getAmStartCmdInternal(final String activityName, final int displayId, final String... keyValuePairs)278 private static String getAmStartCmdInternal(final String activityName, final int displayId, 279 final String... keyValuePairs) { 280 return appendKeyValuePairs( 281 new StringBuilder("am start -n ") 282 .append(activityName) 283 .append(" -f 0x") 284 .append(toHexString(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)) 285 .append(" --display ") 286 .append(displayId), 287 keyValuePairs); 288 } 289 getAmStartCmdInNewTask(final ComponentName activityName)290 protected static String getAmStartCmdInNewTask(final ComponentName activityName) { 291 return "am start -n " + getActivityName(activityName) + " -f 0x18000000"; 292 } 293 getAmStartCmdOverHome(final ComponentName activityName)294 protected static String getAmStartCmdOverHome(final ComponentName activityName) { 295 return "am start --activity-task-on-home -n " + getActivityName(activityName); 296 } 297 298 protected WindowManagerStateHelper mWmState = new WindowManagerStateHelper(); 299 TestTaskOrganizer mTaskOrganizer = new TestTaskOrganizer(); 300 // If the specific test should run using the task organizer or older API. 301 // TODO(b/149338177): Fix all places setting this to fail to be able to use organizer API. 302 public boolean mUseTaskOrganizer = true; 303 getWmState()304 public WindowManagerStateHelper getWmState() { 305 return mWmState; 306 } 307 308 protected BroadcastActionTrigger mBroadcastActionTrigger = new BroadcastActionTrigger(); 309 310 /** 311 * Returns true if the activity is shown before timeout. 312 */ waitForActivityFocused(int timeoutMs, ComponentName componentName)313 protected boolean waitForActivityFocused(int timeoutMs, ComponentName componentName) { 314 long endTime = System.currentTimeMillis() + timeoutMs; 315 while (endTime > System.currentTimeMillis()) { 316 mWmState.computeState(); 317 if (mWmState.hasActivityState(componentName, STATE_RESUMED)) { 318 SystemClock.sleep(200); 319 mWmState.computeState(); 320 break; 321 } 322 SystemClock.sleep(200); 323 mWmState.computeState(); 324 } 325 return getActivityName(componentName).equals(mWmState.getFocusedActivity()); 326 } 327 328 /** 329 * Helper class to process test actions by broadcast. 330 */ 331 protected class BroadcastActionTrigger { 332 createIntentWithAction(String broadcastAction)333 private Intent createIntentWithAction(String broadcastAction) { 334 return new Intent(broadcastAction) 335 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 336 } 337 doAction(String broadcastAction)338 void doAction(String broadcastAction) { 339 mContext.sendBroadcast(createIntentWithAction(broadcastAction)); 340 } 341 finishBroadcastReceiverActivity()342 void finishBroadcastReceiverActivity() { 343 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 344 .putExtra(EXTRA_FINISH_BROADCAST, true)); 345 } 346 launchActivityNewTask(String launchComponent)347 void launchActivityNewTask(String launchComponent) { 348 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 349 .putExtra(KEY_LAUNCH_ACTIVITY, true) 350 .putExtra(KEY_NEW_TASK, true) 351 .putExtra(KEY_TARGET_COMPONENT, launchComponent)); 352 } 353 moveTopTaskToBack()354 void moveTopTaskToBack() { 355 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 356 .putExtra(EXTRA_MOVE_BROADCAST_TO_BACK, true)); 357 } 358 requestOrientation(int orientation)359 void requestOrientation(int orientation) { 360 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 361 .putExtra(EXTRA_BROADCAST_ORIENTATION, orientation)); 362 } 363 dismissKeyguardByFlag()364 void dismissKeyguardByFlag() { 365 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 366 .putExtra(EXTRA_DISMISS_KEYGUARD, true)); 367 } 368 dismissKeyguardByMethod()369 void dismissKeyguardByMethod() { 370 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 371 .putExtra(EXTRA_DISMISS_KEYGUARD_METHOD, true)); 372 } 373 expandPipWithAspectRatio(String extraNum, String extraDenom)374 void expandPipWithAspectRatio(String extraNum, String extraDenom) { 375 mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP) 376 .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR, extraNum) 377 .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR, extraDenom)); 378 } 379 requestOrientationForPip(int orientation)380 void requestOrientationForPip(int orientation) { 381 mContext.sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION) 382 .putExtra(EXTRA_PIP_ORIENTATION, String.valueOf(orientation))); 383 } 384 } 385 386 /** 387 * Helper class to launch / close test activity by instrumentation way. 388 */ 389 protected class TestActivitySession<T extends Activity> implements AutoCloseable { 390 private T mTestActivity; 391 boolean mFinishAfterClose; 392 private static final int ACTIVITY_LAUNCH_TIMEOUT = 10000; 393 private static final int WAIT_SLICE = 50; 394 395 /** 396 * Launches an {@link Activity} on a target display synchronously. 397 * @param activityClass The {@link Activity} class to be launched 398 * @param displayId ID of the target display 399 */ launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId)400 void launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId) { 401 final Intent intent = new Intent(mContext, activityClass) 402 .addFlags(FLAG_ACTIVITY_NEW_TASK); 403 final String className = intent.getComponent().getClassName(); 404 launchTestActivityOnDisplaySync(className, intent, displayId); 405 } 406 407 /** 408 * Launches an {@link Activity} synchronously on a target display. The class name needs to 409 * be provided either implicitly through the {@link Intent} or explicitly as a parameter 410 * 411 * @param className Optional class name of expected activity 412 * @param intent Intent to launch an activity 413 * @param displayId ID for the target display 414 */ launchTestActivityOnDisplaySync(@ullable String className, Intent intent, int displayId)415 void launchTestActivityOnDisplaySync(@Nullable String className, Intent intent, 416 int displayId) { 417 SystemUtil.runWithShellPermissionIdentity(() -> { 418 mTestActivity = launchActivityOnDisplay(className, intent, displayId); 419 // Check activity is launched and resumed. 420 final ComponentName testActivityName = mTestActivity.getComponentName(); 421 waitAndAssertTopResumedActivity(testActivityName, displayId, 422 "Activity must be resumed"); 423 }); 424 } 425 426 /** 427 * Launches an {@link Activity} on a target display asynchronously. 428 * @param activityClass The {@link Activity} class to be launched 429 * @param displayId ID of the target display 430 */ launchTestActivityOnDisplay(Class<T> activityClass, int displayId)431 void launchTestActivityOnDisplay(Class<T> activityClass, int displayId) { 432 final Intent intent = new Intent(mContext, activityClass) 433 .addFlags(FLAG_ACTIVITY_NEW_TASK); 434 final String className = intent.getComponent().getClassName(); 435 SystemUtil.runWithShellPermissionIdentity(() -> { 436 mTestActivity = launchActivityOnDisplay(className, intent, displayId); 437 assertNotNull(mTestActivity); 438 }); 439 } 440 441 /** 442 * Launches an {@link Activity} on a target display. In order to return the correct activity 443 * the class name or an explicit {@link Intent} must be provided. 444 * 445 * @param className Optional class name of expected activity 446 * @param intent {@link Intent} to launch an activity 447 * @param displayId ID for the target display 448 * @return The {@link Activity} that was launched 449 */ launchActivityOnDisplay(@ullable String className, Intent intent, int displayId)450 private T launchActivityOnDisplay(@Nullable String className, Intent intent, 451 int displayId) { 452 final String localClassName = className != null ? className : 453 (intent.getComponent() != null ? intent.getComponent().getClassName() : null); 454 if (localClassName == null || localClassName.isEmpty()) { 455 fail("Must provide either a class name or an intent with a component"); 456 } 457 final Bundle bundle = ActivityOptions.makeBasic() 458 .setLaunchDisplayId(displayId).toBundle(); 459 final ActivityMonitor monitor = mInstrumentation.addMonitor(localClassName, null, 460 false); 461 mContext.startActivity(intent.addFlags(FLAG_ACTIVITY_NEW_TASK), bundle); 462 // Wait for activity launch with timeout. 463 mTestActivity = (T) mInstrumentation.waitForMonitorWithTimeout(monitor, 464 ACTIVITY_LAUNCH_TIMEOUT); 465 assertNotNull(mTestActivity); 466 return mTestActivity; 467 } 468 finishCurrentActivityNoWait()469 void finishCurrentActivityNoWait() { 470 if (mTestActivity != null) { 471 mTestActivity.finishAndRemoveTask(); 472 mTestActivity = null; 473 } 474 } 475 runOnMainSyncAndWait(Runnable runnable)476 void runOnMainSyncAndWait(Runnable runnable) { 477 mInstrumentation.runOnMainSync(runnable); 478 mInstrumentation.waitForIdleSync(); 479 } 480 runOnMainAndAssertWithTimeout(@onNull BooleanSupplier condition, long timeoutMs, String message)481 void runOnMainAndAssertWithTimeout(@NonNull BooleanSupplier condition, long timeoutMs, 482 String message) { 483 final AtomicBoolean result = new AtomicBoolean(); 484 final long expiredTime = System.currentTimeMillis() + timeoutMs; 485 while (!result.get()) { 486 if (System.currentTimeMillis() >= expiredTime) { 487 fail(message); 488 } 489 runOnMainSyncAndWait(() -> { 490 if (condition.getAsBoolean()) { 491 result.set(true); 492 } 493 }); 494 SystemClock.sleep(WAIT_SLICE); 495 } 496 } 497 getActivity()498 T getActivity() { 499 return mTestActivity; 500 } 501 502 @Override close()503 public void close() { 504 if (mTestActivity != null && mFinishAfterClose) { 505 mTestActivity.finishAndRemoveTask(); 506 } 507 } 508 } 509 510 @Before setUp()511 public void setUp() throws Exception { 512 pressWakeupButton(); 513 pressUnlockButton(); 514 launchHomeActivityNoWait(); 515 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 516 517 // Clear launch params for all test packages to make sure each test is run in a clean state. 518 SystemUtil.runWithShellPermissionIdentity( 519 () -> mAtm.clearLaunchParamsForPackages(TEST_PACKAGES)); 520 } 521 522 /** It always executes after {@link org.junit.After}. */ tearDownBase()523 private void tearDownBase() { 524 mObjectTracker.tearDown(mPostAssertionRule::addError); 525 526 SystemUtil.runWithShellPermissionIdentity( 527 () -> mTaskOrganizer.unregisterOrganizerIfNeeded()); 528 // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but 529 // home are cleaned up from the stack at the end of each test. Am force stop shell commands 530 // might be asynchronous and could interrupt the stack cleanup process if executed first. 531 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 532 stopTestPackage(TEST_PACKAGE); 533 stopTestPackage(SECOND_TEST_PACKAGE); 534 stopTestPackage(THIRD_TEST_PACKAGE); 535 launchHomeActivityNoWait(); 536 } 537 538 /** 539 * After home key is pressed ({@link #pressHomeButton} is called), the later launch may be 540 * deferred if the calling uid doesn't have android.permission.STOP_APP_SWITCHES. This method 541 * will resume the temporary stopped state, so the launch won't be affected. 542 */ resumeAppSwitches()543 protected void resumeAppSwitches() { 544 SystemUtil.runWithShellPermissionIdentity(ActivityManager::resumeAppSwitches); 545 } 546 moveTopActivityToPinnedStack(int stackId)547 protected void moveTopActivityToPinnedStack(int stackId) { 548 SystemUtil.runWithShellPermissionIdentity( 549 () -> mAtm.moveTopActivityToPinnedStack(stackId, new Rect(0, 0, 500, 500)) 550 ); 551 } 552 startActivityOnDisplay(int displayId, ComponentName component)553 protected void startActivityOnDisplay(int displayId, ComponentName component) { 554 final ActivityOptions options = ActivityOptions.makeBasic(); 555 options.setLaunchDisplayId(displayId); 556 557 mContext.startActivity(new Intent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 558 .setComponent(component), options.toBundle()); 559 } 560 noHomeScreen()561 protected boolean noHomeScreen() { 562 try { 563 return mContext.getResources().getBoolean( 564 Resources.getSystem().getIdentifier("config_noHomeScreen", "bool", 565 "android")); 566 } catch (Resources.NotFoundException e) { 567 // Assume there's a home screen. 568 return false; 569 } 570 } 571 getSupportsSystemDecorsOnSecondaryDisplays()572 private boolean getSupportsSystemDecorsOnSecondaryDisplays() { 573 try { 574 return mContext.getResources().getBoolean( 575 Resources.getSystem().getIdentifier( 576 "config_supportsSystemDecorsOnSecondaryDisplays", "bool", "android")); 577 } catch (Resources.NotFoundException e) { 578 // Assume this device support system decorations. 579 return true; 580 } 581 } 582 getDefaultSecondaryHomeComponent()583 protected ComponentName getDefaultSecondaryHomeComponent() { 584 assumeTrue(supportsMultiDisplay()); 585 int resId = Resources.getSystem().getIdentifier( 586 "config_secondaryHomePackage", "string", "android"); 587 final Intent intent = new Intent(Intent.ACTION_MAIN); 588 intent.addCategory(Intent.CATEGORY_SECONDARY_HOME); 589 intent.setPackage(mContext.getResources().getString(resId)); 590 final ResolveInfo resolveInfo = 591 mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY); 592 assertNotNull("Should have default secondary home activity", resolveInfo); 593 594 return new ComponentName(resolveInfo.activityInfo.packageName, 595 resolveInfo.activityInfo.name); 596 } 597 598 /** 599 * Insert an input event (ACTION_DOWN -> ACTION_CANCEL) to ensures the display to be focused 600 * without triggering potential clicked to impact the test environment. 601 * (e.g: Keyguard credential activated unexpectedly.) 602 * 603 * @param displayId the display ID to gain focused by inject swipe action 604 */ touchAndCancelOnDisplayCenterSync(int displayId)605 protected void touchAndCancelOnDisplayCenterSync(int displayId) { 606 WindowManagerState.DisplayContent dc = mWmState.getDisplay(displayId); 607 if (dc == null) { 608 // never get wm state before? 609 mWmState.computeState(); 610 dc = mWmState.getDisplay(displayId); 611 } 612 if (dc == null) { 613 log("Cannot tap on display: " + displayId); 614 return; 615 } 616 final Rect bounds = dc.getDisplayRect(); 617 final int x = bounds.left + bounds.width() / 2; 618 final int y = bounds.top + bounds.height() / 2; 619 final long downTime = SystemClock.uptimeMillis(); 620 injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, true /* sync */); 621 622 final long eventTime = SystemClock.uptimeMillis(); 623 final int touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); 624 final int tapX = x + Math.round(touchSlop / 2.0f); 625 final int tapY = y + Math.round(touchSlop / 2.0f); 626 injectMotion(downTime, eventTime, MotionEvent.ACTION_CANCEL, tapX, tapY, displayId, 627 true /* sync */); 628 } 629 tapOnDisplaySync(int x, int y, int displayId)630 protected void tapOnDisplaySync(int x, int y, int displayId) { 631 tapOnDisplay(x, y, displayId, true /* sync*/); 632 } 633 tapOnDisplay(int x, int y, int displayId, boolean sync)634 private void tapOnDisplay(int x, int y, int displayId, boolean sync) { 635 final long downTime = SystemClock.uptimeMillis(); 636 injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId, sync); 637 638 final long upTime = SystemClock.uptimeMillis(); 639 injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId, sync); 640 641 mWmState.waitForWithAmState(state -> state.getFocusedDisplayId() == displayId, 642 "top focused displayId: " + displayId); 643 // This is needed after a tap in multi-display to ensure that the display focus has really 644 // changed, if needed. The call to syncInputTransaction will wait until focus change has 645 // propagated from WMS to native input before returning. 646 mInstrumentation.getUiAutomation().syncInputTransactions(); 647 } 648 tapOnCenter(Rect bounds, int displayId)649 protected void tapOnCenter(Rect bounds, int displayId) { 650 final int tapX = bounds.left + bounds.width() / 2; 651 final int tapY = bounds.top + bounds.height() / 2; 652 tapOnDisplaySync(tapX, tapY, displayId); 653 } 654 tapOnStackCenter(WindowManagerState.ActivityTask stack)655 protected void tapOnStackCenter(WindowManagerState.ActivityTask stack) { 656 tapOnCenter(stack.getBounds(), stack.mDisplayId); 657 } 658 tapOnDisplayCenter(int displayId)659 protected void tapOnDisplayCenter(int displayId) { 660 final Rect bounds = mWmState.getDisplay(displayId).getDisplayRect(); 661 tapOnDisplaySync(bounds.centerX(), bounds.centerY(), displayId); 662 } 663 tapOnDisplayCenterAsync(int displayId)664 protected void tapOnDisplayCenterAsync(int displayId) { 665 final Rect bounds = mWmState.getDisplay(displayId).getDisplayRect(); 666 tapOnDisplay(bounds.centerX(), bounds.centerY(), displayId, false /* sync */); 667 } 668 injectMotion(long downTime, long eventTime, int action, int x, int y, int displayId, boolean sync)669 private static void injectMotion(long downTime, long eventTime, int action, 670 int x, int y, int displayId, boolean sync) { 671 final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, 672 x, y, 0 /* metaState */); 673 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 674 event.setDisplayId(displayId); 675 getInstrumentation().getUiAutomation().injectInputEvent(event, sync); 676 } 677 injectKey(int keyCode, boolean longPress, boolean sync)678 public static void injectKey(int keyCode, boolean longPress, boolean sync) { 679 final long downTime = SystemClock.uptimeMillis(); 680 int repeatCount = 0; 681 KeyEvent downEvent = 682 new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode, repeatCount); 683 getInstrumentation().getUiAutomation().injectInputEvent(downEvent, sync); 684 if (longPress) { 685 repeatCount += 1; 686 KeyEvent repeatEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(), 687 KeyEvent.ACTION_DOWN, keyCode, repeatCount); 688 getInstrumentation().getUiAutomation().injectInputEvent(repeatEvent, sync); 689 } 690 KeyEvent upEvent = new KeyEvent(downTime, SystemClock.uptimeMillis(), 691 KeyEvent.ACTION_UP, keyCode, 0 /* repeatCount */); 692 getInstrumentation().getUiAutomation().injectInputEvent(upEvent, sync); 693 } 694 removeStacksWithActivityTypes(int... activityTypes)695 protected void removeStacksWithActivityTypes(int... activityTypes) { 696 SystemUtil.runWithShellPermissionIdentity( 697 () -> mAtm.removeStacksWithActivityTypes(activityTypes)); 698 waitForIdle(); 699 } 700 removeStacksInWindowingModes(int... windowingModes)701 protected void removeStacksInWindowingModes(int... windowingModes) { 702 SystemUtil.runWithShellPermissionIdentity( 703 () -> mAtm.removeStacksInWindowingModes(windowingModes) 704 ); 705 waitForIdle(); 706 } 707 executeShellCommand(String command)708 public static String executeShellCommand(String command) { 709 log("Shell command: " + command); 710 try { 711 return SystemUtil.runShellCommand(getInstrumentation(), command); 712 } catch (IOException e) { 713 //bubble it up 714 logE("Error running shell command: " + command); 715 throw new RuntimeException(e); 716 } 717 } 718 takeScreenshot()719 protected Bitmap takeScreenshot() { 720 return mInstrumentation.getUiAutomation().takeScreenshot(); 721 } 722 launchActivity(final ComponentName activityName, final String... keyValuePairs)723 protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) { 724 launchActivityNoWait(activityName, keyValuePairs); 725 mWmState.waitForValidState(activityName); 726 } 727 launchActivityNoWait(final ComponentName activityName, final String... keyValuePairs)728 protected void launchActivityNoWait(final ComponentName activityName, 729 final String... keyValuePairs) { 730 executeShellCommand(getAmStartCmd(activityName, keyValuePairs)); 731 } 732 launchActivityInNewTask(final ComponentName activityName)733 protected void launchActivityInNewTask(final ComponentName activityName) { 734 executeShellCommand(getAmStartCmdInNewTask(activityName)); 735 mWmState.waitForValidState(activityName); 736 } 737 waitForIdle()738 protected static void waitForIdle() { 739 getInstrumentation().waitForIdleSync(); 740 } 741 waitForOrFail(String message, BooleanSupplier condition)742 static void waitForOrFail(String message, BooleanSupplier condition) { 743 Condition.waitFor(new Condition<>(message, condition) 744 .setRetryIntervalMs(500) 745 .setRetryLimit(20) 746 .setOnFailure(unusedResult -> fail("FAILED because unsatisfied: " + message))); 747 } 748 749 /** Returns the stack that contains the provided task. */ getStackForTaskId(int taskId)750 protected WindowManagerState.ActivityTask getStackForTaskId(int taskId) { 751 mWmState.computeState(); 752 final List<WindowManagerState.ActivityTask> stacks = mWmState.getRootTasks(); 753 for (WindowManagerState.ActivityTask stack : stacks) { 754 if (stack.getTask(taskId) != null) { 755 return stack; 756 } 757 } 758 return null; 759 } 760 getRootTask(int taskId)761 protected WindowManagerState.ActivityTask getRootTask(int taskId) { 762 mWmState.computeState(); 763 final List<WindowManagerState.ActivityTask> rootTasks = mWmState.getRootTasks(); 764 for (WindowManagerState.ActivityTask rootTask : rootTasks) { 765 if (rootTask.getTaskId() == taskId) { 766 return rootTask; 767 } 768 } 769 return null; 770 } 771 772 /** 773 * Launches the home activity directly. If there is no specific reason to simulate a home key 774 * (which will trigger stop-app-switches), it is the recommended method to go home. 775 */ launchHomeActivityNoWait()776 protected static void launchHomeActivityNoWait() { 777 executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND); 778 } 779 780 /** Launches the home activity directly with waiting for it to be visible. */ launchHomeActivity()781 protected void launchHomeActivity() { 782 launchHomeActivityNoWait(); 783 mWmState.waitForHomeActivityVisible(); 784 } 785 launchActivity(ComponentName activityName, int windowingMode, final String... keyValuePairs)786 protected void launchActivity(ComponentName activityName, int windowingMode, 787 final String... keyValuePairs) { 788 executeShellCommand(getAmStartCmd(activityName, keyValuePairs) 789 + " --windowingMode " + windowingMode); 790 mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 791 .setWindowingMode(windowingMode) 792 .build()); 793 } 794 launchActivityOnDisplay(ComponentName activityName, int windowingMode, int displayId, final String... keyValuePairs)795 protected void launchActivityOnDisplay(ComponentName activityName, int windowingMode, 796 int displayId, final String... keyValuePairs) { 797 executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs) 798 + " --windowingMode " + windowingMode); 799 mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 800 .setWindowingMode(windowingMode) 801 .build()); 802 } 803 launchActivityOnDisplay(ComponentName activityName, int displayId, String... keyValuePairs)804 protected void launchActivityOnDisplay(ComponentName activityName, int displayId, 805 String... keyValuePairs) { 806 launchActivityOnDisplayNoWait(activityName, displayId, keyValuePairs); 807 mWmState.waitForValidState(activityName); 808 } 809 launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, String... keyValuePairs)810 protected void launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, 811 String... keyValuePairs) { 812 executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs)); 813 } 814 815 /** 816 * Launches {@param activityName} into split-screen primary windowing mode and also makes 817 * the recents activity visible to the side of it. 818 * NOTE: Recents view may be combined with home screen on some devices, so using this to wait 819 * for Recents only makes sense when {@link WindowManagerState#isHomeRecentsComponent()} is 820 * {@code false}. 821 */ launchActivityInSplitScreenWithRecents(ComponentName activityName)822 protected void launchActivityInSplitScreenWithRecents(ComponentName activityName) { 823 SystemUtil.runWithShellPermissionIdentity(() -> { 824 launchActivity(activityName); 825 final int taskId = mWmState.getTaskByActivity(activityName).mTaskId; 826 if (mUseTaskOrganizer) { 827 mTaskOrganizer.putTaskInSplitPrimary(taskId); 828 } else { 829 mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, 830 SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, 831 true /* onTop */, false /* animate */, 832 null /* initialBounds */, true /* showRecents */); 833 } 834 835 mWmState.waitForValidState( 836 new WaitForValidActivityState.Builder(activityName) 837 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) 838 .setActivityType(ACTIVITY_TYPE_STANDARD) 839 .build()); 840 mWmState.waitForRecentsActivityVisible(); 841 }); 842 } 843 moveTaskToPrimarySplitScreen(int taskId)844 public void moveTaskToPrimarySplitScreen(int taskId) { 845 moveTaskToPrimarySplitScreen(taskId, false /* showSideActivity */); 846 } 847 848 /** 849 * Moves the device into split-screen with the specified task into the primary stack. 850 * @param taskId The id of the task to move into the primary stack. 851 * @param showSideActivity Whether to show the Recents activity (or a placeholder activity in 852 * place of the Recents activity if home is the recents component). 853 * If {@code true} it will also wait for activity in the primary 854 * split-screen stack to be resumed. 855 */ moveTaskToPrimarySplitScreen(int taskId, boolean showSideActivity)856 public void moveTaskToPrimarySplitScreen(int taskId, boolean showSideActivity) { 857 final boolean isHomeRecentsComponent = mWmState.isHomeRecentsComponent(); 858 SystemUtil.runWithShellPermissionIdentity(() -> { 859 if (mUseTaskOrganizer) { 860 mTaskOrganizer.putTaskInSplitPrimary(taskId); 861 } else { 862 mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, 863 SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */, 864 false /* animate */, null /* initialBounds */, 865 showSideActivity && !isHomeRecentsComponent); 866 } 867 868 mWmState.waitForRecentsActivityVisible(); 869 870 if (showSideActivity) { 871 if (isHomeRecentsComponent) { 872 // Launch Placeholder Side Activity 873 final ComponentName sideActivityName = 874 new ComponentName(mContext, SideActivity.class); 875 mContext.startActivity(new Intent().setComponent(sideActivityName) 876 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 877 mWmState.waitForActivityState(sideActivityName, STATE_RESUMED); 878 } 879 880 // There are two cases when showSideActivity == true: 881 // Case 1: it's 3rd-party launcher and it should show recents, so the primary split 882 // screen won't enter minimized dock, but the activity on primary split screen 883 // should be relaunched. 884 // Case 2: It's not 3rd-party launcher but we launched side activity on secondary 885 // split screen, the activity on primary split screen should enter then leave 886 // minimized dock. 887 // In both cases, we shall wait for the state of the activity on primary split 888 // screen to resumed, so the LifecycleLog won't affect the following tests. 889 mWmState.waitForWithAmState(state -> { 890 final WindowManagerState.ActivityTask stack = 891 state.getStandardStackByWindowingMode( 892 WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); 893 return stack != null && stack.getResumedActivity() != null; 894 }, "activity in the primary split-screen stack must be resumed"); 895 } 896 }); 897 } 898 899 /** 900 * Launches {@param primaryActivity} into split-screen primary windowing mode 901 * and {@param secondaryActivity} to the side in split-screen secondary windowing mode. 902 */ launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, LaunchActivityBuilder secondaryActivity)903 protected void launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, 904 LaunchActivityBuilder secondaryActivity) { 905 // Launch split-screen primary. 906 primaryActivity 907 .setUseInstrumentation() 908 .setWaitForLaunched(true) 909 .execute(); 910 911 final int taskId = mWmState.getTaskByActivity( 912 primaryActivity.mTargetActivity).mTaskId; 913 moveTaskToPrimarySplitScreen(taskId); 914 915 // Launch split-screen secondary 916 // Recents become focused, so we can just launch new task in focused stack 917 secondaryActivity 918 .setUseInstrumentation() 919 .setWaitForLaunched(true) 920 .setNewTask(true) 921 .setMultipleTask(true) 922 .execute(); 923 } 924 setActivityTaskWindowingMode(ComponentName activityName, int windowingMode)925 protected boolean setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) { 926 mWmState.computeState(activityName); 927 final int taskId = mWmState.getTaskByActivity(activityName).mTaskId; 928 boolean[] result = new boolean[1]; 929 SystemUtil.runWithShellPermissionIdentity(() -> { 930 result[0] = mAtm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */); 931 }); 932 if (result[0]) { 933 mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 934 .setActivityType(ACTIVITY_TYPE_STANDARD) 935 .setWindowingMode(windowingMode) 936 .build()); 937 } 938 return result[0]; 939 } 940 941 /** Move activity to stack or on top of the given stack when the stack is a leak task. */ moveActivityToStackOrOnTop(ComponentName activityName, int stackId)942 protected void moveActivityToStackOrOnTop(ComponentName activityName, int stackId) { 943 mWmState.computeState(activityName); 944 WindowManagerState.ActivityTask rootTask = getRootTask(stackId); 945 if (rootTask.getActivities().size() != 0) { 946 // If the root task is a 1-level task, start the activity on top of given task. 947 getLaunchActivityBuilder() 948 .setDisplayId(rootTask.mDisplayId) 949 .setWindowingMode(rootTask.getWindowingMode()) 950 .setActivityType(rootTask.getActivityType()) 951 .setTargetActivity(activityName) 952 .allowMultipleInstances(false) 953 .setUseInstrumentation() 954 .execute(); 955 } else { 956 final int taskId = mWmState.getTaskByActivity(activityName).mTaskId; 957 SystemUtil.runWithShellPermissionIdentity( 958 () -> mAtm.moveTaskToStack(taskId, stackId, true)); 959 } 960 mWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 961 .setStackId(stackId) 962 .build()); 963 } 964 resizeActivityTask( ComponentName activityName, int left, int top, int right, int bottom)965 protected void resizeActivityTask( 966 ComponentName activityName, int left, int top, int right, int bottom) { 967 mWmState.computeState(activityName); 968 final int taskId = mWmState.getTaskByActivity(activityName).mTaskId; 969 SystemUtil.runWithShellPermissionIdentity( 970 () -> mAtm.resizeTask(taskId, new Rect(left, top, right, bottom))); 971 } 972 resizeDockedStack( int stackWidth, int stackHeight, int taskWidth, int taskHeight)973 protected void resizeDockedStack( 974 int stackWidth, int stackHeight, int taskWidth, int taskHeight) { 975 SystemUtil.runWithShellPermissionIdentity(() -> 976 mAtm.resizeDockedStack(new Rect(0, 0, stackWidth, stackHeight), 977 new Rect(0, 0, taskWidth, taskHeight))); 978 } 979 pressAppSwitchButtonAndWaitForRecents()980 protected void pressAppSwitchButtonAndWaitForRecents() { 981 pressAppSwitchButton(); 982 mWmState.waitForRecentsActivityVisible(); 983 mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 984 } 985 986 // Utility method for debugging, not used directly here, but useful, so kept around. printStacksAndTasks()987 protected void printStacksAndTasks() { 988 SystemUtil.runWithShellPermissionIdentity(() -> { 989 final String output = mAtm.listAllStacks(); 990 for (String line : output.split("\\n")) { 991 log(line); 992 } 993 }); 994 } 995 supportsVrMode()996 protected boolean supportsVrMode() { 997 return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE); 998 } 999 supportsPip()1000 protected boolean supportsPip() { 1001 return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE) 1002 || PRETEND_DEVICE_SUPPORTS_PIP; 1003 } 1004 supportsFreeform()1005 protected boolean supportsFreeform() { 1006 return hasDeviceFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT) 1007 || PRETEND_DEVICE_SUPPORTS_FREEFORM; 1008 } 1009 1010 /** Whether or not the device supports lock screen. */ supportsLockScreen()1011 protected boolean supportsLockScreen() { 1012 return supportsInsecureLock() || supportsSecureLock(); 1013 } 1014 1015 /** Whether or not the device supports pin/pattern/password lock. */ supportsSecureLock()1016 protected boolean supportsSecureLock() { 1017 return hasDeviceFeature(FEATURE_SECURE_LOCK_SCREEN); 1018 } 1019 1020 /** Whether or not the device supports "swipe" lock. */ supportsInsecureLock()1021 protected boolean supportsInsecureLock() { 1022 return !hasDeviceFeature(FEATURE_LEANBACK) 1023 && !hasDeviceFeature(FEATURE_WATCH) 1024 && !hasDeviceFeature(FEATURE_EMBEDDED) 1025 && !hasDeviceFeature(FEATURE_AUTOMOTIVE) 1026 && getSupportsInsecureLockScreen(); 1027 } 1028 isWatch()1029 protected boolean isWatch() { 1030 return hasDeviceFeature(FEATURE_WATCH); 1031 } 1032 isCar()1033 protected boolean isCar() { 1034 return hasDeviceFeature(FEATURE_AUTOMOTIVE); 1035 } 1036 isTablet()1037 protected boolean isTablet() { 1038 // Larger than approx 7" tablets 1039 return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600; 1040 } 1041 waitAndAssertActivityState(ComponentName activityName, String state, String message)1042 protected void waitAndAssertActivityState(ComponentName activityName, 1043 String state, String message) { 1044 mWmState.waitForActivityState(activityName, state); 1045 1046 assertTrue(message, mWmState.hasActivityState(activityName, state)); 1047 } 1048 waitAndAssertActivityStateOnDisplay(ComponentName activityName, String state, int displayId, String message)1049 protected void waitAndAssertActivityStateOnDisplay(ComponentName activityName, String state, 1050 int displayId, String message) { 1051 waitAndAssertActivityState(activityName, state, message); 1052 assertEquals(message, mWmState.getDisplayByActivity(activityName), 1053 displayId); 1054 } 1055 waitAndAssertTopResumedActivity(ComponentName activityName, int displayId, String message)1056 public void waitAndAssertTopResumedActivity(ComponentName activityName, int displayId, 1057 String message) { 1058 mWmState.waitForValidState(activityName); 1059 mWmState.waitForActivityState(activityName, STATE_RESUMED); 1060 final String activityClassName = getActivityName(activityName); 1061 mWmState.waitForWithAmState(state -> activityClassName.equals(state.getFocusedActivity()), 1062 "activity to be on top"); 1063 1064 mWmState.assertSanity(); 1065 mWmState.assertFocusedActivity(message, activityName); 1066 assertTrue("Activity must be resumed", 1067 mWmState.hasActivityState(activityName, STATE_RESUMED)); 1068 final int frontStackId = mWmState.getFrontRootTaskId(displayId); 1069 WindowManagerState.ActivityTask frontStackOnDisplay = 1070 mWmState.getRootTask(frontStackId); 1071 assertEquals("Resumed activity of front stack of the target display must match. " + message, 1072 activityClassName, frontStackOnDisplay.mResumedActivity); 1073 mWmState.assertFocusedStack("Top activity's stack must also be on top", frontStackId); 1074 mWmState.assertVisibility(activityName, true /* visible */); 1075 } 1076 1077 // TODO: Switch to using a feature flag, when available. isUiModeLockedToVrHeadset()1078 protected static boolean isUiModeLockedToVrHeadset() { 1079 final String output = runCommandAndPrintOutput("dumpsys uimode"); 1080 1081 Integer curUiMode = null; 1082 Boolean uiModeLocked = null; 1083 for (String line : output.split("\\n")) { 1084 line = line.trim(); 1085 Matcher matcher = sCurrentUiModePattern.matcher(line); 1086 if (matcher.find()) { 1087 curUiMode = Integer.parseInt(matcher.group(1), 16); 1088 } 1089 matcher = sUiModeLockedPattern.matcher(line); 1090 if (matcher.find()) { 1091 uiModeLocked = matcher.group(1).equals("true"); 1092 } 1093 } 1094 1095 boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null) 1096 && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked; 1097 1098 if (uiModeLockedToVrHeadset) { 1099 log("UI mode is locked to VR headset"); 1100 } 1101 1102 return uiModeLockedToVrHeadset; 1103 } 1104 supportsSplitScreenMultiWindow()1105 protected boolean supportsSplitScreenMultiWindow() { 1106 return ActivityTaskManager.supportsSplitScreenMultiWindow(mContext); 1107 } 1108 hasHomeScreen()1109 protected boolean hasHomeScreen() { 1110 if (sHasHomeScreen == null) { 1111 sHasHomeScreen = !noHomeScreen(); 1112 } 1113 return sHasHomeScreen; 1114 } 1115 supportsSystemDecorsOnSecondaryDisplays()1116 protected boolean supportsSystemDecorsOnSecondaryDisplays() { 1117 if (sSupportsSystemDecorsOnSecondaryDisplays == null) { 1118 sSupportsSystemDecorsOnSecondaryDisplays = getSupportsSystemDecorsOnSecondaryDisplays(); 1119 } 1120 return sSupportsSystemDecorsOnSecondaryDisplays; 1121 } 1122 getSupportsInsecureLockScreen()1123 protected boolean getSupportsInsecureLockScreen() { 1124 if (sSupportsInsecureLockScreen == null) { 1125 try { 1126 sSupportsInsecureLockScreen = mContext.getResources().getBoolean( 1127 Resources.getSystem().getIdentifier( 1128 "config_supportsInsecureLockScreen", "bool", "android")); 1129 } catch (Resources.NotFoundException e) { 1130 sSupportsInsecureLockScreen = true; 1131 } 1132 } 1133 return sSupportsInsecureLockScreen; 1134 } 1135 isAssistantOnTop()1136 protected boolean isAssistantOnTop() { 1137 if (sIsAssistantOnTop == null) { 1138 sIsAssistantOnTop = mContext.getResources().getBoolean( 1139 android.R.bool.config_assistantOnTopOfDream); 1140 } 1141 return sIsAssistantOnTop; 1142 } 1143 1144 /** 1145 * Rotation support is indicated by explicitly having both landscape and portrait 1146 * features or not listing either at all. 1147 */ supportsRotation()1148 protected boolean supportsRotation() { 1149 final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE); 1150 final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT); 1151 return (supportsLandscape && supportsPortrait) 1152 || (!supportsLandscape && !supportsPortrait); 1153 } 1154 hasDeviceFeature(final String requiredFeature)1155 protected boolean hasDeviceFeature(final String requiredFeature) { 1156 return mContext.getPackageManager() 1157 .hasSystemFeature(requiredFeature); 1158 } 1159 isDisplayOn(int displayId)1160 protected static boolean isDisplayOn(int displayId) { 1161 final DisplayManager displayManager = getInstrumentation() 1162 .getContext().getSystemService(DisplayManager.class); 1163 final Display display = displayManager.getDisplay(displayId); 1164 return display != null && display.getState() == Display.STATE_ON; 1165 } 1166 perDisplayFocusEnabled()1167 protected static boolean perDisplayFocusEnabled() { 1168 return getInstrumentation().getTargetContext().getResources() 1169 .getBoolean(android.R.bool.config_perDisplayFocusEnabled); 1170 } 1171 remoteInsetsControllerControlsSystemBars()1172 protected static boolean remoteInsetsControllerControlsSystemBars() { 1173 return getInstrumentation().getTargetContext().getResources() 1174 .getBoolean(android.R.bool.config_remoteInsetsControllerControlsSystemBars); 1175 } 1176 1177 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedHomeActivitySession(ComponentName homeActivity)1178 protected HomeActivitySession createManagedHomeActivitySession(ComponentName homeActivity) { 1179 return mObjectTracker.manage(new HomeActivitySession(homeActivity)); 1180 } 1181 1182 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedActivityClientSession()1183 protected ActivitySessionClient createManagedActivityClientSession() { 1184 return mObjectTracker.manage(new ActivitySessionClient(mContext)); 1185 } 1186 1187 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedLockScreenSession()1188 protected LockScreenSession createManagedLockScreenSession() { 1189 return mObjectTracker.manage(new LockScreenSession()); 1190 } 1191 1192 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedRotationSession()1193 protected RotationSession createManagedRotationSession() { 1194 return mObjectTracker.manage(new RotationSession()); 1195 } 1196 1197 /** @see ObjectTracker#manage(AutoCloseable) */ createManagedTestActivitySession()1198 protected <T extends Activity> TestActivitySession<T> createManagedTestActivitySession() { 1199 return new TestActivitySession<T>(); 1200 } 1201 1202 /** 1203 * Test @Rule class that disables screen doze settings before each test method running and 1204 * restoring to initial values after test method finished. 1205 */ 1206 protected static class DisableScreenDozeRule implements TestRule { 1207 1208 /** Copied from android.provider.Settings.Secure since these keys are hiden. */ 1209 private static final String[] DOZE_SETTINGS = { 1210 "doze_enabled", 1211 "doze_always_on", 1212 "doze_pulse_on_pick_up", 1213 "doze_pulse_on_long_press", 1214 "doze_pulse_on_double_tap", 1215 "doze_wake_screen_gesture", 1216 "doze_wake_display_gesture", 1217 "doze_tap_gesture" 1218 }; 1219 get(String key)1220 private String get(String key) { 1221 return executeShellCommand("settings get secure " + key).trim(); 1222 } 1223 put(String key, String value)1224 private void put(String key, String value) { 1225 executeShellCommand("settings put secure " + key + " " + value); 1226 } 1227 1228 @Override apply(final Statement base, final Description description)1229 public Statement apply(final Statement base, final Description description) { 1230 return new Statement() { 1231 @Override 1232 public void evaluate() throws Throwable { 1233 final Map<String, String> initialValues = new HashMap<>(); 1234 Arrays.stream(DOZE_SETTINGS).forEach(k -> initialValues.put(k, get(k))); 1235 try { 1236 Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, "0")); 1237 base.evaluate(); 1238 } finally { 1239 Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, initialValues.get(k))); 1240 } 1241 } 1242 }; 1243 } 1244 } 1245 1246 ComponentName getDefaultHomeComponent() { 1247 final Intent intent = new Intent(ACTION_MAIN); 1248 intent.addCategory(CATEGORY_HOME); 1249 intent.addFlags(FLAG_ACTIVITY_NEW_TASK); 1250 final ResolveInfo resolveInfo = 1251 mContext.getPackageManager().resolveActivity(intent, MATCH_DEFAULT_ONLY); 1252 if (resolveInfo == null) { 1253 throw new AssertionError("Home activity not found"); 1254 } 1255 return new ComponentName(resolveInfo.activityInfo.packageName, 1256 resolveInfo.activityInfo.name); 1257 } 1258 1259 /** 1260 * HomeActivitySession is used to replace the default home component, so that you can use 1261 * your preferred home for testing within the session. The original default home will be 1262 * restored automatically afterward. 1263 */ 1264 protected class HomeActivitySession implements AutoCloseable { 1265 private PackageManager mPackageManager; 1266 private ComponentName mOrigHome; 1267 private ComponentName mSessionHome; 1268 1269 HomeActivitySession(ComponentName sessionHome) { 1270 mSessionHome = sessionHome; 1271 mPackageManager = mContext.getPackageManager(); 1272 mOrigHome = getDefaultHomeComponent(); 1273 1274 SystemUtil.runWithShellPermissionIdentity( 1275 () -> mPackageManager.setComponentEnabledSetting(mSessionHome, 1276 COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP)); 1277 setDefaultHome(mSessionHome); 1278 } 1279 1280 @Override 1281 public void close() { 1282 SystemUtil.runWithShellPermissionIdentity( 1283 () -> mPackageManager.setComponentEnabledSetting(mSessionHome, 1284 COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP)); 1285 if (mOrigHome != null) { 1286 setDefaultHome(mOrigHome); 1287 } 1288 } 1289 1290 private void setDefaultHome(ComponentName componentName) { 1291 executeShellCommand("cmd package set-home-activity --user " 1292 + android.os.Process.myUserHandle().getIdentifier() + " " 1293 + componentName.flattenToString()); 1294 } 1295 } 1296 1297 public class LockScreenSession implements AutoCloseable { 1298 private static final boolean DEBUG = false; 1299 1300 private final boolean mIsLockDisabled; 1301 private boolean mLockCredentialSet; 1302 private boolean mRemoveActivitiesOnClose; 1303 private AmbientDisplayConfiguration mAmbientDisplayConfiguration; 1304 1305 public static final int FLAG_REMOVE_ACTIVITIES_ON_CLOSE = 1; 1306 1307 public LockScreenSession() { 1308 this(0 /* flags */); 1309 } 1310 1311 public LockScreenSession(int flags) { 1312 mIsLockDisabled = isLockDisabled(); 1313 mLockCredentialSet = false; 1314 // Enable lock screen (swipe) by default. 1315 setLockDisabled(false); 1316 if ((flags & FLAG_REMOVE_ACTIVITIES_ON_CLOSE) != 0) { 1317 mRemoveActivitiesOnClose = true; 1318 } 1319 mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); 1320 } 1321 1322 public LockScreenSession setLockCredential() { 1323 mLockCredentialSet = true; 1324 runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL); 1325 return this; 1326 } 1327 1328 public LockScreenSession enterAndConfirmLockCredential() { 1329 // Ensure focus will switch to default display. Meanwhile we cannot tap on center area, 1330 // which may tap on input credential area. 1331 touchAndCancelOnDisplayCenterSync(DEFAULT_DISPLAY); 1332 1333 waitForDeviceIdle(3000); 1334 SystemUtil.runWithShellPermissionIdentity(() -> 1335 mInstrumentation.sendStringSync(LOCK_CREDENTIAL)); 1336 pressEnterButton(); 1337 return this; 1338 } 1339 1340 private void removeLockCredential() { 1341 runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL); 1342 mLockCredentialSet = false; 1343 } 1344 1345 LockScreenSession disableLockScreen() { 1346 setLockDisabled(true); 1347 return this; 1348 } 1349 1350 public LockScreenSession sleepDevice() { 1351 pressSleepButton(); 1352 // Not all device variants lock when we go to sleep, so we need to explicitly lock the 1353 // device. Note that pressSleepButton() above is redundant because the action also 1354 // puts the device to sleep, but kept around for clarity. 1355 mInstrumentation.getUiAutomation().performGlobalAction( 1356 AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN); 1357 if (mAmbientDisplayConfiguration.alwaysOnEnabled( 1358 android.os.Process.myUserHandle().getIdentifier())) { 1359 mWmState.waitForAodShowing(); 1360 } else { 1361 Condition.waitFor("display to turn off", () -> !isDisplayOn(DEFAULT_DISPLAY)); 1362 } 1363 return this; 1364 } 1365 1366 LockScreenSession wakeUpDevice() { 1367 pressWakeupButton(); 1368 return this; 1369 } 1370 1371 LockScreenSession unlockDevice() { 1372 // Make sure the unlock button event is send to the default display. 1373 touchAndCancelOnDisplayCenterSync(DEFAULT_DISPLAY); 1374 1375 pressUnlockButton(); 1376 return this; 1377 } 1378 1379 public LockScreenSession gotoKeyguard(ComponentName... showWhenLockedActivities) { 1380 if (DEBUG && isLockDisabled()) { 1381 logE("LockScreenSession.gotoKeyguard() is called without lock enabled."); 1382 } 1383 sleepDevice(); 1384 wakeUpDevice(); 1385 if (showWhenLockedActivities.length == 0) { 1386 mWmState.waitForKeyguardShowingAndNotOccluded(); 1387 } else { 1388 mWmState.waitForValidState(showWhenLockedActivities); 1389 } 1390 return this; 1391 } 1392 1393 @Override 1394 public void close() { 1395 if (mRemoveActivitiesOnClose) { 1396 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 1397 } 1398 1399 setLockDisabled(mIsLockDisabled); 1400 if (mLockCredentialSet) { 1401 removeLockCredential(); 1402 } 1403 1404 // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for 1405 // the stale credential. 1406 // TODO (b/112015010) If keyguard is occluded, credential cannot be removed as expected. 1407 // LockScreenSession#close is always calls before stop all test activities, 1408 // which could cause keyguard stay at occluded after wakeup. 1409 // If Keyguard is occluded, press back key can close ShowWhenLocked activity. 1410 pressBackButton(); 1411 1412 // If device is unlocked, there might have ShowWhenLocked activity runs on, 1413 // use home key to clear all activity at foreground. 1414 pressHomeButton(); 1415 sleepDevice(); 1416 wakeUpDevice(); 1417 unlockDevice(); 1418 } 1419 1420 /** 1421 * Returns whether the lock screen is disabled. 1422 * 1423 * @return true if the lock screen is disabled, false otherwise. 1424 */ 1425 private boolean isLockDisabled() { 1426 final String isLockDisabled = runCommandAndPrintOutput( 1427 "locksettings get-disabled").trim(); 1428 return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled); 1429 } 1430 1431 /** 1432 * Disable the lock screen. 1433 * 1434 * @param lockDisabled true if should disable, false otherwise. 1435 */ 1436 protected void setLockDisabled(boolean lockDisabled) { 1437 runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled); 1438 } 1439 } 1440 1441 /** Helper class to save, set & wait, and restore rotation related preferences. */ 1442 protected class RotationSession extends SettingsSession<Integer> { 1443 private final SettingsSession<Integer> mUserRotation; 1444 private final HandlerThread mThread; 1445 private final Handler mRunnableHandler; 1446 private final SettingsObserver mRotationObserver; 1447 private int mPreviousDegree; 1448 1449 public RotationSession() { 1450 // Save accelerometer_rotation preference. 1451 super(Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), 1452 Settings.System::getInt, Settings.System::putInt); 1453 mUserRotation = new SettingsSession<>( 1454 Settings.System.getUriFor(Settings.System.USER_ROTATION), 1455 Settings.System::getInt, Settings.System::putInt); 1456 1457 mThread = new HandlerThread("Observer_Thread"); 1458 mThread.start(); 1459 mRunnableHandler = new Handler(mThread.getLooper()); 1460 mRotationObserver = new SettingsObserver(mRunnableHandler); 1461 1462 mPreviousDegree = mUserRotation.get(); 1463 // Disable accelerometer_rotation. 1464 super.set(0); 1465 } 1466 1467 @Override 1468 public void set(@NonNull Integer value) { 1469 set(value, true /* waitDeviceRotation */); 1470 } 1471 1472 /** 1473 * Sets the rotation preference. 1474 * 1475 * @param value The rotation between {@link android.view.Surface#ROTATION_0} ~ 1476 * {@link android.view.Surface#ROTATION_270} 1477 * @param waitDeviceRotation If {@code true}, it will wait until the display has applied the 1478 * rotation. Otherwise it only waits for the settings value has 1479 * been changed. 1480 */ 1481 public void set(@NonNull Integer value, boolean waitDeviceRotation) { 1482 // When the rotation is locked and the SystemUI receives the rotation becoming 0deg, it 1483 // will call freezeRotation to WMS, which will cause USER_ROTATION be set to zero again. 1484 // In order to prevent our test target from being overwritten by SystemUI during 1485 // rotation test, wait for the USER_ROTATION changed then continue testing. 1486 final boolean waitSystemUI = value == ROTATION_0 && mPreviousDegree != ROTATION_0; 1487 final boolean observeRotationSettings = waitSystemUI || !waitDeviceRotation; 1488 if (observeRotationSettings) { 1489 mRotationObserver.observe(); 1490 } 1491 mUserRotation.set(value); 1492 mPreviousDegree = value; 1493 1494 if (waitSystemUI) { 1495 Condition.waitFor(new Condition<>("rotation notified", 1496 // There will receive USER_ROTATION changed twice because when the device 1497 // rotates to 0deg, RotationContextButton will also set ROTATION_0 again. 1498 () -> mRotationObserver.count == 2).setRetryIntervalMs(500)); 1499 } 1500 1501 if (waitDeviceRotation) { 1502 // Wait for the display to apply the rotation. 1503 mWmState.waitForRotation(value); 1504 } else { 1505 // Wait for the settings have been changed. 1506 Condition.waitFor(new Condition<>("rotation setting changed", 1507 () -> mRotationObserver.count > 0).setRetryIntervalMs(100)); 1508 } 1509 1510 if (observeRotationSettings) { 1511 mRotationObserver.stopObserver(); 1512 } 1513 } 1514 1515 @Override 1516 public void close() { 1517 mThread.quitSafely(); 1518 mUserRotation.close(); 1519 // Restore accelerometer_rotation preference. 1520 super.close(); 1521 } 1522 1523 private class SettingsObserver extends ContentObserver { 1524 int count; 1525 1526 SettingsObserver(Handler handler) { super(handler); } 1527 1528 void observe() { 1529 count = 0; 1530 final ContentResolver resolver = mContext.getContentResolver(); 1531 resolver.registerContentObserver(Settings.System.getUriFor( 1532 Settings.System.USER_ROTATION), false, this); 1533 } 1534 1535 void stopObserver() { 1536 count = 0; 1537 final ContentResolver resolver = mContext.getContentResolver(); 1538 resolver.unregisterContentObserver(this); 1539 } 1540 1541 @Override 1542 public void onChange(boolean selfChange) { 1543 count++; 1544 } 1545 } 1546 } 1547 1548 /** 1549 * Returns whether the test device respects settings of locked user rotation mode. 1550 * 1551 * The method sets the locked user rotation settings to the rotation that rotates the display by 1552 * 180 degrees and checks if the actual display rotation changes after that. 1553 * 1554 * This is a necessary assumption check before leveraging user rotation mode to force display 1555 * rotation, because there is no requirement that an Android device that supports both 1556 * orientations needs to support user rotation mode. 1557 * 1558 * @param session the rotation session used to set user rotation 1559 * @param displayId the display ID to check rotation against 1560 * @return {@code true} if test device respects settings of locked user rotation mode; 1561 * {@code false} if not. 1562 */ 1563 protected boolean supportsLockedUserRotation(RotationSession session, int displayId) { 1564 final int origRotation = getDeviceRotation(displayId); 1565 // Use the same orientation as target rotation to avoid affect of app-requested orientation. 1566 final int targetRotation = (origRotation + 2) % 4; 1567 session.set(targetRotation); 1568 final boolean result = (getDeviceRotation(displayId) == targetRotation); 1569 session.set(origRotation); 1570 return result; 1571 } 1572 1573 protected int getDeviceRotation(int displayId) { 1574 final String displays = runCommandAndPrintOutput("dumpsys display displays").trim(); 1575 Pattern pattern = Pattern.compile( 1576 "(mDisplayId=" + displayId + ")([\\s\\S]*?)(mOverrideDisplayInfo)(.*)" 1577 + "(rotation)(\\s+)(\\d+)"); 1578 Matcher matcher = pattern.matcher(displays); 1579 if (matcher.find()) { 1580 final String match = matcher.group(7); 1581 return Integer.parseInt(match); 1582 } 1583 1584 return INVALID_DEVICE_ROTATION; 1585 } 1586 1587 /** 1588 * Creates a {#link ActivitySessionClient} instance with instrumentation context. It is used 1589 * when the caller doen't need try-with-resource. 1590 */ 1591 public static ActivitySessionClient createActivitySessionClient() { 1592 return new ActivitySessionClient(getInstrumentation().getContext()); 1593 } 1594 1595 /** Empties the test journal so the following events won't be mixed-up with previous records. */ 1596 protected void separateTestJournal() { 1597 TestJournalContainer.start(); 1598 } 1599 1600 protected static String runCommandAndPrintOutput(String command) { 1601 final String output = executeShellCommand(command); 1602 log(output); 1603 return output; 1604 } 1605 1606 protected static class LogSeparator { 1607 private final String mUniqueString; 1608 1609 private LogSeparator() { 1610 mUniqueString = UUID.randomUUID().toString(); 1611 } 1612 1613 @Override 1614 public String toString() { 1615 return mUniqueString; 1616 } 1617 } 1618 1619 /** 1620 * Inserts a log separator so we can always find the starting point from where to evaluate 1621 * following logs. 1622 * 1623 * @return Unique log separator. 1624 */ 1625 protected LogSeparator separateLogs() { 1626 final LogSeparator logSeparator = new LogSeparator(); 1627 executeShellCommand("log -t " + LOG_SEPARATOR + " " + logSeparator); 1628 EventLog.writeEvent(EVENT_LOG_SEPARATOR_TAG, logSeparator.mUniqueString); 1629 return logSeparator; 1630 } 1631 1632 protected static String[] getDeviceLogsForComponents( 1633 LogSeparator logSeparator, String... logTags) { 1634 String filters = LOG_SEPARATOR + ":I "; 1635 for (String component : logTags) { 1636 filters += component + ":I "; 1637 } 1638 final String[] result = executeShellCommand("logcat -v brief -d " + filters + " *:S") 1639 .split("\\n"); 1640 if (logSeparator == null) { 1641 return result; 1642 } 1643 1644 // Make sure that we only check logs after the separator. 1645 int i = 0; 1646 boolean lookingForSeparator = true; 1647 while (i < result.length && lookingForSeparator) { 1648 if (result[i].contains(logSeparator.toString())) { 1649 lookingForSeparator = false; 1650 } 1651 i++; 1652 } 1653 final String[] filteredResult = new String[result.length - i]; 1654 for (int curPos = 0; i < result.length; curPos++, i++) { 1655 filteredResult[curPos] = result[i]; 1656 } 1657 return filteredResult; 1658 } 1659 1660 protected static List<Event> getEventLogsForComponents(LogSeparator logSeparator, int... tags) { 1661 List<Event> events = new ArrayList<>(); 1662 1663 int[] searchTags = Arrays.copyOf(tags, tags.length + 1); 1664 searchTags[searchTags.length - 1] = EVENT_LOG_SEPARATOR_TAG; 1665 1666 try { 1667 EventLog.readEvents(searchTags, events); 1668 } catch (IOException e) { 1669 fail("Could not read from event log." + e); 1670 } 1671 1672 for (Iterator<Event> itr = events.iterator(); itr.hasNext(); ) { 1673 Event event = itr.next(); 1674 itr.remove(); 1675 if (event.getTag() == EVENT_LOG_SEPARATOR_TAG && 1676 logSeparator.mUniqueString.equals(event.getData())) { 1677 break; 1678 } 1679 } 1680 return events; 1681 } 1682 1683 protected boolean supportsMultiDisplay() { 1684 return mContext.getPackageManager().hasSystemFeature( 1685 FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS); 1686 } 1687 1688 protected boolean supportsInstallableIme() { 1689 return mContext.getPackageManager().hasSystemFeature(FEATURE_INPUT_METHODS); 1690 } 1691 1692 static class CountSpec<T> { 1693 static final int DONT_CARE = Integer.MIN_VALUE; 1694 static final int EQUALS = 1; 1695 static final int GREATER_THAN = 2; 1696 static final int LESS_THAN = 3; 1697 1698 final T mEvent; 1699 final int mRule; 1700 final int mCount; 1701 final String mMessage; 1702 1703 CountSpec(T event, int rule, int count, String message) { 1704 mEvent = event; 1705 mRule = count == DONT_CARE ? DONT_CARE : rule; 1706 mCount = count; 1707 if (message != null) { 1708 mMessage = message; 1709 } else { 1710 switch (rule) { 1711 case EQUALS: 1712 mMessage = event + " must equal to " + count; 1713 break; 1714 case GREATER_THAN: 1715 mMessage = event + " must be greater than " + count; 1716 break; 1717 case LESS_THAN: 1718 mMessage = event + " must be less than " + count; 1719 break; 1720 default: 1721 mMessage = "Don't care"; 1722 } 1723 } 1724 } 1725 1726 /** @return {@code true} if the given value is satisfied the condition. */ 1727 boolean validate(int value) { 1728 switch (mRule) { 1729 case DONT_CARE: 1730 return true; 1731 case EQUALS: 1732 return value == mCount; 1733 case GREATER_THAN: 1734 return value > mCount; 1735 case LESS_THAN: 1736 return value < mCount; 1737 default: 1738 } 1739 throw new RuntimeException("Unknown CountSpec rule"); 1740 } 1741 } 1742 1743 static <T> CountSpec<T> countSpec(T event, int rule, int count, String message) { 1744 return new CountSpec<>(event, rule, count, message); 1745 } 1746 1747 static <T> CountSpec<T> countSpec(T event, int rule, int count) { 1748 return new CountSpec<>(event, rule, count, null /* message */); 1749 } 1750 1751 static void assertLifecycleCounts(ComponentName activityName, String message, 1752 int createCount, int startCount, int resumeCount, int pauseCount, int stopCount, 1753 int destroyCount, int configChangeCount) { 1754 new ActivityLifecycleCounts(activityName).assertCountWithRetry( 1755 message, 1756 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, createCount), 1757 countSpec(ActivityCallback.ON_START, CountSpec.EQUALS, startCount), 1758 countSpec(ActivityCallback.ON_RESUME, CountSpec.EQUALS, resumeCount), 1759 countSpec(ActivityCallback.ON_PAUSE, CountSpec.EQUALS, pauseCount), 1760 countSpec(ActivityCallback.ON_STOP, CountSpec.EQUALS, stopCount), 1761 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, destroyCount), 1762 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 1763 configChangeCount)); 1764 } 1765 1766 static void assertLifecycleCounts(ComponentName activityName, 1767 int createCount, int startCount, int resumeCount, int pauseCount, int stopCount, 1768 int destroyCount, int configChangeCount) { 1769 assertLifecycleCounts(activityName, "Assert lifecycle of " + getLogTag(activityName), 1770 createCount, startCount, resumeCount, pauseCount, stopCount, 1771 destroyCount, configChangeCount); 1772 } 1773 1774 static void assertSingleLaunch(ComponentName activityName) { 1775 assertLifecycleCounts(activityName, 1776 "activity create, start, and resume", 1777 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */, 1778 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */, 1779 CountSpec.DONT_CARE /* configChangeCount */); 1780 } 1781 1782 static void assertSingleLaunchAndStop(ComponentName activityName) { 1783 assertLifecycleCounts(activityName, 1784 "activity create, start, resume, pause, and stop", 1785 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */, 1786 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */, 1787 CountSpec.DONT_CARE /* configChangeCount */); 1788 } 1789 1790 static void assertSingleStartAndStop(ComponentName activityName) { 1791 assertLifecycleCounts(activityName, 1792 "activity start, resume, pause, and stop", 1793 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */, 1794 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */, 1795 CountSpec.DONT_CARE /* configChangeCount */); 1796 } 1797 1798 static void assertSingleStart(ComponentName activityName) { 1799 assertLifecycleCounts(activityName, 1800 "activity start and resume", 1801 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */, 1802 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */, 1803 CountSpec.DONT_CARE /* configChangeCount */); 1804 } 1805 1806 /** Assert the activity is either relaunched or received configuration changed. */ 1807 static void assertActivityLifecycle(ComponentName activityName, boolean relaunched) { 1808 Condition.<String>waitForResult(activityName + " relaunched", condition -> condition 1809 .setResultSupplier(() -> checkActivityIsRelaunchedOrConfigurationChanged( 1810 getActivityName(activityName), 1811 TestJournalContainer.get(activityName).callbacks, relaunched)) 1812 .setResultValidator(failedReasons -> failedReasons == null) 1813 .setOnFailure(failedReasons -> fail(failedReasons))); 1814 } 1815 1816 /** Assert the activity is either relaunched or received configuration changed. */ 1817 static List<ActivityCallback> assertActivityLifecycle(ActivitySession activitySession, 1818 boolean relaunched) { 1819 final String name = activitySession.getName(); 1820 final List<ActivityCallback> callbackHistory = activitySession.takeCallbackHistory(); 1821 String failedReason = checkActivityIsRelaunchedOrConfigurationChanged( 1822 name, callbackHistory, relaunched); 1823 if (failedReason != null) { 1824 fail(failedReason); 1825 } 1826 return callbackHistory; 1827 } 1828 1829 private static String checkActivityIsRelaunchedOrConfigurationChanged(String name, 1830 List<ActivityCallback> callbackHistory, boolean relaunched) { 1831 final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(callbackHistory); 1832 if (relaunched) { 1833 return lifecycles.validateCount( 1834 countSpec(ActivityCallback.ON_DESTROY, CountSpec.GREATER_THAN, 0, 1835 name + " must have been destroyed."), 1836 countSpec(ActivityCallback.ON_CREATE, CountSpec.GREATER_THAN, 0, 1837 name + " must have been (re)created.")); 1838 } 1839 return lifecycles.validateCount( 1840 countSpec(ActivityCallback.ON_DESTROY, CountSpec.LESS_THAN, 1, 1841 name + " must *NOT* have been destroyed."), 1842 countSpec(ActivityCallback.ON_CREATE, CountSpec.LESS_THAN, 1, 1843 name + " must *NOT* have been (re)created."), 1844 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.GREATER_THAN, 0, 1845 name + " must have received configuration changed.")); 1846 } 1847 1848 static void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch, 1849 int numConfigChange) { 1850 new ActivityLifecycleCounts(activityName).assertCountWithRetry("relaunch or config changed", 1851 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, numRelaunch), 1852 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, numRelaunch), 1853 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 1854 numConfigChange)); 1855 } 1856 1857 static void assertActivityDestroyed(ComponentName activityName) { 1858 new ActivityLifecycleCounts(activityName).assertCountWithRetry("activity destroyed", 1859 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, 1), 1860 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, 0), 1861 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0)); 1862 } 1863 1864 private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)"); 1865 private static final Pattern sUiModeLockedPattern = 1866 Pattern.compile("mUiModeLocked=(true|false)"); 1867 1868 @Nullable 1869 SizeInfo getLastReportedSizesForActivity(ComponentName activityName) { 1870 return Condition.waitForResult("sizes of " + activityName + " to be reported", 1871 condition -> condition.setResultSupplier(() -> { 1872 final ConfigInfo info = TestJournalContainer.get(activityName).lastConfigInfo; 1873 return info != null ? info.sizeInfo : null; 1874 }).setResultValidator(sizeInfo -> sizeInfo != null)); 1875 } 1876 1877 /** Check if a device has display cutout. */ 1878 boolean hasDisplayCutout() { 1879 // Launch an activity to report cutout state 1880 separateTestJournal(); 1881 launchActivity(BROADCAST_RECEIVER_ACTIVITY); 1882 1883 // Read the logs to check if cutout is present 1884 final Boolean displayCutoutPresent = getCutoutStateForActivity(BROADCAST_RECEIVER_ACTIVITY); 1885 assertNotNull("The activity should report cutout state", displayCutoutPresent); 1886 1887 // Finish activity 1888 mBroadcastActionTrigger.finishBroadcastReceiverActivity(); 1889 mWmState.waitForWithAmState( 1890 (state) -> !state.containsActivity(BROADCAST_RECEIVER_ACTIVITY), 1891 "activity to be removed"); 1892 1893 return displayCutoutPresent; 1894 } 1895 1896 /** 1897 * Wait for activity to report cutout state in logs and return it. Will return {@code null} 1898 * after timeout. 1899 */ 1900 @Nullable 1901 private Boolean getCutoutStateForActivity(ComponentName activityName) { 1902 return Condition.waitForResult("cutout state to be reported", condition -> condition 1903 .setResultSupplier(() -> { 1904 final Bundle extras = TestJournalContainer.get(activityName).extras; 1905 return extras.containsKey(EXTRA_CUTOUT_EXISTS) 1906 ? extras.getBoolean(EXTRA_CUTOUT_EXISTS) 1907 : null; 1908 }).setResultValidator(cutoutExists -> cutoutExists != null)); 1909 } 1910 1911 /** Waits for at least one onMultiWindowModeChanged event. */ 1912 ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName) { 1913 final ActivityLifecycleCounts counts = new ActivityLifecycleCounts(activityName); 1914 Condition.waitFor(counts.countWithRetry("waitForOnMultiWindowModeChanged", countSpec( 1915 ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED, CountSpec.GREATER_THAN, 0))); 1916 return counts; 1917 } 1918 1919 static class ActivityLifecycleCounts { 1920 private final int[] mCounts = new int[ActivityCallback.SIZE]; 1921 private final int[] mFirstIndexes = new int[ActivityCallback.SIZE]; 1922 private final int[] mLastIndexes = new int[ActivityCallback.SIZE]; 1923 private ComponentName mActivityName; 1924 1925 ActivityLifecycleCounts(ComponentName componentName) { 1926 mActivityName = componentName; 1927 updateCount(TestJournalContainer.get(componentName).callbacks); 1928 } 1929 1930 ActivityLifecycleCounts(List<ActivityCallback> callbacks) { 1931 updateCount(callbacks); 1932 } 1933 1934 private void updateCount(List<ActivityCallback> callbacks) { 1935 // The callback list could be from the reference of TestJournal. If we are counting for 1936 // retrying, there may be new data added to the list from other threads. 1937 TestJournalContainer.withThreadSafeAccess(() -> { 1938 Arrays.fill(mFirstIndexes, -1); 1939 for (int i = 0; i < callbacks.size(); i++) { 1940 final ActivityCallback callback = callbacks.get(i); 1941 final int ordinal = callback.ordinal(); 1942 mCounts[ordinal]++; 1943 mLastIndexes[ordinal] = i; 1944 if (mFirstIndexes[ordinal] == -1) { 1945 mFirstIndexes[ordinal] = i; 1946 } 1947 } 1948 }); 1949 } 1950 1951 int getCount(ActivityCallback callback) { 1952 return mCounts[callback.ordinal()]; 1953 } 1954 1955 int getFirstIndex(ActivityCallback callback) { 1956 return mFirstIndexes[callback.ordinal()]; 1957 } 1958 1959 int getLastIndex(ActivityCallback callback) { 1960 return mLastIndexes[callback.ordinal()]; 1961 } 1962 1963 @SafeVarargs 1964 final Condition<String> countWithRetry(String message, 1965 CountSpec<ActivityCallback>... countSpecs) { 1966 if (mActivityName == null) { 1967 throw new IllegalStateException( 1968 "It is meaningless to retry without specified activity"); 1969 } 1970 return new Condition<String>(message) 1971 .setOnRetry(() -> { 1972 Arrays.fill(mCounts, 0); 1973 Arrays.fill(mLastIndexes, 0); 1974 updateCount(TestJournalContainer.get(mActivityName).callbacks); 1975 }) 1976 .setResultSupplier(() -> validateCount(countSpecs)) 1977 .setResultValidator(failedReasons -> failedReasons == null); 1978 } 1979 1980 @SafeVarargs 1981 final void assertCountWithRetry(String message, CountSpec<ActivityCallback>... countSpecs) { 1982 if (mActivityName == null) { 1983 throw new IllegalStateException( 1984 "It is meaningless to retry without specified activity"); 1985 } 1986 Condition.<String>waitForResult(countWithRetry(message, countSpecs) 1987 .setOnFailure(failedReasons -> fail(message + ": " + failedReasons))); 1988 } 1989 1990 @SafeVarargs 1991 final String validateCount(CountSpec<ActivityCallback>... countSpecs) { 1992 ArrayList<String> failedReasons = null; 1993 for (CountSpec<ActivityCallback> spec : countSpecs) { 1994 final int realCount = mCounts[spec.mEvent.ordinal()]; 1995 if (!spec.validate(realCount)) { 1996 if (failedReasons == null) { 1997 failedReasons = new ArrayList<>(); 1998 } 1999 failedReasons.add(spec.mMessage + " (got " + realCount + ")"); 2000 } 2001 } 2002 return failedReasons == null ? null : String.join("\n", failedReasons); 2003 } 2004 } 2005 2006 protected void stopTestPackage(final String packageName) { 2007 SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(packageName)); 2008 } 2009 2010 protected LaunchActivityBuilder getLaunchActivityBuilder() { 2011 return new LaunchActivityBuilder(mWmState); 2012 } 2013 2014 protected static class LaunchActivityBuilder implements LaunchProxy { 2015 private final WindowManagerStateHelper mAmWmState; 2016 2017 // The activity to be launched 2018 private ComponentName mTargetActivity = TEST_ACTIVITY; 2019 private boolean mUseApplicationContext; 2020 private boolean mToSide; 2021 private boolean mRandomData; 2022 private boolean mNewTask; 2023 private boolean mMultipleTask; 2024 private boolean mAllowMultipleInstances = true; 2025 private boolean mLaunchTaskBehind; 2026 private boolean mFinishBeforeLaunch; 2027 private int mDisplayId = INVALID_DISPLAY; 2028 private int mWindowingMode = -1; 2029 private int mActivityType = ACTIVITY_TYPE_UNDEFINED; 2030 // A proxy activity that launches other activities including mTargetActivityName 2031 private ComponentName mLaunchingActivity = LAUNCHING_ACTIVITY; 2032 private boolean mReorderToFront; 2033 private boolean mWaitForLaunched; 2034 private boolean mSuppressExceptions; 2035 private boolean mWithShellPermission; 2036 // Use of the following variables indicates that a broadcast receiver should be used instead 2037 // of a launching activity; 2038 private ComponentName mBroadcastReceiver; 2039 private String mBroadcastReceiverAction; 2040 private int mIntentFlags; 2041 private Bundle mExtras; 2042 private LaunchInjector mLaunchInjector; 2043 2044 private enum LauncherType { 2045 INSTRUMENTATION, LAUNCHING_ACTIVITY, BROADCAST_RECEIVER 2046 } 2047 2048 private LauncherType mLauncherType = LauncherType.LAUNCHING_ACTIVITY; 2049 2050 public LaunchActivityBuilder(WindowManagerStateHelper amWmState) { 2051 mAmWmState = amWmState; 2052 mWaitForLaunched = true; 2053 mWithShellPermission = true; 2054 } 2055 2056 public LaunchActivityBuilder setToSide(boolean toSide) { 2057 mToSide = toSide; 2058 return this; 2059 } 2060 2061 public LaunchActivityBuilder setRandomData(boolean randomData) { 2062 mRandomData = randomData; 2063 return this; 2064 } 2065 2066 public LaunchActivityBuilder setNewTask(boolean newTask) { 2067 mNewTask = newTask; 2068 return this; 2069 } 2070 2071 public LaunchActivityBuilder setMultipleTask(boolean multipleTask) { 2072 mMultipleTask = multipleTask; 2073 return this; 2074 } 2075 2076 public LaunchActivityBuilder allowMultipleInstances(boolean allowMultipleInstances) { 2077 mAllowMultipleInstances = allowMultipleInstances; 2078 return this; 2079 } 2080 2081 public LaunchActivityBuilder setLaunchTaskBehind(boolean launchTaskBehind) { 2082 mLaunchTaskBehind = launchTaskBehind; 2083 return this; 2084 } 2085 2086 public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) { 2087 mReorderToFront = reorderToFront; 2088 return this; 2089 } 2090 2091 public LaunchActivityBuilder setUseApplicationContext(boolean useApplicationContext) { 2092 mUseApplicationContext = useApplicationContext; 2093 return this; 2094 } 2095 2096 public LaunchActivityBuilder setFinishBeforeLaunch(boolean finishBeforeLaunch) { 2097 mFinishBeforeLaunch = finishBeforeLaunch; 2098 return this; 2099 } 2100 2101 public ComponentName getTargetActivity() { 2102 return mTargetActivity; 2103 } 2104 2105 public boolean isTargetActivityTranslucent() { 2106 return mAmWmState.isActivityTranslucent(mTargetActivity); 2107 } 2108 2109 public LaunchActivityBuilder setTargetActivity(ComponentName targetActivity) { 2110 mTargetActivity = targetActivity; 2111 return this; 2112 } 2113 2114 public LaunchActivityBuilder setDisplayId(int id) { 2115 mDisplayId = id; 2116 return this; 2117 } 2118 2119 public LaunchActivityBuilder setWindowingMode(int windowingMode) { 2120 mWindowingMode = windowingMode; 2121 return this; 2122 } 2123 2124 public LaunchActivityBuilder setActivityType(int type) { 2125 mActivityType = type; 2126 return this; 2127 } 2128 2129 public LaunchActivityBuilder setLaunchingActivity(ComponentName launchingActivity) { 2130 mLaunchingActivity = launchingActivity; 2131 mLauncherType = LauncherType.LAUNCHING_ACTIVITY; 2132 return this; 2133 } 2134 2135 public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) { 2136 mWaitForLaunched = shouldWait; 2137 return this; 2138 } 2139 2140 /** Use broadcast receiver as a launchpad for activities. */ 2141 public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver, 2142 final String broadcastAction) { 2143 mBroadcastReceiver = broadcastReceiver; 2144 mBroadcastReceiverAction = broadcastAction; 2145 mLauncherType = LauncherType.BROADCAST_RECEIVER; 2146 return this; 2147 } 2148 2149 /** Use {@link android.app.Instrumentation} as a launchpad for activities. */ 2150 public LaunchActivityBuilder setUseInstrumentation() { 2151 mLauncherType = LauncherType.INSTRUMENTATION; 2152 // Calling startActivity() from outside of an Activity context requires the 2153 // FLAG_ACTIVITY_NEW_TASK flag. 2154 setNewTask(true); 2155 return this; 2156 } 2157 2158 public LaunchActivityBuilder setSuppressExceptions(boolean suppress) { 2159 mSuppressExceptions = suppress; 2160 return this; 2161 } 2162 2163 public LaunchActivityBuilder setWithShellPermission(boolean withShellPermission) { 2164 mWithShellPermission = withShellPermission; 2165 return this; 2166 } 2167 2168 @Override 2169 public boolean shouldWaitForLaunched() { 2170 return mWaitForLaunched; 2171 } 2172 2173 public LaunchActivityBuilder setIntentFlags(int flags) { 2174 mIntentFlags = flags; 2175 return this; 2176 } 2177 2178 public LaunchActivityBuilder setIntentExtra(Consumer<Bundle> extrasConsumer) { 2179 if (extrasConsumer != null) { 2180 mExtras = new Bundle(); 2181 extrasConsumer.accept(mExtras); 2182 } 2183 return this; 2184 } 2185 2186 @Override 2187 public Bundle getExtras() { 2188 return mExtras; 2189 } 2190 2191 @Override 2192 public void setLaunchInjector(LaunchInjector injector) { 2193 mLaunchInjector = injector; 2194 } 2195 2196 @Override 2197 public void execute() { 2198 switch (mLauncherType) { 2199 case INSTRUMENTATION: 2200 if (mWithShellPermission) { 2201 SystemUtil.runWithShellPermissionIdentity(this::launchUsingInstrumentation); 2202 } else { 2203 launchUsingInstrumentation(); 2204 } 2205 break; 2206 case LAUNCHING_ACTIVITY: 2207 case BROADCAST_RECEIVER: 2208 launchUsingShellCommand(); 2209 } 2210 2211 if (mWaitForLaunched) { 2212 mAmWmState.waitForValidState(mTargetActivity); 2213 } 2214 } 2215 2216 /** Launch an activity using instrumentation. */ 2217 private void launchUsingInstrumentation() { 2218 final Bundle b = new Bundle(); 2219 b.putBoolean(KEY_LAUNCH_ACTIVITY, true); 2220 b.putBoolean(KEY_LAUNCH_TO_SIDE, mToSide); 2221 b.putBoolean(KEY_RANDOM_DATA, mRandomData); 2222 b.putBoolean(KEY_NEW_TASK, mNewTask); 2223 b.putBoolean(KEY_MULTIPLE_TASK, mMultipleTask); 2224 b.putBoolean(KEY_MULTIPLE_INSTANCES, mAllowMultipleInstances); 2225 b.putBoolean(KEY_LAUNCH_TASK_BEHIND, mLaunchTaskBehind); 2226 b.putBoolean(KEY_REORDER_TO_FRONT, mReorderToFront); 2227 b.putInt(KEY_DISPLAY_ID, mDisplayId); 2228 b.putInt(KEY_WINDOWING_MODE, mWindowingMode); 2229 b.putInt(KEY_ACTIVITY_TYPE, mActivityType); 2230 b.putBoolean(KEY_USE_APPLICATION_CONTEXT, mUseApplicationContext); 2231 b.putString(KEY_TARGET_COMPONENT, getActivityName(mTargetActivity)); 2232 b.putBoolean(KEY_SUPPRESS_EXCEPTIONS, mSuppressExceptions); 2233 b.putInt(KEY_INTENT_FLAGS, mIntentFlags); 2234 b.putBundle(KEY_INTENT_EXTRAS, getExtras()); 2235 final Context context = getInstrumentation().getContext(); 2236 launchActivityFromExtras(context, b, mLaunchInjector); 2237 } 2238 2239 /** Build and execute a shell command to launch an activity. */ 2240 private void launchUsingShellCommand() { 2241 StringBuilder commandBuilder = new StringBuilder(); 2242 if (mBroadcastReceiver != null && mBroadcastReceiverAction != null) { 2243 // Use broadcast receiver to launch the target. 2244 commandBuilder.append("am broadcast -a ").append(mBroadcastReceiverAction) 2245 .append(" -p ").append(mBroadcastReceiver.getPackageName()) 2246 // Include stopped packages 2247 .append(" -f 0x00000020"); 2248 } else { 2249 // If new task flag isn't set the windowing mode of launcher activity will be the 2250 // windowing mode of the target activity, so we need to launch launcher activity in 2251 // it. 2252 String amStartCmd = 2253 (mWindowingMode == -1 || mNewTask) 2254 ? getAmStartCmd(mLaunchingActivity) 2255 : getAmStartCmd(mLaunchingActivity, mWindowingMode); 2256 // Use launching activity to launch the target. 2257 commandBuilder.append(amStartCmd) 2258 .append(" -f 0x20000020"); 2259 } 2260 2261 // Add a flag to ensure we actually mean to launch an activity. 2262 commandBuilder.append(" --ez " + KEY_LAUNCH_ACTIVITY + " true"); 2263 2264 if (mToSide) { 2265 commandBuilder.append(" --ez " + KEY_LAUNCH_TO_SIDE + " true"); 2266 } 2267 if (mRandomData) { 2268 commandBuilder.append(" --ez " + KEY_RANDOM_DATA + " true"); 2269 } 2270 if (mNewTask) { 2271 commandBuilder.append(" --ez " + KEY_NEW_TASK + " true"); 2272 } 2273 if (mMultipleTask) { 2274 commandBuilder.append(" --ez " + KEY_MULTIPLE_TASK + " true"); 2275 } 2276 if (mAllowMultipleInstances) { 2277 commandBuilder.append(" --ez " + KEY_MULTIPLE_INSTANCES + " true"); 2278 } 2279 if (mReorderToFront) { 2280 commandBuilder.append(" --ez " + KEY_REORDER_TO_FRONT + " true"); 2281 } 2282 if (mFinishBeforeLaunch) { 2283 commandBuilder.append(" --ez " + KEY_FINISH_BEFORE_LAUNCH + " true"); 2284 } 2285 if (mDisplayId != INVALID_DISPLAY) { 2286 commandBuilder.append(" --ei " + KEY_DISPLAY_ID + " ").append(mDisplayId); 2287 } 2288 if (mWindowingMode != -1) { 2289 commandBuilder.append(" --ei " + KEY_WINDOWING_MODE + " ").append(mWindowingMode); 2290 } 2291 if (mActivityType != ACTIVITY_TYPE_UNDEFINED) { 2292 commandBuilder.append(" --ei " + KEY_ACTIVITY_TYPE + " ").append(mActivityType); 2293 } 2294 2295 if (mUseApplicationContext) { 2296 commandBuilder.append(" --ez " + KEY_USE_APPLICATION_CONTEXT + " true"); 2297 } 2298 2299 if (mTargetActivity != null) { 2300 // {@link ActivityLauncher} parses this extra string by 2301 // {@link ComponentName#unflattenFromString(String)}. 2302 commandBuilder.append(" --es " + KEY_TARGET_COMPONENT + " ") 2303 .append(getActivityName(mTargetActivity)); 2304 } 2305 2306 if (mSuppressExceptions) { 2307 commandBuilder.append(" --ez " + KEY_SUPPRESS_EXCEPTIONS + " true"); 2308 } 2309 2310 if (mIntentFlags != 0) { 2311 commandBuilder.append(" --ei " + KEY_INTENT_FLAGS + " ").append(mIntentFlags); 2312 } 2313 2314 if (mLaunchInjector != null) { 2315 commandBuilder.append(" --ez " + KEY_FORWARD + " true"); 2316 mLaunchInjector.setupShellCommand(commandBuilder); 2317 } 2318 executeShellCommand(commandBuilder.toString()); 2319 } 2320 } 2321 2322 /** 2323 * The actions which wraps a test method. It is used to set necessary rules that cannot be 2324 * overridden by subclasses. It executes in the outer scope of {@link Before} and {@link After}. 2325 */ 2326 protected class WrapperRule implements TestRule { 2327 private final Runnable mBefore; 2328 private final Runnable mAfter; 2329 2330 protected WrapperRule(Runnable before, Runnable after) { 2331 mBefore = before; 2332 mAfter = after; 2333 } 2334 2335 @Override 2336 public Statement apply(final Statement base, final Description description) { 2337 return new Statement() { 2338 @Override 2339 public void evaluate() { 2340 if (mBefore != null) { 2341 mBefore.run(); 2342 } 2343 try { 2344 base.evaluate(); 2345 } catch (Throwable e) { 2346 mPostAssertionRule.addError(e); 2347 } finally { 2348 if (mAfter != null) { 2349 mAfter.run(); 2350 } 2351 } 2352 } 2353 }; 2354 } 2355 } 2356 2357 /** 2358 * The post assertion to ensure all test methods don't violate the generic rule. It is also used 2359 * to collect multiple errors. 2360 */ 2361 private class PostAssertionRule extends ErrorCollector { 2362 @Override 2363 protected void verify() throws Throwable { 2364 if (!sStackTaskLeakFound) { 2365 // Skip empty stack/task check if a leakage was already found in previous test, or 2366 // all tests afterward would also fail (since the leakage is always there) and fire 2367 // unnecessary false alarms. 2368 try { 2369 mWmState.assertNoneEmptyTasks(); 2370 } catch (Throwable t) { 2371 sStackTaskLeakFound = true; 2372 addError(t); 2373 } 2374 } 2375 super.verify(); 2376 } 2377 } 2378 2379 /** 2380 * Activity used in place of recents when home is the recents component. It should only be used 2381 * by {@link #moveTaskToPrimarySplitScreen}. 2382 */ 2383 public static class SideActivity extends Activity { 2384 } 2385 2386 /** Activity that can handle all config changes. */ 2387 public static class ConfigChangeHandlingActivity extends CommandSession.BasicTestActivity { 2388 } 2389 } 2390