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