/* * Copyright (C) 2017 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.cts.statsd.atom; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import com.android.internal.os.StatsdConfigProto.FieldValueMatcher; import com.android.internal.os.StatsdConfigProto.MessageMatcher; import com.android.internal.os.StatsdConfigProto.Position; import com.android.internal.os.StatsdConfigProto.StatsdConfig; import com.android.os.StatsLog.EventMetricData; import com.android.tradefed.log.LogUtil; import java.util.Arrays; import java.util.List; /** * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app. */ public class DeviceAtomTestCase extends AtomTestCase { public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk"; public static final String DEVICE_SIDE_TEST_PACKAGE = "com.android.server.cts.device.statsd"; public static final long DEVICE_SIDE_TEST_PACKAGE_VERSION = 10; public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME = "com.android.server.cts.device.statsd.StatsdCtsForegroundService"; private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT = "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService"; public static final long DEVICE_SIDE_TEST_PKG_HASH = Long.parseUnsignedLong("15694052924544098582"); // Constants from device side tests (not directly accessible here). public static final String KEY_ACTION = "action"; public static final String ACTION_LMK = "action.lmk"; public static final String CONFIG_NAME = "cts_config"; @Override protected void setUp() throws Exception { super.setUp(); getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE); installTestApp(); Thread.sleep(1000); } @Override protected void tearDown() throws Exception { getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE); super.tearDown(); } /** * Performs a device-side test by calling a method on the app and returns its stats events. * @param methodName the name of the method in the app's AtomTests to perform * @param atom atom tag (from atoms.proto) * @param key atom's field corresponding to state * @param stateOn 'on' value * @param stateOff 'off' value * @param minTimeDiffMs max allowed time between start and stop * @param maxTimeDiffMs min allowed time between start and stop * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop) * @return list of events with the app's uid matching the configuration defined by the params. */ protected List doDeviceMethodOnOff( String methodName, int atom, int key, int stateOn, int stateOff, int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception { StatsdConfig.Builder conf = createConfigBuilder(); addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn)); addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff)); List data = doDeviceMethod(methodName, conf); if (demandExactlyTwo) { assertThat(data).hasSize(2); } else { assertThat(data.size()).isAtLeast(2); } assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs); return data; } /** * * @param methodName the name of the method in the app's AtomTests to perform * @param cfg statsd configuration * @return list of events with the app's uid matching the configuration. */ protected List doDeviceMethod(String methodName, StatsdConfig.Builder cfg) throws Exception { removeConfig(CONFIG_ID); getReportList(); // Clears previous data on disk. uploadConfig(cfg); int appUid = getUid(); LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid); runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName); return getEventMetricDataList(); } protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception { StatsdConfig.Builder conf = createConfigBuilder(); addAtomEvent(conf, atomTag, useAttribution); uploadConfig(conf); } /** * Adds an event to the config for an atom that matches the given key AND has the app's uid. * @param conf configuration * @param atomTag atom tag (from atoms.proto) * @param fvm FieldValueMatcher.Builder for the relevant key */ @Override protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm) throws Exception { final int UID_KEY = 1; FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY); addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid)); } /** * Adds an event to the config for an atom that matches the app's uid. * @param conf configuration * @param atomTag atom tag (from atoms.proto) * @param useAttribution If true, the atom has a uid within an attribution node. Else, the atom * has a uid but not in an attribution node. */ protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, boolean useAttribution) throws Exception { final int UID_KEY = 1; FieldValueMatcher.Builder fvmUid; if (useAttribution) { fvmUid = createAttributionFvm(UID_KEY); } else { fvmUid = createFvm(UID_KEY).setEqString(DEVICE_SIDE_TEST_PACKAGE); } addAtomEvent(conf, atomTag, Arrays.asList(fvmUid)); } /** * Creates a FieldValueMatcher for atoms that use AttributionNode */ protected FieldValueMatcher.Builder createAttributionFvm(int field) { final int ATTRIBUTION_NODE_UID_KEY = 1; return createFvm(field).setPosition(Position.ANY) .setMatchesTuple(MessageMatcher.newBuilder() .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY) .setEqString(DEVICE_SIDE_TEST_PACKAGE))); } /** * Gets the uid of the test app. */ protected int getUid() throws Exception { int currentUser = getDevice().getCurrentUser(); String uidLine = getDevice().executeShellCommand("cmd package list packages -U --user " + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE); String[] uidLineParts = uidLine.split(":"); // 3rd entry is package uid assertThat(uidLineParts.length).isGreaterThan(2); int uid = Integer.parseInt(uidLineParts[2].trim()); assertThat(uid).isGreaterThan(10000); return uid; } /** * Installs the test apk. */ protected void installTestApp() throws Exception { installPackage(DEVICE_SIDE_TEST_APK, true); LogUtil.CLog.i("Installing device-side test app with uid " + getUid()); allowBackgroundServices(); } /** * Uninstalls the test apk. */ protected void uninstallPackage() throws Exception{ getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE); } /** * Required to successfully start a background service from adb in Android O. */ protected void allowBackgroundServices() throws Exception { getDevice().executeShellCommand(String.format( "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE)); } /** * Runs a (background) service to perform the given action. * @param actionValue the action code constants indicating the desired action to perform. */ protected void executeBackgroundService(String actionValue) throws Exception { allowBackgroundServices(); getDevice().executeShellCommand(String.format( "am startservice -n '%s' -e %s %s", DEVICE_SIDE_BG_SERVICE_COMPONENT, KEY_ACTION, actionValue)); } /** Make the test app standby-active so it can run syncs and jobs immediately. */ protected void allowImmediateSyncs() throws Exception { getDevice().executeShellCommand("am set-standby-bucket " + DEVICE_SIDE_TEST_PACKAGE + " active"); } /** * Runs the specified activity. */ protected void runActivity(String activity, String actionKey, String actionValue) throws Exception { runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG); } /** * Runs the specified activity. */ protected void runActivity(String activity, String actionKey, String actionValue, long waitTime) throws Exception { try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) { Thread.sleep(waitTime); } } /** * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity * when closed. * *

Example usage: *

     *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
     *         doStuff();
     *     }
     * 
*/ protected AutoCloseable withActivity(String activity, String actionKey, String actionValue) throws Exception { String intentString = null; if (actionKey != null && actionValue != null) { intentString = actionKey + " " + actionValue; } if (intentString == null) { getDevice().executeShellCommand( "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity); } else { getDevice().executeShellCommand( "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " + intentString); } return () -> { getDevice().executeShellCommand( "am force-stop " + DEVICE_SIDE_TEST_PACKAGE); Thread.sleep(WAIT_TIME_SHORT); }; } protected void resetBatteryStats() throws Exception { getDevice().executeShellCommand("dumpsys batterystats --reset"); } protected void clearProcStats() throws Exception { getDevice().executeShellCommand("dumpsys procstats --clear"); } protected void startProcStatsTesting() throws Exception { getDevice().executeShellCommand("dumpsys procstats --start-testing"); } protected void stopProcStatsTesting() throws Exception { getDevice().executeShellCommand("dumpsys procstats --stop-testing"); } protected void commitProcStatsToDisk() throws Exception { getDevice().executeShellCommand("dumpsys procstats --commit"); } protected void rebootDeviceAndWaitUntilReady() throws Exception { rebootDevice(); // Wait for 2 mins. assertWithMessage("Device failed to boot") .that(getDevice().waitForBootComplete(120_000)).isTrue(); assertWithMessage("Stats service failed to start") .that(waitForStatsServiceStart(60_000)).isTrue(); Thread.sleep(2_000); } protected boolean waitForStatsServiceStart(final long waitTime) throws Exception { LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime); int counter = 1; long startTime = System.currentTimeMillis(); while ((System.currentTimeMillis() - startTime) < waitTime) { if ("running".equals(getProperty("init.svc.statsd"))) { return true; } Thread.sleep(Math.min(200 * counter, 2_000)); counter++; } LogUtil.CLog.w("Stats service did not start after %d ms", waitTime); return false; } boolean getNetworkStatsCombinedSubTypeEnabled() throws Exception { final String output = getDevice().executeShellCommand( "settings get global netstats_combine_subtype_enabled").trim(); return output.equals("1"); } void setNetworkStatsCombinedSubTypeEnabled(boolean enable) throws Exception { getDevice().executeShellCommand("settings put global netstats_combine_subtype_enabled " + (enable ? "1" : "0")); } }