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