• 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.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