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