• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions;
27 
28 import static com.google.common.truth.Truth.assertWithMessage;
29 
30 import static org.mockito.ArgumentMatchers.anyInt;
31 import static org.mockito.ArgumentMatchers.anyLong;
32 import static org.mockito.ArgumentMatchers.argThat;
33 import static org.mockito.ArgumentMatchers.eq;
34 import static org.mockito.Mockito.clearInvocations;
35 import static org.mockito.Mockito.never;
36 import static org.mockito.Mockito.timeout;
37 
38 import android.app.ActivityOptions;
39 import android.app.ActivityOptions.SourceInfo;
40 import android.app.WaitResult;
41 import android.app.WindowConfiguration;
42 import android.content.Intent;
43 import android.os.IBinder;
44 import android.os.SystemClock;
45 import android.platform.test.annotations.Presubmit;
46 import android.util.ArrayMap;
47 
48 import androidx.test.filters.SmallTest;
49 
50 import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
51 
52 import org.junit.After;
53 import org.junit.Before;
54 import org.junit.Test;
55 import org.junit.runner.RunWith;
56 import org.mockito.ArgumentMatcher;
57 
58 import java.util.Arrays;
59 import java.util.concurrent.TimeUnit;
60 import java.util.function.ToIntFunction;
61 
62 /**
63  * Tests for the {@link ActivityMetricsLaunchObserver} class.
64  *
65  * Build/Install/Run:
66  *  atest WmTests:ActivityMetricsLaunchObserverTests
67  */
68 @SmallTest
69 @Presubmit
70 @RunWith(WindowTestRunner.class)
71 public class ActivityMetricsLaunchObserverTests extends WindowTestsBase {
72     private ActivityMetricsLogger mActivityMetricsLogger;
73     private ActivityMetricsLogger.LaunchingState mLaunchingState;
74     private ActivityMetricsLaunchObserver mLaunchObserver;
75     private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry;
76 
77     private ActivityRecord mTrampolineActivity;
78     private ActivityRecord mTopActivity;
79     private ActivityOptions mActivityOptions;
80     private boolean mLaunchTopByTrampoline;
81     private boolean mNewActivityCreated = true;
82 
83     @Before
setUpAMLO()84     public void setUpAMLO() {
85         mLaunchObserver = mock(ActivityMetricsLaunchObserver.class);
86 
87         // ActivityTaskSupervisor always creates its own instance of ActivityMetricsLogger.
88         mActivityMetricsLogger = mSupervisor.getActivityMetricsLogger();
89 
90         mLaunchObserverRegistry = mActivityMetricsLogger.getLaunchObserverRegistry();
91         mLaunchObserverRegistry.registerLaunchObserver(mLaunchObserver);
92 
93         // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful.
94         // This seems to be the easiest way to create an ActivityRecord.
95         mTrampolineActivity = new ActivityBuilder(mAtm)
96                 .setCreateTask(true)
97                 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TrampolineActivity"))
98                 .build();
99         mTopActivity = new ActivityBuilder(mAtm)
100                 .setTask(mTrampolineActivity.getTask())
101                 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TopActivity"))
102                 .build();
103         // becomes invisible when covered by mTopActivity
104         mTrampolineActivity.mVisibleRequested = false;
105     }
106 
107     @After
tearDownAMLO()108     public void tearDownAMLO() {
109         if (mLaunchObserverRegistry != null) {  // Don't NPE if setUp failed.
110             mLaunchObserverRegistry.unregisterLaunchObserver(mLaunchObserver);
111         }
112     }
113 
114     static class ActivityRecordMatcher implements ArgumentMatcher</*@ActivityRecordProto*/ byte[]> {
115         private final @ActivityRecordProto byte[] mExpected;
116 
ActivityRecordMatcher(ActivityRecord activityRecord)117         public ActivityRecordMatcher(ActivityRecord activityRecord) {
118             mExpected = activityRecordToProto(activityRecord);
119         }
120 
matches(@ctivityRecordProto byte[] actual)121         public boolean matches(@ActivityRecordProto byte[] actual) {
122             return Arrays.equals(mExpected, actual);
123         }
124     }
125 
activityRecordToProto(ActivityRecord record)126     static @ActivityRecordProto byte[] activityRecordToProto(ActivityRecord record) {
127         return ActivityMetricsLogger.convertActivityRecordToProto(record);
128     }
129 
eqProto(ActivityRecord record)130     static @ActivityRecordProto byte[] eqProto(ActivityRecord record) {
131         return argThat(new ActivityRecordMatcher(record));
132     }
133 
verifyAsync(T mock)134     private <T> T verifyAsync(T mock) {
135         // With WindowTestRunner, all test methods are inside WM lock, so we have to unblock any
136         // messages that are waiting for the lock.
137         waitHandlerIdle(mAtm.mH);
138         // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout.
139         return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5)));
140     }
141 
verifyOnActivityLaunchFinished(ActivityRecord activity)142     private void verifyOnActivityLaunchFinished(ActivityRecord activity) {
143         verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(activity), anyLong());
144     }
145 
onIntentStarted(Intent intent)146     private void onIntentStarted(Intent intent) {
147         notifyActivityLaunching(intent);
148 
149         // If it is launching top activity from trampoline activity, the observer shouldn't receive
150         // onActivityLaunched because the activities should belong to the same transition.
151         if (!mLaunchTopByTrampoline) {
152             verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), anyLong());
153         }
154         verifyNoMoreInteractions(mLaunchObserver);
155     }
156 
157     @Test
testOnIntentFailed()158     public void testOnIntentFailed() {
159         onIntentStarted(new Intent("testOnIntentFailed"));
160 
161         // Bringing an intent that's already running 'to front' is not considered
162         // as an ACTIVITY_LAUNCHED state transition.
163         notifyActivityLaunched(START_TASK_TO_FRONT, null /* launchedActivity */);
164 
165         verifyAsync(mLaunchObserver).onIntentFailed();
166         verifyNoMoreInteractions(mLaunchObserver);
167     }
168 
169     @Test
testLaunchState()170     public void testLaunchState() {
171         final ToIntFunction<Boolean> launchTemplate = doRelaunch -> {
172             clearInvocations(mLaunchObserver);
173             onActivityLaunched(mTopActivity);
174             notifyTransitionStarting(mTopActivity);
175             if (doRelaunch) {
176                 mActivityMetricsLogger.notifyActivityRelaunched(mTopActivity);
177             }
178             final ActivityMetricsLogger.TransitionInfoSnapshot info =
179                     notifyWindowsDrawn(mTopActivity);
180             verifyOnActivityLaunchFinished(mTopActivity);
181             return info.getLaunchState();
182         };
183 
184         final WindowProcessController app = mTopActivity.app;
185         // Assume that the process is started (ActivityBuilder has mocked the returned value of
186         // ATMS#getProcessController) but the activity has not attached process.
187         mTopActivity.app = null;
188         assertWithMessage("Warm launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
189                 .isEqualTo(WaitResult.LAUNCH_STATE_WARM);
190 
191         mTopActivity.app = app;
192         mNewActivityCreated = false;
193         assertWithMessage("Hot launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
194                 .isEqualTo(WaitResult.LAUNCH_STATE_HOT);
195 
196         assertWithMessage("Relaunch").that(launchTemplate.applyAsInt(true /* doRelaunch */))
197                 .isEqualTo(WaitResult.LAUNCH_STATE_RELAUNCH);
198 
199         mTopActivity.app = null;
200         mNewActivityCreated = true;
201         doReturn(null).when(mAtm).getProcessController(app.mName, app.mUid);
202         assertWithMessage("Cold launch").that(launchTemplate.applyAsInt(false /* doRelaunch */))
203                 .isEqualTo(WaitResult.LAUNCH_STATE_COLD);
204     }
205 
onActivityLaunched(ActivityRecord activity)206     private void onActivityLaunched(ActivityRecord activity) {
207         onIntentStarted(activity.intent);
208         notifyActivityLaunched(START_SUCCESS, activity);
209 
210         verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(activity), anyInt());
211         verifyNoMoreInteractions(mLaunchObserver);
212     }
213 
214     @Test
testOnActivityLaunchFinished()215     public void testOnActivityLaunchFinished() {
216         onActivityLaunched(mTopActivity);
217 
218         notifyTransitionStarting(mTopActivity);
219         notifyWindowsDrawn(mTopActivity);
220 
221         verifyOnActivityLaunchFinished(mTopActivity);
222         verifyNoMoreInteractions(mLaunchObserver);
223     }
224 
225     @Test
testOnActivityLaunchCancelled_hasDrawn()226     public void testOnActivityLaunchCancelled_hasDrawn() {
227         onActivityLaunched(mTopActivity);
228 
229         mTopActivity.mVisibleRequested = true;
230         doReturn(true).when(mTopActivity).isReportedDrawn();
231 
232         // Cannot time already-visible activities.
233         notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
234 
235         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity));
236         verifyNoMoreInteractions(mLaunchObserver);
237     }
238 
239     @Test
testOnActivityLaunchCancelled_finishedBeforeDrawn()240     public void testOnActivityLaunchCancelled_finishedBeforeDrawn() {
241         doReturn(true).when(mTopActivity).isReportedDrawn();
242 
243         // Create an activity with different process that meets process switch.
244         final ActivityRecord noDrawnActivity = new ActivityBuilder(mAtm)
245                 .setTask(mTopActivity.getTask())
246                 .setProcessName("other")
247                 .build();
248 
249         notifyActivityLaunching(noDrawnActivity.intent);
250         notifyActivityLaunched(START_SUCCESS, noDrawnActivity);
251 
252         noDrawnActivity.mVisibleRequested = false;
253         mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity);
254 
255         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(noDrawnActivity));
256     }
257 
258     @Test
testOnActivityLaunchWhileSleeping()259     public void testOnActivityLaunchWhileSleeping() {
260         notifyActivityLaunching(mTopActivity.intent);
261         notifyActivityLaunched(START_SUCCESS, mTopActivity);
262         doReturn(true).when(mTopActivity.mDisplayContent).isSleeping();
263         mTopActivity.setState(Task.ActivityState.RESUMED, "test");
264         mTopActivity.setVisibility(false);
265         waitHandlerIdle(mAtm.mH);
266         // Not cancel immediately because in one of real cases, the keyguard may be going away or
267         // occluded later, then the activity can be drawn.
268         verify(mLaunchObserver, never()).onActivityLaunchCancelled(eqProto(mTopActivity));
269     }
270 
271     @Test
testOnReportFullyDrawn()272     public void testOnReportFullyDrawn() {
273         // Create an invisible event that should be cancelled after the next event starts.
274         onActivityLaunched(mTrampolineActivity);
275         mTrampolineActivity.mVisibleRequested = false;
276 
277         mActivityOptions = ActivityOptions.makeBasic();
278         mActivityOptions.setSourceInfo(SourceInfo.TYPE_LAUNCHER, SystemClock.uptimeMillis() - 10);
279         onIntentStarted(mTopActivity.intent);
280         notifyActivityLaunched(START_SUCCESS, mTopActivity);
281         verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTopActivity), anyInt());
282         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTrampolineActivity));
283 
284         // The activity reports fully drawn before windows drawn, then the fully drawn event will
285         // be pending (see {@link WindowingModeTransitionInfo#pendingFullyDrawn}).
286         mActivityMetricsLogger.logAppTransitionReportedDrawn(mTopActivity, false);
287         notifyTransitionStarting(mTopActivity);
288         // The pending fully drawn event should send when the actual windows drawn event occurs.
289         final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity);
290         assertWithMessage("Record start source").that(info.sourceType)
291                 .isEqualTo(SourceInfo.TYPE_LAUNCHER);
292         assertWithMessage("Record event time").that(info.sourceEventDelayMs).isAtLeast(10);
293 
294         verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong());
295         verifyOnActivityLaunchFinished(mTopActivity);
296         verifyNoMoreInteractions(mLaunchObserver);
297 
298         final ActivityMetricsLogger.TransitionInfoSnapshot fullyDrawnInfo = mActivityMetricsLogger
299                 .logAppTransitionReportedDrawn(mTopActivity, false /* restoredFromBundle */);
300         assertWithMessage("Invisible event must be dropped").that(fullyDrawnInfo).isNull();
301     }
302 
onActivityLaunchedTrampoline()303     private void onActivityLaunchedTrampoline() {
304         onIntentStarted(mTrampolineActivity.intent);
305         notifyActivityLaunched(START_SUCCESS, mTrampolineActivity);
306 
307         verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTrampolineActivity), anyInt());
308 
309         // A second, distinct, activity launch is coalesced into the current app launch sequence.
310         mLaunchTopByTrampoline = true;
311         onIntentStarted(mTopActivity.intent);
312         notifyActivityLaunched(START_SUCCESS, mTopActivity);
313 
314         // The observer shouldn't receive onActivityLaunched for an existing transition.
315         verifyNoMoreInteractions(mLaunchObserver);
316     }
317 
notifyActivityLaunching(Intent intent)318     private void notifyActivityLaunching(Intent intent) {
319         final ActivityMetricsLogger.LaunchingState previousState = mLaunchingState;
320         mLaunchingState = mActivityMetricsLogger.notifyActivityLaunching(intent,
321                 mLaunchTopByTrampoline ? mTrampolineActivity : null /* caller */,
322                 mLaunchTopByTrampoline ? mTrampolineActivity.getUid() : 0);
323         if (mLaunchTopByTrampoline) {
324             // The transition of TrampolineActivity has not been completed, so when the next
325             // activity is starting from it, the same launching state should be returned.
326             assertWithMessage("Use existing launching state for a caller in active transition")
327                     .that(previousState).isEqualTo(mLaunchingState);
328         }
329     }
330 
notifyActivityLaunched(int resultCode, ActivityRecord activity)331     private void notifyActivityLaunched(int resultCode, ActivityRecord activity) {
332         mActivityMetricsLogger.notifyActivityLaunched(mLaunchingState, resultCode,
333                 mNewActivityCreated, activity, mActivityOptions);
334     }
335 
notifyTransitionStarting(ActivityRecord activity)336     private void notifyTransitionStarting(ActivityRecord activity) {
337         final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>();
338         reasons.put(activity, ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN);
339         mActivityMetricsLogger.notifyTransitionStarting(reasons);
340     }
341 
notifyWindowsDrawn(ActivityRecord r)342     private ActivityMetricsLogger.TransitionInfoSnapshot notifyWindowsDrawn(ActivityRecord r) {
343         return mActivityMetricsLogger.notifyWindowsDrawn(r, SystemClock.elapsedRealtimeNanos());
344     }
345 
346     @Test
testOnActivityLaunchFinishedTrampoline()347     public void testOnActivityLaunchFinishedTrampoline() {
348         onActivityLaunchedTrampoline();
349 
350         notifyTransitionStarting(mTopActivity);
351         notifyWindowsDrawn(mTrampolineActivity);
352 
353         assertWithMessage("Trampoline activity is drawn but the top activity is not yet")
354                 .that(mLaunchingState.allDrawn()).isFalse();
355 
356         notifyWindowsDrawn(mTopActivity);
357 
358         verifyOnActivityLaunchFinished(mTopActivity);
359         verifyNoMoreInteractions(mLaunchObserver);
360     }
361 
362     @Test
testDoNotCountInvisibleActivityToBeDrawn()363     public void testDoNotCountInvisibleActivityToBeDrawn() {
364         onActivityLaunchedTrampoline();
365         mTrampolineActivity.setVisibility(false);
366         notifyWindowsDrawn(mTopActivity);
367 
368         assertWithMessage("Trampoline activity is invisible so there should be no undrawn windows")
369                 .that(mLaunchingState.allDrawn()).isTrue();
370 
371         // Since the activity is drawn, the launch event should be reported.
372         notifyTransitionStarting(mTopActivity);
373         verifyOnActivityLaunchFinished(mTopActivity);
374         mLaunchTopByTrampoline = false;
375         clearInvocations(mLaunchObserver);
376 
377         // Another round without setting visibility of the trampoline activity.
378         onActivityLaunchedTrampoline();
379         notifyWindowsDrawn(mTopActivity);
380         // If the transition can start, the invisible activities should be discarded and the launch
381         // event be reported successfully.
382         notifyTransitionStarting(mTopActivity);
383         verifyOnActivityLaunchFinished(mTopActivity);
384     }
385 
386     @Test
testOnActivityLaunchCancelledTrampoline()387     public void testOnActivityLaunchCancelledTrampoline() {
388         onActivityLaunchedTrampoline();
389 
390         doReturn(true).when(mTopActivity).isReportedDrawn();
391 
392         // Cannot time already-visible activities.
393         notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity);
394 
395         verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity));
396         verifyNoMoreInteractions(mLaunchObserver);
397     }
398 
399     @Test
testActivityDrawnBeforeTransition()400     public void testActivityDrawnBeforeTransition() {
401         mTopActivity.setVisible(false);
402         notifyActivityLaunching(mTopActivity.intent);
403         // Assume the activity is launched the second time consecutively. The drawn event is from
404         // the first time (omitted in test) launch that is earlier than transition.
405         doReturn(true).when(mTopActivity).isReportedDrawn();
406         notifyWindowsDrawn(mTopActivity);
407         notifyActivityLaunched(START_SUCCESS, mTopActivity);
408         // If the launching activity was drawn when starting transition, the launch event should
409         // be reported successfully.
410         notifyTransitionStarting(mTopActivity);
411 
412         verifyOnActivityLaunchFinished(mTopActivity);
413     }
414 
415     @Test
testActivityRecordProtoIsNotTooBig()416     public void testActivityRecordProtoIsNotTooBig() {
417         // The ActivityRecordProto must not be too big, otherwise converting it at runtime
418         // will become prohibitively expensive.
419         assertWithMessage("mTopActivity: %s", mTopActivity)
420                 .that(activityRecordToProto(mTopActivity).length)
421                 .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
422 
423         assertWithMessage("mTrampolineActivity: %s", mTrampolineActivity)
424                 .that(activityRecordToProto(mTrampolineActivity).length)
425                 .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE);
426     }
427 
428     @Test
testConcurrentLaunches()429     public void testConcurrentLaunches() {
430         onActivityLaunched(mTopActivity);
431         final ActivityMetricsLogger.LaunchingState previousState = mLaunchingState;
432 
433         final ActivityRecord otherActivity = new ActivityBuilder(mAtm)
434                 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "OtherActivity"))
435                 .setCreateTask(true)
436                 .build();
437         // Assume the calling uid is different from the uid of TopActivity, so a new launching
438         // state should be created here.
439         onActivityLaunched(otherActivity);
440 
441         assertWithMessage("Different callers should get 2 indepedent launching states")
442                 .that(previousState).isNotEqualTo(mLaunchingState);
443         transitToDrawnAndVerifyOnLaunchFinished(otherActivity);
444 
445         // The first transition should still be valid.
446         transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
447     }
448 
449     @Test
testConsecutiveLaunchNewTask()450     public void testConsecutiveLaunchNewTask() {
451         final IBinder launchCookie = mock(IBinder.class);
452         mTrampolineActivity.noDisplay = true;
453         mTrampolineActivity.mLaunchCookie = launchCookie;
454         onActivityLaunched(mTrampolineActivity);
455         final ActivityRecord activityOnNewTask = new ActivityBuilder(mAtm)
456                 .setCreateTask(true)
457                 .build();
458         mActivityMetricsLogger.notifyActivityLaunching(activityOnNewTask.intent,
459                 mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
460         notifyActivityLaunched(START_SUCCESS, activityOnNewTask);
461 
462         transitToDrawnAndVerifyOnLaunchFinished(activityOnNewTask);
463         assertWithMessage("Trampoline's cookie must be transferred").that(
464                 mTrampolineActivity.mLaunchCookie).isNull();
465         assertWithMessage("The last launch task has the transferred cookie").that(
466                 activityOnNewTask.mLaunchCookie).isEqualTo(launchCookie);
467     }
468 
469     @Test
testConsecutiveLaunchOnDifferentDisplay()470     public void testConsecutiveLaunchOnDifferentDisplay() {
471         onActivityLaunched(mTopActivity);
472 
473         final Task stack = new TaskBuilder(mSupervisor)
474                 .setDisplay(addNewDisplayContentAt(DisplayContent.POSITION_BOTTOM))
475                 .build();
476         final ActivityRecord activityOnNewDisplay = new ActivityBuilder(mAtm)
477                 .setTask(stack)
478                 .setProcessName("new")
479                 .build();
480 
481         // Before TopActivity is drawn, it launches another activity on a different display.
482         mActivityMetricsLogger.notifyActivityLaunching(activityOnNewDisplay.intent,
483                 mTopActivity /* caller */, mTopActivity.getUid());
484         notifyActivityLaunched(START_SUCCESS, activityOnNewDisplay);
485 
486         // There should be 2 events instead of coalescing as one event.
487         transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
488         transitToDrawnAndVerifyOnLaunchFinished(activityOnNewDisplay);
489     }
490 
491     @Test
testConsecutiveLaunchWithDifferentWindowingMode()492     public void testConsecutiveLaunchWithDifferentWindowingMode() {
493         mTopActivity.setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
494         mTrampolineActivity.mVisibleRequested = true;
495         onActivityLaunched(mTrampolineActivity);
496         mActivityMetricsLogger.notifyActivityLaunching(mTopActivity.intent,
497                 mTrampolineActivity /* caller */, mTrampolineActivity.getUid());
498         notifyActivityLaunched(START_SUCCESS, mTopActivity);
499         // Different windowing modes should be independent launch events.
500         transitToDrawnAndVerifyOnLaunchFinished(mTrampolineActivity);
501         transitToDrawnAndVerifyOnLaunchFinished(mTopActivity);
502     }
503 
transitToDrawnAndVerifyOnLaunchFinished(ActivityRecord activity)504     private void transitToDrawnAndVerifyOnLaunchFinished(ActivityRecord activity) {
505         notifyTransitionStarting(activity);
506         notifyWindowsDrawn(activity);
507 
508         verifyOnActivityLaunchFinished(activity);
509     }
510 }
511