1 /*
2  * Copyright (C) 2017 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 package android.cts.statsd.atom;
17 
18 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
19 import com.android.internal.os.StatsdConfigProto.MessageMatcher;
20 import com.android.internal.os.StatsdConfigProto.Position;
21 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
22 import com.android.os.StatsLog.EventMetricData;
23 import com.android.tradefed.log.LogUtil;
24 
25 import java.util.Arrays;
26 import java.util.List;
27 
28 /**
29  * Base class for testing Statsd atoms that report a uid. Tests are performed via a device-side app.
30  */
31 public class DeviceAtomTestCase extends AtomTestCase {
32 
33     public static final String DEVICE_SIDE_TEST_APK = "CtsStatsdApp.apk";
34     public static final String DEVICE_SIDE_TEST_PACKAGE =
35             "com.android.server.cts.device.statsd";
36     public static final String DEVICE_SIDE_TEST_FOREGROUND_SERVICE_NAME =
37             "com.android.server.cts.device.statsd.StatsdCtsForegroundService";
38     private static final String DEVICE_SIDE_BG_SERVICE_COMPONENT =
39             "com.android.server.cts.device.statsd/.StatsdCtsBackgroundService";
40     public static final long DEVICE_SIDE_TEST_PKG_HASH =
41             Long.parseUnsignedLong("15694052924544098582");
42 
43     // Constants from device side tests (not directly accessible here).
44     public static final String KEY_ACTION = "action";
45     public static final String ACTION_LMK = "action.lmk";
46 
47     public static final String CONFIG_NAME = "cts_config";
48 
49     @Override
setUp()50     protected void setUp() throws Exception {
51         super.setUp();
52         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
53         installTestApp();
54         Thread.sleep(1000);
55     }
56 
57     @Override
tearDown()58     protected void tearDown() throws Exception {
59         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
60         super.tearDown();
61     }
62 
63     /**
64      * Performs a device-side test by calling a method on the app and returns its stats events.
65      * @param methodName the name of the method in the app's AtomTests to perform
66      * @param atom atom tag (from atoms.proto)
67      * @param key atom's field corresponding to state
68      * @param stateOn 'on' value
69      * @param stateOff 'off' value
70      * @param minTimeDiffMs max allowed time between start and stop
71      * @param maxTimeDiffMs min allowed time between start and stop
72      * @param demandExactlyTwo whether there must be precisely two events logged (1 start, 1 stop)
73      * @return list of events with the app's uid matching the configuration defined by the params.
74      */
doDeviceMethodOnOff( String methodName, int atom, int key, int stateOn, int stateOff, int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo)75     protected List<EventMetricData> doDeviceMethodOnOff(
76             String methodName, int atom, int key, int stateOn, int stateOff,
77             int minTimeDiffMs, int maxTimeDiffMs, boolean demandExactlyTwo) throws Exception {
78         StatsdConfig.Builder conf = createConfigBuilder();
79         addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOn));
80         addAtomEvent(conf, atom, createFvm(key).setEqInt(stateOff));
81         List<EventMetricData> data = doDeviceMethod(methodName, conf);
82 
83         if (demandExactlyTwo) {
84             assertEquals(2, data.size());
85         } else {
86             assertTrue("data.size() [" + data.size() + "] should be >= 2", data.size() >= 2);
87         }
88         assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMs, maxTimeDiffMs);
89         return data;
90     }
91 
92     /**
93      *
94      * @param methodName the name of the method in the app's AtomTests to perform
95      * @param cfg statsd configuration
96      * @return list of events with the app's uid matching the configuration.
97      */
doDeviceMethod(String methodName, StatsdConfig.Builder cfg)98     protected List<EventMetricData> doDeviceMethod(String methodName, StatsdConfig.Builder cfg)
99             throws Exception {
100         removeConfig(CONFIG_ID);
101         getReportList();  // Clears previous data on disk.
102         uploadConfig(cfg);
103         int appUid = getUid();
104         LogUtil.CLog.d("\nPerforming device-side test of " + methodName + " for uid " + appUid);
105         runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", methodName);
106 
107         return getEventMetricDataList();
108     }
109 
createAndUploadConfig(int atomTag, boolean useAttribution)110     protected void createAndUploadConfig(int atomTag, boolean useAttribution) throws Exception {
111         StatsdConfig.Builder conf = createConfigBuilder();
112         addAtomEvent(conf, atomTag, useAttribution);
113         uploadConfig(conf);
114     }
115 
116     /**
117      * Adds an event to the config for an atom that matches the given key AND has the app's uid.
118      * @param conf configuration
119      * @param atomTag atom tag (from atoms.proto)
120      * @param fvm FieldValueMatcher.Builder for the relevant key
121      */
122     @Override
addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)123     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)
124             throws Exception {
125 
126         final int UID_KEY = 1;
127         FieldValueMatcher.Builder fvmUid = createAttributionFvm(UID_KEY);
128         addAtomEvent(conf, atomTag, Arrays.asList(fvm, fvmUid));
129     }
130 
131     /**
132      * Adds an event to the config for an atom that matches the app's uid.
133      * @param conf configuration
134      * @param atomTag atom tag (from atoms.proto)
135      * @param useAttribution if the atom has a uid or AttributionNode
136      */
addAtomEvent(StatsdConfig.Builder conf, int atomTag, boolean useAttribution)137     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
138             boolean useAttribution) throws Exception {
139         final int UID_KEY = 1;
140         FieldValueMatcher.Builder fvmUid;
141         if (useAttribution) {
142             fvmUid = createAttributionFvm(UID_KEY);
143         } else {
144             fvmUid = createFvm(UID_KEY).setEqInt(getUid());
145         }
146         addAtomEvent(conf, atomTag, Arrays.asList(fvmUid));
147     }
148 
149     /**
150      * Creates a FieldValueMatcher for atoms that use AttributionNode
151      */
createAttributionFvm(int field)152     protected FieldValueMatcher.Builder createAttributionFvm(int field) {
153         final int ATTRIBUTION_NODE_UID_KEY = 1;
154         return createFvm(field).setPosition(Position.ANY)
155                 .setMatchesTuple(MessageMatcher.newBuilder()
156                         .addFieldValueMatcher(createFvm(ATTRIBUTION_NODE_UID_KEY)
157                                 .setEqString(DEVICE_SIDE_TEST_PACKAGE)));
158     }
159 
160     /**
161      * Gets the uid of the test app.
162      */
getUid()163     protected int getUid() throws Exception {
164         int currentUser = getDevice().getCurrentUser();
165         String uidLine = getDevice().executeShellCommand("cmd package list packages -U --user "
166                 + currentUser + " " + DEVICE_SIDE_TEST_PACKAGE);
167         String[] uidLineParts = uidLine.split(":");
168         // 3rd entry is package uid
169         assertTrue(uidLineParts.length > 2);
170         int uid = Integer.parseInt(uidLineParts[2].trim());
171         assertTrue(uid > 10000);
172         return uid;
173     }
174 
175     /**
176      * Installs the test apk.
177      */
installTestApp()178     protected void installTestApp() throws Exception {
179         installPackage(DEVICE_SIDE_TEST_APK, true);
180         LogUtil.CLog.i("Installing device-side test app with uid " + getUid());
181         allowBackgroundServices();
182     }
183 
184     /**
185      * Uninstalls the test apk.
186      */
uninstallPackage()187     protected void uninstallPackage() throws Exception{
188         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
189     }
190 
191     /**
192      * Required to successfully start a background service from adb in O.
193      */
allowBackgroundServices()194     protected void allowBackgroundServices() throws Exception {
195         getDevice().executeShellCommand(String.format(
196                 "cmd deviceidle tempwhitelist %s", DEVICE_SIDE_TEST_PACKAGE));
197     }
198 
199     /**
200      * Runs a (background) service to perform the given action.
201      * @param actionValue the action code constants indicating the desired action to perform.
202      */
executeBackgroundService(String actionValue)203     protected void executeBackgroundService(String actionValue) throws Exception {
204         allowBackgroundServices();
205         getDevice().executeShellCommand(String.format(
206                 "am startservice -n '%s' -e %s %s",
207                 DEVICE_SIDE_BG_SERVICE_COMPONENT,
208                 KEY_ACTION, actionValue));
209     }
210 
211 
212     /** Make the test app standby-active so it can run syncs and jobs immediately. */
allowImmediateSyncs()213     protected void allowImmediateSyncs() throws Exception {
214         getDevice().executeShellCommand("am set-standby-bucket "
215                 + DEVICE_SIDE_TEST_PACKAGE + " active");
216     }
217 
218     /**
219      * Runs the specified activity.
220      */
runActivity(String activity, String actionKey, String actionValue)221     protected void runActivity(String activity, String actionKey, String actionValue)
222             throws Exception {
223         runActivity(activity, actionKey, actionValue, WAIT_TIME_LONG);
224     }
225 
226     /**
227      * Runs the specified activity.
228      */
runActivity(String activity, String actionKey, String actionValue, long waitTime)229     protected void runActivity(String activity, String actionKey, String actionValue,
230             long waitTime) throws Exception {
231         try (AutoCloseable a = withActivity(activity, actionKey, actionValue)) {
232             Thread.sleep(waitTime);
233         }
234     }
235 
236     /**
237      * Starts the specified activity and returns an {@link AutoCloseable} that stops the activity
238      * when closed.
239      *
240      * <p>Example usage:
241      * <pre>
242      *     try (AutoClosable a = withActivity("activity", "action", "action-value")) {
243      *         doStuff();
244      *     }
245      * </pre>
246      */
withActivity(String activity, String actionKey, String actionValue)247     protected AutoCloseable withActivity(String activity, String actionKey, String actionValue)
248             throws Exception {
249         String intentString = null;
250         if (actionKey != null && actionValue != null) {
251             intentString = actionKey + " " + actionValue;
252         }
253         if (intentString == null) {
254             getDevice().executeShellCommand(
255                     "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity);
256         } else {
257             getDevice().executeShellCommand(
258                     "am start -n " + DEVICE_SIDE_TEST_PACKAGE + "/." + activity + " -e " +
259                             intentString);
260         }
261         return () -> {
262             getDevice().executeShellCommand(
263                     "am force-stop " + DEVICE_SIDE_TEST_PACKAGE);
264             Thread.sleep(WAIT_TIME_SHORT);
265         };
266     }
267 
resetBatteryStats()268     protected void resetBatteryStats() throws Exception {
269         getDevice().executeShellCommand("dumpsys batterystats --reset");
270     }
271 
clearProcStats()272     protected void clearProcStats() throws Exception {
273         getDevice().executeShellCommand("dumpsys procstats --clear");
274     }
275 
startProcStatsTesting()276     protected void startProcStatsTesting() throws Exception {
277         getDevice().executeShellCommand("dumpsys procstats --start-testing");
278     }
279 
stopProcStatsTesting()280     protected void stopProcStatsTesting() throws Exception {
281         getDevice().executeShellCommand("dumpsys procstats --stop-testing");
282     }
283 
commitProcStatsToDisk()284     protected void commitProcStatsToDisk() throws Exception {
285         getDevice().executeShellCommand("dumpsys procstats --commit");
286     }
287 
rebootDeviceAndWaitUntilReady()288     protected void rebootDeviceAndWaitUntilReady() throws Exception {
289         rebootDevice();
290         // Wait for 2 mins.
291         assertTrue("Device failed to boot", getDevice().waitForBootComplete(120_000));
292         assertTrue("Stats service failed to start", waitForStatsServiceStart(60_000));
293         Thread.sleep(2_000);
294     }
295 
waitForStatsServiceStart(final long waitTime)296     protected boolean waitForStatsServiceStart(final long waitTime) throws Exception {
297         LogUtil.CLog.i("Waiting %d ms for stats service to start", waitTime);
298         int counter = 1;
299         long startTime = System.currentTimeMillis();
300         while ((System.currentTimeMillis() - startTime) < waitTime) {
301             if ("running".equals(getProperty("init.svc.statsd"))) {
302                 return true;
303             }
304             Thread.sleep(Math.min(200 * counter, 2_000));
305             counter++;
306         }
307         LogUtil.CLog.w("Stats service did not start after %d ms", waitTime);
308         return false;
309     }
310 }
311