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