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