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