1 /*
2  * Copyright (C) 2018 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 com.android.server.wm;
18 
19 import static android.app.ActivityManager.START_SUCCESS;
20 import static android.app.ActivityManager.START_TASK_TO_FRONT;
21 import static android.content.ComponentName.createRelative;
22 import static android.view.WindowManager.TRANSIT_OPEN;
23 
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
29 
30 import static com.google.common.truth.Truth.assertWithMessage;
31 
32 import static org.junit.Assert.assertNull;
33 import static org.mockito.ArgumentMatchers.any;
34 import static org.mockito.ArgumentMatchers.anyBoolean;
35 import static org.mockito.ArgumentMatchers.anyInt;
36 import static org.mockito.ArgumentMatchers.anyLong;
37 import static org.mockito.ArgumentMatchers.eq;
38 import static org.mockito.Mockito.clearInvocations;
39 import static org.mockito.Mockito.never;
40 import static org.mockito.Mockito.timeout;
41 
42 import android.app.ActivityOptions;
43 import android.app.ActivityOptions.SourceInfo;
44 import android.app.WaitResult;
45 import android.app.WindowConfiguration;
46 import android.content.ComponentName;
47 import android.content.Intent;
48 import android.os.IBinder;
49 import android.os.SystemClock;
50 import android.platform.test.annotations.Presubmit;
51 import android.util.ArrayMap;
52 import android.util.Log;
53 import android.window.WindowContainerToken;
54 
55 import androidx.test.filters.SmallTest;
56 
57 import org.junit.Before;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 import org.mockito.ArgumentCaptor;
61 
62 import java.util.concurrent.TimeUnit;
63 import java.util.function.ToIntFunction;
64 
65 /**
66  * Tests for the {@link ActivityMetricsLaunchObserver} class.
67  *
68  * Build/Install/Run:
69  *  atest WmTests:ActivityMetricsLaunchObserverTests
70  */
71 @SmallTest
72 @Presubmit
73 @RunWith(WindowTestRunner.class)
74 public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
75     private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5);
76     private ActivityMetricsLogger mActivityMetricsLogger;
77     private ActivityMetricsLogger.LaunchingState mLaunchingState;
78     private ActivityMetricsLaunchObserver mLaunchObserver;
79 
80     private ActivityRecord mTrampolineActivity;
81     private ActivityRecord mTopActivity;
82     private ActivityOptions mActivityOptions;
83     private Transition mTransition;
84     private boolean mLaunchTopByTrampoline;
85     private boolean mNewActivityCreated = true;
86     private long mExpectedStartedId;
87     private final ArrayMap<ComponentName, Long> mLastLaunchedIds = new ArrayMap<>();
88 
89     @Before
setUpAMLO()90     public void setUpAMLO() {
91         mLaunchObserver = mock(ActivityMetricsLaunchObserver.class);
92 
93         // ActivityTaskSupervisor always creates its own instance of ActivityMetricsLogger.
94         mActivityMetricsLogger = mSupervisor.getActivityMetricsLogger();
95         mActivityMetricsLogger.getLaunchObserverRegistry().registerLaunchObserver(mLaunchObserver);
96 
97         // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful.
98         // This seems to be the easiest way to create an ActivityRecord.
99         mTrampolineActivity = new ActivityBuilder(mAtm)
100                 .setCreateTask(true)
101                 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TrampolineActivity"))
102                 .build();
103         mTopActivity = new ActivityBuilder(mAtm)
104                 .setTask(mTrampolineActivity.getTask())
105                 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TopActivity"))
106                 .build();
107         mTopActivity.mDisplayContent.mOpeningApps.add(mTopActivity);
108         mTransition = new Transition(TRANSIT_OPEN, 0 /* flags */,
109                 mTopActivity.mTransitionController, createTestBLASTSyncEngine());
110         mTransition.mParticipants.add(mTopActivity);
111         mTopActivity.mTransitionController.moveToCollecting(mTransition);
112         // becomes invisible when covered by mTopActivity
113         mTrampolineActivity.setVisibleRequested(false);
114     }
115 
verifyAsync(T mock)116     private <T> T verifyAsync(T mock) {
117         // With WindowTestRunner, all test methods are inside WM lock, so we have to unblock any
118         // messages that are waiting for the lock.
119         waitHandlerIdle(mAtm.mH);
120         // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
121         return verify(mock, timeout(TIMEOUT_MS));
122     }
123 
verifyOnActivityLaunched(ActivityRecord activity)124     private void verifyOnActivityLaunched(ActivityRecord activity) {
125         final ArgumentCaptor<Long> idCaptor = ArgumentCaptor.forClass(Long.class);
126         verifyAsync(mLaunchObserver).onActivityLaunched(idCaptor.capture(),
127                 eq(activity.mActivityComponent), anyInt());
128         final long id = idCaptor.getValue();
129         setExpectedStartedId(id, activity);
130         mLastLaunchedIds.put(activity.mActivityComponent, id);
131     }
132 
verifyOnActivityLaunchFinished(ActivityRecord activity)133     private void verifyOnActivityLaunchFinished(ActivityRecord activity) {
134         verifyAsync(mLaunchObserver).onActivityLaunchFinished(eq(mExpectedStartedId),
135                 eq(activity.mActivityComponent), anyLong());
136     }
137 
setExpectedStartedId(long id, Object reason)138     private void setExpectedStartedId(long id, Object reason) {
139         mExpectedStartedId = id;
140         Log.i("AMLTest", "setExpectedStartedId=" + id + " from " + reason);
141     }
142 
setLastExpectedStartedId(ActivityRecord r)143     private void setLastExpectedStartedId(ActivityRecord r) {
144         setExpectedStartedId(getLastStartedId(r), r);
145     }
146 
getLastStartedId(ActivityRecord r)147     private long getLastStartedId(ActivityRecord r) {
148         final Long id = mLastLaunchedIds.get(r.mActivityComponent);
149         return id != null ? id : -1;
150     }
151 
eqLastStartedId(ActivityRecord r)152     private long eqLastStartedId(ActivityRecord r) {
153         return eq(getLastStartedId(r));
154     }
155 
onIntentStarted(Intent intent)156     private long onIntentStarted(Intent intent) {
157         notifyActivityLaunching(intent);
158 
159         long timestamp = -1;
160         // If it is launching top activity from trampoline activity, the observer shouldn't receive
161         // onActivityLaunched because the activities should belong to the same transition.
162         if (!mLaunchTopByTrampoline) {
163             final ArgumentCaptor<Long> captor = ArgumentCaptor.forClass(Long.class);
164             verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), captor.capture());
165             timestamp = captor.getValue();
166         }
167         verifyNoMoreInteractions(mLaunchObserver);
168         return timestamp;
169     }
170 
171     @Test
testOnIntentFailed()172     public void testOnIntentFailed() {
173         final long id = onIntentStarted(new Intent("testOnIntentFailed"));
174 
175         // Bringing an intent that's already running 'to front' is not considered
176         // as an ACTIVITY_LAUNCHED state transition.
177         notifyActivityLaunched(START_TASK_TO_FRONT, null /* launchedActivity */);
178 
179         verifyAsync(mLaunchObserver).onIntentFailed(eq(id));
180         verifyNoMoreInteractions(mLaunchObserver);
181     }
182 
183     @Test
testLaunchState()184     public void testLaunchState() {
185         final ToIntFunction<Runnable> launchTemplate = action -> {
186             clearInvocations(mLaunchObserver);
187             onActivityLaunched(mTopActivity);
188             notifyTransitionStarting(mTopActivity);
189             if (action != null) {
190                 action.run();
191             }
192             final ActivityMetricsLogger.TransitionInfoSnapshot info =
193                     notifyWindowsDrawn(mTopActivity);
194             verifyOnActivityLaunchFinished(mTopActivity);
195             return info.getLaunchState();
196         };
197 
198         final WindowProcessController app = mTopActivity.app;
199         // Assume that the process is started (ActivityBuilder has mocked the returned value of
200         // ATMS#getProcessController) but the activity has not attached process.
201         mTopActivity.app = null;
202         assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(null))
203                 .isEqualTo(WaitResult.LAUNCH_STATE_WARM);
204 
205         mTopActivity.app = app;
206         mNewActivityCreated = false;
207         assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(null))
208                 .isEqualTo(WaitResult.LAUNCH_STATE_HOT);
209 
210         assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(
211                 () -> mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity)))
212                 .isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH);
213 
214         assertWithMessage("Cold launch by restart").that(launchTemplate.applyAsInt(
215                 () -> mActivityMetricsLogger.notifyBindApplication(
216                         mTopActivity.info.applicationInfo)))
217                 .isEqualTo(WaitResult.LAUNCH_STATE_COLD);
218 
219         mTopActivity.app = null;
220         mNewActivityCreated = true;
221         doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid);
222         assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(null))
223                 .isEqualTo(WaitResult.LAUNCH_STATE_COLD);
224     }
225 
onActivityLaunched(ActivityRecord activity)226     private void onActivityLaunched(ActivityRecord activity) {
227         onIntentStarted(activity.intent);
228         notifyAndVerifyActivityLaunched(activity);
229 
230         verifyNoMoreInteractions(mLaunchObserver);
231     }
232 
233     @Test
testOnActivityLaunchFinished()234     public void testOnActivityLaunchFinished() {
235         onActivityLaunched(mTopActivity);
236 
237         notifyTransitionStarting(mTopActivity);
238         notifyWindowsDrawn(mTopActivity);
239 
240         verifyOnActivityLaunchFinished(mTopActivity);
241         verifyNoMoreInteractions(mLaunchObserver);
242     }
243 
244     @Test
testOnActivityLaunchCancelled_hasDrawn()245     public void testOnActivityLaunchCancelled_hasDrawn() {
246         onActivityLaunched(mTopActivity);
247 
248         mTopActivity.setVisibleRequested(true);
249         doReturn(true).when(mTopActivity).isReportedDrawn();
250 
251         // Cannot time already-visible activities.
252         notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
253 
254         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(mTopActivity));
255         verifyNoMoreInteractions(mLaunchObserver);
256     }
257 
258     @Test
testOnActivityLaunchCancelled_finishedBeforeDrawn()259     public void testOnActivityLaunchCancelled_finishedBeforeDrawn() {
260         doReturn(true).when(mTopActivity).isReportedDrawn();
261 
262         // Create an activity with different process that meets process switch.
263         final ActivityRecord noDrawnActivity = new ActivityBuilder(mAtm)
264                 .setTask(mTopActivity.getTask())
265                 .setProcessName("other")
266                 .build();
267 
268         notifyActivityLaunching(noDrawnActivity.intent);
269         notifyAndVerifyActivityLaunched(noDrawnActivity);
270 
271         noDrawnActivity.setVisibleRequested(false);
272         mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
273 
274         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(noDrawnActivity));
275 
276         // If an activity is removed immediately before visibility update, it should cancel too.
277         final ActivityRecord removedImm = new ActivityBuilder(mAtm).setCreateTask(true).build();
278         clearInvocations(mLaunchObserver);
279         onActivityLaunched(removedImm);
280         removedImm.removeImmediately();
281         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqLastStartedId(removedImm));
282     }
283 
284     @Test
testOnActivityLaunchWhileSleeping()285     public void testOnActivityLaunchWhileSleeping() {
286         notifyActivityLaunching(mTrampolineActivity.intent);
287         notifyAndVerifyActivityLaunched(mTrampolineActivity);
288         doReturn(true).when(mTrampolineActivity.mDisplayContent).isSleeping();
289         mTrampolineActivity.setState(ActivityRecord.State.RESUMED, "test");
290         mTrampolineActivity.setVisibility(false);
291         waitHandlerIdle(mAtm.mH);
292         // Not cancel immediately because in one of real cases, the keyguard may be going away or
293         // occluded later, then the activity can be drawn.
294         verify(mLaunchObserver, never()).onActivityLaunchCancelled(
295                 eqLastStartedId(mTrampolineActivity));
296 
297         clearInvocations(mLaunchObserver);
298         mLaunchTopByTrampoline = true;
299         mTopActivity.setVisibleRequested(false);
300         notifyActivityLaunching(mTopActivity.intent);
301         // It should schedule a message with UNKNOWN_VISIBILITY_CHECK_DELAY_MS to check whether
302         // the launch event is still valid.
303         notifyActivityLaunched(START_SUCCESS, mTopActivity);
304 
305         // The posted message will acquire wm lock, so the test needs to release the lock to verify.
306         final Throwable error = awaitInWmLock(() -> {
307             try {
308                 verify(mLaunchObserver, timeout(TIMEOUT_MS)).onActivityLaunchCancelled(
309                         mExpectedStartedId);
310             } catch (Throwable e) {
311                 // Catch any errors including assertion because this runs in another thread.
312                 return e;
313             }
314             return null;
315         });
316         // The launch event must be cancelled because the activity keeps invisible.
317         if (error != null) {
318             throw new AssertionError(error);
319         }
320     }
321 
322     @Test
testOnReportFullyDrawn()323     public void testOnReportFullyDrawn() {
324         // Create an invisible event that should be cancelled after the next event starts.
325         final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true).build();
326         onActivityLaunched(prev);
327         prev.setVisibleRequested(false);
328 
329         mActivityOptions = ActivityOptions.makeBasic();
330         mActivityOptions.setSourceInfo(SourceInfo.TYPE_LAUNCHER, SystemClock.uptimeMillis() - 10);
331         onIntentStarted(mTopActivity.intent);
332         notifyAndVerifyActivityLaunched(mTopActivity);
333         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eq(getLastStartedId(prev)));
334 
335         // The activity reports fully drawn before windows drawn, then the fully drawn event will
336         // be pending (see {@link WindowingModeTransitionInfo#pendingFullyDrawn}).
337         mActivityMetricsLogger.notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
338         notifyTransitionStarting(mTopActivity);
339         // The pending fully drawn event should send when the actual windows drawn event occurs.
340         final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity);
341         assertWithMessage("Record start source").that(info.sourceType)
342                 .isEqualTo(SourceInfo.TYPE_LAUNCHER);
343         assertWithMessage("Record event time").that(info.sourceEventDelayMs).isAtLeast(10);
344 
345         verifyAsync(mLaunchObserver).onReportFullyDrawn(eq(mExpectedStartedId), anyLong());
346         verifyOnActivityLaunchFinished(mTopActivity);
347         verifyNoMoreInteractions(mLaunchObserver);
348 
349         final ActivityMetricsLogger.TransitionInfoSnapshot fullyDrawnInfo = mActivityMetricsLogger
350                 .notifyFullyDrawn(mTopActivity, false /* restoredFromBundle */);
351         assertWithMessage("Invisible event must be dropped").that(fullyDrawnInfo).isNull();
352     }
353 
onActivityLaunchedTrampoline()354     private void onActivityLaunchedTrampoline() {
355         onIntentStarted(mTrampolineActivity.intent);
356         notifyAndVerifyActivityLaunched(mTrampolineActivity);
357 
358         // A second, distinct, activity launch is coalesced into the current app launch sequence.
359         mLaunchTopByTrampoline = true;
360         onIntentStarted(mTopActivity.intent);
361         notifyActivityLaunched(START_SUCCESS, mTopActivity);
362 
363         // The observer shouldn't receive onActivityLaunched for an existing transition.
364         verifyNoMoreInteractions(mLaunchObserver);
365     }
366 
notifyActivityLaunching(Intent intent)367     private void notifyActivityLaunching(Intent intent) {
368         final ActivityMetricsLogger.LaunchingState previousState = mLaunchingState;
369         mLaunchingState = mActivityMetricsLogger.notifyActivityLaunching(intent,
370                 mLaunchTopByTrampoline ? mTrampolineActivity : null /* caller */,
371                 mLaunchTopByTrampoline ? mTrampolineActivity.getUid() : 0);
372         if (mLaunchTopByTrampoline) {
373             // The transition of TrampolineActivity has not been completed, so when the next
374             // activity is starting from it, the same launching state should be returned.
375             assertWithMessage("Use existing launching state for a caller in active transition")
376                     .that(previousState).isEqualTo(mLaunchingState);
377         }
378     }
379 
notifyActivityLaunched(int resultCode, ActivityRecord activity)380     private void notifyActivityLaunched(int resultCode, ActivityRecord activity) {
381         mActivityMetricsLogger.notifyActivityLaunched(mLaunchingState, resultCode,
382                 mNewActivityCreated, activity, mActivityOptions);
383     }
384 
notifyAndVerifyActivityLaunched(ActivityRecord activity)385     private void notifyAndVerifyActivityLaunched(ActivityRecord activity) {
386         notifyActivityLaunched(START_SUCCESS, activity);
387         verifyOnActivityLaunched(activity);
388     }
389 
notifyTransitionStarting(ActivityRecord activity)390     private void notifyTransitionStarting(ActivityRecord activity) {
391         final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
392         reasons.put(activity, ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN);
393         mActivityMetricsLogger.notifyTransitionStarting(reasons);
394     }
395 
notifyWindowsDrawn(ActivityRecord r)396     private ActivityMetricsLogger.TransitionInfoSnapshot notifyWindowsDrawn(ActivityRecord r) {
397         return mActivityMetricsLogger.notifyWindowsDrawn(r);
398     }
399 
400     @Test
testInTaskActivityStart()401     public void testInTaskActivityStart() {
402         mTrampolineActivity.setVisible(true);
403         doReturn(true).when(mTrampolineActivity).isReportedDrawn();
404         spyOn(mActivityMetricsLogger);
405 
406         onActivityLaunched(mTopActivity);
407         transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
408 
409         verify(mActivityMetricsLogger, timeout(TIMEOUT_MS)).logInTaskActivityStart(
410                 any(), anyBoolean(), anyInt());
411     }
412 
413     @Test
testOnActivityLaunchFinishedTrampoline()414     public void testOnActivityLaunchFinishedTrampoline() {
415         onActivityLaunchedTrampoline();
416 
417         notifyTransitionStarting(mTopActivity);
418         notifyWindowsDrawn(mTrampolineActivity);
419 
420         assertWithMessage("Trampoline activity is drawn but the top activity is not yet")
421                 .that(mLaunchingState.allDrawn()).isFalse();
422 
423         notifyWindowsDrawn(mTopActivity);
424 
425         verifyOnActivityLaunchFinished(mTopActivity);
426         verifyNoMoreInteractions(mLaunchObserver);
427     }
428 
429     @Test
testDoNotCountInvisibleActivityToBeDrawn()430     public void testDoNotCountInvisibleActivityToBeDrawn() {
431         onActivityLaunchedTrampoline();
432         mTrampolineActivity.setVisibility(false);
433         notifyWindowsDrawn(mTopActivity);
434 
435         assertWithMessage("Trampoline activity is invisible so there should be no undrawn windows")
436                 .that(mLaunchingState.allDrawn()).isTrue();
437 
438         // Since the activity is drawn, the launch event should be reported.
439         notifyTransitionStarting(mTopActivity);
440         verifyOnActivityLaunchFinished(mTopActivity);
441         mLaunchTopByTrampoline = false;
442         clearInvocations(mLaunchObserver);
443 
444         // Another round without setting visibility of the trampoline activity.
445         onActivityLaunchedTrampoline();
446         mTrampolineActivity.setState(ActivityRecord.State.PAUSING, "test");
447         notifyWindowsDrawn(mTopActivity);
448         // If the transition can start, the invisible activities should be discarded and the launch
449         // event be reported successfully.
450         notifyTransitionStarting(mTopActivity);
451         verifyOnActivityLaunchFinished(mTopActivity);
452     }
453 
454     @Test
testOnActivityLaunchCancelledTrampoline()455     public void testOnActivityLaunchCancelledTrampoline() {
456         onActivityLaunchedTrampoline();
457 
458         doReturn(true).when(mTopActivity).isReportedDrawn();
459 
460         // Cannot time already-visible activities.
461         notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
462 
463         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(mExpectedStartedId);
464         verifyNoMoreInteractions(mLaunchObserver);
465     }
466 
467     @Test
testActivityDrawnBeforeTransition()468     public void testActivityDrawnBeforeTransition() {
469         mTopActivity.setVisible(false);
470         onIntentStarted(mTopActivity.intent);
471         // Assume the activity is launched the second time consecutively. The drawn event is from
472         // the first time (omitted in test) launch that is earlier than transition.
473         doReturn(true).when(mTopActivity).isReportedDrawn();
474         notifyWindowsDrawn(mTopActivity);
475         verifyNoMoreInteractions(mLaunchObserver);
476 
477         notifyActivityLaunched(START_SUCCESS, mTopActivity);
478         // If the launching activity was drawn when starting transition, the launch event should
479         // be reported successfully.
480         notifyTransitionStarting(mTopActivity);
481 
482         verifyOnActivityLaunched(mTopActivity);
483         verifyOnActivityLaunchFinished(mTopActivity);
484     }
485 
486     @Test
testActivityDrawnWithoutTransition()487     public void testActivityDrawnWithoutTransition() {
488         mTopActivity.mDisplayContent.mOpeningApps.remove(mTopActivity);
489         mTransition.mParticipants.remove(mTopActivity);
490         onIntentStarted(mTopActivity.intent);
491         notifyAndVerifyActivityLaunched(mTopActivity);
492         notifyWindowsDrawn(mTopActivity);
493         // Even if there is no notifyTransitionStarting, the launch event can still be reported
494         // because the drawn activity is not involved in transition.
495         verifyOnActivityLaunchFinished(mTopActivity);
496     }
497 
498     @Test
testConcurrentLaunches()499     public void testConcurrentLaunches() {
500         onActivityLaunched(mTopActivity);
501         clearInvocations(mLaunchObserver);
502         final ActivityMetricsLogger.LaunchingState previousState = mLaunchingState;
503 
504         final ActivityRecord otherActivity = new ActivityBuilder(mAtm)
505                 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "OtherActivity"))
506                 .setCreateTask(true)
507                 .build();
508         // Assume the calling uid is different from the uid of TopActivity, so a new launching
509         // state should be created here.
510         onActivityLaunched(otherActivity);
511 
512         assertWithMessage("Different callers should get 2 independent launching states")
513                 .that(previousState).isNotEqualTo(mLaunchingState);
514         setLastExpectedStartedId(otherActivity);
515         transitToDrawnAndVerifyOnLaunchFinished(otherActivity);
516 
517         // The first transition should still be valid.
518         setLastExpectedStartedId(mTopActivity);
519         transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
520     }
521 
522     @Test
testConsecutiveLaunch()523     public void testConsecutiveLaunch() {
524         onActivityLaunched(mTrampolineActivity);
525         mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
526                 mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
527 
528         // Simulate a corner case that the trampoline activity is removed by CLEAR_TASK.
529         // The 2 launch events can still be coalesced to one by matching the uid.
530         mTrampolineActivity.takeFromHistory();
531         assertNull(mTrampolineActivity.getTask());
532 
533         notifyActivityLaunched(START_SUCCESS, mTopActivity);
534         transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
535     }
536 
537     @Test
testConsecutiveLaunchNewTask()538     public void testConsecutiveLaunchNewTask() {
539         final IBinder launchCookie = mock(IBinder.class);
540         final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
541         mTrampolineActivity.noDisplay = true;
542         mTrampolineActivity.mLaunchCookie = launchCookie;
543         mTrampolineActivity.mLaunchRootTask = launchRootTask;
544         onActivityLaunched(mTrampolineActivity);
545         final ActivityRecord activityOnNewTask = new ActivityBuilder(mAtm)
546                 .setCreateTask(true)
547                 .build();
548         mActivityMetricsLogger.notifyActivityLaunching(activityOnNewTask.intent,
549                 mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
550         notifyActivityLaunched(START_SUCCESS, activityOnNewTask);
551 
552         transitToDrawnAndVerifyOnLaunchFinished(activityOnNewTask);
553         assertWithMessage("Trampoline's cookie must be transferred").that(
554                 mTrampolineActivity.mLaunchCookie).isNull();
555         assertWithMessage("The last launch task has the transferred cookie").that(
556                 activityOnNewTask.mLaunchCookie).isEqualTo(launchCookie);
557         assertWithMessage("Trampoline's launch root task must be transferred").that(
558                 mTrampolineActivity.mLaunchRootTask).isNull();
559         assertWithMessage("The last launch task has the transferred launch root task").that(
560                 activityOnNewTask.mLaunchRootTask).isEqualTo(launchRootTask);
561     }
562 
563     @Test
testConsecutiveLaunchOnDifferentDisplay()564     public void testConsecutiveLaunchOnDifferentDisplay() {
565         onActivityLaunched(mTopActivity);
566 
567         final Task stack = new TaskBuilder(mSupervisor)
568                 .setDisplay(addNewDisplayContentAt(DisplayContent.POSITION_BOTTOM))
569                 .build();
570         final ActivityRecord activityOnNewDisplay = new ActivityBuilder(mAtm)
571                 .setTask(stack)
572                 .setProcessName("new")
573                 .build();
574 
575         // Before TopActivity is drawn, it launches another activity on a different display.
576         mActivityMetricsLogger.notifyActivityLaunching(activityOnNewDisplay.intent,
577                 mTopActivity /* caller */, mTopActivity.getUid());
578         notifyAndVerifyActivityLaunched(activityOnNewDisplay);
579 
580         // There should be 2 events instead of coalescing as one event.
581         setLastExpectedStartedId(mTopActivity);
582         transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
583         setLastExpectedStartedId(activityOnNewDisplay);
584         transitToDrawnAndVerifyOnLaunchFinished(activityOnNewDisplay);
585 
586         assertWithMessage("The launching state must not include the separated launch")
587                 .that(mLaunchingState.contains(activityOnNewDisplay)).isFalse();
588     }
589 
590     @Test
testConsecutiveLaunchWithDifferentWindowingMode()591     public void testConsecutiveLaunchWithDifferentWindowingMode() {
592         mTopActivity.setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
593         mTrampolineActivity.setVisibleRequested(true);
594         onActivityLaunched(mTrampolineActivity);
595         mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
596                 mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
597         notifyAndVerifyActivityLaunched(mTopActivity);
598         // Different windowing modes should be independent launch events.
599         setLastExpectedStartedId(mTrampolineActivity);
600         transitToDrawnAndVerifyOnLaunchFinished(mTrampolineActivity);
601         setLastExpectedStartedId(mTopActivity);
602         transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
603     }
604 
transitToDrawnAndVerifyOnLaunchFinished(ActivityRecord activity)605     private void transitToDrawnAndVerifyOnLaunchFinished(ActivityRecord activity) {
606         notifyTransitionStarting(activity);
607         notifyWindowsDrawn(activity);
608 
609         verifyOnActivityLaunchFinished(activity);
610     }
611 }
612