1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.server.wm; 18 19 import static android.app.AppOpsManager.MODE_ERRORED; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 22 import static android.server.wm.ComponentNameUtils.getActivityName; 23 import static android.server.wm.backgroundactivity.common.CommonComponents.COMMON_FOREGROUND_ACTIVITY_EXTRAS; 24 import static android.server.wm.backgroundactivity.common.CommonComponents.TEST_SERVICE; 25 26 import static com.android.compatibility.common.util.SystemUtil.runShellCommand; 27 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity; 28 29 import static com.google.common.truth.Truth.assertWithMessage; 30 31 import static org.junit.Assert.assertEquals; 32 import static org.junit.Assert.assertNull; 33 import static org.junit.Assert.assertTrue; 34 35 import android.app.PendingIntent; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.os.Build; 40 import android.os.SystemClock; 41 import android.os.UserManager; 42 import android.server.wm.WindowManagerState.Task; 43 import android.server.wm.backgroundactivity.appa.Components; 44 import android.server.wm.backgroundactivity.common.ITestService; 45 import android.util.Log; 46 47 import androidx.annotation.CallSuper; 48 49 import com.android.compatibility.common.util.AppOpsUtils; 50 import com.android.compatibility.common.util.DeviceConfigStateHelper; 51 52 import org.junit.After; 53 import org.junit.Before; 54 55 import java.time.Duration; 56 import java.time.Instant; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.HashMap; 60 import java.util.List; 61 import java.util.Map; 62 import java.util.function.Predicate; 63 import java.util.stream.Collectors; 64 import java.util.stream.Stream; 65 66 public abstract class BackgroundActivityTestBase extends ActivityManagerTestBase { 67 68 private static final String TAG = BackgroundActivityTestBase.class.getSimpleName(); 69 70 static final String APP_A_PACKAGE = "android.server.wm.backgroundactivity.appa"; 71 static final Components APP_A = Components.get(APP_A_PACKAGE); 72 static final Components APP_A_33 = Components.get(APP_A_PACKAGE + "33"); 73 74 static final String APP_B_PACKAGE = "android.server.wm.backgroundactivity.appb"; 75 static final Components APP_B = Components.get(APP_B_PACKAGE); 76 static final Components APP_B_33 = Components.get(APP_B_PACKAGE + "33"); 77 78 static final String APP_C_PACKAGE = "android.server.wm.backgroundactivity.appc"; 79 static final Components APP_C = Components.get(APP_C_PACKAGE); 80 static final Components APP_C_33 = Components.get(APP_C_PACKAGE + "33"); 81 static final Components APP_ASM_OPT_IN = 82 Components.get("android.server.wm.backgroundactivity.appasmoptin"); 83 84 static final String APP_ASM_OPT_OUT_PACKAGE = 85 "android.server.wm.backgroundactivity.appasmoptout"; 86 static final Components APP_ASM_OPT_OUT = Components.get(APP_ASM_OPT_OUT_PACKAGE); 87 88 static final List<Components> ALL_APPS = 89 List.of(APP_A, APP_A_33, APP_B, APP_B_33, APP_C, APP_C_33, APP_ASM_OPT_OUT); 90 91 static final String SHELL_PACKAGE = "com.android.shell"; 92 // This can be long as the activity should start 93 static final Duration ACTIVITY_FOCUS_TIMEOUT = Duration.ofSeconds(10); 94 // Here we don't expect the activity to start, so we always have to wait. Keep this short. 95 static final Duration ACTIVITY_NOT_FOCUS_TIMEOUT = Duration.ofSeconds(3); 96 97 // TODO(b/258792202): Cleanup with feature flag 98 static final String NAMESPACE_WINDOW_MANAGER = "window_manager"; 99 static final String ASM_RESTRICTIONS_ENABLED = 100 "ActivitySecurity__asm_restrictions_enabled"; 101 private static final int TEST_SERVICE_SETUP_TIMEOUT_MS = 2000; 102 public static final int FOCUS_LOSS_TIMEOUT_MS = 10_000; 103 final DeviceConfigStateHelper mDeviceConfig = 104 new DeviceConfigStateHelper(NAMESPACE_WINDOW_MANAGER); 105 final List<TaskStateDump> mTaskStateDumps = new ArrayList<>(); 106 final Instant mTestStartTime = Instant.now(); 107 108 private final Map<ComponentName, FutureConnection<ITestService>> mServiceConnections = 109 new HashMap<>(); 110 111 @Before enableFeatureFlags()112 public void enableFeatureFlags() { 113 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 114 mDeviceConfig.set(ASM_RESTRICTIONS_ENABLED, "1"); 115 } 116 } 117 118 @After disableFeatureFlags()119 public void disableFeatureFlags() throws Exception { 120 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 121 mDeviceConfig.close(); 122 } else { 123 try { 124 mDeviceConfig.close(); 125 } catch (Exception e) { 126 Log.w(TAG, "Failed to tear down feature flags.", e); 127 } 128 } 129 } 130 131 @Override 132 @Before 133 @CallSuper setUp()134 public void setUp() throws Exception { 135 // disable SAW appopp (it's granted automatically when installed in CTS) 136 for (Components components : ALL_APPS) { 137 AppOpsUtils.setOpMode(components.APP_PACKAGE_NAME, "android:system_alert_window", 138 MODE_ERRORED); 139 assertEquals(AppOpsUtils.getOpMode(components.APP_PACKAGE_NAME, 140 "android:system_alert_window"), 141 MODE_ERRORED); 142 } 143 144 super.setUp(); 145 146 for (Components app : ALL_APPS) { 147 assertNull(mWmState.getTaskByActivity(app.BACKGROUND_ACTIVITY)); 148 assertNull(mWmState.getTaskByActivity(app.FOREGROUND_ACTIVITY)); 149 runShellCommand("cmd deviceidle tempwhitelist -d 100000 " 150 + app.APP_PACKAGE_NAME); 151 } 152 } 153 154 @After tearDown()155 public void tearDown() throws Exception { 156 // We do this before anything else, because having an active device owner can prevent us 157 // from being able to force stop apps. (b/142061276) 158 for (Components app : ALL_APPS) { 159 runWithShellPermissionIdentity(() -> { 160 runShellCommand("dpm remove-active-admin --user 0 " 161 + app.SIMPLE_ADMIN_RECEIVER.flattenToString()); 162 if (UserManager.isHeadlessSystemUserMode()) { 163 // Must also remove the PO from current user 164 runShellCommand("dpm remove-active-admin --user cur " 165 + app.SIMPLE_ADMIN_RECEIVER.flattenToString()); 166 } 167 }); 168 stopTestPackage(app.APP_PACKAGE_NAME); 169 AppOpsUtils.reset(app.APP_PACKAGE_NAME); 170 171 } 172 AppOpsUtils.reset(SHELL_PACKAGE); 173 for (FutureConnection<ITestService> fc : mServiceConnections.values()) { 174 mContext.unbindService(fc); 175 } 176 } 177 assertPinnedStackDoesNotExist()178 void assertPinnedStackDoesNotExist() { 179 mWmState.assertDoesNotContainStack("Must not contain pinned stack.", 180 WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD); 181 } assertTaskStackIsEmpty(ComponentName sourceComponent)182 void assertTaskStackIsEmpty(ComponentName sourceComponent) { 183 Task task = mWmState.getTaskByActivity(sourceComponent); 184 assertWithMessage("task for %s", sourceComponent.flattenToShortString()).that(task) 185 .isNull(); 186 } 187 assertTaskStackHasComponents(ComponentName sourceComponent, ComponentName... expectedComponents)188 void assertTaskStackHasComponents(ComponentName sourceComponent, 189 ComponentName... expectedComponents) { 190 Task task = mWmState.getTaskByActivity(sourceComponent); 191 assertWithMessage("task for %s", sourceComponent.flattenToShortString()).that(task) 192 .isNotNull(); 193 Log.d(TAG, "Task for " + sourceComponent.flattenToShortString() + ": " + task 194 + " Activities: " + task.mActivities); 195 List<String> actualNames = getActivityNames(task.mActivities); 196 List<String> expectedNames = Arrays.stream(expectedComponents) 197 .map((c) -> c.flattenToShortString()).collect(Collectors.toList()); 198 199 assertWithMessage("task activities").that(actualNames) 200 .containsExactlyElementsIn(expectedNames).inOrder(); 201 } 202 assertTaskDoesNotHaveVisibleComponents(ComponentName sourceComponent, ComponentName... expectedComponents)203 void assertTaskDoesNotHaveVisibleComponents(ComponentName sourceComponent, 204 ComponentName... expectedComponents) { 205 Task task = mWmState.getTaskByActivity(sourceComponent); 206 Log.d(TAG, "Task for " + sourceComponent.flattenToShortString() + ": " + task); 207 List<WindowManagerState.Activity> actual = getVisibleActivities(task.mActivities); 208 Log.v(TAG, "Task activities: all=" + task.mActivities + ", visible=" + actual); 209 if (actual == null) { 210 return; 211 } 212 List<String> actualNames = getActivityNames(actual); 213 List<String> expectedNames = Arrays.stream(expectedComponents) 214 .map((c) -> c.flattenToShortString()).collect(Collectors.toList()); 215 216 assertWithMessage("task activities").that(actualNames).containsNoneIn(expectedNames); 217 } 218 getVisibleActivities( List<WindowManagerState.Activity> activities)219 List<WindowManagerState.Activity> getVisibleActivities( 220 List<WindowManagerState.Activity> activities) { 221 return activities.stream().filter(WindowManagerState.Activity::isVisible) 222 .collect(Collectors.toList()); 223 } 224 getActivityNames(List<WindowManagerState.Activity> activities)225 List<String> getActivityNames(List<WindowManagerState.Activity> activities) { 226 return activities.stream().map(a -> a.getName()).collect(Collectors.toList()); 227 } 228 getLaunchActivitiesBroadcast(Components app, ComponentName... componentNames)229 Intent getLaunchActivitiesBroadcast(Components app, 230 ComponentName... componentNames) { 231 Intent broadcastIntent = new Intent( 232 app.FOREGROUND_ACTIVITY_ACTIONS.LAUNCH_BACKGROUND_ACTIVITIES); 233 Intent[] intents = Stream.of(componentNames) 234 .map(c -> { 235 Intent intent = new Intent(); 236 intent.setComponent(c); 237 return intent; 238 }) 239 .toArray(Intent[]::new); 240 broadcastIntent.putExtra(app.FOREGROUND_ACTIVITY_EXTRA.LAUNCH_INTENTS, intents); 241 return broadcastIntent; 242 } 243 getLaunchActivitiesBroadcast(Components app, PendingIntent... pendingIntents)244 Intent getLaunchActivitiesBroadcast(Components app, 245 PendingIntent... pendingIntents) { 246 Intent broadcastIntent = new Intent( 247 app.FOREGROUND_ACTIVITY_ACTIONS.LAUNCH_BACKGROUND_ACTIVITIES); 248 broadcastIntent.putExtra(app.FOREGROUND_ACTIVITY_EXTRA.LAUNCH_PENDING_INTENTS, 249 pendingIntents); 250 return broadcastIntent; 251 } 252 getLaunchAndFinishActivitiesBroadcast(Components app, PendingIntent... pendingIntents)253 Intent getLaunchAndFinishActivitiesBroadcast(Components app, PendingIntent... pendingIntents) { 254 Intent broadcastIntent = new Intent( 255 app.FOREGROUND_ACTIVITY_ACTIONS.LAUNCH_BACKGROUND_ACTIVITIES); 256 broadcastIntent.putExtra(app.FOREGROUND_ACTIVITY_EXTRA.LAUNCH_PENDING_INTENTS, 257 pendingIntents); 258 broadcastIntent.putExtra(app.FOREGROUND_ACTIVITY_EXTRA.LAUNCH_FOR_RESULT_AND_FINISH, true); 259 return broadcastIntent; 260 } 261 262 class ActivityStartVerifier { 263 private Intent mBroadcastIntent = new Intent(); 264 private Intent mLaunchIntent = new Intent(); 265 setupTaskWithForegroundActivity(Components app)266 ActivityStartVerifier setupTaskWithForegroundActivity(Components app) { 267 setupTaskWithForegroundActivity(app, -1); 268 return this; 269 } 270 setupTaskWithForegroundActivity(Components app, int id)271 ActivityStartVerifier setupTaskWithForegroundActivity(Components app, int id) { 272 Intent intent = new Intent(); 273 intent.setComponent(app.FOREGROUND_ACTIVITY); 274 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 275 intent.putExtra(COMMON_FOREGROUND_ACTIVITY_EXTRAS.ACTIVITY_ID, id); 276 mContext.startActivity(intent); 277 mWmState.waitForValidState(app.FOREGROUND_ACTIVITY); 278 return this; 279 } 280 setupTaskWithEmbeddingActivity(Components app)281 ActivityStartVerifier setupTaskWithEmbeddingActivity(Components app) { 282 Intent intent = new Intent(); 283 intent.setComponent(app.FOREGROUND_EMBEDDING_ACTIVITY); 284 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 285 mContext.startActivity(intent); 286 mWmState.waitForValidState(app.FOREGROUND_EMBEDDING_ACTIVITY); 287 return this; 288 } 289 startFromForegroundActivity(Components app)290 ActivityStartVerifier startFromForegroundActivity(Components app) { 291 mBroadcastIntent.setAction( 292 app.FOREGROUND_ACTIVITY_ACTIONS.LAUNCH_BACKGROUND_ACTIVITIES); 293 return this; 294 } 295 startFromForegroundActivity(Components app, int id)296 ActivityStartVerifier startFromForegroundActivity(Components app, int id) { 297 startFromForegroundActivity(app); 298 mBroadcastIntent.putExtra(COMMON_FOREGROUND_ACTIVITY_EXTRAS.ACTIVITY_ID, id); 299 return this; 300 } 301 startFromEmbeddingActivity(Components app)302 ActivityStartVerifier startFromEmbeddingActivity(Components app) { 303 mBroadcastIntent.setAction( 304 app.FOREGROUND_EMBEDDING_ACTIVITY_ACTIONS.LAUNCH_EMBEDDED_ACTIVITY); 305 return this; 306 } 307 withBroadcastExtra(String key, boolean value)308 ActivityStartVerifier withBroadcastExtra(String key, boolean value) { 309 mBroadcastIntent.putExtra(key, value); 310 return this; 311 } 312 activity(ComponentName to)313 ActivityStartVerifier activity(ComponentName to) { 314 mLaunchIntent.setComponent(to); 315 mBroadcastIntent.putExtra(COMMON_FOREGROUND_ACTIVITY_EXTRAS.LAUNCH_INTENTS, 316 new Intent[]{mLaunchIntent}); 317 return this; 318 } 319 activity(ComponentName to, int id)320 ActivityStartVerifier activity(ComponentName to, int id) { 321 activity(to); 322 mLaunchIntent.putExtra(COMMON_FOREGROUND_ACTIVITY_EXTRAS.ACTIVITY_ID, id); 323 return this; 324 } 325 326 // Start an action, expecting the given activity's component name to be started 327 // for this action. action(String action)328 ActivityStartVerifier action(String action) { 329 mLaunchIntent.setAction(action); 330 mBroadcastIntent.putExtra(COMMON_FOREGROUND_ACTIVITY_EXTRAS.LAUNCH_INTENTS, 331 new Intent[]{mLaunchIntent}); 332 return this; 333 } 334 activityIntoNewTask(ComponentName to)335 ActivityStartVerifier activityIntoNewTask(ComponentName to) { 336 activity(to); 337 mLaunchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 338 return this; 339 } 340 allowCrossUidLaunch()341 ActivityStartVerifier allowCrossUidLaunch() { 342 mLaunchIntent.putExtra(COMMON_FOREGROUND_ACTIVITY_EXTRAS.ALLOW_CROSS_UID, true); 343 return this; 344 } 345 346 /** 347 * Broadcasts the specified intents, asserts that the launch succeeded or failed, then 348 * resets all ActivityStartVerifier state (i.e - intent component and flags) so the 349 * ActivityStartVerifier can be reused. 350 */ executeAndAssertLaunch(boolean succeeds)351 ActivityStartVerifier executeAndAssertLaunch(boolean succeeds) { 352 mContext.sendBroadcast(mBroadcastIntent); 353 354 ComponentName launchedComponent = mLaunchIntent.getComponent(); 355 mWmState.waitForValidState(launchedComponent); 356 if (succeeds) { 357 assertActivityFocused(launchedComponent); 358 } else { 359 assertActivityNotFocused(launchedComponent); 360 } 361 362 // Reset intents to remove any added flags 363 reset(); 364 return this; 365 } 366 reset()367 void reset() { 368 mBroadcastIntent = new Intent(); 369 mLaunchIntent = new Intent(); 370 } 371 thenAssert(Runnable run)372 ActivityStartVerifier thenAssert(Runnable run) { 373 run.run(); 374 return this; 375 } 376 thenAssertTaskStack(ComponentName... expectedComponents)377 ActivityStartVerifier thenAssertTaskStack(ComponentName... expectedComponents) { 378 assertTaskStackHasComponents(expectedComponents[expectedComponents.length - 1], 379 expectedComponents); 380 return this; 381 } 382 executeAndWaitForFocusLoss(ComponentName activityToLoseFocus)383 ActivityStartVerifier executeAndWaitForFocusLoss(ComponentName activityToLoseFocus) { 384 mContext.sendBroadcast(mBroadcastIntent); 385 waitForActivityState(FOCUS_LOSS_TIMEOUT_MS, activityToLoseFocus, 386 WindowManagerState.STATE_PAUSED); 387 388 // Reset intents to remove any added flags 389 reset(); 390 return this; 391 } 392 thenAssertTaskHasLostFocus(ComponentName taskWithoutFocus)393 ComponentName thenAssertTaskHasLostFocus(ComponentName taskWithoutFocus) { 394 assertWithMessage("Task should have lost focus: " + taskWithoutFocus).that( 395 mWmState.getFocusedActivity()) 396 .isNotEqualTo(getActivityName(taskWithoutFocus)); 397 return ComponentName.unflattenFromString(mWmState.getFocusedActivity()); 398 } 399 400 /** 401 * <pre> 402 * | expectedRootActivity | expectedEmbeddedActivities | 403 * | fragment 1 - left | fragment 0 - right | 404 * |----------------------|----------------------------| 405 * | | A4 | top 406 * | | A3 | 407 * | A1 | A2 | bottom 408 * </pre> 409 * @param expectedEmbeddedActivities The expected activities on the right side of the split 410 * (fragment 0), top to bottom 411 * @param expectedRootActivity The expected activity on the left side of the split 412 * (fragment 1) 413 */ thenAssertEmbeddingTaskStack( ComponentName[] expectedEmbeddedActivities, ComponentName expectedRootActivity)414 ActivityStartVerifier thenAssertEmbeddingTaskStack( 415 ComponentName[] expectedEmbeddedActivities, ComponentName expectedRootActivity) { 416 List<WindowManagerState.TaskFragment> fragments = mWmState.getTaskByActivity( 417 expectedRootActivity).getTaskFragments(); 418 assertEquals(2, fragments.size()); 419 420 List<WindowManagerState.Activity> embeddedActivities = fragments.get(0).getActivities(); 421 List<WindowManagerState.Activity> rootActivity = fragments.get(1).getActivities(); 422 423 assertEquals(1, rootActivity.size()); 424 assertEquals(expectedRootActivity.flattenToShortString(), 425 rootActivity.get(0).getName()); 426 427 assertEquals(expectedEmbeddedActivities.length, embeddedActivities.size()); 428 for (int i = 0; i < expectedEmbeddedActivities.length; i++) { 429 assertEquals(expectedEmbeddedActivities[i].flattenToShortString(), 430 embeddedActivities.get(i).getName()); 431 } 432 return this; 433 } 434 } 435 436 /** Asserts the activity is the top focused activity among all displays before timeout. */ assertActivityFocused(ComponentName componentName)437 protected void assertActivityFocused(ComponentName componentName) { 438 assertActivityFocused(ACTIVITY_FOCUS_TIMEOUT, componentName); 439 } 440 441 /** Asserts the activity is the top focused activity on its own display before timeout. */ assertActivityFocusedOnMainDisplay(ComponentName componentName)442 protected void assertActivityFocusedOnMainDisplay(ComponentName componentName) { 443 String activityName = getActivityName(componentName); 444 waitForCondition(ACTIVITY_FOCUS_TIMEOUT, 445 mWmState -> activityName.equals(mWmState.getTopActivityName(getMainDisplayId()))); 446 assertWithMessage( 447 "activity " + activityName 448 + " should be on top of main display within " 449 + ACTIVITY_FOCUS_TIMEOUT) 450 .that(mWmState.getTopActivityName(getMainDisplayId())) 451 .isEqualTo(activityName); } 452 assertActivityNotFocused(ComponentName componentName)453 protected void assertActivityNotFocused(ComponentName componentName) { 454 assertActivityNotFocused(ACTIVITY_NOT_FOCUS_TIMEOUT, componentName); 455 } 456 457 /** Asserts the activity is focused before timeout. */ assertActivityFocused(Duration timeout, ComponentName componentName)458 protected void assertActivityFocused(Duration timeout, ComponentName componentName) { 459 assertActivityFocused(timeout, componentName, 460 "activity should be focused within " + timeout); 461 } 462 463 /** Asserts the activity is not focused until timeout. */ assertActivityNotFocused(Duration timeout, ComponentName componentName)464 protected void assertActivityNotFocused(Duration timeout, ComponentName componentName) { 465 assertActivityNotFocused(timeout, componentName, 466 "activity should not be focused within " + timeout); 467 } 468 waitForActivityResumed(Duration timeout, ComponentName componentName)469 private void waitForActivityResumed(Duration timeout, ComponentName componentName) { 470 waitForActivityResumed((int) timeout.toMillis(), componentName); 471 } 472 473 /** Asserts the activity is focused before timeout. */ assertActivityFocused(Duration timeout, ComponentName componentName, String message)474 protected void assertActivityFocused(Duration timeout, ComponentName componentName, 475 String message) { 476 String activityName = getActivityName(componentName); 477 waitForCondition(timeout, wmState -> activityName.equals(mWmState.getFocusedActivity())); 478 assertWithMessage( 479 "activity " + activityName + " should be focused within " 480 + timeout) 481 .that(mWmState.getFocusedActivity()) 482 .isEqualTo(activityName); 483 } 484 485 /** Asserts the activity is not focused until timeout. */ assertActivityNotFocused(Duration timeout, ComponentName componentName, String message)486 protected void assertActivityNotFocused(Duration timeout, ComponentName componentName, 487 String message) { 488 String activityName = getActivityName(componentName); 489 waitForCondition(timeout, mWmState -> 490 // mWmState.hasActivityState(componentName, WindowManagerState.STATE_RESUMED) 491 mWmState.getFocusedActivity().equals(activityName) 492 ); 493 recordTaskStateDump("Assertion"); 494 assertWithMessage( 495 "activity " + activityName 496 + " should NOT be focused within " + timeout + " but was after " 497 + (Duration.between(mTestStartTime, Instant.now())) 498 + allTaskStateDumps() 499 ) 500 .that(mWmState.getFocusedActivity()) 501 .isNotEqualTo(activityName); 502 } 503 assertActivityNotFocused(ComponentName... componentNames)504 protected void assertActivityNotFocused(ComponentName... componentNames) { 505 List<String> activityNames = Stream.of(componentNames) 506 .map(ComponentNameUtils::getActivityName) 507 .toList(); 508 waitForCondition(ACTIVITY_FOCUS_TIMEOUT, mWmState -> 509 activityNames.contains(mWmState.getFocusedActivity())); 510 assertWithMessage( 511 "activities " + activityNames + " should NOT be focused within " 512 + ACTIVITY_FOCUS_TIMEOUT) 513 .that(mWmState.getFocusedActivity()) 514 .isNotIn(activityNames); 515 } 516 517 getTestService(Components c)518 protected TestServiceClient getTestService(Components c) throws Exception { 519 return getTestService(new ComponentName(c.APP_PACKAGE_NAME, TEST_SERVICE)); 520 } 521 getTestService(ComponentName componentName)522 private TestServiceClient getTestService(ComponentName componentName) throws Exception { 523 FutureConnection<ITestService> futureConnection = mServiceConnections.get(componentName); 524 if (futureConnection == null) { 525 // need to setup new test service connection for the component 526 Intent bindIntent = new Intent(); 527 bindIntent.setComponent(componentName); 528 futureConnection = new FutureConnection<>(ITestService.Stub::asInterface); 529 mServiceConnections.put(componentName, futureConnection); 530 boolean success = mContext.bindService(bindIntent, futureConnection, 531 Context.BIND_AUTO_CREATE); 532 assertTrue("Failed to setup " + componentName.toString(), success); 533 } 534 return new TestServiceClient(futureConnection.get(TEST_SERVICE_SETUP_TIMEOUT_MS)); 535 } 536 waitForCondition(Duration timeout, Predicate<WindowManagerStateHelper> predicate)537 private void waitForCondition(Duration timeout, Predicate<WindowManagerStateHelper> predicate) { 538 long endTime = System.currentTimeMillis() + timeout.toMillis(); 539 while (endTime > System.currentTimeMillis()) { 540 recordTaskStateDump("waitForCondition"); // computes mWmState! 541 if (predicate.test(mWmState)) { 542 break; 543 } 544 SystemClock.sleep(200); 545 } 546 } 547 dumpWc(List<String> result, String prefix, WindowManagerState.WindowContainer wc)548 void dumpWc(List<String> result, String prefix, WindowManagerState.WindowContainer wc) { 549 StringBuilder title = new StringBuilder(); 550 title.append(prefix + "-name: " + wc.mName + " (" + wc.getClass() + ")"); 551 if (wc.isVisible()) { 552 title.append(" VISIBLE"); 553 } 554 if (wc.isFullscreen()) { 555 title.append(" FULLSCREEN"); 556 } 557 if (wc instanceof Task t) { 558 title.append(" taskId: " + t.getTaskId()); 559 title.append(" display: " + t.mDisplayId); 560 } 561 if (wc instanceof WindowManagerState.TaskFragment t) { 562 title.append(" display: " + t.mDisplayId); 563 } 564 if (wc instanceof WindowManagerState.Activity a) { 565 title.append(" type: " + a.getActivityType()); 566 title.append(" " + a.getState()); 567 } 568 if (wc.getBounds() != null) { 569 title.append(" bounds:" + wc.getBounds().toShortString()); 570 } 571 result.add(title.toString()); 572 dumpWc(result, prefix, "children", wc.getChildren()); 573 } 574 dumpWc(List<String> result, String prefix, String name, List<? extends WindowManagerState.WindowContainer> wcList)575 void dumpWc(List<String> result, String prefix, String name, 576 List<? extends WindowManagerState.WindowContainer> wcList) { 577 if (!wcList.isEmpty()) { 578 result.add(prefix + " -" + name); 579 for (WindowManagerState.WindowContainer w : wcList) { 580 dumpWc(result, prefix + " ", w); 581 } 582 } 583 } 584 taskToString(Task t)585 String taskToString(Task t) { 586 List<String> result = new ArrayList<>(); 587 dumpWc(result, "", t); 588 return String.join("\n", result); 589 } 590 TaskStateDump(String name, Instant t, String meta, List<String> taskStates)591 record TaskStateDump(String name, Instant t, String meta, List<String> taskStates) {} 592 593 /** 594 * Records the current task state for debugging purposes. 595 * 596 * The progression of state can be retrieved with {@link #allTaskStateDumps()}. 597 * 598 * @param name A name associated with the point in time the state was recorded. 599 */ recordTaskStateDump(String name)600 public void recordTaskStateDump(String name) { 601 mWmState.computeState(); 602 mTaskStateDumps.add(new TaskStateDump(name, Instant.now(), 603 "focused: " + mWmState.getFocusedActivity() 604 + " displays: " + mWmState.getDisplays(), 605 mWmState.getRootTasks().stream().map(this::taskToString).toList())); 606 } 607 608 /** 609 * Return a dump of the state progression (as recorded by {@link #recordTaskStateDump(String)}. 610 * 611 * The text representation is intended to be read by humans and the format may change. 612 */ allTaskStateDumps()613 public String allTaskStateDumps() { 614 Instant now = Instant.now(); 615 StringBuilder sb = new StringBuilder(); 616 TaskStateDump lastDump = new TaskStateDump("none", Instant.EPOCH, "none", List.of()); 617 for (TaskStateDump dump : mTaskStateDumps) { 618 if ("waitForCondition".equals(dump.name) && "waitForCondition".equals(lastDump.name) 619 && dump.meta.equals(lastDump.meta) 620 && dump.taskStates.equals(lastDump.taskStates)) { 621 // this is just waiting for a change that didn't happen yet 622 continue; 623 } 624 sb.append("\n----- " + dump.name + " t=" + Duration.between(mTestStartTime, dump.t) 625 + " (" + dump.t + ") -----\n"); 626 if (!dump.meta.equals(lastDump.meta)) { 627 sb.append(dump.meta + "\n"); 628 } 629 if (!dump.taskStates.equals(lastDump.taskStates)) { 630 for (String s : dump.taskStates) { 631 if (lastDump.taskStates.contains(s)) { 632 sb.append(s.substring(0, s.indexOf("\n")) + " <unchanged>"); 633 } else { 634 sb.append(s); 635 } 636 sb.append("\n"); 637 } 638 } 639 lastDump = dump; 640 } 641 return sb.toString(); 642 } 643 waitAndAssertActivityRemoved(ComponentName componentName)644 protected void waitAndAssertActivityRemoved(ComponentName componentName) { 645 recordTaskStateDump("waitAndAssertActivityRemoved " + getActivityName(componentName)); 646 try { 647 mWmState.waitAndAssertActivityRemoved(componentName); 648 recordTaskStateDump("activityRemoved " + getActivityName(componentName)); 649 } catch (AssertionError e) { 650 throw new AssertionError(e.getMessage() + "\n" + allTaskStateDumps()); 651 } 652 } 653 } 654