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 package com.android.car.am;
17 
18 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
19 
20 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 import static com.google.common.truth.Truth.assertWithMessage;
24 
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertNotNull;
28 import static org.junit.Assert.assertThrows;
29 import static org.junit.Assert.assertTrue;
30 import static org.mockito.ArgumentMatchers.anyInt;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.verify;
33 
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.app.Activity;
37 import android.app.ActivityManager;
38 import android.app.ActivityOptions;
39 import android.app.Instrumentation.ActivityMonitor;
40 import android.app.TaskInfo;
41 import android.car.test.util.DisplayUtils.VirtualDisplaySession;
42 import android.content.ComponentName;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.graphics.Rect;
46 import android.os.Binder;
47 import android.os.Bundle;
48 import android.os.IBinder;
49 import android.os.SystemClock;
50 import android.util.Log;
51 import android.view.Display;
52 import android.view.SurfaceControl;
53 
54 import androidx.test.filters.MediumTest;
55 
56 import com.android.compatibility.common.util.PollingCheck;
57 import com.android.wm.shell.ShellTaskOrganizer;
58 import com.android.wm.shell.common.HandlerExecutor;
59 import com.android.wm.shell.common.SyncTransactionQueue;
60 import com.android.wm.shell.common.TransactionPool;
61 import com.android.wm.shell.fullscreen.FullscreenTaskListener;
62 
63 import com.google.common.truth.Expect;
64 
65 import org.junit.After;
66 import org.junit.Before;
67 import org.junit.Rule;
68 import org.junit.Test;
69 import org.junit.rules.TestName;
70 import org.junit.runner.RunWith;
71 import org.mockito.ArgumentCaptor;
72 import org.mockito.Captor;
73 import org.mockito.Mock;
74 import org.mockito.junit.MockitoJUnitRunner;
75 
76 import java.util.concurrent.CopyOnWriteArrayList;
77 import java.util.concurrent.CountDownLatch;
78 import java.util.concurrent.TimeUnit;
79 import java.util.function.BooleanSupplier;
80 
81 @RunWith(MockitoJUnitRunner.class)
82 @MediumTest
83 public class CarActivityServiceTaskMonitorUnitTest {
84     private static final String TAG = CarActivityServiceTaskMonitorUnitTest.class.getSimpleName();
85 
86     private static final long ACTIVITY_TIMEOUT_MS = 5000;
87     private static final long NO_ACTIVITY_TIMEOUT_MS = 1000;
88     private static final long DEFAULT_TIMEOUT_MS = 10_000;
89     private static final int SLEEP_MS = 50;
90     private static final long SHORT_MIRRORING_TOKEN_TIMEOUT_MS = 100;
91 
92     private static final CopyOnWriteArrayList<TempActivity> sTestActivities =
93             new CopyOnWriteArrayList<>();
94 
95     private CarActivityService mService;
96     @Mock
97     private IBinder mToken;
98     @Captor
99     ArgumentCaptor<IBinder.DeathRecipient> mDeathRecipientCaptor;
100 
101     private ShellTaskOrganizer mTaskOrganizer;
102     private FullscreenTaskListener mFullscreenTaskListener;
103 
104     private final ComponentName mActivityA = new ComponentName(getTestContext(), ActivityA.class);
105     private final ComponentName mActivityB = new ComponentName(getTestContext(), ActivityB.class);
106     private final ComponentName mActivityC = new ComponentName(getTestContext(), ActivityC.class);
107     private final ComponentName mBlockingActivity = new ComponentName(
108             getTestContext(), BlockingActivity.class);
109 
110     @Rule
111     public final Expect expect = Expect.create();
112     @Rule
113     public TestName mTestName = new TestName();
114 
115     @Before
setUp()116     public void setUp() throws Exception {
117         long timeOutMs = DEFAULT_TIMEOUT_MS;
118         if (mTestName.getMethodName().contains("ExpiredToken")) {
119             timeOutMs = SHORT_MIRRORING_TOKEN_TIMEOUT_MS;
120         }
121         mService = new CarActivityService(getContext(), timeOutMs);
122         mService.init();
123         mService.registerTaskMonitor(mToken);
124         setUpTaskOrganizer();
125     }
126 
127     @After
tearDown()128     public void tearDown() throws InterruptedException {
129         tearDownTaskOrganizer();
130         for (TempActivity activity : sTestActivities) {
131             activity.finish();
132             activity.waitForDestroyed();
133         }
134         sTestActivities.clear();
135         mService.unregisterTaskMonitor(mToken);
136         // Any remaining ActivityListeners will be flushed in release().
137         mService.release();
138         mService = null;
139     }
140 
setUpTaskOrganizer()141     private void setUpTaskOrganizer() throws Exception {
142         Context context = getContext();
143         HandlerExecutor mExecutor = new HandlerExecutor(context.getMainThreadHandler());
144         mTaskOrganizer = new ShellTaskOrganizer(mExecutor);
145         TransactionPool transactionPool = new TransactionPool();
146         SyncTransactionQueue syncQueue = new SyncTransactionQueue(transactionPool, mExecutor);
147         mFullscreenTaskListener = new TestTaskListener(syncQueue);
148         mTaskOrganizer.addListenerForType(mFullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN);
149         mTaskOrganizer.registerOrganizer();
150     }
151 
tearDownTaskOrganizer()152     private void tearDownTaskOrganizer() {
153         mTaskOrganizer.removeListener(mFullscreenTaskListener);
154         mTaskOrganizer.unregisterOrganizer();
155     }
156 
157     private class TestTaskListener extends FullscreenTaskListener {
TestTaskListener(SyncTransactionQueue syncQueue)158         TestTaskListener(SyncTransactionQueue syncQueue) {
159             super(syncQueue);
160         }
161 
162         @Override
onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)163         public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
164             super.onTaskAppeared(taskInfo, leash);
165             mService.onTaskAppeared(mToken, taskInfo, leash);
166         }
167 
168         @Override
onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)169         public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
170             super.onTaskInfoChanged(taskInfo);
171             mService.onTaskInfoChanged(mToken, taskInfo);
172         }
173 
174         @Override
onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)175         public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
176             super.onTaskVanished(taskInfo);
177             mService.onTaskVanished(mToken, taskInfo);
178         }
179     }
180 
181     @Test
testActivityCameOnTop()182     public void testActivityCameOnTop() throws Exception {
183         startActivityAndAssertCameOnTop(mActivityA);
184 
185         startActivityAndAssertCameOnTop(mActivityB);
186     }
187 
188     @Test
testActivityChangedInBackstackOnTaskInfoChanged()189     public void testActivityChangedInBackstackOnTaskInfoChanged() throws Exception {
190         FilteredListener listener = startActivityAndAssertCameOnTop(mActivityA);
191 
192         // When some activity is launched from another, the baseIntent of the activity becomes
193         // the launching activity due to which an onActivityLaunched callback is received. The
194         // purpose of launching home here is that the baseIntent is not set and the correct
195         // onActivityChanged callback is received.
196         launchHomeScreenUsingIntent();
197 
198         listener.assertTopTaskActivityChangedInBackstack();
199     }
200 
launchHomeScreenUsingIntent()201     private void launchHomeScreenUsingIntent() {
202         Intent intent = new Intent(Intent.ACTION_MAIN)
203                 .addCategory(Intent.CATEGORY_HOME)
204                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
205         getTestContext().startActivity(intent);
206     }
207 
208     @Test
testMultipleActivityListeners()209     public void testMultipleActivityListeners() throws Exception {
210         FilteredListener listener1 = new FilteredListener(mActivityA);
211         mService.registerActivityListener(listener1);
212         FilteredListener listener2 = new FilteredListener(mActivityA);
213         mService.registerActivityListener(listener2);
214 
215         startActivity(mActivityA, Display.DEFAULT_DISPLAY);
216 
217         listener2.assertTopTaskActivityCameOnTop();
218         assertThat(listener1.mActivityCameOnTop.getCount()).isEqualTo(0);
219     }
220 
221     @Test
testUnregisterActivityListener()222     public void testUnregisterActivityListener() throws Exception {
223         FilteredListener listener1 = new FilteredListener(mActivityA);
224         mService.registerActivityListener(listener1);
225         FilteredListener listener2 = new FilteredListener(mActivityA);
226         mService.registerActivityListener(listener2);
227         mService.unregisterActivityListener(listener1);
228 
229         startActivity(mActivityA, Display.DEFAULT_DISPLAY);
230 
231         listener2.assertTopTaskActivityCameOnTop();
232         assertThat(listener1.mActivityCameOnTop.getCount()).isEqualTo(1);
233     }
234 
235     @Test
testDeathRecipientIsSet()236     public void testDeathRecipientIsSet() throws Exception {
237         FilteredListener listenerA = new FilteredListener(mActivityA);
238         mService.registerActivityListener(listenerA);
239 
240         verify(mToken).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
241     }
242 
243     @Test
testBinderDied_cleansUpDeathRecipient()244     public void testBinderDied_cleansUpDeathRecipient() throws Exception {
245         FilteredListener listenerA = new FilteredListener(mActivityA);
246         mService.registerActivityListener(listenerA);
247 
248         verify(mToken).linkToDeath(mDeathRecipientCaptor.capture(), anyInt());
249         mDeathRecipientCaptor.getValue().binderDied();
250 
251         // Checks if binderDied() will clean-up the death recipient.
252         verify(mToken).unlinkToDeath(eq(mDeathRecipientCaptor.getValue()), anyInt());
253 
254         startActivity(mActivityA);
255         // Starting a Activity shouldn't trigger the listener since the token is invalid.
256         assertWithMessage("Shouldn't trigger the ActivityListener")
257                 .that(listenerA.waitForTopTaskActivityCameOnTop(NO_ACTIVITY_TIMEOUT_MS)).isFalse();
258     }
259 
260     @Test
testActivityBlocking()261     public void testActivityBlocking() throws Exception {
262         Intent blockingIntent = new Intent().setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
263         blockingIntent.setComponent(mBlockingActivity);
264 
265         // start a deny listed activity
266         FilteredListener listenerDenyListed = startActivityAndAssertCameOnTop(mActivityC);
267 
268         // Instead of start activity, invoke blockActivity.
269         FilteredListener listenerBlocking = new FilteredListener(mBlockingActivity);
270         mService.registerActivityListener(listenerBlocking);
271         mService.blockActivity(listenerDenyListed.mTopTask, blockingIntent);
272         listenerBlocking.assertTopTaskActivityCameOnTop();
273     }
274 
275     @Test
testRemovesFromTopTasks()276     public void testRemovesFromTopTasks() throws Exception {
277         FilteredListener listenerA = new FilteredListener(mActivityA);
278         mService.registerActivityListener(listenerA);
279         Activity launchedActivity = startActivity(mActivityA);
280         listenerA.assertTopTaskActivityCameOnTop();
281         assertTrue(topTasksHasComponent(mActivityA));
282 
283         getInstrumentation().runOnMainSync(launchedActivity::finish);
284         listenerA.assertTopTaskActivityChangedInBackstack();
285     }
286 
287     @Test
testGetTopTasksOnMultiDisplay()288     public void testGetTopTasksOnMultiDisplay() throws Exception {
289         // TaskOrganizer gets the callbacks only on the tasks launched in the actual Surface.
290         try (VirtualDisplaySession session = new VirtualDisplaySession()) {
291             int virtualDisplayId = session.createDisplayWithDefaultDisplayMetricsAndWait(
292                     getTestContext(), /* isPrivate= */ false).getDisplayId();
293 
294             startActivityAndAssertCameOnTop(mActivityA);
295             assertTrue(topTasksHasComponent(mActivityA));
296 
297             startActivityAndAssertCameOnTop(mActivityB, virtualDisplayId);
298             assertTrue(topTasksHasComponent(mActivityB));
299             assertTrue(topTasksHasComponent(mActivityA));
300 
301             startActivityAndAssertCameOnTop(mActivityC, virtualDisplayId);
302             assertTrue(topTasksHasComponent(mActivityC));
303             assertFalse(topTasksHasComponent(mActivityB));
304             assertTrue(topTasksHasComponent(mActivityA));
305         }
306     }
307 
308     @Test
testGetTopTasksOnDefaultDisplay()309     public void testGetTopTasksOnDefaultDisplay() throws Exception {
310         startActivityAndAssertCameOnTop(mActivityA);
311         assertTrue(topTasksHasComponent(mActivityA));
312 
313         startActivityAndAssertCameOnTop(mActivityB);
314         assertTrue(topTasksHasComponent(mActivityB));
315         assertFalse(topTasksHasComponent(mActivityA));
316     }
317 
318     @Test
testGetTaskInfoForTopActivity()319     public void testGetTaskInfoForTopActivity() throws Exception {
320         startActivityAndAssertCameOnTop(mActivityA);
321 
322         TaskInfo taskInfo = mService.getTaskInfoForTopActivity(mActivityA);
323         assertNotNull(taskInfo);
324         assertEquals(mActivityA, taskInfo.topActivity);
325     }
326 
327     @Test
testRestartTask()328     public void testRestartTask() throws Exception {
329         startActivityAndAssertCameOnTop(mActivityA);
330 
331         startActivityAndAssertCameOnTop(mActivityB);
332 
333         FilteredListener listenerRestartA = new FilteredListener(mActivityA);
334         mService.registerActivityListener(listenerRestartA);
335 
336         // ActivityA and ActivityB are in the same package, so ActivityA becomes the root task of
337         // ActivityB, so when we restarts ActivityB, it'll start ActivityA.
338         TaskInfo taskInfo = mService.getTaskInfoForTopActivity(mActivityB);
339         mService.restartTask(taskInfo.taskId);
340 
341         listenerRestartA.assertTopTaskActivityCameOnTop();
342     }
343 
344     @Test
testCreateMirroredToken_throwsExceptionForNonExistentTask()345     public void testCreateMirroredToken_throwsExceptionForNonExistentTask() {
346         int nonExistentTaskId = -999;
347         assertThrows(IllegalArgumentException.class,
348                 () -> mService.createTaskMirroringToken(nonExistentTaskId));
349     }
350 
351     @Test
testCreateMirroredToken_returnsToken()352     public void testCreateMirroredToken_returnsToken() throws Exception {
353         FilteredListener listenerA = startActivityAndAssertCameOnTop(mActivityA);
354 
355         IBinder token = mService.createTaskMirroringToken(listenerA.mTopTask.taskId);
356         assertThat(token).isNotNull();
357     }
358 
359     @Test
testGetMirroredSurface_throwsExceptionForInvalidToken()360     public void testGetMirroredSurface_throwsExceptionForInvalidToken() {
361         IBinder invalidToken = new Binder();
362         Rect outBounds = new Rect();
363         assertThrows(IllegalArgumentException.class,
364                 () -> mService.getMirroredSurface(invalidToken, outBounds));
365     }
366 
367     @Test
testGetMirroredSurface_throwsExceptionForForgedToken()368     public void testGetMirroredSurface_throwsExceptionForForgedToken() {
369         CarActivityService fakeService = new CarActivityService(getContext());
370         IBinder forgedToken = fakeService.createDisplayMirroringToken(Display.DEFAULT_DISPLAY);
371         Rect outBounds = new Rect();
372         assertThrows(IllegalArgumentException.class,
373                 () -> mService.getMirroredSurface(forgedToken, outBounds));
374     }
375 
376     @Test
testGetMirroredSurface_throwsExceptionForExpiredToken()377     public void testGetMirroredSurface_throwsExceptionForExpiredToken() throws Exception {
378         FilteredListener listenerA = startActivityAndAssertCameOnTop(mActivityA);
379 
380         IBinder token = mService.createTaskMirroringToken(listenerA.mTopTask.taskId);
381         Rect outBounds = new Rect();
382 
383         SystemClock.sleep(SHORT_MIRRORING_TOKEN_TIMEOUT_MS * 2);
384 
385         assertThrows(IllegalArgumentException.class,
386                 () -> mService.getMirroredSurface(token, outBounds));
387     }
388 
389     @Test
testGetMirroredSurface_returnsNullForInvisibleToken()390     public void testGetMirroredSurface_returnsNullForInvisibleToken() throws Exception {
391         FilteredListener listenerA = startActivityAndAssertCameOnTop(mActivityA);
392 
393         IBinder token = mService.createTaskMirroringToken(listenerA.mTopTask.taskId);
394 
395         // Uses the Activity with the different taskAffinity to make the previous Task hidden.
396         startActivityAndAssertCameOnTop(mBlockingActivity);
397         // Now the Surface of the token will be invisible.
398 
399         Rect outBounds = new Rect();
400         PollingCheck.waitFor(() -> mService.getMirroredSurface(token, outBounds) == null,
401                 "The mirrored surface couldn't become invisible");
402     }
403 
404     @Test
testGetMirroredSurface_returnsSurface()405     public void testGetMirroredSurface_returnsSurface() throws Exception {
406         FilteredListener listenerA = startActivityAndAssertCameOnTop(mActivityA);
407 
408         IBinder token = mService.createTaskMirroringToken(listenerA.mTopTask.taskId);
409         Rect outBounds = new Rect();
410 
411         SurfaceControl mirror = mService.getMirroredSurface(token, outBounds);
412 
413         expect.that(outBounds.isEmpty()).isFalse();
414         expect.that(outBounds).isEqualTo(
415                 listenerA.mTopTask.getConfiguration().windowConfiguration.getBounds());
416         assertThat(mirror).isNotNull();
417         assertThat(mirror.isValid()).isTrue();
418     }
419 
startActivityAndAssertCameOnTop(ComponentName activity)420     private FilteredListener startActivityAndAssertCameOnTop(ComponentName activity)
421             throws InterruptedException {
422         return startActivityAndAssertCameOnTop(activity, Display.DEFAULT_DISPLAY);
423     }
424 
startActivityAndAssertCameOnTop( ComponentName activity, int displayId)425     private FilteredListener startActivityAndAssertCameOnTop(
426             ComponentName activity, int displayId) throws InterruptedException {
427         FilteredListener listener = new FilteredListener(activity);
428         mService.registerActivityListener(listener);
429         startActivity(activity, displayId);
430         listener.assertTopTaskActivityCameOnTop();
431         return listener;
432     }
433 
waitUntil(BooleanSupplier condition)434     private void waitUntil(BooleanSupplier condition) {
435         for (long i = DEFAULT_TIMEOUT_MS / SLEEP_MS; !condition.getAsBoolean() && i > 0; --i) {
436             SystemClock.sleep(SLEEP_MS);
437         }
438         if (!condition.getAsBoolean()) {
439             throw new RuntimeException("failed while waiting for condition to become true");
440         }
441     }
442 
topTasksHasComponent(ComponentName component)443     private boolean topTasksHasComponent(ComponentName component) {
444         for (TaskInfo topTaskInfoContainer : mService.getVisibleTasksInternal()) {
445             if (topTaskInfoContainer.topActivity.equals(component)) {
446                 return true;
447             }
448         }
449         return false;
450     }
451 
452     /** Activity that closes itself after some timeout to clean up the screen. */
453     public static class TempActivity extends Activity {
454         private final CountDownLatch mDestroyed = new CountDownLatch(1);
455         private static final long QUIET_TIME_TO_BE_CONSIDERED_IDLE_STATE = 1000;  // ms
456         @Override
onCreate(@ullable Bundle savedInstanceState)457         protected void onCreate(@Nullable Bundle savedInstanceState) {
458             super.onCreate(savedInstanceState);
459             sTestActivities.add(this);
460         }
461 
462         @Override
onDestroy()463         protected void onDestroy() {
464             super.onDestroy();
465             mDestroyed.countDown();
466         }
467 
waitForDestroyed()468         private boolean waitForDestroyed() throws InterruptedException {
469             return mDestroyed.await(QUIET_TIME_TO_BE_CONSIDERED_IDLE_STATE, TimeUnit.MILLISECONDS);
470         }
471     }
472 
473     public static class ActivityA extends TempActivity {}
474 
475     public static class ActivityB extends TempActivity {}
476 
477     public static class ActivityC extends TempActivity {}
478 
479     public static class BlockingActivity extends TempActivity {}
480 
getContext()481     private static Context getContext() {
482         return getInstrumentation().getTargetContext();
483     }
484 
getTestContext()485     private static Context getTestContext() {
486         return getInstrumentation().getContext();
487     }
488 
startActivity(ComponentName name)489     private static Activity startActivity(ComponentName name) {
490         return startActivity(name, Display.DEFAULT_DISPLAY);
491     }
492 
startActivity(ComponentName name, int displayId)493     private static Activity startActivity(ComponentName name, int displayId) {
494         ActivityMonitor monitor = new ActivityMonitor(name.getClassName(), null, false);
495         getInstrumentation().addMonitor(monitor);
496 
497         Intent intent = new Intent();
498         intent.setComponent(name);
499         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
500 
501         Bundle bundle = null;
502         if (displayId != Display.DEFAULT_DISPLAY) {
503             ActivityOptions options = ActivityOptions.makeBasic();
504             options.setLaunchDisplayId(displayId);
505             bundle = options.toBundle();
506         }
507         getContext().startActivity(intent, bundle);
508         return monitor.waitForActivityWithTimeout(ACTIVITY_TIMEOUT_MS);
509     }
510 
511     private static final class FilteredListener implements CarActivityService.ActivityListener {
512         private final ComponentName mDesiredComponent;
513         private final CountDownLatch mActivityCameOnTop = new CountDownLatch(1);
514         private final CountDownLatch mActivityChangedInBackstack = new CountDownLatch(1);
515         private TaskInfo mTopTask;
516 
517         /**
518          * Creates an instance of a {@link CarActivityService.ActivityListener}
519          * that filters based on the component name or does not filter if component name is null.
520          */
FilteredListener(@onNull ComponentName desiredComponent)521         private FilteredListener(@NonNull ComponentName desiredComponent) {
522             mDesiredComponent = desiredComponent;
523         }
524 
525         @Override
onActivityCameOnTop(TaskInfo topTask)526         public void onActivityCameOnTop(TaskInfo topTask) {
527             if (isActivityOutsideTestPackage(topTask)) {
528                 return;
529             }
530             if (!topTask.topActivity.equals(mDesiredComponent)) {
531                 Log.d(TAG,
532                         String.format("onActivityCameOnTop#Unexpected component: %s. Expected: %s",
533                                 topTask.topActivity.getClassName(), mDesiredComponent));
534                 return;
535             }
536             if (mTopTask == null) {  // We are interested in the first one only.
537                 mTopTask = topTask;
538             }
539             mActivityCameOnTop.countDown();
540         }
541 
542         @Override
onActivityChangedInBackstack(TaskInfo taskInfo)543         public void onActivityChangedInBackstack(TaskInfo taskInfo) {
544             if (isActivityOutsideTestPackage(taskInfo)) {
545                 return;
546             }
547             if (!taskInfo.baseIntent.getComponent().equals(mDesiredComponent)) {
548                 Log.d(TAG, String.format(
549                         "onActivityChangedInBackstack#Unexpected component: %s. Expected: %s",
550                         taskInfo.baseIntent.getComponent(), mDesiredComponent));
551                 return;
552             }
553             mActivityChangedInBackstack.countDown();
554         }
555 
isActivityOutsideTestPackage(TaskInfo taskInfo)556         private boolean isActivityOutsideTestPackage(TaskInfo taskInfo) {
557             if (taskInfo.topActivity != null && !getTestContext().getPackageName().equals(
558                     taskInfo.topActivity.getPackageName())) {
559                 Log.d(TAG, "Component launched from other package: "
560                         + taskInfo.topActivity.getClassName());
561                 return true;
562             }
563             return false;
564         }
565 
assertTopTaskActivityCameOnTop()566         private void assertTopTaskActivityCameOnTop() throws InterruptedException {
567             assertThat(waitForTopTaskActivityCameOnTop(DEFAULT_TIMEOUT_MS)).isTrue();
568         }
569 
waitForTopTaskActivityCameOnTop(long timeoutMs)570         private boolean waitForTopTaskActivityCameOnTop(long timeoutMs)
571                 throws InterruptedException {
572             return mActivityCameOnTop.await(timeoutMs, TimeUnit.MILLISECONDS);
573         }
574 
assertTopTaskActivityChangedInBackstack()575         private void assertTopTaskActivityChangedInBackstack() throws InterruptedException {
576             assertThat(waitForTopTaskActivityChangedInBackstack(DEFAULT_TIMEOUT_MS)).isTrue();
577         }
578 
waitForTopTaskActivityChangedInBackstack(long timeoutMs)579         private boolean waitForTopTaskActivityChangedInBackstack(long timeoutMs)
580                 throws InterruptedException {
581             return mActivityChangedInBackstack.await(timeoutMs, TimeUnit.MILLISECONDS);
582         }
583     }
584 }
585