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 android.server.wm;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.os.SystemClock.sleep;
21 import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
22 import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
23 import static android.server.wm.WindowManagerState.STATE_STOPPED;
24 import static android.server.wm.app.Components.ENTRY_POINT_ALIAS_ACTIVITY;
25 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
26 import static android.server.wm.app.Components.NO_DISPLAY_ACTIVITY;
27 import static android.server.wm.app.Components.REPORT_FULLY_DRAWN_ACTIVITY;
28 import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
29 import static android.server.wm.app.Components.TEST_ACTIVITY;
30 import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
31 import static android.server.wm.app.Components.TRANSLUCENT_TOP_ACTIVITY;
32 import static android.server.wm.app.Components.TopActivity.EXTRA_FINISH_IN_ON_CREATE;
33 import static android.server.wm.second.Components.SECOND_ACTIVITY;
34 import static android.server.wm.third.Components.THIRD_ACTIVITY;
35 import static android.util.TimeUtils.formatDuration;
36 
37 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
38 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CANCELLED;
39 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
40 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
41 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_REPORTED_DRAWN;
42 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_REPORTED_DRAWN_MS;
43 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_STARTING_WINDOW_DELAY_MS;
44 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS;
45 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CLASS_NAME;
46 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_COLD_LAUNCH;
47 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_HOT_LAUNCH;
48 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
49 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
50 
51 import static org.hamcrest.MatcherAssert.assertThat;
52 import static org.hamcrest.Matchers.containsString;
53 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
54 import static org.hamcrest.Matchers.lessThanOrEqualTo;
55 import static org.junit.Assert.assertEquals;
56 import static org.junit.Assert.assertNotEquals;
57 import static org.junit.Assert.assertNotNull;
58 import static org.junit.Assert.assertNull;
59 import static org.junit.Assert.fail;
60 
61 import android.app.Activity;
62 import android.content.ComponentName;
63 import android.metrics.LogMaker;
64 import android.metrics.MetricsReader;
65 import android.os.SystemClock;
66 import android.platform.test.annotations.Presubmit;
67 import android.server.wm.CommandSession.ActivitySessionClient;
68 import android.support.test.metricshelper.MetricsAsserts;
69 import android.util.EventLog.Event;
70 
71 
72 import androidx.annotation.NonNull;
73 import androidx.annotation.Nullable;
74 
75 import com.android.compatibility.common.util.SystemUtil;
76 
77 import org.hamcrest.collection.IsIn;
78 import org.junit.Before;
79 import org.junit.Test;
80 
81 import java.util.Arrays;
82 import java.util.List;
83 import java.util.Objects;
84 import java.util.Queue;
85 import java.util.concurrent.TimeUnit;
86 import java.util.function.IntConsumer;
87 
88 /**
89  * CTS device tests for {@link com.android.server.wm.ActivityMetricsLogger}.
90  * Build/Install/Run:
91  * atest CtsWindowManagerDeviceTestCases:ActivityMetricsLoggerTests
92  */
93 @Presubmit
94 public class ActivityMetricsLoggerTests extends ActivityManagerTestBase {
95     private static final String TAG_ATM = "ActivityTaskManager";
96     private static final String LAUNCH_STATE_COLD = "COLD";
97     private static final String LAUNCH_STATE_WARM = "WARM";
98     private static final String LAUNCH_STATE_HOT = "HOT";
99     private static final String LAUNCH_STATE_RELAUNCH = "RELAUNCH";
100     private static final int EVENT_WM_ACTIVITY_LAUNCH_TIME = 30009;
101     private final MetricsReader mMetricsReader = new MetricsReader();
102     private long mPreUptimeMs;
103     private LogSeparator mLogSeparator;
104 
105     @Before
setUp()106     public void setUp() throws Exception {
107         super.setUp();
108         mPreUptimeMs = SystemClock.uptimeMillis();
109         mMetricsReader.checkpoint(); // clear out old logs
110         mLogSeparator = separateLogs(); // add a new separator for logs
111     }
112 
113     /**
114      * Launch an app and verify:
115      * - appropriate metrics logs are added
116      * - "Displayed activity ..." log is added to logcat
117      * - am_activity_launch_time event is generated
118      * In all three cases, verify the delay measurements are the same.
119      */
120     @Test
testAppLaunchIsLogged()121     public void testAppLaunchIsLogged() {
122         launchAndWaitForActivity(TEST_ACTIVITY);
123 
124         final LogMaker metricsLog = waitForMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
125         final String[] deviceLogs = getDeviceLogsForComponents(mLogSeparator, TAG_ATM);
126         final List<Event> eventLogs = getEventLogsForComponents(mLogSeparator,
127                 EVENT_WM_ACTIVITY_LAUNCH_TIME);
128 
129         final long postUptimeMs = SystemClock.uptimeMillis();
130         assertMetricsLogs(TEST_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs, postUptimeMs);
131         assertTransitionIsStartingWindow(metricsLog);
132         final int windowsDrawnDelayMs =
133                 (int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
134         final String expectedLog =
135                 "Displayed " + TEST_ACTIVITY.flattenToShortString()
136                         + ": " + formatDuration(windowsDrawnDelayMs);
137         assertLogsContain(deviceLogs, expectedLog);
138         assertEventLogsContainsLaunchTime(eventLogs, TEST_ACTIVITY, windowsDrawnDelayMs);
139     }
140 
assertMetricsLogs(ComponentName componentName, int category, LogMaker log, long preUptimeMs, long postUptimeMs)141     private void assertMetricsLogs(ComponentName componentName,
142             int category, LogMaker log, long preUptimeMs, long postUptimeMs) {
143         assertNotNull("did not find the metrics log for: " + componentName
144                 + " category:" + categoryToString(category), log);
145         int startUptimeSec =
146                 ((Number) log.getTaggedData(APP_TRANSITION_DEVICE_UPTIME_SECONDS)).intValue();
147         int preUptimeSec = (int) (TimeUnit.MILLISECONDS.toSeconds(preUptimeMs));
148         int postUptimeSec = (int) (TimeUnit.MILLISECONDS.toSeconds(postUptimeMs));
149         long testElapsedTimeMs = postUptimeMs - preUptimeMs;
150         assertThat("must be either cold or warm launch", log.getType(),
151                 IsIn.oneOf(TYPE_TRANSITION_COLD_LAUNCH, TYPE_TRANSITION_WARM_LAUNCH));
152         assertThat("reported uptime should be after the app was started", startUptimeSec,
153                 greaterThanOrEqualTo(preUptimeSec));
154         assertThat("reported uptime should be before assertion time", startUptimeSec,
155                 lessThanOrEqualTo(postUptimeSec));
156         assertNotNull("log should have delay", log.getTaggedData(APP_TRANSITION_DELAY_MS));
157         assertNotNull("log should have windows drawn delay",
158                 log.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS));
159         long windowsDrawnDelayMs = (int) log.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
160         assertThat("windows drawn delay should be less that total elapsed time",
161                 windowsDrawnDelayMs,  lessThanOrEqualTo(testElapsedTimeMs));
162     }
163 
assertTransitionIsStartingWindow(LogMaker log)164     private void assertTransitionIsStartingWindow(LogMaker log) {
165         assertEquals("transition should be started because of starting window",
166                 1 /* APP_TRANSITION_STARTING_WINDOW */, log.getSubtype());
167         assertNotNull("log should have starting window delay",
168                 log.getTaggedData(APP_TRANSITION_STARTING_WINDOW_DELAY_MS));
169     }
170 
assertEventLogsContainsLaunchTime(List<Event> events, ComponentName componentName, int windowsDrawnDelayMs)171     private void assertEventLogsContainsLaunchTime(List<Event> events, ComponentName componentName,
172             int windowsDrawnDelayMs) {
173         verifyLaunchTimeEventLogs(events, componentName,
174                 delay -> assertEquals("Unexpected windows drawn delay for " + componentName,
175                         delay, windowsDrawnDelayMs));
176     }
177 
verifyLaunchTimeEventLogs(List<Event> launchTimeEvents, ComponentName componentName, IntConsumer launchTimeVerifier)178     private void verifyLaunchTimeEventLogs(List<Event> launchTimeEvents,
179             ComponentName componentName, IntConsumer launchTimeVerifier) {
180         for (Event event : launchTimeEvents) {
181             final Object[] arr = (Object[]) event.getData();
182             assertEquals(4, arr.length);
183             final String name = (String) arr[2];
184             final int launchTime = (int) arr[3];
185             if (name.equals(componentName.flattenToShortString())) {
186                 launchTimeVerifier.accept(launchTime);
187                 return;
188             }
189         }
190         fail("Could not find wm_activity_launch_time for " + componentName);
191     }
192 
193     /**
194      * Start an activity that reports full drawn and verify:
195      * - fully drawn metrics are added to metrics logs
196      * - "Fully drawn activity ..." log is added to logcat
197      * In both cases verify fully drawn delay measurements are equal.
198      * See {@link Activity#reportFullyDrawn()}
199      */
200     @Test
testAppFullyDrawnReportIsLogged()201     public void testAppFullyDrawnReportIsLogged() {
202         launchAndWaitForActivity(REPORT_FULLY_DRAWN_ACTIVITY);
203 
204         // Sleep until activity under test has reported drawn (after 500ms)
205         sleep(1000);
206 
207         final LogMaker metricsLog = waitForMetricsLog(REPORT_FULLY_DRAWN_ACTIVITY,
208                 APP_TRANSITION_REPORTED_DRAWN);
209         final String[] deviceLogs = getDeviceLogsForComponents(mLogSeparator, TAG_ATM);
210 
211         assertNotNull("did not find the metrics log for: " + REPORT_FULLY_DRAWN_ACTIVITY
212                 + " category:" + APP_TRANSITION_REPORTED_DRAWN, metricsLog);
213         assertThat("test activity has a 500ms delay before reporting fully drawn",
214                 (long) metricsLog.getTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS),
215                 greaterThanOrEqualTo(500L));
216         assertEquals(TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE, metricsLog.getType());
217 
218         final long fullyDrawnDelayMs =
219                 (long) metricsLog.getTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS);
220         final String expectedLog =
221                 "Fully drawn " + REPORT_FULLY_DRAWN_ACTIVITY.flattenToShortString()
222                         + ": " + formatDuration(fullyDrawnDelayMs);
223         assertLogsContain(deviceLogs, expectedLog);
224     }
225 
226     /**
227      * Warm launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
228      * totalTime is set correctly. Make sure the reported value is consistent with value reported to
229      * metrics logs. Verify we output the correct launch state.
230      */
231     @Test
testAppWarmLaunchSetsWaitResultDelayData()232     public void testAppWarmLaunchSetsWaitResultDelayData() {
233         try (ActivitySessionClient client = createActivitySessionClient()) {
234             client.startActivity(getLaunchActivityBuilder()
235                     .setUseInstrumentation()
236                     .setTargetActivity(TEST_ACTIVITY)
237                     .setWaitForLaunched(true));
238             separateTestJournal();
239             // The activity will be finished when closing the session client.
240         }
241         assertActivityDestroyed(TEST_ACTIVITY);
242         mMetricsReader.checkpoint(); // clear out old logs
243 
244         // This is warm launch because its process should be alive after the above steps.
245         final String amStartOutput = SystemUtil.runShellCommand(
246                 "am start -W " + TEST_ACTIVITY.flattenToShortString());
247 
248         final LogMaker metricsLog = waitForMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
249         final int windowsDrawnDelayMs =
250                 (int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
251 
252         assertEquals("Expected a cold launch.", metricsLog.getType(), TYPE_TRANSITION_WARM_LAUNCH);
253 
254         assertLaunchComponentStateAndTime(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_WARM,
255                 windowsDrawnDelayMs);
256     }
257 
258     /**
259      * Hot launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
260      * totalTime is set correctly. Make sure the reported value is consistent with value reported to
261      * metrics logs. Verify we output the correct launch state.
262      */
263     @Test
testAppHotLaunchSetsWaitResultDelayData()264     public void testAppHotLaunchSetsWaitResultDelayData() {
265         SystemUtil.runShellCommand("am start -S -W " + TEST_ACTIVITY.flattenToShortString());
266 
267         // Test hot launch
268         launchHomeActivityNoWait();
269         waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED, "Activity should be stopped");
270         mMetricsReader.checkpoint(); // clear out old logs
271 
272         final String amStartOutput = SystemUtil.runShellCommand(
273                 "am start -W " + TEST_ACTIVITY.flattenToShortString());
274 
275         final LogMaker metricsLog = waitForMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
276         final int windowsDrawnDelayMs =
277                 (int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
278 
279         assertEquals("Expected a cold launch.", metricsLog.getType(), TYPE_TRANSITION_HOT_LAUNCH);
280 
281         assertLaunchComponentStateAndTime(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_HOT,
282                 windowsDrawnDelayMs);
283     }
284 
285     /**
286      * Launch an existing background activity after the device configuration is changed and the
287      * activity doesn't declare to handle the change. The state should be RELAUNCH instead of HOT.
288      */
289     @Test
testAppRelaunchSetsWaitResultDelayData()290     public void testAppRelaunchSetsWaitResultDelayData() {
291         final String startTestActivityCmd = "am start -W " + TEST_ACTIVITY.flattenToShortString();
292         SystemUtil.runShellCommand(startTestActivityCmd);
293 
294         // Launch another task and make sure a configuration change triggers relaunch.
295         launchAndWaitForActivity(SECOND_ACTIVITY);
296         separateTestJournal();
297 
298         final FontScaleSession fontScaleSession = createManagedFontScaleSession();
299         fontScaleSession.set(fontScaleSession.get() + 0.1f);
300         assertActivityLifecycle(SECOND_ACTIVITY, true /* relaunched */);
301 
302         // Move the task of test activity to front.
303         final String amStartOutput = SystemUtil.runShellCommand(startTestActivityCmd);
304         assertLaunchComponentState(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_RELAUNCH);
305     }
306 
307     /**
308      * Cold launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
309      * totalTime is set correctly. Make sure the reported value is consistent with value reported to
310      * metrics logs. Verify we output the correct launch state.
311      */
312     @Test
testAppColdLaunchSetsWaitResultDelayData()313     public void testAppColdLaunchSetsWaitResultDelayData() {
314         final String amStartOutput = SystemUtil.runShellCommand(
315                 "am start -S -W " + TEST_ACTIVITY.flattenToShortString());
316 
317         final LogMaker metricsLog = waitForMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
318         final int windowsDrawnDelayMs =
319                 (int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
320 
321         assertEquals("Expected a cold launch.", metricsLog.getType(), TYPE_TRANSITION_COLD_LAUNCH);
322 
323         assertLaunchComponentStateAndTime(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_COLD,
324                 windowsDrawnDelayMs);
325     }
326 
327     /**
328      * Launch an app that is already visible and verify we handle cases where we will not
329      * receive a windows drawn message.
330      * see b/117148004
331      */
332     @Test
testLaunchOfVisibleApp()333     public void testLaunchOfVisibleApp() {
334         // Launch an activity.
335         launchAndWaitForActivity(SECOND_ACTIVITY);
336 
337         // Launch a translucent activity on top.
338         launchAndWaitForActivity(TRANSLUCENT_TEST_ACTIVITY);
339 
340         // Launch the first activity again. This will not trigger a windows drawn message since
341         // its windows were visible before launching.
342         mMetricsReader.checkpoint(); // clear out old logs
343         launchAndWaitForActivity(SECOND_ACTIVITY);
344 
345         LogMaker metricsLog = getMetricsLog(SECOND_ACTIVITY, APP_TRANSITION);
346         // Verify transition logs are absent since we cannot measure windows drawn delay.
347         assertNull("transition logs should be reset.", metricsLog);
348 
349         // Verify metrics for subsequent launches are generated as expected.
350         mPreUptimeMs = SystemClock.uptimeMillis();
351         mMetricsReader.checkpoint(); // clear out old logs
352 
353         launchAndWaitForActivity(THIRD_ACTIVITY);
354 
355         long postUptimeMs = SystemClock.uptimeMillis();
356         metricsLog = waitForMetricsLog(THIRD_ACTIVITY, APP_TRANSITION);
357         assertMetricsLogs(THIRD_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs,
358                 postUptimeMs);
359         assertTransitionIsStartingWindow(metricsLog);
360     }
361 
362     @Test
testAppLaunchCancelledSameTask()363     public void testAppLaunchCancelledSameTask() {
364         launchAndWaitForActivity(TEST_ACTIVITY);
365 
366         // Start a non-opaque activity in a different process in the same task.
367         getLaunchActivityBuilder()
368                 .setUseInstrumentation()
369                 .setTargetActivity(TRANSLUCENT_TOP_ACTIVITY)
370                 .setIntentExtra(extra -> extra.putBoolean(EXTRA_FINISH_IN_ON_CREATE, true))
371                 .setWaitForLaunched(false)
372                 .execute();
373 
374         final LogMaker metricsLog = waitForMetricsLog(TRANSLUCENT_TOP_ACTIVITY,
375                 APP_TRANSITION_CANCELLED);
376 
377         assertNotNull("Metrics log APP_TRANSITION_CANCELLED not found", metricsLog);
378     }
379 
380     /**
381      * Launch a NoDisplay activity and verify it does not affect subsequent activity launch
382      * metrics. NoDisplay activities do not draw any windows and may be incorrectly identified as a
383      * trampoline activity. See b/80380150 (Long warm launch times reported in dev play console)
384      */
385     @Test
testNoDisplayActivityLaunch()386     public void testNoDisplayActivityLaunch() {
387         launchAndWaitForActivity(NO_DISPLAY_ACTIVITY);
388 
389         mPreUptimeMs = SystemClock.uptimeMillis();
390         launchAndWaitForActivity(SECOND_ACTIVITY);
391 
392         final LogMaker metricsLog = waitForMetricsLog(SECOND_ACTIVITY, APP_TRANSITION);
393         final long postUptimeMs = SystemClock.uptimeMillis();
394         assertMetricsLogs(SECOND_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs, postUptimeMs);
395         assertTransitionIsStartingWindow(metricsLog);
396     }
397 
398     /**
399      * Launch an activity with a trampoline activity and verify launch metrics measures the complete
400      * launch sequence from when the trampoline activity is launching to when the target activity
401      * draws on screen.
402      */
403     @Test
testTrampolineActivityLaunch()404     public void testTrampolineActivityLaunch() {
405         // Launch a trampoline activity that will launch single task activity.
406         launchAndWaitForActivity(ENTRY_POINT_ALIAS_ACTIVITY);
407         final LogMaker metricsLog = waitForMetricsLog(SINGLE_TASK_ACTIVITY, APP_TRANSITION);
408         final long postUptimeMs = SystemClock.uptimeMillis();
409         assertMetricsLogs(SINGLE_TASK_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs,
410                         postUptimeMs);
411     }
412 
413     /**
414      * Launch an activity which will start another activity immediately. The reported component
415      * name should be the last one with valid launch state.
416      */
417     @Test
testConsecutiveLaunch()418     public void testConsecutiveLaunch() {
419         final String amStartOutput = executeShellCommand("am start --ez " + KEY_LAUNCH_ACTIVITY
420                 + " true --es " + KEY_TARGET_COMPONENT + " " + TEST_ACTIVITY.flattenToShortString()
421                 + " -W " + LAUNCHING_ACTIVITY.flattenToShortString());
422         assertLaunchComponentState(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_COLD);
423     }
424 
425     @Test
testLaunchTimeEventLogNonProcessSwitch()426     public void testLaunchTimeEventLogNonProcessSwitch() {
427         launchAndWaitForActivity(SINGLE_TASK_ACTIVITY);
428         mLogSeparator = separateLogs();
429 
430         // Launch another activity in the same process.
431         launchAndWaitForActivity(TEST_ACTIVITY);
432         final List<Event> eventLogs = getEventLogsForComponents(mLogSeparator,
433                 EVENT_WM_ACTIVITY_LAUNCH_TIME);
434         verifyLaunchTimeEventLogs(eventLogs, TEST_ACTIVITY, time -> assertNotEquals(0, time));
435     }
436 
launchAndWaitForActivity(ComponentName activity)437     private void launchAndWaitForActivity(ComponentName activity) {
438         getLaunchActivityBuilder()
439                 .setUseInstrumentation()
440                 .setTargetActivity(activity)
441                 .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
442                 .setWaitForLaunched(true)
443                 .execute();
444     }
445 
446     @NonNull
waitForMetricsLog(ComponentName componentName, int category)447     private LogMaker waitForMetricsLog(ComponentName componentName, int category) {
448         final String categoryName = categoryToString(category);
449         final String message = componentName.toShortString() + " with category " + categoryName;
450         final LogMaker metricsLog = Condition.waitForResult(new Condition<LogMaker>(message)
451                 .setResultSupplier(() -> getMetricsLog(componentName, category))
452                 .setResultValidator(Objects::nonNull));
453         if (metricsLog != null) {
454             return metricsLog;
455         }
456 
457         // Fallback to check again from raw event log. Not sure if sometimes MetricsAsserts cannot
458         // find the expected log.
459         final LogMaker template = new LogMaker(category);
460         final List<Event> eventLogs = getEventLogsForComponents(mLogSeparator,
461                 android.util.EventLog.getTagCode("sysui_multi_action"));
462         for (Event event : eventLogs) {
463             final LogMaker log = new LogMaker((Object[]) event.getData());
464             if (isComponentMatched(log, componentName) && template.isSubsetOf(log)) {
465                 return log;
466             }
467         }
468 
469         throw new AssertionError("Log should have " + categoryName + " of " + componentName);
470     }
471 
472     @Nullable
getMetricsLog(ComponentName componentName, int category)473     private LogMaker getMetricsLog(ComponentName componentName, int category) {
474         final Queue<LogMaker> startLogs = MetricsAsserts.findMatchingLogs(mMetricsReader,
475                 new LogMaker(category));
476         return startLogs.stream().filter(log -> isComponentMatched(log, componentName))
477                 .findFirst().orElse(null);
478     }
479 
isComponentMatched(LogMaker log, ComponentName componentName)480     private static boolean isComponentMatched(LogMaker log, ComponentName componentName) {
481         final String actualClassName = (String) log.getTaggedData(FIELD_CLASS_NAME);
482         final String actualPackageName = log.getPackageName();
483         return componentName.getClassName().equals(actualClassName) &&
484                 componentName.getPackageName().equals(actualPackageName);
485     }
486 
categoryToString(int category)487     private static String categoryToString(int category) {
488         return (category == APP_TRANSITION ? "APP_TRANSITION"
489                 : category == APP_TRANSITION_CANCELLED ? "APP_TRANSITION_CANCELLED"
490                 : category == APP_TRANSITION_REPORTED_DRAWN ? "APP_TRANSITION_REPORTED_DRAWN"
491                 : "Unknown") + "(" + category + ")";
492     }
493 
assertLaunchComponentState(String amStartOutput, ComponentName component, String state)494     private static void assertLaunchComponentState(String amStartOutput, ComponentName component,
495             String state) {
496         assertThat("did not find component in am start output.", amStartOutput,
497                 containsString(component.flattenToShortString()));
498         assertThat("did not find launch state in am start output.", amStartOutput,
499                 containsString(state));
500     }
501 
assertLaunchComponentStateAndTime(String amStartOutput, ComponentName component, String state, int windowsDrawnDelayMs)502     private static void assertLaunchComponentStateAndTime(String amStartOutput,
503             ComponentName component, String state, int windowsDrawnDelayMs) {
504         assertLaunchComponentState(amStartOutput, component, state);
505         assertThat("did not find windows drawn delay time in am start output.", amStartOutput,
506                 containsString(Integer.toString(windowsDrawnDelayMs)));
507     }
508 
assertLogsContain(String[] logs, String expectedLog)509     private void assertLogsContain(String[] logs, String expectedLog) {
510         for (String line : logs) {
511             if (line.contains(expectedLog)) {
512                 return;
513             }
514         }
515         fail("Expected to find '" + expectedLog + "' in " + Arrays.toString(logs));
516     }
517 }
518