blob: 93461c7f54c64dd0c417ccbe0f46f55dd2b0c427 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License
*/
package android.server.wm.activity;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.os.SystemClock.sleep;
import static android.server.wm.ActivityLauncher.KEY_LAUNCH_ACTIVITY;
import static android.server.wm.ActivityLauncher.KEY_TARGET_COMPONENT;
import static android.server.wm.WindowManagerState.STATE_STOPPED;
import static android.server.wm.app.Components.ENTRY_POINT_ALIAS_ACTIVITY;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.server.wm.app.Components.NO_DISPLAY_ACTIVITY;
import static android.server.wm.app.Components.REPORT_FULLY_DRAWN_ACTIVITY;
import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
import static android.server.wm.app.Components.TRANSLUCENT_TOP_ACTIVITY;
import static android.server.wm.app.Components.TopActivity.EXTRA_FINISH_IN_ON_CREATE;
import static android.server.wm.second.Components.SECOND_ACTIVITY;
import static android.server.wm.third.Components.THIRD_ACTIVITY;
import static android.util.TimeUtils.formatDuration;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_CANCELLED;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_DEVICE_UPTIME_SECONDS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_REPORTED_DRAWN;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_REPORTED_DRAWN_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_STARTING_WINDOW_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.FIELD_CLASS_NAME;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_COLD_LAUNCH;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_HOT_LAUNCH;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import android.app.Activity;
import android.content.ComponentName;
import android.metrics.LogMaker;
import android.metrics.MetricsReader;
import android.os.Process;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.server.wm.ActivityManagerTestBase;
import android.server.wm.CommandSession.ActivitySessionClient;
import android.server.wm.Condition;
import android.server.wm.StateLogger;
import android.support.test.metricshelper.MetricsAsserts;
import android.util.EventLog.Event;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.compatibility.common.util.SystemUtil;
import org.hamcrest.collection.IsIn;
import org.junit.Before;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.TimeUnit;
import java.util.function.IntConsumer;
/**
* CTS device tests for {@link com.android.server.wm.ActivityMetricsLogger}.
* Build/Install/Run:
* atest CtsWindowManagerDeviceActivity:ActivityMetricsLoggerTests
*/
@Presubmit
public class ActivityMetricsLoggerTests extends ActivityManagerTestBase {
private static final String TAG_ATM = "ActivityTaskManager";
private static final String LAUNCH_STATE_COLD = "COLD";
private static final String LAUNCH_STATE_WARM = "WARM";
private static final String LAUNCH_STATE_HOT = "HOT";
private static final String LAUNCH_STATE_RELAUNCH = "RELAUNCH";
private static final int EVENT_WM_ACTIVITY_LAUNCH_TIME = 30009;
private static final int APP_TRANSITION_STARTING_WINDOW = 1;
private static final int APP_TRANSITION_WINDOWS_DRAWN = 2;
private static final int APP_TRANSITION_TIMEOUT = 3;
private final MetricsReader mMetricsReader = new MetricsReader();
private long mPreUptimeMs;
private LogSeparator mLogSeparator;
@Before
public void setUp() throws Exception {
super.setUp();
mPreUptimeMs = SystemClock.uptimeMillis();
mMetricsReader.checkpoint(); // clear out old logs
mLogSeparator = separateLogs(); // add a new separator for logs
}
/**
* Launch an app and verify:
* - appropriate metrics logs are added
* - "Displayed activity ..." log is added to logcat
* - am_activity_launch_time event is generated
* In all three cases, verify the delay measurements are the same.
*/
@Test
public void testAppLaunchIsLogged() {
launchAndWaitForActivity(TEST_ACTIVITY);
final LogMaker metricsLog = waitForMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
final String[] deviceLogs = getDeviceLogsForComponents(mLogSeparator, TAG_ATM);
final List<Event> eventLogs = getEventLogsForComponents(mLogSeparator,
EVENT_WM_ACTIVITY_LAUNCH_TIME);
final long postUptimeMs = SystemClock.uptimeMillis();
assertMetricsLogs(TEST_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs, postUptimeMs);
assertTransitionStartReason(metricsLog);
final int windowsDrawnDelayMs =
(int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
final String expectedLog =
"Displayed " + TEST_ACTIVITY.flattenToShortString() + " for user "
+ Process.myUserHandle().getIdentifier()
+ ": " + formatDuration(windowsDrawnDelayMs);
assertLogsContain(deviceLogs, expectedLog);
assertEventLogsContainsLaunchTime(eventLogs, TEST_ACTIVITY, windowsDrawnDelayMs);
}
private void assertMetricsLogs(ComponentName componentName,
int category, LogMaker log, long preUptimeMs, long postUptimeMs) {
assertNotNull("did not find the metrics log for: " + componentName
+ " category:" + categoryToString(category), log);
int startUptimeSec =
((Number) log.getTaggedData(APP_TRANSITION_DEVICE_UPTIME_SECONDS)).intValue();
int preUptimeSec = (int) (TimeUnit.MILLISECONDS.toSeconds(preUptimeMs));
int postUptimeSec = (int) (TimeUnit.MILLISECONDS.toSeconds(postUptimeMs));
long testElapsedTimeMs = postUptimeMs - preUptimeMs;
assertThat("must be either cold or warm launch", log.getType(),
IsIn.oneOf(TYPE_TRANSITION_COLD_LAUNCH, TYPE_TRANSITION_WARM_LAUNCH));
assertThat("reported uptime should be after the app was started", startUptimeSec,
greaterThanOrEqualTo(preUptimeSec));
assertThat("reported uptime should be before assertion time", startUptimeSec,
lessThanOrEqualTo(postUptimeSec));
assertNotNull("log should have delay", log.getTaggedData(APP_TRANSITION_DELAY_MS));
assertNotNull("log should have windows drawn delay",
log.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS));
long windowsDrawnDelayMs = (int) log.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
assertThat("windows drawn delay should be less that total elapsed time",
windowsDrawnDelayMs, lessThanOrEqualTo(testElapsedTimeMs));
}
private void assertTransitionStartReason(LogMaker log) {
if (isLeanBack()) return;
final int transitionStartReason = log.getSubtype();
switch (transitionStartReason) {
case APP_TRANSITION_STARTING_WINDOW:
assertNotNull("Transition started by starting window should include delay time",
log.getTaggedData(APP_TRANSITION_STARTING_WINDOW_DELAY_MS));
break;
case APP_TRANSITION_WINDOWS_DRAWN:
// It is fine if the activity is drawn faster than the starting window.
StateLogger.logAlways("APP_TRANSITION_WINDOWS_DRAWN");
break;
case APP_TRANSITION_TIMEOUT:
// There might be other issues in system, but it isn't in the scope of this test.
StateLogger.logE("APP_TRANSITION_TIMEOUT");
break;
default:
fail("Unexpected transition start reason " + transitionStartReason);
}
}
private void assertEventLogsContainsLaunchTime(List<Event> events, ComponentName componentName,
int windowsDrawnDelayMs) {
verifyLaunchTimeEventLogs(events, componentName,
delay -> assertEquals("Unexpected windows drawn delay for " + componentName,
delay, windowsDrawnDelayMs));
}
private void verifyLaunchTimeEventLogs(List<Event> launchTimeEvents,
ComponentName componentName, IntConsumer launchTimeVerifier) {
for (Event event : launchTimeEvents) {
final Object[] arr = (Object[]) event.getData();
assertEquals(4, arr.length);
final String name = (String) arr[2];
final int launchTime = (int) arr[3];
if (name.equals(componentName.flattenToShortString())) {
launchTimeVerifier.accept(launchTime);
return;
}
}
fail("Could not find wm_activity_launch_time for " + componentName);
}
/**
* Start an activity that reports full drawn and verify:
* - fully drawn metrics are added to metrics logs
* - "Fully drawn activity ..." log is added to logcat
* In both cases verify fully drawn delay measurements are equal.
* See {@link Activity#reportFullyDrawn()}
*/
@Test
public void testAppFullyDrawnReportIsLogged() {
launchAndWaitForActivity(REPORT_FULLY_DRAWN_ACTIVITY);
// Sleep until activity under test has reported drawn (after 500ms)
sleep(1000);
final LogMaker metricsLog = waitForMetricsLog(REPORT_FULLY_DRAWN_ACTIVITY,
APP_TRANSITION_REPORTED_DRAWN);
final String[] deviceLogs = getDeviceLogsForComponents(mLogSeparator, TAG_ATM);
assertNotNull("did not find the metrics log for: " + REPORT_FULLY_DRAWN_ACTIVITY
+ " category:" + APP_TRANSITION_REPORTED_DRAWN, metricsLog);
assertThat("test activity has a 500ms delay before reporting fully drawn",
(long) metricsLog.getTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS),
greaterThanOrEqualTo(500L));
assertEquals(TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE, metricsLog.getType());
final long fullyDrawnDelayMs =
(long) metricsLog.getTaggedData(APP_TRANSITION_REPORTED_DRAWN_MS);
final String expectedLog =
"Fully drawn " + REPORT_FULLY_DRAWN_ACTIVITY.flattenToShortString()
+ ": " + formatDuration(fullyDrawnDelayMs);
assertLogsContain(deviceLogs, expectedLog);
}
/**
* Warm launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
* totalTime is set correctly. Make sure the reported value is consistent with value reported to
* metrics logs. Verify we output the correct launch state.
*/
@Test
public void testAppWarmLaunchSetsWaitResultDelayData() {
try (ActivitySessionClient client = createActivitySessionClient()) {
client.startActivity(getLaunchActivityBuilder()
.setUseInstrumentation()
.setTargetActivity(TEST_ACTIVITY)
.setWaitForLaunched(true));
separateTestJournal();
// The activity will be finished when closing the session client.
}
assertActivityDestroyed(TEST_ACTIVITY);
mMetricsReader.checkpoint(); // clear out old logs
// This is warm launch because its process should be alive after the above steps.
final String amStartOutput = SystemUtil.runShellCommand(
"am start -W " + TEST_ACTIVITY.flattenToShortString());
final LogMaker metricsLog = waitForMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
final int windowsDrawnDelayMs =
(int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
assertEquals("Expected a cold launch.", metricsLog.getType(), TYPE_TRANSITION_WARM_LAUNCH);
assertLaunchComponentStateAndTime(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_WARM,
windowsDrawnDelayMs);
}
/**
* Hot launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
* totalTime is set correctly. Make sure the reported value is consistent with value reported to
* metrics logs. Verify we output the correct launch state.
*/
@Test
public void testAppHotLaunchSetsWaitResultDelayData() {
SystemUtil.runShellCommand("am start -S -W " + TEST_ACTIVITY.flattenToShortString());
// Test hot launch
launchHomeActivityNoWait();
waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED, "Activity should be stopped");
mMetricsReader.checkpoint(); // clear out old logs
final String amStartOutput = SystemUtil.runShellCommand(
"am start -W " + TEST_ACTIVITY.flattenToShortString());
final LogMaker metricsLog = waitForMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
final int windowsDrawnDelayMs =
(int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
assertEquals("Expected a cold launch.", metricsLog.getType(), TYPE_TRANSITION_HOT_LAUNCH);
assertLaunchComponentStateAndTime(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_HOT,
windowsDrawnDelayMs);
}
/**
* Launch an existing background activity after the device configuration is changed and the
* activity doesn't declare to handle the change. The state should be RELAUNCH instead of HOT.
*/
@Test
public void testAppRelaunchSetsWaitResultDelayData() {
final String startTestActivityCmd = "am start -W " + TEST_ACTIVITY.flattenToShortString();
SystemUtil.runShellCommand(startTestActivityCmd);
launchHomeActivityNoWait();
waitAndAssertActivityState(TEST_ACTIVITY, STATE_STOPPED, "Activity should be stopped");
final float originalFontScale = mContext.getResources().getConfiguration().fontScale;
final FontScaleSession fontScaleSession = createManagedFontScaleSession();
fontScaleSession.set(fontScaleSession.get() + 0.1f);
Condition.waitFor("font scale changed", () ->
originalFontScale != mContext.getResources().getConfiguration().fontScale);
// Move the task of test activity to front.
final String amStartOutput = SystemUtil.runShellCommand(startTestActivityCmd);
assertLaunchComponentState(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_RELAUNCH);
}
/**
* Cold launch an activity with wait option and verify that {@link android.app.WaitResult#totalTime}
* totalTime is set correctly. Make sure the reported value is consistent with value reported to
* metrics logs. Verify we output the correct launch state.
*/
@Test
public void testAppColdLaunchSetsWaitResultDelayData() {
final String amStartOutput = SystemUtil.runShellCommand(
"am start -S -W " + TEST_ACTIVITY.flattenToShortString());
final LogMaker metricsLog = waitForMetricsLog(TEST_ACTIVITY, APP_TRANSITION);
final int windowsDrawnDelayMs =
(int) metricsLog.getTaggedData(APP_TRANSITION_WINDOWS_DRAWN_DELAY_MS);
assertEquals("Expected a cold launch.", metricsLog.getType(), TYPE_TRANSITION_COLD_LAUNCH);
assertLaunchComponentStateAndTime(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_COLD,
windowsDrawnDelayMs);
}
/**
* Launch an app that is already visible and verify we handle cases where we will not
* receive a windows drawn message.
* see b/117148004
*/
@Test
public void testLaunchOfVisibleApp() {
// Launch an activity.
launchAndWaitForActivity(SECOND_ACTIVITY);
// Launch a translucent activity on top.
launchAndWaitForActivity(TRANSLUCENT_TEST_ACTIVITY);
// Launch the first activity again. This will not trigger a windows drawn message since
// its windows were visible before launching.
mMetricsReader.checkpoint(); // clear out old logs
launchAndWaitForActivity(SECOND_ACTIVITY);
LogMaker metricsLog = getMetricsLog(SECOND_ACTIVITY, APP_TRANSITION);
// Verify transition logs are absent since we cannot measure windows drawn delay.
assertNull("transition logs should be reset.", metricsLog);
// Verify metrics for subsequent launches are generated as expected.
mPreUptimeMs = SystemClock.uptimeMillis();
mMetricsReader.checkpoint(); // clear out old logs
launchAndWaitForActivity(THIRD_ACTIVITY);
long postUptimeMs = SystemClock.uptimeMillis();
metricsLog = waitForMetricsLog(THIRD_ACTIVITY, APP_TRANSITION);
assertMetricsLogs(THIRD_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs,
postUptimeMs);
assertTransitionStartReason(metricsLog);
}
@Test
public void testAppLaunchCancelledSameTask() {
launchAndWaitForActivity(TEST_ACTIVITY);
// Start a non-opaque activity in a different process in the same task.
getLaunchActivityBuilder()
.setUseInstrumentation()
.setTargetActivity(TRANSLUCENT_TOP_ACTIVITY)
.setIntentExtra(extra -> extra.putBoolean(EXTRA_FINISH_IN_ON_CREATE, true))
.setWaitForLaunched(false)
.execute();
final LogMaker metricsLog = waitForMetricsLog(TRANSLUCENT_TOP_ACTIVITY,
APP_TRANSITION_CANCELLED);
assertNotNull("Metrics log APP_TRANSITION_CANCELLED not found", metricsLog);
}
/**
* Launch a NoDisplay activity and verify it does not affect subsequent activity launch
* metrics. NoDisplay activities do not draw any windows and may be incorrectly identified as a
* trampoline activity. See b/80380150 (Long warm launch times reported in dev play console)
*/
@Test
public void testNoDisplayActivityLaunch() {
launchAndWaitForActivity(NO_DISPLAY_ACTIVITY);
mPreUptimeMs = SystemClock.uptimeMillis();
launchAndWaitForActivity(SECOND_ACTIVITY);
final LogMaker metricsLog = waitForMetricsLog(SECOND_ACTIVITY, APP_TRANSITION);
final long postUptimeMs = SystemClock.uptimeMillis();
assertMetricsLogs(SECOND_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs, postUptimeMs);
assertTransitionStartReason(metricsLog);
}
/**
* Launch an activity with a trampoline activity and verify launch metrics measures the complete
* launch sequence from when the trampoline activity is launching to when the target activity
* draws on screen.
*/
@Test
public void testTrampolineActivityLaunch() {
// Launch a trampoline activity that will launch single task activity.
launchAndWaitForActivity(ENTRY_POINT_ALIAS_ACTIVITY);
final LogMaker metricsLog = waitForMetricsLog(SINGLE_TASK_ACTIVITY, APP_TRANSITION);
final long postUptimeMs = SystemClock.uptimeMillis();
assertMetricsLogs(SINGLE_TASK_ACTIVITY, APP_TRANSITION, metricsLog, mPreUptimeMs,
postUptimeMs);
}
/**
* Launch an activity which will start another activity immediately. The reported component
* name should be the last one with valid launch state.
*/
@Test
public void testConsecutiveLaunch() {
final String amStartOutput = SystemUtil.runShellCommand(
"am start --ez " + KEY_LAUNCH_ACTIVITY
+ " true --es " + KEY_TARGET_COMPONENT + " " + TEST_ACTIVITY.flattenToShortString()
+ " -W " + LAUNCHING_ACTIVITY.flattenToShortString());
assertLaunchComponentState(amStartOutput, TEST_ACTIVITY, LAUNCH_STATE_COLD);
}
@Test
public void testLaunchTimeEventLogNonProcessSwitch() {
launchAndWaitForActivity(SINGLE_TASK_ACTIVITY);
mLogSeparator = separateLogs();
// Launch another activity in the same process.
launchAndWaitForActivity(TEST_ACTIVITY);
final List<Event> eventLogs = getEventLogsForComponents(mLogSeparator,
EVENT_WM_ACTIVITY_LAUNCH_TIME);
verifyLaunchTimeEventLogs(eventLogs, TEST_ACTIVITY, time -> assertNotEquals(0, time));
}
private void launchAndWaitForActivity(ComponentName activity) {
getLaunchActivityBuilder()
.setUseInstrumentation()
.setTargetActivity(activity)
.setWindowingMode(WINDOWING_MODE_FULLSCREEN)
.setWaitForLaunched(true)
.execute();
}
@NonNull
private LogMaker waitForMetricsLog(ComponentName componentName, int category) {
final String categoryName = categoryToString(category);
final String message = componentName.toShortString() + " with category " + categoryName;
final LogMaker metricsLog = Condition.waitForResult(new Condition<LogMaker>(message)
.setResultSupplier(() -> getMetricsLog(componentName, category))
.setResultValidator(Objects::nonNull));
if (metricsLog != null) {
return metricsLog;
}
// Fallback to check again from raw event log. Not sure if sometimes MetricsAsserts cannot
// find the expected log.
final LogMaker template = new LogMaker(category);
final List<Event> eventLogs = getEventLogsForComponents(mLogSeparator,
android.util.EventLog.getTagCode("sysui_multi_action"));
for (Event event : eventLogs) {
final LogMaker log = new LogMaker((Object[]) event.getData());
if (isComponentMatched(log, componentName) && template.isSubsetOf(log)) {
return log;
}
}
throw new AssertionError("Log should have " + categoryName + " of " + componentName);
}
@Nullable
private LogMaker getMetricsLog(ComponentName componentName, int category) {
final Queue<LogMaker> startLogs = MetricsAsserts.findMatchingLogs(mMetricsReader,
new LogMaker(category));
return startLogs.stream().filter(log -> isComponentMatched(log, componentName))
.findFirst().orElse(null);
}
private static boolean isComponentMatched(LogMaker log, ComponentName componentName) {
final String actualClassName = (String) log.getTaggedData(FIELD_CLASS_NAME);
final String actualPackageName = log.getPackageName();
return componentName.getClassName().equals(actualClassName) &&
componentName.getPackageName().equals(actualPackageName);
}
private static String categoryToString(int category) {
return (category == APP_TRANSITION ? "APP_TRANSITION"
: category == APP_TRANSITION_CANCELLED ? "APP_TRANSITION_CANCELLED"
: category == APP_TRANSITION_REPORTED_DRAWN ? "APP_TRANSITION_REPORTED_DRAWN"
: "Unknown") + "(" + category + ")";
}
private static void assertLaunchComponentState(String amStartOutput, ComponentName component,
String state) {
assertThat("did not find component in am start output.", amStartOutput,
containsString(component.flattenToShortString()));
assertThat("did not find launch state in am start output.", amStartOutput,
containsString(state));
}
private static void assertLaunchComponentStateAndTime(String amStartOutput,
ComponentName component, String state, int windowsDrawnDelayMs) {
assertLaunchComponentState(amStartOutput, component, state);
assertThat("did not find windows drawn delay time in am start output.", amStartOutput,
containsString(Integer.toString(windowsDrawnDelayMs)));
}
private void assertLogsContain(String[] logs, String expectedLog) {
for (String line : logs) {
if (line.contains(expectedLog)) {
return;
}
}
fail("Expected to find '" + expectedLog + "' in " + Arrays.toString(logs));
}
}