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.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT; 20 import static android.app.Instrumentation.ActivityMonitor; 21 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT; 22 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; 23 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 24 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; 25 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY; 26 import static android.content.Intent.ACTION_MAIN; 27 import static android.content.Intent.CATEGORY_HOME; 28 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; 29 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; 30 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 31 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; 32 import static android.content.pm.PackageManager.DONT_KILL_APP; 33 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS; 34 import static android.content.pm.PackageManager.FEATURE_AUTOMOTIVE; 35 import static android.content.pm.PackageManager.FEATURE_EMBEDDED; 36 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT; 37 import static android.content.pm.PackageManager.FEATURE_LEANBACK; 38 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; 39 import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE; 40 import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT; 41 import static android.content.pm.PackageManager.FEATURE_SECURE_LOCK_SCREEN; 42 import static android.content.pm.PackageManager.FEATURE_VR_MODE_HIGH_PERFORMANCE; 43 import static android.content.pm.PackageManager.FEATURE_WATCH; 44 import static android.content.pm.PackageManager.MATCH_DEFAULT_ONLY; 45 import static android.server.wm.ActivityLauncher.KEY_ACTIVITY_TYPE; 46 import static android.server.wm.ActivityLauncher.KEY_DISPLAY_ID; 47 import static android.server.wm.ActivityLauncher.KEY_INTENT_EXTRAS; 48 import static android.server.wm.ActivityLauncher.KEY_INTENT_FLAGS; 49 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY; 50 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_TO_SIDE; 51 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_INSTANCES; 52 import static android.server.wm.ActivityLauncher.KEY_MULTIPLE_TASK; 53 import static android.server.wm.ActivityLauncher.KEY_NEW_TASK; 54 import static android.server.wm.ActivityLauncher.KEY_RANDOM_DATA; 55 import static android.server.wm.ActivityLauncher.KEY_REORDER_TO_FRONT; 56 import static android.server.wm.ActivityLauncher.KEY_SUPPRESS_EXCEPTIONS; 57 import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT; 58 import static android.server.wm.ActivityLauncher.KEY_USE_APPLICATION_CONTEXT; 59 import static android.server.wm.ActivityLauncher.KEY_USE_INSTRUMENTATION; 60 import static android.server.wm.ActivityLauncher.launchActivityFromExtras; 61 import static android.server.wm.ActivityManagerState.STATE_RESUMED; 62 import static android.server.wm.ComponentNameUtils.getActivityName; 63 import static android.server.wm.ComponentNameUtils.getLogTag; 64 import static android.server.wm.StateLogger.log; 65 import static android.server.wm.StateLogger.logAlways; 66 import static android.server.wm.StateLogger.logE; 67 import static android.server.wm.UiDeviceUtils.pressAppSwitchButton; 68 import static android.server.wm.UiDeviceUtils.pressBackButton; 69 import static android.server.wm.UiDeviceUtils.pressEnterButton; 70 import static android.server.wm.UiDeviceUtils.pressHomeButton; 71 import static android.server.wm.UiDeviceUtils.pressSleepButton; 72 import static android.server.wm.UiDeviceUtils.pressUnlockButton; 73 import static android.server.wm.UiDeviceUtils.pressWakeupButton; 74 import static android.server.wm.UiDeviceUtils.waitForDeviceIdle; 75 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY; 76 import static android.server.wm.app.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST; 77 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION; 78 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_CUTOUT_EXISTS; 79 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD; 80 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_DISMISS_KEYGUARD_METHOD; 81 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_FINISH_BROADCAST; 82 import static android.server.wm.app.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK; 83 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY; 84 import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP; 85 import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION; 86 import static android.server.wm.app.Components.PipActivity.EXTRA_PIP_ORIENTATION; 87 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR; 88 import static android.server.wm.app.Components.PipActivity.EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR; 89 import static android.server.wm.app.Components.TEST_ACTIVITY; 90 import static android.server.wm.second.Components.SECOND_ACTIVITY; 91 import static android.server.wm.third.Components.THIRD_ACTIVITY; 92 import static android.view.Display.DEFAULT_DISPLAY; 93 import static android.view.Display.INVALID_DISPLAY; 94 import static android.view.Surface.ROTATION_0; 95 96 import static androidx.test.InstrumentationRegistry.getInstrumentation; 97 98 import static org.junit.Assert.assertEquals; 99 import static org.junit.Assert.assertNotNull; 100 import static org.junit.Assert.assertTrue; 101 import static org.junit.Assert.fail; 102 103 import static java.lang.Integer.toHexString; 104 105 import android.accessibilityservice.AccessibilityService; 106 import android.app.Activity; 107 import android.app.ActivityManager; 108 import android.app.ActivityOptions; 109 import android.app.ActivityTaskManager; 110 import android.content.ComponentName; 111 import android.content.ContentResolver; 112 import android.content.Context; 113 import android.content.Intent; 114 import android.content.pm.PackageManager; 115 import android.content.pm.ResolveInfo; 116 import android.content.res.Resources; 117 import android.database.ContentObserver; 118 import android.graphics.Bitmap; 119 import android.graphics.Rect; 120 import android.hardware.display.AmbientDisplayConfiguration; 121 import android.hardware.display.DisplayManager; 122 import android.os.Bundle; 123 import android.os.Handler; 124 import android.os.HandlerThread; 125 import android.os.SystemClock; 126 import android.os.UserHandle; 127 import android.provider.Settings; 128 import android.server.wm.CommandSession.ActivityCallback; 129 import android.server.wm.CommandSession.ActivitySession; 130 import android.server.wm.CommandSession.ConfigInfo; 131 import android.server.wm.CommandSession.LaunchInjector; 132 import android.server.wm.CommandSession.LaunchProxy; 133 import android.server.wm.CommandSession.SizeInfo; 134 import android.server.wm.TestJournalProvider.TestJournalContainer; 135 import android.server.wm.settings.SettingsSession; 136 import android.util.EventLog; 137 import android.util.EventLog.Event; 138 import android.view.Display; 139 import android.view.InputDevice; 140 import android.view.MotionEvent; 141 142 import androidx.annotation.NonNull; 143 import androidx.annotation.Nullable; 144 import androidx.test.rule.ActivityTestRule; 145 146 import com.android.compatibility.common.util.SystemUtil; 147 148 import org.junit.After; 149 import org.junit.Before; 150 import org.junit.Rule; 151 import org.junit.rules.TestRule; 152 import org.junit.runner.Description; 153 import org.junit.runners.model.Statement; 154 155 import java.io.IOException; 156 import java.util.ArrayList; 157 import java.util.Arrays; 158 import java.util.Collections; 159 import java.util.HashMap; 160 import java.util.HashSet; 161 import java.util.Iterator; 162 import java.util.List; 163 import java.util.Map; 164 import java.util.UUID; 165 import java.util.concurrent.Callable; 166 import java.util.concurrent.TimeUnit; 167 import java.util.concurrent.atomic.AtomicBoolean; 168 import java.util.function.BooleanSupplier; 169 import java.util.function.Consumer; 170 import java.util.regex.Matcher; 171 import java.util.regex.Pattern; 172 173 public abstract class ActivityManagerTestBase { 174 private static final boolean PRETEND_DEVICE_SUPPORTS_PIP = false; 175 private static final boolean PRETEND_DEVICE_SUPPORTS_FREEFORM = false; 176 private static final String LOG_SEPARATOR = "LOG_SEPARATOR"; 177 // Use one of the test tags as a separator 178 private static final int EVENT_LOG_SEPARATOR_TAG = 42; 179 180 protected static final int[] ALL_ACTIVITY_TYPE_BUT_HOME = { 181 ACTIVITY_TYPE_STANDARD, ACTIVITY_TYPE_ASSISTANT, ACTIVITY_TYPE_RECENTS, 182 ACTIVITY_TYPE_UNDEFINED 183 }; 184 185 private static final String TEST_PACKAGE = TEST_ACTIVITY.getPackageName(); 186 private static final String SECOND_TEST_PACKAGE = SECOND_ACTIVITY.getPackageName(); 187 private static final String THIRD_TEST_PACKAGE = THIRD_ACTIVITY.getPackageName(); 188 private static final List<String> TEST_PACKAGES; 189 190 static { 191 final List<String> testPackages = new ArrayList<>(3); 192 testPackages.add(TEST_PACKAGE); 193 testPackages.add(SECOND_TEST_PACKAGE); 194 testPackages.add(THIRD_TEST_PACKAGE); 195 testPackages.add("android.server.wm.cts"); 196 TEST_PACKAGES = Collections.unmodifiableList(testPackages); 197 } 198 199 protected static final String AM_START_HOME_ACTIVITY_COMMAND = 200 "am start -a android.intent.action.MAIN -c android.intent.category.HOME"; 201 202 private static final String LOCK_CREDENTIAL = "1234"; 203 204 private static final int UI_MODE_TYPE_MASK = 0x0f; 205 private static final int UI_MODE_TYPE_VR_HEADSET = 0x07; 206 207 private static Boolean sHasHomeScreen = null; 208 private static Boolean sSupportsSystemDecorsOnSecondaryDisplays = null; 209 private static Boolean sSupportsInsecureLockScreen = null; 210 211 protected static final int INVALID_DEVICE_ROTATION = -1; 212 213 protected Context mContext; 214 protected ActivityManager mAm; 215 protected ActivityTaskManager mAtm; 216 217 /** 218 * Callable to clear launch params for all test packages. 219 */ 220 private final Callable<Void> mClearLaunchParamsCallable = () -> { 221 mAtm.clearLaunchParamsForPackages(TEST_PACKAGES); 222 return null; 223 }; 224 225 @Rule 226 public final ActivityTestRule<SideActivity> mSideActivityRule = 227 new ActivityTestRule<>(SideActivity.class, true /* initialTouchMode */, 228 false /* launchActivity */); 229 230 /** 231 * @return the am command to start the given activity with the following extra key/value pairs. 232 * {@param keyValuePairs} must be a list of arguments defining each key/value extra. 233 */ 234 // TODO: Make this more generic, for instance accepting flags or extras of other types. getAmStartCmd(final ComponentName activityName, final String... keyValuePairs)235 protected static String getAmStartCmd(final ComponentName activityName, 236 final String... keyValuePairs) { 237 return getAmStartCmdInternal(getActivityName(activityName), keyValuePairs); 238 } 239 getAmStartCmdInternal(final String activityName, final String... keyValuePairs)240 private static String getAmStartCmdInternal(final String activityName, 241 final String... keyValuePairs) { 242 return appendKeyValuePairs( 243 new StringBuilder("am start -n ").append(activityName), 244 keyValuePairs); 245 } 246 appendKeyValuePairs( final StringBuilder cmd, final String... keyValuePairs)247 private static String appendKeyValuePairs( 248 final StringBuilder cmd, final String... keyValuePairs) { 249 if (keyValuePairs.length % 2 != 0) { 250 throw new RuntimeException("keyValuePairs must be pairs of key/value arguments"); 251 } 252 for (int i = 0; i < keyValuePairs.length; i += 2) { 253 final String key = keyValuePairs[i]; 254 final String value = keyValuePairs[i + 1]; 255 cmd.append(" --es ") 256 .append(key) 257 .append(" ") 258 .append(value); 259 } 260 return cmd.toString(); 261 } 262 getAmStartCmd(final ComponentName activityName, final int displayId, final String... keyValuePair)263 protected static String getAmStartCmd(final ComponentName activityName, final int displayId, 264 final String... keyValuePair) { 265 return getAmStartCmdInternal(getActivityName(activityName), displayId, keyValuePair); 266 } 267 getAmStartCmdInternal(final String activityName, final int displayId, final String... keyValuePairs)268 private static String getAmStartCmdInternal(final String activityName, final int displayId, 269 final String... keyValuePairs) { 270 return appendKeyValuePairs( 271 new StringBuilder("am start -n ") 272 .append(activityName) 273 .append(" -f 0x") 274 .append(toHexString(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK)) 275 .append(" --display ") 276 .append(displayId), 277 keyValuePairs); 278 } 279 getAmStartCmdInNewTask(final ComponentName activityName)280 protected static String getAmStartCmdInNewTask(final ComponentName activityName) { 281 return "am start -n " + getActivityName(activityName) + " -f 0x18000000"; 282 } 283 getAmStartCmdOverHome(final ComponentName activityName)284 protected static String getAmStartCmdOverHome(final ComponentName activityName) { 285 return "am start --activity-task-on-home -n " + getActivityName(activityName); 286 } 287 288 protected ActivityAndWindowManagersState mAmWmState = new ActivityAndWindowManagersState(); 289 getAmWmState()290 public ActivityAndWindowManagersState getAmWmState() { 291 return mAmWmState; 292 } 293 294 protected BroadcastActionTrigger mBroadcastActionTrigger = new BroadcastActionTrigger(); 295 296 /** 297 * Helper class to process test actions by broadcast. 298 */ 299 protected class BroadcastActionTrigger { 300 createIntentWithAction(String broadcastAction)301 private Intent createIntentWithAction(String broadcastAction) { 302 return new Intent(broadcastAction) 303 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND); 304 } 305 doAction(String broadcastAction)306 void doAction(String broadcastAction) { 307 mContext.sendBroadcast(createIntentWithAction(broadcastAction)); 308 } 309 finishBroadcastReceiverActivity()310 void finishBroadcastReceiverActivity() { 311 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 312 .putExtra(EXTRA_FINISH_BROADCAST, true)); 313 } 314 launchActivityNewTask(String launchComponent)315 void launchActivityNewTask(String launchComponent) { 316 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 317 .putExtra(KEY_LAUNCH_ACTIVITY, true) 318 .putExtra(KEY_NEW_TASK, true) 319 .putExtra(KEY_TARGET_COMPONENT, launchComponent)); 320 } 321 moveTopTaskToBack()322 void moveTopTaskToBack() { 323 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 324 .putExtra(EXTRA_MOVE_BROADCAST_TO_BACK, true)); 325 } 326 requestOrientation(int orientation)327 void requestOrientation(int orientation) { 328 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 329 .putExtra(EXTRA_BROADCAST_ORIENTATION, orientation)); 330 } 331 dismissKeyguardByFlag()332 void dismissKeyguardByFlag() { 333 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 334 .putExtra(EXTRA_DISMISS_KEYGUARD, true)); 335 } 336 dismissKeyguardByMethod()337 void dismissKeyguardByMethod() { 338 mContext.sendBroadcast(createIntentWithAction(ACTION_TRIGGER_BROADCAST) 339 .putExtra(EXTRA_DISMISS_KEYGUARD_METHOD, true)); 340 } 341 expandPipWithAspectRatio(String extraNum, String extraDenom)342 void expandPipWithAspectRatio(String extraNum, String extraDenom) { 343 mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP) 344 .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_NUMERATOR, extraNum) 345 .putExtra(EXTRA_SET_ASPECT_RATIO_WITH_DELAY_DENOMINATOR, extraDenom)); 346 } 347 requestOrientationForPip(int orientation)348 void requestOrientationForPip(int orientation) { 349 mContext.sendBroadcast(createIntentWithAction(ACTION_SET_REQUESTED_ORIENTATION) 350 .putExtra(EXTRA_PIP_ORIENTATION, String.valueOf(orientation))); 351 } 352 } 353 354 /** 355 * Helper class to launch / close test activity by instrumentation way. 356 */ 357 protected class TestActivitySession<T extends Activity> implements AutoCloseable { 358 private T mTestActivity; 359 boolean mFinishAfterClose; 360 private static final int ACTIVITY_LAUNCH_TIMEOUT = 10000; 361 private static final int WAIT_SLICE = 50; 362 launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId)363 void launchTestActivityOnDisplaySync(Class<T> activityClass, int displayId) { 364 launchTestActivityOnDisplaySync(new Intent(mContext, activityClass), displayId); 365 } 366 launchTestActivityOnDisplaySync(Intent intent, int displayId)367 void launchTestActivityOnDisplaySync(Intent intent, int displayId) { 368 SystemUtil.runWithShellPermissionIdentity(() -> { 369 final Bundle bundle = ActivityOptions.makeBasic() 370 .setLaunchDisplayId(displayId).toBundle(); 371 final ActivityMonitor monitor = getInstrumentation() 372 .addMonitor((String) null, null, false); 373 mContext.startActivity(intent.addFlags(FLAG_ACTIVITY_NEW_TASK), bundle); 374 // Wait for activity launch with timeout. 375 mTestActivity = (T) monitor.waitForActivityWithTimeout(ACTIVITY_LAUNCH_TIMEOUT); 376 assertNotNull(mTestActivity); 377 // Check activity is launched and resumed. 378 final ComponentName testActivityName = mTestActivity.getComponentName(); 379 waitAndAssertTopResumedActivity(testActivityName, displayId, 380 "Activity must be resumed"); 381 }); 382 } 383 finishCurrentActivityNoWait()384 void finishCurrentActivityNoWait() { 385 if (mTestActivity != null) { 386 mTestActivity.finishAndRemoveTask(); 387 mTestActivity = null; 388 } 389 } 390 runOnMainSyncAndWait(Runnable runnable)391 void runOnMainSyncAndWait(Runnable runnable) { 392 getInstrumentation().runOnMainSync(runnable); 393 getInstrumentation().waitForIdleSync(); 394 } 395 runOnMainAndAssertWithTimeout(@onNull BooleanSupplier condition, long timeoutMs, String message)396 void runOnMainAndAssertWithTimeout(@NonNull BooleanSupplier condition, long timeoutMs, 397 String message) { 398 final AtomicBoolean result = new AtomicBoolean(); 399 final long expiredTime = System.currentTimeMillis() + timeoutMs; 400 while (!result.get()) { 401 if (System.currentTimeMillis() >= expiredTime) { 402 fail(message); 403 } 404 runOnMainSyncAndWait(() -> { 405 if (condition.getAsBoolean()) { 406 result.set(true); 407 } 408 }); 409 SystemClock.sleep(WAIT_SLICE); 410 } 411 } 412 getActivity()413 T getActivity() { 414 return mTestActivity; 415 } 416 417 @Override close()418 public void close() throws Exception { 419 if (mTestActivity != null && mFinishAfterClose) { 420 mTestActivity.finishAndRemoveTask(); 421 } 422 } 423 } 424 425 @Before setUp()426 public void setUp() throws Exception { 427 mContext = getInstrumentation().getContext(); 428 mAm = mContext.getSystemService(ActivityManager.class); 429 mAtm = mContext.getSystemService(ActivityTaskManager.class); 430 431 pressWakeupButton(); 432 pressUnlockButton(); 433 pressHomeButton(); 434 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 435 436 // Clear launch params for all test packages to make sure each test is run in a clean state. 437 SystemUtil.callWithShellPermissionIdentity(mClearLaunchParamsCallable); 438 } 439 440 @After tearDown()441 public void tearDown() throws Exception { 442 // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but 443 // home are cleaned up from the stack at the end of each test. Am force stop shell commands 444 // might be asynchronous and could interrupt the stack cleanup process if executed first. 445 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 446 stopTestPackage(TEST_PACKAGE); 447 stopTestPackage(SECOND_TEST_PACKAGE); 448 stopTestPackage(THIRD_TEST_PACKAGE); 449 pressHomeButton(); 450 451 } 452 moveTopActivityToPinnedStack(int stackId)453 protected void moveTopActivityToPinnedStack(int stackId) { 454 SystemUtil.runWithShellPermissionIdentity( 455 () -> mAtm.moveTopActivityToPinnedStack(stackId, new Rect(0, 0, 500, 500)) 456 ); 457 } 458 startActivityOnDisplay(int displayId, ComponentName component)459 protected void startActivityOnDisplay(int displayId, ComponentName component) { 460 final ActivityOptions options = ActivityOptions.makeBasic(); 461 options.setLaunchDisplayId(displayId); 462 463 mContext.startActivity(new Intent().addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 464 .setComponent(component), options.toBundle()); 465 } 466 noHomeScreen()467 protected boolean noHomeScreen() { 468 try { 469 return mContext.getResources().getBoolean( 470 Resources.getSystem().getIdentifier("config_noHomeScreen", "bool", 471 "android")); 472 } catch (Resources.NotFoundException e) { 473 // Assume there's a home screen. 474 return false; 475 } 476 } 477 getSupportsSystemDecorsOnSecondaryDisplays()478 private boolean getSupportsSystemDecorsOnSecondaryDisplays() { 479 try { 480 return mContext.getResources().getBoolean( 481 Resources.getSystem().getIdentifier( 482 "config_supportsSystemDecorsOnSecondaryDisplays", "bool", "android")); 483 } catch (Resources.NotFoundException e) { 484 // Assume this device support system decorations. 485 return true; 486 } 487 } 488 getDefaultSecondaryHomeComponent()489 protected ComponentName getDefaultSecondaryHomeComponent() { 490 int resId = Resources.getSystem().getIdentifier( 491 "config_secondaryHomeComponent", "string", "android"); 492 return ComponentName.unflattenFromString(mContext.getResources().getString(resId)); 493 } 494 tapOnDisplay(int x, int y, int displayId)495 protected void tapOnDisplay(int x, int y, int displayId) { 496 final long downTime = SystemClock.uptimeMillis(); 497 injectMotion(downTime, downTime, MotionEvent.ACTION_DOWN, x, y, displayId); 498 499 final long upTime = SystemClock.uptimeMillis(); 500 injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId); 501 } 502 tapOnStackCenter(ActivityManagerState.ActivityStack stack)503 protected void tapOnStackCenter(ActivityManagerState.ActivityStack stack) { 504 final Rect sideStackBounds = stack.getBounds(); 505 final int tapX = sideStackBounds.left + sideStackBounds.width() / 2; 506 final int tapY = sideStackBounds.top + sideStackBounds.height() / 2; 507 tapOnDisplay(tapX, tapY, stack.mDisplayId); 508 } 509 injectMotion(long downTime, long eventTime, int action, int x, int y, int displayId)510 private static void injectMotion(long downTime, long eventTime, int action, 511 int x, int y, int displayId) { 512 final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action, 513 x, y, 0 /* metaState */); 514 event.setSource(InputDevice.SOURCE_TOUCHSCREEN); 515 event.setDisplayId(displayId); 516 getInstrumentation().getUiAutomation().injectInputEvent(event, true /* sync */); 517 } 518 removeStacksWithActivityTypes(int... activityTypes)519 protected void removeStacksWithActivityTypes(int... activityTypes) { 520 SystemUtil.runWithShellPermissionIdentity( 521 () -> mAtm.removeStacksWithActivityTypes(activityTypes)); 522 waitForIdle(); 523 } 524 removeStacksInWindowingModes(int... windowingModes)525 protected void removeStacksInWindowingModes(int... windowingModes) { 526 SystemUtil.runWithShellPermissionIdentity( 527 () -> mAtm.removeStacksInWindowingModes(windowingModes) 528 ); 529 waitForIdle(); 530 } 531 executeShellCommand(String command)532 public static String executeShellCommand(String command) { 533 log("Shell command: " + command); 534 try { 535 return SystemUtil.runShellCommand(getInstrumentation(), command); 536 } catch (IOException e) { 537 //bubble it up 538 logE("Error running shell command: " + command); 539 throw new RuntimeException(e); 540 } 541 } 542 takeScreenshot()543 protected Bitmap takeScreenshot() { 544 return getInstrumentation().getUiAutomation().takeScreenshot(); 545 } 546 launchActivity(final ComponentName activityName, final String... keyValuePairs)547 protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) { 548 launchActivityNoWait(activityName, keyValuePairs); 549 mAmWmState.waitForValidState(activityName); 550 } 551 launchActivityNoWait(final ComponentName activityName, final String... keyValuePairs)552 protected void launchActivityNoWait(final ComponentName activityName, 553 final String... keyValuePairs) { 554 executeShellCommand(getAmStartCmd(activityName, keyValuePairs)); 555 } 556 launchActivityInNewTask(final ComponentName activityName)557 protected void launchActivityInNewTask(final ComponentName activityName) { 558 executeShellCommand(getAmStartCmdInNewTask(activityName)); 559 mAmWmState.waitForValidState(activityName); 560 } 561 waitForIdle()562 private static void waitForIdle() { 563 getInstrumentation().waitForIdleSync(); 564 } 565 566 /** Returns the set of stack ids. */ getStackIds()567 private HashSet<Integer> getStackIds() { 568 mAmWmState.computeState(true); 569 final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks(); 570 final HashSet<Integer> stackIds = new HashSet<>(); 571 for (ActivityManagerState.ActivityStack s : stacks) { 572 stackIds.add(s.mStackId); 573 } 574 return stackIds; 575 } 576 577 /** Returns the stack that contains the provided task. */ getStackForTaskId(int taskId)578 protected ActivityManagerState.ActivityStack getStackForTaskId(int taskId) { 579 mAmWmState.computeState(true); 580 final List<ActivityManagerState.ActivityStack> stacks = mAmWmState.getAmState().getStacks(); 581 for (ActivityManagerState.ActivityStack stack : stacks) { 582 for (ActivityManagerState.ActivityTask task : stack.mTasks) { 583 if (task.mTaskId == taskId) { 584 return stack; 585 } 586 } 587 } 588 return null; 589 } 590 launchHomeActivity()591 protected void launchHomeActivity() { 592 executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND); 593 mAmWmState.waitForHomeActivityVisible(); 594 } 595 launchActivity(ComponentName activityName, int windowingMode, final String... keyValuePairs)596 protected void launchActivity(ComponentName activityName, int windowingMode, 597 final String... keyValuePairs) { 598 executeShellCommand(getAmStartCmd(activityName, keyValuePairs) 599 + " --windowingMode " + windowingMode); 600 mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 601 .setWindowingMode(windowingMode) 602 .build()); 603 } 604 launchActivityOnDisplay(ComponentName activityName, int windowingMode, int displayId, final String... keyValuePairs)605 protected void launchActivityOnDisplay(ComponentName activityName, int windowingMode, 606 int displayId, final String... keyValuePairs) { 607 executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs) 608 + " --windowingMode " + windowingMode); 609 mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 610 .setWindowingMode(windowingMode) 611 .build()); 612 } 613 launchActivityOnDisplay(ComponentName activityName, int displayId, String... keyValuePairs)614 protected void launchActivityOnDisplay(ComponentName activityName, int displayId, 615 String... keyValuePairs) { 616 launchActivityOnDisplayNoWait(activityName, displayId, keyValuePairs); 617 mAmWmState.waitForValidState(activityName); 618 } 619 launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, String... keyValuePairs)620 protected void launchActivityOnDisplayNoWait(ComponentName activityName, int displayId, 621 String... keyValuePairs) { 622 executeShellCommand(getAmStartCmd(activityName, displayId, keyValuePairs)); 623 } 624 625 /** 626 * Launches {@param activityName} into split-screen primary windowing mode and also makes 627 * the recents activity visible to the side of it. 628 * NOTE: Recents view may be combined with home screen on some devices, so using this to wait 629 * for Recents only makes sense when {@link ActivityManagerState#isHomeRecentsComponent()} is 630 * {@code false}. 631 */ launchActivityInSplitScreenWithRecents(ComponentName activityName)632 protected void launchActivityInSplitScreenWithRecents(ComponentName activityName) { 633 launchActivityInSplitScreenWithRecents(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT); 634 } 635 launchActivityInSplitScreenWithRecents(ComponentName activityName, int createMode)636 protected void launchActivityInSplitScreenWithRecents(ComponentName activityName, 637 int createMode) { 638 SystemUtil.runWithShellPermissionIdentity(() -> { 639 launchActivity(activityName); 640 final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId; 641 mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, 642 true /* onTop */, false /* animate */, 643 null /* initialBounds */, true /* showRecents */); 644 645 mAmWmState.waitForValidState( 646 new WaitForValidActivityState.Builder(activityName) 647 .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) 648 .setActivityType(ACTIVITY_TYPE_STANDARD) 649 .build()); 650 mAmWmState.waitForRecentsActivityVisible(); 651 }); 652 } 653 moveTaskToPrimarySplitScreen(int taskId)654 public void moveTaskToPrimarySplitScreen(int taskId) { 655 moveTaskToPrimarySplitScreen(taskId, false /* showRecents */); 656 } 657 658 /** 659 * Moves the device into split-screen with the specified task into the primary stack. 660 * @param taskId The id of the task to move into the primary stack. 661 * @param showSideActivity Whether to show the Recents activity (or a placeholder activity in 662 * place of the Recents activity if home is the recents component) 663 */ moveTaskToPrimarySplitScreen(int taskId, boolean showSideActivity)664 public void moveTaskToPrimarySplitScreen(int taskId, boolean showSideActivity) { 665 final boolean isHomeRecentsComponent = mAmWmState.getAmState().isHomeRecentsComponent(); 666 SystemUtil.runWithShellPermissionIdentity(() -> { 667 mAtm.setTaskWindowingModeSplitScreenPrimary(taskId, 668 SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */, 669 false /* animate */, 670 null /* initialBounds */, showSideActivity && !isHomeRecentsComponent); 671 mAmWmState.waitForRecentsActivityVisible(); 672 673 if (isHomeRecentsComponent && showSideActivity) { 674 // Launch Placeholder Side Activity 675 final Activity sideActivity = mSideActivityRule.launchActivity( 676 new Intent()); 677 mAmWmState.waitForActivityState(sideActivity.getComponentName(), STATE_RESUMED); 678 } 679 }); 680 } 681 682 /** 683 * Launches {@param primaryActivity} into split-screen primary windowing mode 684 * and {@param secondaryActivity} to the side in split-screen secondary windowing mode. 685 */ launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, LaunchActivityBuilder secondaryActivity)686 protected void launchActivitiesInSplitScreen(LaunchActivityBuilder primaryActivity, 687 LaunchActivityBuilder secondaryActivity) { 688 // Launch split-screen primary. 689 primaryActivity 690 .setUseInstrumentation() 691 .setWaitForLaunched(true) 692 .execute(); 693 694 final int taskId = mAmWmState.getAmState().getTaskByActivity( 695 primaryActivity.mTargetActivity).mTaskId; 696 moveTaskToPrimarySplitScreen(taskId); 697 698 // Launch split-screen secondary 699 // Recents become focused, so we can just launch new task in focused stack 700 secondaryActivity 701 .setUseInstrumentation() 702 .setWaitForLaunched(true) 703 .setNewTask(true) 704 .setMultipleTask(true) 705 .execute(); 706 } 707 setActivityTaskWindowingMode(ComponentName activityName, int windowingMode)708 protected void setActivityTaskWindowingMode(ComponentName activityName, int windowingMode) { 709 mAmWmState.computeState(activityName); 710 final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId; 711 SystemUtil.runWithShellPermissionIdentity( 712 () -> mAtm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */)); 713 mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 714 .setActivityType(ACTIVITY_TYPE_STANDARD) 715 .setWindowingMode(windowingMode) 716 .build()); 717 } 718 moveActivityToStack(ComponentName activityName, int stackId)719 protected void moveActivityToStack(ComponentName activityName, int stackId) { 720 mAmWmState.computeState(activityName); 721 final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId; 722 SystemUtil.runWithShellPermissionIdentity( 723 () -> mAtm.moveTaskToStack(taskId, stackId, true)); 724 725 mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName) 726 .setStackId(stackId) 727 .build()); 728 } 729 resizeActivityTask( ComponentName activityName, int left, int top, int right, int bottom)730 protected void resizeActivityTask( 731 ComponentName activityName, int left, int top, int right, int bottom) { 732 mAmWmState.computeState(activityName); 733 final int taskId = mAmWmState.getAmState().getTaskByActivity(activityName).mTaskId; 734 SystemUtil.runWithShellPermissionIdentity( 735 () -> mAtm.resizeTask(taskId, new Rect(left, top, right, bottom))); 736 } 737 resizeDockedStack( int stackWidth, int stackHeight, int taskWidth, int taskHeight)738 protected void resizeDockedStack( 739 int stackWidth, int stackHeight, int taskWidth, int taskHeight) { 740 SystemUtil.runWithShellPermissionIdentity(() -> 741 mAtm.resizeDockedStack(new Rect(0, 0, stackWidth, stackHeight), 742 new Rect(0, 0, taskWidth, taskHeight))); 743 } 744 resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth, int stackHeight)745 protected void resizeStack(int stackId, int stackLeft, int stackTop, int stackWidth, 746 int stackHeight) { 747 SystemUtil.runWithShellPermissionIdentity(() -> mAtm.resizeStack(stackId, 748 new Rect(stackLeft, stackTop, stackWidth, stackHeight))); 749 } 750 pressAppSwitchButtonAndWaitForRecents()751 protected void pressAppSwitchButtonAndWaitForRecents() { 752 pressAppSwitchButton(); 753 mAmWmState.waitForRecentsActivityVisible(); 754 mAmWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY); 755 } 756 757 // Utility method for debugging, not used directly here, but useful, so kept around. printStacksAndTasks()758 protected void printStacksAndTasks() { 759 SystemUtil.runWithShellPermissionIdentity(() -> { 760 final String output = mAtm.listAllStacks(); 761 for (String line : output.split("\\n")) { 762 log(line); 763 } 764 }); 765 } 766 supportsVrMode()767 protected boolean supportsVrMode() { 768 return hasDeviceFeature(FEATURE_VR_MODE_HIGH_PERFORMANCE); 769 } 770 supportsPip()771 protected boolean supportsPip() { 772 return hasDeviceFeature(FEATURE_PICTURE_IN_PICTURE) 773 || PRETEND_DEVICE_SUPPORTS_PIP; 774 } 775 supportsFreeform()776 protected boolean supportsFreeform() { 777 return hasDeviceFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT) 778 || PRETEND_DEVICE_SUPPORTS_FREEFORM; 779 } 780 781 /** Whether or not the device supports pin/pattern/password lock. */ supportsSecureLock()782 protected boolean supportsSecureLock() { 783 return hasDeviceFeature(FEATURE_SECURE_LOCK_SCREEN); 784 } 785 786 /** Whether or not the device supports "swipe" lock. */ supportsInsecureLock()787 protected boolean supportsInsecureLock() { 788 return !hasDeviceFeature(FEATURE_LEANBACK) 789 && !hasDeviceFeature(FEATURE_WATCH) 790 && !hasDeviceFeature(FEATURE_EMBEDDED) 791 && !hasDeviceFeature(FEATURE_AUTOMOTIVE) 792 && getSupportsInsecureLockScreen(); 793 } 794 isWatch()795 protected boolean isWatch() { 796 return hasDeviceFeature(FEATURE_WATCH); 797 } 798 isTablet()799 protected boolean isTablet() { 800 // Larger than approx 7" tablets 801 return mContext.getResources().getConfiguration().smallestScreenWidthDp >= 600; 802 } 803 waitAndAssertActivityState(ComponentName activityName, String state, String message)804 protected void waitAndAssertActivityState(ComponentName activityName, 805 String state, String message) { 806 mAmWmState.waitForActivityState(activityName, state); 807 808 assertTrue(message, mAmWmState.getAmState().hasActivityState(activityName, state)); 809 } 810 waitAndAssertTopResumedActivity(ComponentName activityName, int displayId, String message)811 public void waitAndAssertTopResumedActivity(ComponentName activityName, int displayId, 812 String message) { 813 mAmWmState.waitForValidState(activityName); 814 mAmWmState.waitForActivityState(activityName, STATE_RESUMED); 815 final String activityClassName = getActivityName(activityName); 816 mAmWmState.waitForWithAmState(state -> 817 activityClassName.equals(state.getFocusedActivity()), 818 "Waiting for activity to be on top"); 819 820 mAmWmState.assertSanity(); 821 mAmWmState.assertFocusedActivity(message, activityName); 822 assertTrue("Activity must be resumed", 823 mAmWmState.getAmState().hasActivityState(activityName, STATE_RESUMED)); 824 final int frontStackId = mAmWmState.getAmState().getFrontStackId(displayId); 825 ActivityManagerState.ActivityStack frontStackOnDisplay = 826 mAmWmState.getAmState().getStackById(frontStackId); 827 assertEquals("Resumed activity of front stack of the target display must match. " + message, 828 activityClassName, frontStackOnDisplay.mResumedActivity); 829 mAmWmState.assertFocusedStack("Top activity's stack must also be on top", frontStackId); 830 mAmWmState.assertVisibility(activityName, true /* visible */); 831 } 832 833 // TODO: Switch to using a feature flag, when available. isUiModeLockedToVrHeadset()834 protected static boolean isUiModeLockedToVrHeadset() { 835 final String output = runCommandAndPrintOutput("dumpsys uimode"); 836 837 Integer curUiMode = null; 838 Boolean uiModeLocked = null; 839 for (String line : output.split("\\n")) { 840 line = line.trim(); 841 Matcher matcher = sCurrentUiModePattern.matcher(line); 842 if (matcher.find()) { 843 curUiMode = Integer.parseInt(matcher.group(1), 16); 844 } 845 matcher = sUiModeLockedPattern.matcher(line); 846 if (matcher.find()) { 847 uiModeLocked = matcher.group(1).equals("true"); 848 } 849 } 850 851 boolean uiModeLockedToVrHeadset = (curUiMode != null) && (uiModeLocked != null) 852 && ((curUiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_VR_HEADSET) && uiModeLocked; 853 854 if (uiModeLockedToVrHeadset) { 855 log("UI mode is locked to VR headset"); 856 } 857 858 return uiModeLockedToVrHeadset; 859 } 860 supportsSplitScreenMultiWindow()861 protected boolean supportsSplitScreenMultiWindow() { 862 return ActivityTaskManager.supportsSplitScreenMultiWindow(mContext); 863 } 864 hasHomeScreen()865 protected boolean hasHomeScreen() { 866 if (sHasHomeScreen == null) { 867 sHasHomeScreen = !noHomeScreen(); 868 } 869 return sHasHomeScreen; 870 } 871 supportsSystemDecorsOnSecondaryDisplays()872 protected boolean supportsSystemDecorsOnSecondaryDisplays() { 873 if (sSupportsSystemDecorsOnSecondaryDisplays == null) { 874 sSupportsSystemDecorsOnSecondaryDisplays = getSupportsSystemDecorsOnSecondaryDisplays(); 875 } 876 return sSupportsSystemDecorsOnSecondaryDisplays; 877 } 878 getSupportsInsecureLockScreen()879 protected boolean getSupportsInsecureLockScreen() { 880 if (sSupportsInsecureLockScreen == null) { 881 try { 882 sSupportsInsecureLockScreen = mContext.getResources().getBoolean( 883 Resources.getSystem().getIdentifier( 884 "config_supportsInsecureLockScreen", "bool", "android")); 885 } catch (Resources.NotFoundException e) { 886 sSupportsInsecureLockScreen = true; 887 } 888 } 889 return sSupportsInsecureLockScreen; 890 } 891 892 /** 893 * Rotation support is indicated by explicitly having both landscape and portrait 894 * features or not listing either at all. 895 */ supportsRotation()896 protected boolean supportsRotation() { 897 final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE); 898 final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT); 899 return (supportsLandscape && supportsPortrait) 900 || (!supportsLandscape && !supportsPortrait); 901 } 902 hasDeviceFeature(final String requiredFeature)903 protected boolean hasDeviceFeature(final String requiredFeature) { 904 return mContext.getPackageManager() 905 .hasSystemFeature(requiredFeature); 906 } 907 isDisplayOn(int displayId)908 protected static boolean isDisplayOn(int displayId) { 909 final DisplayManager displayManager = getInstrumentation() 910 .getContext().getSystemService(DisplayManager.class); 911 final Display display = displayManager.getDisplay(displayId); 912 return display != null && display.getState() == Display.STATE_ON; 913 } 914 perDisplayFocusEnabled()915 protected static boolean perDisplayFocusEnabled() { 916 return getInstrumentation().getTargetContext().getResources() 917 .getBoolean(android.R.bool.config_perDisplayFocusEnabled); 918 } 919 920 /** 921 * Test @Rule class that disables screen doze settings before each test method running and 922 * restoring to initial values after test method finished. 923 */ 924 protected static class DisableScreenDozeRule implements TestRule { 925 926 /** Copied from android.provider.Settings.Secure since these keys are hiden. */ 927 private static final String[] DOZE_SETTINGS = { 928 "doze_enabled", 929 "doze_always_on", 930 "doze_pulse_on_pick_up", 931 "doze_pulse_on_long_press", 932 "doze_pulse_on_double_tap" 933 }; 934 get(String key)935 private String get(String key) { 936 return executeShellCommand("settings get secure " + key).trim(); 937 } 938 put(String key, String value)939 private void put(String key, String value) { 940 executeShellCommand("settings put secure " + key + " " + value); 941 } 942 943 @Override apply(final Statement base, final Description description)944 public Statement apply(final Statement base, final Description description) { 945 return new Statement() { 946 @Override 947 public void evaluate() throws Throwable { 948 final Map<String, String> initialValues = new HashMap<>(); 949 Arrays.stream(DOZE_SETTINGS).forEach(k -> initialValues.put(k, get(k))); 950 try { 951 Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, "0")); 952 base.evaluate(); 953 } finally { 954 Arrays.stream(DOZE_SETTINGS).forEach(k -> put(k, initialValues.get(k))); 955 } 956 } 957 }; 958 } 959 } 960 961 /** 962 * HomeActivitySession is used to replace the default home component, so that you can use 963 * your preferred home for testing within the session. The original default home will be 964 * restored automatically afterward. 965 */ 966 protected class HomeActivitySession implements AutoCloseable { 967 private PackageManager mPackageManager; 968 private ComponentName mOrigHome; 969 private ComponentName mSessionHome; 970 971 public HomeActivitySession(ComponentName sessionHome) { 972 mSessionHome = sessionHome; 973 mPackageManager = mContext.getPackageManager(); 974 975 final Intent intent = new Intent(ACTION_MAIN); 976 intent.addCategory(CATEGORY_HOME); 977 intent.addFlags(FLAG_ACTIVITY_NEW_TASK); 978 final ResolveInfo resolveInfo = 979 mPackageManager.resolveActivity(intent, MATCH_DEFAULT_ONLY); 980 if (resolveInfo != null) { 981 mOrigHome = new ComponentName(resolveInfo.activityInfo.packageName, 982 resolveInfo.activityInfo.name); 983 } 984 985 SystemUtil.runWithShellPermissionIdentity( 986 () -> mPackageManager.setComponentEnabledSetting(mSessionHome, 987 COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP)); 988 setDefaultHome(mSessionHome); 989 } 990 991 @Override 992 public void close() { 993 SystemUtil.runWithShellPermissionIdentity( 994 () -> mPackageManager.setComponentEnabledSetting(mSessionHome, 995 COMPONENT_ENABLED_STATE_DISABLED, DONT_KILL_APP)); 996 if (mOrigHome != null) { 997 setDefaultHome(mOrigHome); 998 } 999 } 1000 1001 private void setDefaultHome(ComponentName componentName) { 1002 executeShellCommand("cmd package set-home-activity --user " 1003 + android.os.Process.myUserHandle().getIdentifier() + " " 1004 + componentName.flattenToString()); 1005 } 1006 } 1007 1008 protected class LockScreenSession implements AutoCloseable { 1009 private static final boolean DEBUG = false; 1010 1011 private final boolean mIsLockDisabled; 1012 private boolean mLockCredentialSet; 1013 private boolean mRemoveActivitiesOnClose; 1014 private AmbientDisplayConfiguration mAmbientDisplayConfiguration; 1015 1016 public static final int FLAG_REMOVE_ACTIVITIES_ON_CLOSE = 1; 1017 1018 public LockScreenSession() { 1019 this(0 /* flags */); 1020 } 1021 1022 public LockScreenSession(int flags) { 1023 mIsLockDisabled = isLockDisabled(); 1024 mLockCredentialSet = false; 1025 // Enable lock screen (swipe) by default. 1026 setLockDisabled(false); 1027 if ((flags & FLAG_REMOVE_ACTIVITIES_ON_CLOSE) != 0) { 1028 mRemoveActivitiesOnClose = true; 1029 } 1030 mAmbientDisplayConfiguration = new AmbientDisplayConfiguration(mContext); 1031 } 1032 1033 public LockScreenSession setLockCredential() { 1034 mLockCredentialSet = true; 1035 runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL); 1036 return this; 1037 } 1038 1039 public LockScreenSession enterAndConfirmLockCredential() { 1040 // Ensure focus will switch to default display. Meanwhile we cannot tap on center area, 1041 // which may tap on input credential area. 1042 tapOnDisplay(10, 10, DEFAULT_DISPLAY); 1043 1044 waitForDeviceIdle(3000); 1045 SystemUtil.runWithShellPermissionIdentity(() -> 1046 getInstrumentation().sendStringSync(LOCK_CREDENTIAL)); 1047 pressEnterButton(); 1048 return this; 1049 } 1050 1051 private void removeLockCredential() { 1052 runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL); 1053 mLockCredentialSet = false; 1054 } 1055 1056 LockScreenSession disableLockScreen() { 1057 setLockDisabled(true); 1058 return this; 1059 } 1060 1061 LockScreenSession sleepDevice() { 1062 pressSleepButton(); 1063 // Not all device variants lock when we go to sleep, so we need to explicitly lock the 1064 // device. Note that pressSleepButton() above is redundant because the action also 1065 // puts the device to sleep, but kept around for clarity. 1066 getInstrumentation().getUiAutomation().performGlobalAction( 1067 AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN); 1068 if (mAmbientDisplayConfiguration.alwaysOnEnabled( 1069 android.os.Process.myUserHandle().getIdentifier())) { 1070 mAmWmState.waitForAodShowing(); 1071 } else { 1072 for (int retry = 1; isDisplayOn(DEFAULT_DISPLAY) && retry <= 5; retry++) { 1073 logAlways("***Waiting for display to turn off... retry=" + retry); 1074 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); 1075 } 1076 } 1077 return this; 1078 } 1079 1080 LockScreenSession wakeUpDevice() { 1081 pressWakeupButton(); 1082 return this; 1083 } 1084 1085 LockScreenSession unlockDevice() { 1086 pressUnlockButton(); 1087 return this; 1088 } 1089 1090 public LockScreenSession gotoKeyguard(ComponentName... showWhenLockedActivities) { 1091 if (DEBUG && isLockDisabled()) { 1092 logE("LockScreenSession.gotoKeyguard() is called without lock enabled."); 1093 } 1094 sleepDevice(); 1095 wakeUpDevice(); 1096 if (showWhenLockedActivities.length == 0) { 1097 mAmWmState.waitForKeyguardShowingAndNotOccluded(); 1098 } else { 1099 mAmWmState.waitForValidState(showWhenLockedActivities); 1100 } 1101 return this; 1102 } 1103 1104 @Override 1105 public void close() { 1106 if (mRemoveActivitiesOnClose) { 1107 removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME); 1108 } 1109 1110 setLockDisabled(mIsLockDisabled); 1111 if (mLockCredentialSet) { 1112 removeLockCredential(); 1113 } 1114 1115 // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for 1116 // the stale credential. 1117 // TODO (b/112015010) If keyguard is occluded, credential cannot be removed as expected. 1118 // LockScreenSession#close is always calls before stop all test activities, 1119 // which could cause keyguard stay at occluded after wakeup. 1120 // If Keyguard is occluded, press back key can close ShowWhenLocked activity. 1121 pressBackButton(); 1122 1123 // If device is unlocked, there might have ShowWhenLocked activity runs on, 1124 // use home key to clear all activity at foreground. 1125 pressHomeButton(); 1126 sleepDevice(); 1127 wakeUpDevice(); 1128 unlockDevice(); 1129 } 1130 1131 /** 1132 * Returns whether the lock screen is disabled. 1133 * 1134 * @return true if the lock screen is disabled, false otherwise. 1135 */ 1136 private boolean isLockDisabled() { 1137 final String isLockDisabled = runCommandAndPrintOutput( 1138 "locksettings get-disabled").trim(); 1139 return !"null".equals(isLockDisabled) && Boolean.parseBoolean(isLockDisabled); 1140 } 1141 1142 /** 1143 * Disable the lock screen. 1144 * 1145 * @param lockDisabled true if should disable, false otherwise. 1146 */ 1147 protected void setLockDisabled(boolean lockDisabled) { 1148 runCommandAndPrintOutput("locksettings set-disabled " + lockDisabled); 1149 } 1150 } 1151 1152 /** Helper class to save, set & wait, and restore rotation related preferences. */ 1153 protected class RotationSession extends SettingsSession<Integer> { 1154 private final SettingsSession<Integer> mUserRotation; 1155 private final HandlerThread mThread; 1156 private final Handler mRunnableHandler; 1157 private final SettingsObserver mRotationObserver; 1158 private int mPreviousDegree; 1159 1160 public RotationSession() { 1161 // Save accelerometer_rotation preference. 1162 super(Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION), 1163 Settings.System::getInt, Settings.System::putInt); 1164 mUserRotation = new SettingsSession<>( 1165 Settings.System.getUriFor(Settings.System.USER_ROTATION), 1166 Settings.System::getInt, Settings.System::putInt); 1167 1168 mThread = new HandlerThread("Observer_Thread"); 1169 mThread.start(); 1170 mRunnableHandler = new Handler(mThread.getLooper()); 1171 mRotationObserver = new SettingsObserver(mRunnableHandler); 1172 1173 mPreviousDegree = mUserRotation.get(); 1174 // Disable accelerometer_rotation. 1175 super.set(0); 1176 } 1177 1178 @Override 1179 public void set(@NonNull Integer value) { 1180 // When the rotation is locked and the SystemUI receives the rotation becoming 0deg, it 1181 // will call freezeRotation to WMS, which will cause USER_ROTATION be set to zero again. 1182 // In order to prevent our test target from being overwritten by SystemUI during 1183 // rotation test, wait for the USER_ROTATION changed then continue testing. 1184 final boolean waitSystemUI = value == ROTATION_0 && mPreviousDegree != ROTATION_0; 1185 if (waitSystemUI) { 1186 mRotationObserver.observe(); 1187 } 1188 mUserRotation.set(value); 1189 mPreviousDegree = value; 1190 1191 if (waitSystemUI) { 1192 waitForRotationNotified(); 1193 } 1194 // Wait for settling rotation. 1195 mAmWmState.waitForRotation(value); 1196 1197 if (waitSystemUI) { 1198 mRotationObserver.stopObserver(); 1199 } 1200 } 1201 1202 @Override 1203 public void close() throws Exception { 1204 mThread.quitSafely(); 1205 mUserRotation.close(); 1206 // Restore accelerometer_rotation preference. 1207 super.close(); 1208 } 1209 1210 private void waitForRotationNotified() { 1211 for (int retry = 1; retry <= 5; retry++) { 1212 // There will receive USER_ROTATION changed twice because when the device rotates to 1213 // 0deg, RotationContextButton will also set ROTATION_0 again. 1214 if (mRotationObserver.count == 2) { 1215 return; 1216 } 1217 logAlways("waitForRotationNotified retry=" + retry); 1218 SystemClock.sleep(500); 1219 } 1220 logE("waitForRotationNotified skip"); 1221 } 1222 1223 private class SettingsObserver extends ContentObserver { 1224 int count; 1225 1226 SettingsObserver(Handler handler) { super(handler); } 1227 1228 void observe() { 1229 count = 0; 1230 final ContentResolver resolver = mContext.getContentResolver(); 1231 resolver.registerContentObserver(Settings.System.getUriFor( 1232 Settings.System.USER_ROTATION), false, this); 1233 } 1234 1235 void stopObserver() { 1236 count = 0; 1237 final ContentResolver resolver = mContext.getContentResolver(); 1238 resolver.unregisterContentObserver(this); 1239 } 1240 1241 @Override 1242 public void onChange(boolean selfChange) { 1243 count++; 1244 } 1245 } 1246 } 1247 1248 /** 1249 * Returns whether the test device respects settings of locked user rotation mode. 1250 * 1251 * The method sets the locked user rotation settings to the rotation that rotates the display by 1252 * 180 degrees and checks if the actual display rotation changes after that. 1253 * 1254 * This is a necessary assumption check before leveraging user rotation mode to force display 1255 * rotation, because there is no requirement that an Android device that supports both 1256 * orientations needs to support user rotation mode. 1257 * 1258 * @param session the rotation session used to set user rotation 1259 * @param displayId the display ID to check rotation against 1260 * @return {@code true} if test device respects settings of locked user rotation mode; 1261 * {@code false} if not. 1262 */ 1263 protected boolean supportsLockedUserRotation(RotationSession session, int displayId) 1264 throws Exception { 1265 final int origRotation = getDeviceRotation(displayId); 1266 // Use the same orientation as target rotation to avoid affect of app-requested orientation. 1267 final int targetRotation = (origRotation + 2) % 4; 1268 session.set(targetRotation); 1269 final boolean result = (getDeviceRotation(displayId) == targetRotation); 1270 session.set(origRotation); 1271 return result; 1272 } 1273 1274 protected int getDeviceRotation(int displayId) { 1275 final String displays = runCommandAndPrintOutput("dumpsys display displays").trim(); 1276 Pattern pattern = Pattern.compile( 1277 "(mDisplayId=" + displayId + ")([\\s\\S]*?)(mOverrideDisplayInfo)(.*)" 1278 + "(rotation)(\\s+)(\\d+)"); 1279 Matcher matcher = pattern.matcher(displays); 1280 if (matcher.find()) { 1281 final String match = matcher.group(7); 1282 return Integer.parseInt(match); 1283 } 1284 1285 return INVALID_DEVICE_ROTATION; 1286 } 1287 1288 /** Empties the test journal so the following events won't be mixed-up with previous records. */ 1289 protected void separateTestJournal() { 1290 TestJournalContainer.start(); 1291 } 1292 1293 protected static String runCommandAndPrintOutput(String command) { 1294 final String output = executeShellCommand(command); 1295 log(output); 1296 return output; 1297 } 1298 1299 protected static class LogSeparator { 1300 private final String mUniqueString; 1301 1302 private LogSeparator() { 1303 mUniqueString = UUID.randomUUID().toString(); 1304 } 1305 1306 @Override 1307 public String toString() { 1308 return mUniqueString; 1309 } 1310 } 1311 1312 /** 1313 * Inserts a log separator so we can always find the starting point from where to evaluate 1314 * following logs. 1315 * 1316 * @return Unique log separator. 1317 */ 1318 protected LogSeparator separateLogs() { 1319 final LogSeparator logSeparator = new LogSeparator(); 1320 executeShellCommand("log -t " + LOG_SEPARATOR + " " + logSeparator); 1321 EventLog.writeEvent(EVENT_LOG_SEPARATOR_TAG, logSeparator.mUniqueString); 1322 return logSeparator; 1323 } 1324 1325 protected static String[] getDeviceLogsForComponents( 1326 LogSeparator logSeparator, String... logTags) { 1327 String filters = LOG_SEPARATOR + ":I "; 1328 for (String component : logTags) { 1329 filters += component + ":I "; 1330 } 1331 final String[] result = executeShellCommand("logcat -v brief -d " + filters + " *:S") 1332 .split("\\n"); 1333 if (logSeparator == null) { 1334 return result; 1335 } 1336 1337 // Make sure that we only check logs after the separator. 1338 int i = 0; 1339 boolean lookingForSeparator = true; 1340 while (i < result.length && lookingForSeparator) { 1341 if (result[i].contains(logSeparator.toString())) { 1342 lookingForSeparator = false; 1343 } 1344 i++; 1345 } 1346 final String[] filteredResult = new String[result.length - i]; 1347 for (int curPos = 0; i < result.length; curPos++, i++) { 1348 filteredResult[curPos] = result[i]; 1349 } 1350 return filteredResult; 1351 } 1352 1353 protected static List<Event> getEventLogsForComponents(LogSeparator logSeparator, int... tags) { 1354 List<Event> events = new ArrayList<>(); 1355 1356 int[] searchTags = Arrays.copyOf(tags, tags.length + 1); 1357 searchTags[searchTags.length - 1] = EVENT_LOG_SEPARATOR_TAG; 1358 1359 try { 1360 EventLog.readEvents(searchTags, events); 1361 } catch (IOException e) { 1362 fail("Could not read from event log." + e); 1363 } 1364 1365 for (Iterator<Event> itr = events.iterator(); itr.hasNext(); ) { 1366 Event event = itr.next(); 1367 itr.remove(); 1368 if (event.getTag() == EVENT_LOG_SEPARATOR_TAG && 1369 logSeparator.mUniqueString.equals(event.getData())) { 1370 break; 1371 } 1372 } 1373 return events; 1374 } 1375 1376 protected boolean supportsMultiDisplay() { 1377 return mContext.getPackageManager().hasSystemFeature( 1378 FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS); 1379 } 1380 1381 /** 1382 * Base helper class for retrying validator success. 1383 */ 1384 private abstract static class RetryValidator { 1385 1386 private static final int RETRY_LIMIT = 5; 1387 private static final long RETRY_INTERVAL = TimeUnit.SECONDS.toMillis(1); 1388 1389 /** 1390 * @return Error string if validation is failed, null if everything is fine. 1391 **/ 1392 @Nullable 1393 protected abstract String validate(); 1394 1395 /** 1396 * Executes {@link #validate()}. Retries {@link #RETRY_LIMIT} times with 1397 * {@link #RETRY_INTERVAL} interval. 1398 * 1399 * @param waitingMessage logging message while waiting validation. 1400 */ 1401 void assertValidator(String waitingMessage) { 1402 String resultString = null; 1403 for (int retry = 1; retry <= RETRY_LIMIT; retry++) { 1404 resultString = validate(); 1405 if (resultString == null) { 1406 return; 1407 } 1408 logAlways(waitingMessage + ": " + resultString); 1409 SystemClock.sleep(RETRY_INTERVAL); 1410 } 1411 fail(resultString); 1412 } 1413 } 1414 1415 static class CountSpec<T> { 1416 static final int DONT_CARE = Integer.MIN_VALUE; 1417 static final int EQUALS = 1; 1418 static final int GREATER_THAN = 2; 1419 static final int LESS_THAN = 3; 1420 1421 final T mEvent; 1422 final int mRule; 1423 final int mCount; 1424 final String mMessage; 1425 1426 CountSpec(T event, int rule, int count, String message) { 1427 mEvent = event; 1428 mRule = count == DONT_CARE ? DONT_CARE : rule; 1429 mCount = count; 1430 if (message != null) { 1431 mMessage = message; 1432 } else { 1433 switch (rule) { 1434 case EQUALS: 1435 mMessage = event + " + must equal to " + count; 1436 break; 1437 case GREATER_THAN: 1438 mMessage = event + " + must be greater than " + count; 1439 break; 1440 case LESS_THAN: 1441 mMessage = event + " + must be less than " + count; 1442 break; 1443 default: 1444 mMessage = "Don't care"; 1445 } 1446 } 1447 } 1448 1449 /** @return {@code true} if the given value is satisfied the condition. */ 1450 boolean validate(int value) { 1451 switch (mRule) { 1452 case DONT_CARE: 1453 return true; 1454 case EQUALS: 1455 return value == mCount; 1456 case GREATER_THAN: 1457 return value > mCount; 1458 case LESS_THAN: 1459 return value < mCount; 1460 default: 1461 } 1462 throw new RuntimeException("Unknown CountSpec rule"); 1463 } 1464 } 1465 1466 static <T> CountSpec<T> countSpec(T event, int rule, int count, String message) { 1467 return new CountSpec<>(event, rule, count, message); 1468 } 1469 1470 static <T> CountSpec<T> countSpec(T event, int rule, int count) { 1471 return new CountSpec<>(event, rule, count, null /* message */); 1472 } 1473 1474 static void assertLifecycleCounts(ComponentName activityName, String message, 1475 int createCount, int startCount, int resumeCount, int pauseCount, int stopCount, 1476 int destroyCount, int configChangeCount) { 1477 new ActivityLifecycleCounts(activityName).assertCountWithRetry( 1478 message, 1479 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, createCount), 1480 countSpec(ActivityCallback.ON_START, CountSpec.EQUALS, startCount), 1481 countSpec(ActivityCallback.ON_RESUME, CountSpec.EQUALS, resumeCount), 1482 countSpec(ActivityCallback.ON_PAUSE, CountSpec.EQUALS, pauseCount), 1483 countSpec(ActivityCallback.ON_STOP, CountSpec.EQUALS, stopCount), 1484 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, destroyCount), 1485 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 1486 configChangeCount)); 1487 } 1488 1489 static void assertLifecycleCounts(ComponentName activityName, 1490 int createCount, int startCount, int resumeCount, int pauseCount, int stopCount, 1491 int destroyCount, int configChangeCount) { 1492 assertLifecycleCounts(activityName, "Assert lifecycle of " + getLogTag(activityName), 1493 createCount, startCount, resumeCount, pauseCount, stopCount, 1494 destroyCount, configChangeCount); 1495 } 1496 1497 static void assertSingleLaunch(ComponentName activityName) { 1498 assertLifecycleCounts(activityName, 1499 "***Waiting for activity create, start, and resume", 1500 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */, 1501 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */, 1502 CountSpec.DONT_CARE /* configChangeCount */); 1503 } 1504 1505 static void assertSingleLaunchAndStop(ComponentName activityName) { 1506 assertLifecycleCounts(activityName, 1507 "***Waiting for activity create, start, resume, pause, and stop", 1508 1 /* createCount */, 1 /* startCount */, 1 /* resumeCount */, 1509 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */, 1510 CountSpec.DONT_CARE /* configChangeCount */); 1511 } 1512 1513 static void assertSingleStartAndStop(ComponentName activityName) { 1514 assertLifecycleCounts(activityName, 1515 "***Waiting for activity start, resume, pause, and stop", 1516 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */, 1517 1 /* pauseCount */, 1 /* stopCount */, 0 /* destroyCount */, 1518 CountSpec.DONT_CARE /* configChangeCount */); 1519 } 1520 1521 static void assertSingleStart(ComponentName activityName) { 1522 assertLifecycleCounts(activityName, 1523 "***Waiting for activity start and resume", 1524 0 /* createCount */, 1 /* startCount */, 1 /* resumeCount */, 1525 0 /* pauseCount */, 0 /* stopCount */, 0 /* destroyCount */, 1526 CountSpec.DONT_CARE /* configChangeCount */); 1527 } 1528 1529 /** Assert the activity is either relaunched or received configuration changed. */ 1530 static void assertActivityLifecycle(ComponentName activityName, boolean relaunched) { 1531 new RetryValidator() { 1532 1533 @Nullable 1534 @Override 1535 protected String validate() { 1536 final String failedReason = checkActivityIsRelaunchedOrConfigurationChanged( 1537 getActivityName(activityName), 1538 TestJournalContainer.get(activityName).callbacks, relaunched); 1539 if (failedReason != null) { 1540 return failedReason; 1541 } 1542 return null; 1543 } 1544 }.assertValidator("***Waiting for valid lifecycle state"); 1545 } 1546 1547 /** Assert the activity is either relaunched or received configuration changed. */ 1548 static List<ActivityCallback> assertActivityLifecycle(ActivitySession activitySession, 1549 boolean relaunched) { 1550 final String name = activitySession.getName(); 1551 final List<ActivityCallback> callbackHistory = activitySession.takeCallbackHistory(); 1552 String failedReason = checkActivityIsRelaunchedOrConfigurationChanged( 1553 name, callbackHistory, relaunched); 1554 if (failedReason != null) { 1555 fail(failedReason); 1556 } 1557 return callbackHistory; 1558 } 1559 1560 private static String checkActivityIsRelaunchedOrConfigurationChanged(String name, 1561 List<ActivityCallback> callbackHistory, boolean relaunched) { 1562 final ActivityLifecycleCounts lifecycles = new ActivityLifecycleCounts(callbackHistory); 1563 if (relaunched) { 1564 return lifecycles.validateCount( 1565 countSpec(ActivityCallback.ON_DESTROY, CountSpec.GREATER_THAN, 0, 1566 name + " must have been destroyed."), 1567 countSpec(ActivityCallback.ON_CREATE, CountSpec.GREATER_THAN, 0, 1568 name + " must have been (re)created.")); 1569 } 1570 return lifecycles.validateCount( 1571 countSpec(ActivityCallback.ON_DESTROY, CountSpec.LESS_THAN, 1, 1572 name + " must *NOT* have been destroyed."), 1573 countSpec(ActivityCallback.ON_CREATE, CountSpec.LESS_THAN, 1, 1574 name + " must *NOT* have been (re)created."), 1575 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.GREATER_THAN, 0, 1576 name + " must have received configuration changed.")); 1577 } 1578 1579 static void assertRelaunchOrConfigChanged(ComponentName activityName, int numRelaunch, 1580 int numConfigChange) { 1581 new ActivityLifecycleCounts(activityName).assertCountWithRetry( 1582 "***Waiting for relaunch or config changed", 1583 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, numRelaunch), 1584 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, numRelaunch), 1585 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 1586 numConfigChange)); 1587 } 1588 1589 static void assertActivityDestroyed(ComponentName activityName) { 1590 new ActivityLifecycleCounts(activityName).assertCountWithRetry( 1591 "***Waiting for activity destroyed", 1592 countSpec(ActivityCallback.ON_DESTROY, CountSpec.EQUALS, 1), 1593 countSpec(ActivityCallback.ON_CREATE, CountSpec.EQUALS, 0), 1594 countSpec(ActivityCallback.ON_CONFIGURATION_CHANGED, CountSpec.EQUALS, 0)); 1595 } 1596 1597 private static final Pattern sCurrentUiModePattern = Pattern.compile("mCurUiMode=0x(\\d+)"); 1598 private static final Pattern sUiModeLockedPattern = 1599 Pattern.compile("mUiModeLocked=(true|false)"); 1600 1601 @Nullable 1602 SizeInfo getLastReportedSizesForActivity(ComponentName activityName) { 1603 for (int retry = 1; retry <= 5; retry++) { 1604 final ConfigInfo result = TestJournalContainer.get(activityName).lastConfigInfo; 1605 if (result != null && result.sizeInfo != null) { 1606 return result.sizeInfo; 1607 } 1608 logAlways("***Waiting for sizes to be reported... retry=" + retry); 1609 SystemClock.sleep(1000); 1610 } 1611 logE("***Waiting for activity size failed: activityName=" + getActivityName(activityName)); 1612 return null; 1613 } 1614 1615 /** Check if a device has display cutout. */ 1616 boolean hasDisplayCutout() { 1617 // Launch an activity to report cutout state 1618 separateTestJournal(); 1619 launchActivity(BROADCAST_RECEIVER_ACTIVITY); 1620 1621 // Read the logs to check if cutout is present 1622 final Boolean displayCutoutPresent = getCutoutStateForActivity(BROADCAST_RECEIVER_ACTIVITY); 1623 assertNotNull("The activity should report cutout state", displayCutoutPresent); 1624 1625 // Finish activity 1626 mBroadcastActionTrigger.finishBroadcastReceiverActivity(); 1627 mAmWmState.waitForWithAmState( 1628 (state) -> !state.containsActivity(BROADCAST_RECEIVER_ACTIVITY), 1629 "Waiting for activity to be removed"); 1630 1631 return displayCutoutPresent; 1632 } 1633 1634 /** 1635 * Wait for activity to report cutout state in logs and return it. Will return {@code null} 1636 * after timeout. 1637 */ 1638 @Nullable 1639 private Boolean getCutoutStateForActivity(ComponentName activityName) { 1640 final String logTag = getLogTag(activityName); 1641 for (int retry = 1; retry <= 5; retry++) { 1642 final Bundle extras = TestJournalContainer.get(activityName).extras; 1643 if (extras.containsKey(EXTRA_CUTOUT_EXISTS)) { 1644 return extras.getBoolean(EXTRA_CUTOUT_EXISTS); 1645 } 1646 logAlways("***Waiting for cutout state to be reported... retry=" + retry); 1647 SystemClock.sleep(1000); 1648 } 1649 logE("***Waiting for activity cutout state failed: activityName=" + logTag); 1650 return null; 1651 } 1652 1653 /** Waits for at least one onMultiWindowModeChanged event. */ 1654 ActivityLifecycleCounts waitForOnMultiWindowModeChanged(ComponentName activityName) { 1655 int retry = 1; 1656 ActivityLifecycleCounts result; 1657 do { 1658 result = new ActivityLifecycleCounts(activityName); 1659 if (result.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED) >= 1) { 1660 return result; 1661 } 1662 logAlways("***waitForOnMultiWindowModeChanged... retry=" + retry); 1663 SystemClock.sleep(TimeUnit.SECONDS.toMillis(1)); 1664 } while (retry++ <= 5); 1665 return result; 1666 } 1667 1668 static class ActivityLifecycleCounts { 1669 final int[] mCounts = new int[ActivityCallback.SIZE]; 1670 final int[] mLastIndexes = new int[ActivityCallback.SIZE]; 1671 final List<ActivityCallback> mCallbackHistory; 1672 1673 ActivityLifecycleCounts(ComponentName componentName) { 1674 this(TestJournalContainer.get(componentName).callbacks); 1675 } 1676 1677 ActivityLifecycleCounts(List<ActivityCallback> callbacks) { 1678 mCallbackHistory = callbacks; 1679 for (int i = 0; i < callbacks.size(); i++) { 1680 final ActivityCallback callback = callbacks.get(i); 1681 final int ordinal = callback.ordinal(); 1682 mCounts[ordinal]++; 1683 mLastIndexes[ordinal] = i; 1684 } 1685 } 1686 1687 int getCount(ActivityCallback callback) { 1688 return mCounts[callback.ordinal()]; 1689 } 1690 1691 int getLastIndex(ActivityCallback callback) { 1692 return mLastIndexes[callback.ordinal()]; 1693 } 1694 1695 @SafeVarargs 1696 final void assertCountWithRetry(String message, CountSpec<ActivityCallback>... countSpecs) { 1697 new RetryValidator() { 1698 @Override 1699 protected String validate() { 1700 return validateCount(countSpecs); 1701 } 1702 }.assertValidator(message); 1703 } 1704 1705 @SafeVarargs 1706 final String validateCount(CountSpec<ActivityCallback>... countSpecs) { 1707 ArrayList<String> failedReasons = null; 1708 for (CountSpec<ActivityCallback> spec : countSpecs) { 1709 final int realCount = mCounts[spec.mEvent.ordinal()]; 1710 if (!spec.validate(realCount)) { 1711 if (failedReasons == null) { 1712 failedReasons = new ArrayList<>(); 1713 } 1714 failedReasons.add(spec.mMessage); 1715 } 1716 } 1717 return failedReasons == null ? null : String.join("\n", failedReasons); 1718 } 1719 } 1720 1721 protected void stopTestPackage(final String packageName) { 1722 SystemUtil.runWithShellPermissionIdentity(() -> mAm.forceStopPackage(packageName)); 1723 } 1724 1725 protected LaunchActivityBuilder getLaunchActivityBuilder() { 1726 return new LaunchActivityBuilder(mAmWmState); 1727 } 1728 1729 protected static class LaunchActivityBuilder implements LaunchProxy { 1730 private final ActivityAndWindowManagersState mAmWmState; 1731 1732 // The activity to be launched 1733 private ComponentName mTargetActivity = TEST_ACTIVITY; 1734 private boolean mUseApplicationContext; 1735 private boolean mToSide; 1736 private boolean mRandomData; 1737 private boolean mNewTask; 1738 private boolean mMultipleTask; 1739 private boolean mAllowMultipleInstances = true; 1740 private int mDisplayId = INVALID_DISPLAY; 1741 private int mActivityType = ACTIVITY_TYPE_UNDEFINED; 1742 // A proxy activity that launches other activities including mTargetActivityName 1743 private ComponentName mLaunchingActivity = LAUNCHING_ACTIVITY; 1744 private boolean mReorderToFront; 1745 private boolean mWaitForLaunched; 1746 private boolean mSuppressExceptions; 1747 private boolean mWithShellPermission; 1748 // Use of the following variables indicates that a broadcast receiver should be used instead 1749 // of a launching activity; 1750 private ComponentName mBroadcastReceiver; 1751 private String mBroadcastReceiverAction; 1752 private int mIntentFlags; 1753 private Bundle mExtras; 1754 private LaunchInjector mLaunchInjector; 1755 1756 private enum LauncherType { 1757 INSTRUMENTATION, LAUNCHING_ACTIVITY, BROADCAST_RECEIVER 1758 } 1759 1760 private LauncherType mLauncherType = LauncherType.LAUNCHING_ACTIVITY; 1761 1762 public LaunchActivityBuilder(ActivityAndWindowManagersState amWmState) { 1763 mAmWmState = amWmState; 1764 mWaitForLaunched = true; 1765 mWithShellPermission = true; 1766 } 1767 1768 public LaunchActivityBuilder setToSide(boolean toSide) { 1769 mToSide = toSide; 1770 return this; 1771 } 1772 1773 public LaunchActivityBuilder setRandomData(boolean randomData) { 1774 mRandomData = randomData; 1775 return this; 1776 } 1777 1778 public LaunchActivityBuilder setNewTask(boolean newTask) { 1779 mNewTask = newTask; 1780 return this; 1781 } 1782 1783 public LaunchActivityBuilder setMultipleTask(boolean multipleTask) { 1784 mMultipleTask = multipleTask; 1785 return this; 1786 } 1787 1788 public LaunchActivityBuilder allowMultipleInstances(boolean allowMultipleInstances) { 1789 mAllowMultipleInstances = allowMultipleInstances; 1790 return this; 1791 } 1792 1793 public LaunchActivityBuilder setReorderToFront(boolean reorderToFront) { 1794 mReorderToFront = reorderToFront; 1795 return this; 1796 } 1797 1798 public LaunchActivityBuilder setUseApplicationContext(boolean useApplicationContext) { 1799 mUseApplicationContext = useApplicationContext; 1800 return this; 1801 } 1802 1803 public ComponentName getTargetActivity() { 1804 return mTargetActivity; 1805 } 1806 1807 public boolean isTargetActivityTranslucent() { 1808 return mAmWmState.getAmState().isActivityTranslucent(mTargetActivity); 1809 } 1810 1811 public LaunchActivityBuilder setTargetActivity(ComponentName targetActivity) { 1812 mTargetActivity = targetActivity; 1813 return this; 1814 } 1815 1816 public LaunchActivityBuilder setDisplayId(int id) { 1817 mDisplayId = id; 1818 return this; 1819 } 1820 1821 public LaunchActivityBuilder setActivityType(int type) { 1822 mActivityType = type; 1823 return this; 1824 } 1825 1826 public LaunchActivityBuilder setLaunchingActivity(ComponentName launchingActivity) { 1827 mLaunchingActivity = launchingActivity; 1828 mLauncherType = LauncherType.LAUNCHING_ACTIVITY; 1829 return this; 1830 } 1831 1832 public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) { 1833 mWaitForLaunched = shouldWait; 1834 return this; 1835 } 1836 1837 /** Use broadcast receiver as a launchpad for activities. */ 1838 public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver, 1839 final String broadcastAction) { 1840 mBroadcastReceiver = broadcastReceiver; 1841 mBroadcastReceiverAction = broadcastAction; 1842 mLauncherType = LauncherType.BROADCAST_RECEIVER; 1843 return this; 1844 } 1845 1846 /** Use {@link android.app.Instrumentation} as a launchpad for activities. */ 1847 public LaunchActivityBuilder setUseInstrumentation() { 1848 mLauncherType = LauncherType.INSTRUMENTATION; 1849 // Calling startActivity() from outside of an Activity context requires the 1850 // FLAG_ACTIVITY_NEW_TASK flag. 1851 setNewTask(true); 1852 return this; 1853 } 1854 1855 public LaunchActivityBuilder setSuppressExceptions(boolean suppress) { 1856 mSuppressExceptions = suppress; 1857 return this; 1858 } 1859 1860 public LaunchActivityBuilder setWithShellPermission(boolean withShellPermission) { 1861 mWithShellPermission = withShellPermission; 1862 return this; 1863 } 1864 1865 @Override 1866 public boolean shouldWaitForLaunched() { 1867 return mWaitForLaunched; 1868 } 1869 1870 public LaunchActivityBuilder setIntentFlags(int flags) { 1871 mIntentFlags = flags; 1872 return this; 1873 } 1874 1875 public LaunchActivityBuilder setIntentExtra(Consumer<Bundle> extrasConsumer) { 1876 if (extrasConsumer != null) { 1877 mExtras = new Bundle(); 1878 extrasConsumer.accept(mExtras); 1879 } 1880 return this; 1881 } 1882 1883 @Override 1884 public Bundle getExtras() { 1885 return mExtras; 1886 } 1887 1888 @Override 1889 public void setLaunchInjector(LaunchInjector injector) { 1890 mLaunchInjector = injector; 1891 } 1892 1893 @Override 1894 public void execute() { 1895 switch (mLauncherType) { 1896 case INSTRUMENTATION: 1897 if (mWithShellPermission) { 1898 SystemUtil.runWithShellPermissionIdentity(this::launchUsingInstrumentation); 1899 } else { 1900 launchUsingInstrumentation(); 1901 } 1902 break; 1903 case LAUNCHING_ACTIVITY: 1904 case BROADCAST_RECEIVER: 1905 launchUsingShellCommand(); 1906 } 1907 1908 if (mWaitForLaunched) { 1909 mAmWmState.waitForValidState(mTargetActivity); 1910 } 1911 } 1912 1913 /** Launch an activity using instrumentation. */ 1914 private void launchUsingInstrumentation() { 1915 final Bundle b = new Bundle(); 1916 b.putBoolean(KEY_USE_INSTRUMENTATION, true); 1917 b.putBoolean(KEY_LAUNCH_ACTIVITY, true); 1918 b.putBoolean(KEY_LAUNCH_TO_SIDE, mToSide); 1919 b.putBoolean(KEY_RANDOM_DATA, mRandomData); 1920 b.putBoolean(KEY_NEW_TASK, mNewTask); 1921 b.putBoolean(KEY_MULTIPLE_TASK, mMultipleTask); 1922 b.putBoolean(KEY_MULTIPLE_INSTANCES, mAllowMultipleInstances); 1923 b.putBoolean(KEY_REORDER_TO_FRONT, mReorderToFront); 1924 b.putInt(KEY_DISPLAY_ID, mDisplayId); 1925 b.putInt(KEY_ACTIVITY_TYPE, mActivityType); 1926 b.putBoolean(KEY_USE_APPLICATION_CONTEXT, mUseApplicationContext); 1927 b.putString(KEY_TARGET_COMPONENT, getActivityName(mTargetActivity)); 1928 b.putBoolean(KEY_SUPPRESS_EXCEPTIONS, mSuppressExceptions); 1929 b.putInt(KEY_INTENT_FLAGS, mIntentFlags); 1930 b.putBundle(KEY_INTENT_EXTRAS, getExtras()); 1931 final Context context = getInstrumentation().getContext(); 1932 launchActivityFromExtras(context, b, mLaunchInjector); 1933 } 1934 1935 /** Build and execute a shell command to launch an activity. */ 1936 private void launchUsingShellCommand() { 1937 StringBuilder commandBuilder = new StringBuilder(); 1938 if (mBroadcastReceiver != null && mBroadcastReceiverAction != null) { 1939 // Use broadcast receiver to launch the target. 1940 commandBuilder.append("am broadcast -a ").append(mBroadcastReceiverAction) 1941 .append(" -p ").append(mBroadcastReceiver.getPackageName()) 1942 // Include stopped packages 1943 .append(" -f 0x00000020"); 1944 } else { 1945 // Use launching activity to launch the target. 1946 commandBuilder.append(getAmStartCmd(mLaunchingActivity)) 1947 .append(" -f 0x20000020"); 1948 } 1949 1950 // Add a flag to ensure we actually mean to launch an activity. 1951 commandBuilder.append(" --ez " + KEY_LAUNCH_ACTIVITY + " true"); 1952 1953 if (mToSide) { 1954 commandBuilder.append(" --ez " + KEY_LAUNCH_TO_SIDE + " true"); 1955 } 1956 if (mRandomData) { 1957 commandBuilder.append(" --ez " + KEY_RANDOM_DATA + " true"); 1958 } 1959 if (mNewTask) { 1960 commandBuilder.append(" --ez " + KEY_NEW_TASK + " true"); 1961 } 1962 if (mMultipleTask) { 1963 commandBuilder.append(" --ez " + KEY_MULTIPLE_TASK + " true"); 1964 } 1965 if (mAllowMultipleInstances) { 1966 commandBuilder.append(" --ez " + KEY_MULTIPLE_INSTANCES + " true"); 1967 } 1968 if (mReorderToFront) { 1969 commandBuilder.append(" --ez " + KEY_REORDER_TO_FRONT + " true"); 1970 } 1971 if (mDisplayId != INVALID_DISPLAY) { 1972 commandBuilder.append(" --ei " + KEY_DISPLAY_ID + " ").append(mDisplayId); 1973 } 1974 if (mActivityType != ACTIVITY_TYPE_UNDEFINED) { 1975 commandBuilder.append(" --ei " + KEY_ACTIVITY_TYPE + " ").append(mActivityType); 1976 } 1977 1978 if (mUseApplicationContext) { 1979 commandBuilder.append(" --ez " + KEY_USE_APPLICATION_CONTEXT + " true"); 1980 } 1981 1982 if (mTargetActivity != null) { 1983 // {@link ActivityLauncher} parses this extra string by 1984 // {@link ComponentName#unflattenFromString(String)}. 1985 commandBuilder.append(" --es " + KEY_TARGET_COMPONENT + " ") 1986 .append(getActivityName(mTargetActivity)); 1987 } 1988 1989 if (mSuppressExceptions) { 1990 commandBuilder.append(" --ez " + KEY_SUPPRESS_EXCEPTIONS + " true"); 1991 } 1992 1993 if (mIntentFlags != 0) { 1994 commandBuilder.append(" --ei " + KEY_INTENT_FLAGS + " ").append(mIntentFlags); 1995 } 1996 1997 if (mLaunchInjector != null) { 1998 mLaunchInjector.setupShellCommand(commandBuilder); 1999 } 2000 executeShellCommand(commandBuilder.toString()); 2001 } 2002 } 2003 2004 // Activity used in place of recents when home is the recents component. 2005 public static class SideActivity extends Activity { 2006 } 2007 } 2008