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