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