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.mock; 24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; 25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyNoMoreInteractions; 26 27 import static com.google.common.truth.Truth.assertWithMessage; 28 29 import static org.mockito.ArgumentMatchers.anyInt; 30 import static org.mockito.ArgumentMatchers.anyLong; 31 import static org.mockito.ArgumentMatchers.argThat; 32 import static org.mockito.ArgumentMatchers.eq; 33 import static org.mockito.Mockito.timeout; 34 35 import android.app.WaitResult; 36 import android.content.Intent; 37 import android.os.SystemClock; 38 import android.platform.test.annotations.Presubmit; 39 import android.util.ArrayMap; 40 41 import androidx.test.filters.SmallTest; 42 43 import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto; 44 45 import org.junit.After; 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 import org.mockito.ArgumentMatcher; 50 51 import java.util.Arrays; 52 import java.util.concurrent.TimeUnit; 53 54 /** 55 * Tests for the {@link ActivityMetricsLaunchObserver} class. 56 * 57 * Build/Install/Run: 58 * atest WmTests:ActivityMetricsLaunchObserverTests 59 */ 60 @SmallTest 61 @Presubmit 62 @RunWith(WindowTestRunner.class) 63 public class ActivityMetricsLaunchObserverTests extends ActivityTestsBase { 64 private ActivityMetricsLogger mActivityMetricsLogger; 65 private ActivityMetricsLogger.LaunchingState mLaunchingState; 66 private ActivityMetricsLaunchObserver mLaunchObserver; 67 private ActivityMetricsLaunchObserverRegistry mLaunchObserverRegistry; 68 69 private ActivityRecord mTrampolineActivity; 70 private ActivityRecord mTopActivity; 71 private boolean mLaunchTopByTrampoline; 72 73 @Before setUpAMLO()74 public void setUpAMLO() { 75 mLaunchObserver = mock(ActivityMetricsLaunchObserver.class); 76 77 // ActivityStackSupervisor always creates its own instance of ActivityMetricsLogger. 78 mActivityMetricsLogger = mSupervisor.getActivityMetricsLogger(); 79 80 mLaunchObserverRegistry = mActivityMetricsLogger.getLaunchObserverRegistry(); 81 mLaunchObserverRegistry.registerLaunchObserver(mLaunchObserver); 82 83 // Sometimes we need an ActivityRecord for ActivityMetricsLogger to do anything useful. 84 // This seems to be the easiest way to create an ActivityRecord. 85 mTrampolineActivity = new ActivityBuilder(mService) 86 .setCreateTask(true) 87 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TrampolineActivity")) 88 .build(); 89 mTopActivity = new ActivityBuilder(mService) 90 .setTask(mTrampolineActivity.getTask()) 91 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "TopActivity")) 92 .build(); 93 } 94 95 @After tearDownAMLO()96 public void tearDownAMLO() { 97 if (mLaunchObserverRegistry != null) { // Don't NPE if setUp failed. 98 mLaunchObserverRegistry.unregisterLaunchObserver(mLaunchObserver); 99 } 100 } 101 102 static class ActivityRecordMatcher implements ArgumentMatcher</*@ActivityRecordProto*/ byte[]> { 103 private final @ActivityRecordProto byte[] mExpected; 104 ActivityRecordMatcher(ActivityRecord activityRecord)105 public ActivityRecordMatcher(ActivityRecord activityRecord) { 106 mExpected = activityRecordToProto(activityRecord); 107 } 108 matches(@ctivityRecordProto byte[] actual)109 public boolean matches(@ActivityRecordProto byte[] actual) { 110 return Arrays.equals(mExpected, actual); 111 } 112 } 113 activityRecordToProto(ActivityRecord record)114 static @ActivityRecordProto byte[] activityRecordToProto(ActivityRecord record) { 115 return ActivityMetricsLogger.convertActivityRecordToProto(record); 116 } 117 eqProto(ActivityRecord record)118 static @ActivityRecordProto byte[] eqProto(ActivityRecord record) { 119 return argThat(new ActivityRecordMatcher(record)); 120 } 121 verifyAsync(T mock)122 private <T> T verifyAsync(T mock) { 123 // With WindowTestRunner, all test methods are inside WM lock, so we have to unblock any 124 // messages that are waiting for the lock. 125 waitHandlerIdle(mService.mH); 126 // AMLO callbacks happen on a separate thread than AML calls, so we need to use a timeout. 127 return verify(mock, timeout(TimeUnit.SECONDS.toMillis(5))); 128 } 129 verifyOnActivityLaunchFinished(ActivityRecord activity)130 private void verifyOnActivityLaunchFinished(ActivityRecord activity) { 131 verifyAsync(mLaunchObserver).onActivityLaunchFinished(eqProto(activity), anyLong()); 132 } 133 onIntentStarted(Intent intent)134 private void onIntentStarted(Intent intent) { 135 notifyActivityLaunching(intent); 136 137 // If it is launching top activity from trampoline activity, the observer shouldn't receive 138 // onActivityLaunched because the activities should belong to the same transition. 139 if (!mLaunchTopByTrampoline) { 140 verifyAsync(mLaunchObserver).onIntentStarted(eq(intent), anyLong()); 141 } 142 verifyNoMoreInteractions(mLaunchObserver); 143 } 144 145 @Test testOnIntentFailed()146 public void testOnIntentFailed() { 147 onIntentStarted(new Intent("testOnIntentFailed")); 148 149 // Bringing an intent that's already running 'to front' is not considered 150 // as an ACTIVITY_LAUNCHED state transition. 151 notifyActivityLaunched(START_TASK_TO_FRONT, null /* launchedActivity */); 152 153 verifyAsync(mLaunchObserver).onIntentFailed(); 154 verifyNoMoreInteractions(mLaunchObserver); 155 } 156 onActivityLaunched(ActivityRecord activity)157 private void onActivityLaunched(ActivityRecord activity) { 158 onIntentStarted(activity.intent); 159 notifyActivityLaunched(START_SUCCESS, activity); 160 161 verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(activity), anyInt()); 162 verifyNoMoreInteractions(mLaunchObserver); 163 } 164 165 @Test testOnActivityLaunchFinished()166 public void testOnActivityLaunchFinished() { 167 // Assume that the process is started (ActivityBuilder has mocked the returned value of 168 // ATMS#getProcessController) but the activity has not attached process. 169 mTopActivity.app = null; 170 onActivityLaunched(mTopActivity); 171 172 notifyTransitionStarting(mTopActivity); 173 final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity); 174 assertWithMessage("Warm launch").that(info.getLaunchState()) 175 .isEqualTo(WaitResult.LAUNCH_STATE_WARM); 176 177 verifyOnActivityLaunchFinished(mTopActivity); 178 verifyNoMoreInteractions(mLaunchObserver); 179 } 180 181 @Test testOnActivityLaunchCancelled_hasDrawn()182 public void testOnActivityLaunchCancelled_hasDrawn() { 183 onActivityLaunched(mTopActivity); 184 185 mTopActivity.mVisibleRequested = mTopActivity.mDrawn = true; 186 187 // Cannot time already-visible activities. 188 notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity); 189 190 verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity)); 191 verifyNoMoreInteractions(mLaunchObserver); 192 } 193 194 @Test testOnActivityLaunchCancelled_finishedBeforeDrawn()195 public void testOnActivityLaunchCancelled_finishedBeforeDrawn() { 196 mTopActivity.mVisibleRequested = mTopActivity.mDrawn = true; 197 198 // Suppress resume when creating the record because we want to notify logger manually. 199 mSupervisor.beginDeferResume(); 200 // Create an activity with different process that meets process switch. 201 final ActivityRecord noDrawnActivity = new ActivityBuilder(mService) 202 .setTask(mTopActivity.getTask()) 203 .setProcessName("other") 204 .build(); 205 mSupervisor.readyToResume(); 206 207 notifyActivityLaunching(noDrawnActivity.intent); 208 notifyActivityLaunched(START_SUCCESS, noDrawnActivity); 209 210 noDrawnActivity.mVisibleRequested = false; 211 mActivityMetricsLogger.notifyVisibilityChanged(noDrawnActivity); 212 213 verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(noDrawnActivity)); 214 } 215 216 @Test testOnReportFullyDrawn()217 public void testOnReportFullyDrawn() { 218 onActivityLaunched(mTopActivity); 219 220 // The activity reports fully drawn before windows drawn, then the fully drawn event will 221 // be pending (see {@link WindowingModeTransitionInfo#pendingFullyDrawn}). 222 mActivityMetricsLogger.logAppTransitionReportedDrawn(mTopActivity, false); 223 notifyTransitionStarting(mTopActivity); 224 // The pending fully drawn event should send when the actual windows drawn event occurs. 225 final ActivityMetricsLogger.TransitionInfoSnapshot info = notifyWindowsDrawn(mTopActivity); 226 assertWithMessage("Hot launch").that(info.getLaunchState()) 227 .isEqualTo(WaitResult.LAUNCH_STATE_HOT); 228 229 verifyAsync(mLaunchObserver).onReportFullyDrawn(eqProto(mTopActivity), anyLong()); 230 verifyOnActivityLaunchFinished(mTopActivity); 231 verifyNoMoreInteractions(mLaunchObserver); 232 } 233 onActivityLaunchedTrampoline()234 private void onActivityLaunchedTrampoline() { 235 onIntentStarted(mTrampolineActivity.intent); 236 notifyActivityLaunched(START_SUCCESS, mTrampolineActivity); 237 238 verifyAsync(mLaunchObserver).onActivityLaunched(eqProto(mTrampolineActivity), anyInt()); 239 240 // A second, distinct, activity launch is coalesced into the current app launch sequence. 241 mLaunchTopByTrampoline = true; 242 onIntentStarted(mTopActivity.intent); 243 notifyActivityLaunched(START_SUCCESS, mTopActivity); 244 245 // The observer shouldn't receive onActivityLaunched for an existing transition. 246 verifyNoMoreInteractions(mLaunchObserver); 247 } 248 notifyActivityLaunching(Intent intent)249 private void notifyActivityLaunching(Intent intent) { 250 final ActivityMetricsLogger.LaunchingState previousState = mLaunchingState; 251 mLaunchingState = mActivityMetricsLogger.notifyActivityLaunching(intent, 252 mLaunchTopByTrampoline ? mTrampolineActivity : null /* caller */); 253 if (mLaunchTopByTrampoline) { 254 // The transition of TrampolineActivity has not been completed, so when the next 255 // activity is starting from it, the same launching state should be returned. 256 assertWithMessage("Use existing launching state for a caller in active transition") 257 .that(previousState).isEqualTo(mLaunchingState); 258 } 259 } 260 notifyActivityLaunched(int resultCode, ActivityRecord activity)261 private void notifyActivityLaunched(int resultCode, ActivityRecord activity) { 262 mActivityMetricsLogger.notifyActivityLaunched(mLaunchingState, resultCode, activity); 263 } 264 notifyTransitionStarting(ActivityRecord activity)265 private void notifyTransitionStarting(ActivityRecord activity) { 266 final ArrayMap<WindowContainer, Integer> reasons = new ArrayMap<>(); 267 reasons.put(activity, ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN); 268 mActivityMetricsLogger.notifyTransitionStarting(reasons); 269 } 270 notifyWindowsDrawn(ActivityRecord r)271 private ActivityMetricsLogger.TransitionInfoSnapshot notifyWindowsDrawn(ActivityRecord r) { 272 return mActivityMetricsLogger.notifyWindowsDrawn(r, SystemClock.elapsedRealtimeNanos()); 273 } 274 275 @Test testOnActivityLaunchFinishedTrampoline()276 public void testOnActivityLaunchFinishedTrampoline() { 277 onActivityLaunchedTrampoline(); 278 279 notifyTransitionStarting(mTopActivity); 280 notifyWindowsDrawn(mTrampolineActivity); 281 282 assertWithMessage("Trampoline activity is drawn but the top activity is not yet") 283 .that(mLaunchingState.allDrawn()).isFalse(); 284 285 notifyWindowsDrawn(mTopActivity); 286 287 verifyOnActivityLaunchFinished(mTopActivity); 288 verifyNoMoreInteractions(mLaunchObserver); 289 } 290 291 @Test testDoNotCountInvisibleActivityToBeDrawn()292 public void testDoNotCountInvisibleActivityToBeDrawn() { 293 onActivityLaunchedTrampoline(); 294 mTrampolineActivity.setVisibility(false); 295 notifyWindowsDrawn(mTopActivity); 296 297 assertWithMessage("Trampoline activity is invisble so there should be no undrawn windows") 298 .that(mLaunchingState.allDrawn()).isTrue(); 299 } 300 301 @Test testOnActivityLaunchCancelledTrampoline()302 public void testOnActivityLaunchCancelledTrampoline() { 303 onActivityLaunchedTrampoline(); 304 305 mTopActivity.mDrawn = true; 306 307 // Cannot time already-visible activities. 308 notifyActivityLaunched(START_TASK_TO_FRONT, mTopActivity); 309 310 verifyAsync(mLaunchObserver).onActivityLaunchCancelled(eqProto(mTopActivity)); 311 verifyNoMoreInteractions(mLaunchObserver); 312 } 313 314 @Test testActivityDrawnBeforeTransition()315 public void testActivityDrawnBeforeTransition() { 316 mTopActivity.setVisible(false); 317 notifyActivityLaunching(mTopActivity.intent); 318 // Assume the activity is launched the second time consecutively. The drawn event is from 319 // the first time (omitted in test) launch that is earlier than transition. 320 mTopActivity.mDrawn = true; 321 notifyWindowsDrawn(mTopActivity); 322 notifyActivityLaunched(START_SUCCESS, mTopActivity); 323 // If the launching activity was drawn when starting transition, the launch event should 324 // be reported successfully. 325 notifyTransitionStarting(mTopActivity); 326 327 verifyOnActivityLaunchFinished(mTopActivity); 328 } 329 330 @Test testActivityRecordProtoIsNotTooBig()331 public void testActivityRecordProtoIsNotTooBig() { 332 // The ActivityRecordProto must not be too big, otherwise converting it at runtime 333 // will become prohibitively expensive. 334 assertWithMessage("mTopActivity: %s", mTopActivity) 335 .that(activityRecordToProto(mTopActivity).length) 336 .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); 337 338 assertWithMessage("mTrampolineActivity: %s", mTrampolineActivity) 339 .that(activityRecordToProto(mTrampolineActivity).length) 340 .isAtMost(ActivityMetricsLogger.LAUNCH_OBSERVER_ACTIVITY_RECORD_PROTO_CHUNK_SIZE); 341 } 342 343 @Test testConcurrentLaunches()344 public void testConcurrentLaunches() { 345 onActivityLaunched(mTopActivity); 346 final ActivityMetricsLogger.LaunchingState previousState = mLaunchingState; 347 348 final ActivityRecord otherActivity = new ActivityBuilder(mService) 349 .setComponent(createRelative(DEFAULT_COMPONENT_PACKAGE_NAME, "OtherActivity")) 350 .setCreateTask(true) 351 .build(); 352 // Assume the calling uid is different from the uid of TopActivity, so a new launching 353 // state should be created here. 354 onActivityLaunched(otherActivity); 355 356 assertWithMessage("Different callers should get 2 indepedent launching states") 357 .that(previousState).isNotEqualTo(mLaunchingState); 358 transitToDrawnAndVerifyOnLaunchFinished(otherActivity); 359 360 // The first transition should still be valid. 361 transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); 362 } 363 364 @Test testConsecutiveLaunchOnDifferentDisplay()365 public void testConsecutiveLaunchOnDifferentDisplay() { 366 onActivityLaunched(mTopActivity); 367 368 final ActivityStack stack = new StackBuilder(mRootWindowContainer) 369 .setDisplay(addNewDisplayContentAt(DisplayContent.POSITION_BOTTOM)) 370 .setCreateActivity(false) 371 .build(); 372 final ActivityRecord activityOnNewDisplay = new ActivityBuilder(mService) 373 .setStack(stack) 374 .setCreateTask(true) 375 .setProcessName("new") 376 .build(); 377 378 // Before TopActivity is drawn, it launches another activity on a different display. 379 mActivityMetricsLogger.notifyActivityLaunching(activityOnNewDisplay.intent, 380 mTopActivity /* caller */); 381 notifyActivityLaunched(START_SUCCESS, activityOnNewDisplay); 382 383 // There should be 2 events instead of coalescing as one event. 384 transitToDrawnAndVerifyOnLaunchFinished(mTopActivity); 385 transitToDrawnAndVerifyOnLaunchFinished(activityOnNewDisplay); 386 } 387 transitToDrawnAndVerifyOnLaunchFinished(ActivityRecord activity)388 private void transitToDrawnAndVerifyOnLaunchFinished(ActivityRecord activity) { 389 notifyTransitionStarting(activity); 390 notifyWindowsDrawn(activity); 391 392 verifyOnActivityLaunchFinished(activity); 393 } 394 } 395