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