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