• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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