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 static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_APK;
19 import static android.cts.statsd.atom.DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE;
20 
21 import android.os.BatteryStatsProto;
22 import android.os.StatsDataDumpProto;
23 import android.service.battery.BatteryServiceDumpProto;
24 import android.service.batterystats.BatteryStatsServiceDumpProto;
25 import android.service.procstats.ProcessStatsServiceDumpProto;
26 
27 import com.android.annotations.Nullable;
28 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
29 import com.android.internal.os.StatsdConfigProto.EventMetric;
30 import com.android.internal.os.StatsdConfigProto.FieldFilter;
31 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
32 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
33 import com.android.internal.os.StatsdConfigProto.GaugeMetric;
34 import com.android.internal.os.StatsdConfigProto.Predicate;
35 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
36 import com.android.internal.os.StatsdConfigProto.SimplePredicate;
37 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
38 import com.android.internal.os.StatsdConfigProto.TimeUnit;
39 import com.android.os.AtomsProto.AppBreadcrumbReported;
40 import com.android.os.AtomsProto.Atom;
41 import com.android.os.AtomsProto.ProcessStatsPackageProto;
42 import com.android.os.AtomsProto.ProcessStatsProto;
43 import com.android.os.AtomsProto.ProcessStatsStateProto;
44 import com.android.os.StatsLog.ConfigMetricsReport;
45 import com.android.os.StatsLog.ConfigMetricsReportList;
46 import com.android.os.StatsLog.DurationMetricData;
47 import com.android.os.StatsLog.EventMetricData;
48 import com.android.os.StatsLog.GaugeMetricData;
49 import com.android.os.StatsLog.CountMetricData;
50 import com.android.os.StatsLog.StatsLogReport;
51 import com.android.os.StatsLog.ValueMetricData;
52 import com.android.tradefed.device.DeviceNotAvailableException;
53 import com.android.tradefed.log.LogUtil;
54 import com.android.tradefed.util.CommandResult;
55 import com.android.tradefed.util.CommandStatus;
56 
57 import com.google.common.io.Files;
58 import com.google.protobuf.ByteString;
59 
60 import java.io.File;
61 import java.text.SimpleDateFormat;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Comparator;
65 import java.util.Date;
66 import java.util.List;
67 import java.util.Set;
68 import java.util.function.Function;
69 import java.util.stream.Collectors;
70 
71 /**
72  * Base class for testing Statsd atoms.
73  * Validates reporting of statsd logging based on different events
74  */
75 public class AtomTestCase extends BaseTestCase {
76 
77     /**
78      * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
79      * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
80      */
81     public static final boolean OPTIONAL_TESTS_ENABLED = false;
82 
83     public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
84     public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
85     public static final String DUMP_BATTERY_CMD = "dumpsys battery";
86     public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
87     public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
88     public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
89     public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
90     /** ID of the config, which evaluates to -1572883457. */
91     public static final long CONFIG_ID = "cts_config".hashCode();
92 
93     public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
94     public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
95     public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
96     public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
97     public static final String FEATURE_CAMERA = "android.hardware.camera";
98     public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
99     public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
100     public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
101     public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
102     public static final String FEATURE_PC = "android.hardware.type.pc";
103     public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
104     public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
105     public static final String FEATURE_WATCH = "android.hardware.type.watch";
106     public static final String FEATURE_WIFI = "android.hardware.wifi";
107 
108     protected static final int WAIT_TIME_SHORT = 500;
109     protected static final int WAIT_TIME_LONG = 2_000;
110 
111     protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
112     protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
113 
114     @Override
setUp()115     protected void setUp() throws Exception {
116         super.setUp();
117 
118         if (statsdDisabled()) {
119             return;
120         }
121 
122         // Uninstall to clear the history in case it's still on the device.
123         removeConfig(CONFIG_ID);
124         getReportList(); // Clears data.
125     }
126 
127     @Override
tearDown()128     protected void tearDown() throws Exception {
129         removeConfig(CONFIG_ID);
130         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
131         super.tearDown();
132     }
133 
134     /**
135      * Determines whether logcat indicates that incidentd fired since the given device date.
136      */
didIncidentdFireSince(String date)137     protected boolean didIncidentdFireSince(String date) throws Exception {
138         final String INCIDENTD_TAG = "incidentd";
139         final String INCIDENTD_STARTED_STRING = "reportIncident";
140         // TODO: Do something more robust than this in case of delayed logging.
141         Thread.sleep(1000);
142         String log = getLogcatSince(date, String.format(
143                 "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
144         return log.contains(INCIDENTD_STARTED_STRING);
145     }
146 
checkDeviceFor(String methodName)147     protected boolean checkDeviceFor(String methodName) throws Exception {
148         try {
149             installPackage(DEVICE_SIDE_TEST_APK, true);
150             runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
151             // Test passes, meaning that the answer is true.
152             LogUtil.CLog.d(methodName + "() indicates true.");
153             return true;
154         } catch (AssertionError e) {
155             // Method is designed to fail if the answer is false.
156             LogUtil.CLog.d(methodName + "() indicates false.");
157             return false;
158         }
159     }
160 
161     /**
162      * Returns a protobuf-encoded perfetto config that enables the kernel
163      * ftrace tracer with sched_switch for 10 seconds.
164      * See https://android.googlesource.com/platform/external/perfetto/+/master/docs/trace-config.md
165      * for details on how to generate this.
166      */
getPerfettoConfig()167     protected ByteString getPerfettoConfig() {
168         return ByteString.copyFrom(new byte[] { 0xa, 0x3, 0x8, (byte) 0x80, 0x1, 0x12, 0x23, 0xa,
169                         0x21, 0xa, 0xc, 0x6c, 0x69, 0x6e, 0x75, 0x78, 0x2e, 0x66, 0x74, 0x72, 0x61,
170                         0x63, 0x65, 0x10, 0x0, (byte) 0xa2, 0x6, 0xe, 0xa, 0xc, 0x73, 0x63, 0x68,
171                         0x65, 0x64, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x18, (byte) 0x90,
172                         0x4e, (byte) 0x98, 0x01, 0x01 });
173     }
174 
175     /**
176      * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
177      * run too close of for too many times and hits the upload limit.
178      */
resetPerfettoGuardrails()179     protected void resetPerfettoGuardrails() throws Exception {
180         final String cmd = "perfetto --reset-guardrails";
181         CommandResult cr = getDevice().executeShellV2Command(cmd);
182         if (cr.getStatus() != CommandStatus.SUCCESS)
183             throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr()));
184     }
185 
186     /**
187      * Determines whether perfetto enabled the kernel ftrace tracer.
188      */
isSystemTracingEnabled()189     protected boolean isSystemTracingEnabled() throws Exception {
190         final String path = "/sys/kernel/debug/tracing/tracing_on";
191         String tracing_on = getDevice().executeShellCommand("cat " + path);
192         if (tracing_on.startsWith("0"))
193             return false;
194         if (tracing_on.startsWith("1"))
195             return true;
196         throw new Exception(String.format("Unexpected state for %s = %s", path, tracing_on));
197     }
198 
createConfigBuilder()199     protected static StatsdConfig.Builder createConfigBuilder() {
200         return StatsdConfig.newBuilder().setId(CONFIG_ID)
201                 .addAllowedLogSource("AID_SYSTEM")
202                 .addAllowedLogSource("AID_BLUETOOTH")
203                 // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
204                 .addAllowedLogSource("com.android.bluetooth")
205                 .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE);
206     }
207 
createAndUploadConfig(int atomTag)208     protected void createAndUploadConfig(int atomTag) throws Exception {
209         StatsdConfig.Builder conf = createConfigBuilder();
210         addAtomEvent(conf, atomTag);
211         uploadConfig(conf);
212     }
213 
uploadConfig(StatsdConfig.Builder config)214     protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
215         uploadConfig(config.build());
216     }
217 
uploadConfig(StatsdConfig config)218     protected void uploadConfig(StatsdConfig config) throws Exception {
219         LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
220         File configFile = File.createTempFile("statsdconfig", ".config");
221         configFile.deleteOnExit();
222         Files.write(config.toByteArray(), configFile);
223         String remotePath = "/data/local/tmp/" + configFile.getName();
224         getDevice().pushFile(configFile, remotePath);
225         getDevice().executeShellCommand(
226                 String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD,
227                         String.valueOf(CONFIG_ID)));
228         getDevice().executeShellCommand("rm " + remotePath);
229     }
230 
removeConfig(long configId)231     protected void removeConfig(long configId) throws Exception {
232         getDevice().executeShellCommand(
233                 String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId)));
234     }
235 
236     /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
getEventMetricDataList()237     protected List<EventMetricData> getEventMetricDataList() throws Exception {
238         ConfigMetricsReportList reportList = getReportList();
239         return getEventMetricDataList(reportList);
240     }
241 
242     /**
243      *  Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
244      */
getSortedConfigMetricsReports( ConfigMetricsReportList configMetricsReportList)245     protected List<ConfigMetricsReport> getSortedConfigMetricsReports(
246             ConfigMetricsReportList configMetricsReportList) {
247         return configMetricsReportList.getReportsList().stream()
248                 .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos))
249                 .collect(Collectors.toList());
250     }
251 
252     /**
253      * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
254      * contain a single report).
255      */
getEventMetricDataList(ConfigMetricsReportList reportList)256     protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
257             throws Exception {
258         assertTrue("Expected one report", reportList.getReportsCount() == 1);
259         ConfigMetricsReport report = reportList.getReports(0);
260 
261         List<EventMetricData> data = new ArrayList<>();
262         for (StatsLogReport metric : report.getMetricsList()) {
263             data.addAll(metric.getEventMetrics().getDataList());
264         }
265         data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
266 
267         LogUtil.CLog.d("Get EventMetricDataList as following:\n");
268         for (EventMetricData d : data) {
269             LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
270         }
271         return data;
272     }
273 
getGaugeMetricDataList()274     protected List<Atom> getGaugeMetricDataList() throws Exception {
275         ConfigMetricsReportList reportList = getReportList();
276         assertTrue("Expected one report.", reportList.getReportsCount() == 1);
277         // only config
278         ConfigMetricsReport report = reportList.getReports(0);
279         assertEquals("Expected one metric in the report.", 1, report.getMetricsCount());
280 
281         List<Atom> data = new ArrayList<>();
282         for (GaugeMetricData gaugeMetricData :
283                 report.getMetrics(0).getGaugeMetrics().getDataList()) {
284             assertTrue("Expected one bucket.", gaugeMetricData.getBucketInfoCount() == 1);
285             for (Atom atom : gaugeMetricData.getBucketInfo(0).getAtomList()) {
286                 data.add(atom);
287             }
288         }
289 
290         LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
291         for (Atom d : data) {
292             LogUtil.CLog.d("Atom:\n" + d.toString());
293         }
294         return data;
295     }
296 
297     /**
298      * Gets the statsd report and extract duration metric data.
299      * Note that this also deletes that report from statsd.
300      */
getDurationMetricDataList()301     protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
302         ConfigMetricsReportList reportList = getReportList();
303         assertTrue("Expected one report", reportList.getReportsCount() == 1);
304         ConfigMetricsReport report = reportList.getReports(0);
305 
306         List<DurationMetricData> data = new ArrayList<>();
307         for (StatsLogReport metric : report.getMetricsList()) {
308             data.addAll(metric.getDurationMetrics().getDataList());
309         }
310 
311         LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
312         for (DurationMetricData d : data) {
313             LogUtil.CLog.d("Duration " + d);
314         }
315         return data;
316     }
317 
318     /**
319      * Gets the statsd report and extract count metric data.
320      * Note that this also deletes that report from statsd.
321      */
getCountMetricDataList()322     protected List<CountMetricData> getCountMetricDataList() throws Exception {
323         ConfigMetricsReportList reportList = getReportList();
324         assertTrue("Expected one report", reportList.getReportsCount() == 1);
325         ConfigMetricsReport report = reportList.getReports(0);
326 
327         List<CountMetricData> data = new ArrayList<>();
328         for (StatsLogReport metric : report.getMetricsList()) {
329             data.addAll(metric.getCountMetrics().getDataList());
330         }
331 
332         LogUtil.CLog.d("Got CountMetricDataList as following:\n");
333         for (CountMetricData d : data) {
334             LogUtil.CLog.d("Count " + d);
335         }
336         return data;
337     }
338 
339     /**
340      * Gets the statsd report and extract value metric data.
341      * Note that this also deletes that report from statsd.
342      */
getValueMetricDataList()343     protected List<ValueMetricData> getValueMetricDataList() throws Exception {
344         ConfigMetricsReportList reportList = getReportList();
345         assertTrue("Expected one report", reportList.getReportsCount() == 1);
346         ConfigMetricsReport report = reportList.getReports(0);
347 
348         List<ValueMetricData> data = new ArrayList<>();
349         for (StatsLogReport metric : report.getMetricsList()) {
350             data.addAll(metric.getValueMetrics().getDataList());
351         }
352 
353         LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
354         for (ValueMetricData d : data) {
355             LogUtil.CLog.d("Value " + d);
356         }
357         return data;
358     }
359 
getStatsLogReport()360     protected StatsLogReport getStatsLogReport() throws Exception {
361         ConfigMetricsReport report = getConfigMetricsReport();
362         assertTrue(report.hasUidMap());
363         assertEquals(1, report.getMetricsCount());
364         return report.getMetrics(0);
365     }
366 
getConfigMetricsReport()367     protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
368         ConfigMetricsReportList reportList = getReportList();
369         assertEquals(1, reportList.getReportsCount());
370         return reportList.getReports(0);
371     }
372 
373     /** Gets the statsd report. Note that this also deletes that report from statsd. */
getReportList()374     protected ConfigMetricsReportList getReportList() throws Exception {
375         try {
376             ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
377                     String.join(" ", DUMP_REPORT_CMD, String.valueOf(CONFIG_ID),
378                             "--include_current_bucket", "--proto"));
379             return reportList;
380         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
381             LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
382                     + "Perhaps there is not a valid statsd config for the requested "
383                     + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
384             throw (e);
385         }
386     }
387 
getBatteryStatsProto()388     protected BatteryStatsProto getBatteryStatsProto() throws Exception {
389         try {
390             BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(),
391                     String.join(" ", DUMP_BATTERYSTATS_CMD,
392                             "--proto")).getBatterystats();
393             LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString());
394             return batteryStatsProto;
395         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
396             LogUtil.CLog.e("Failed to dump batterystats proto");
397             throw (e);
398         }
399     }
400 
401     /** Gets reports from the statsd data incident section from the stats dumpsys. */
getReportsFromStatsDataDumpProto()402     protected List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception {
403         try {
404             StatsDataDumpProto statsProto = getDump(StatsDataDumpProto.parser(),
405                     String.join(" ", DUMPSYS_STATS_CMD, "--proto"));
406             // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists.
407             List<ConfigMetricsReportList> reports
408                     = new ArrayList<>(statsProto.getConfigMetricsReportListCount());
409             for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) {
410                 reports.add(ConfigMetricsReportList.parseFrom(reportListBytes));
411             }
412             LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString());
413             return reports;
414         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
415             LogUtil.CLog.e("Failed to dumpsys stats proto");
416             throw (e);
417         }
418     }
419 
getProcStatsProto()420     protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
421         try {
422 
423             List<ProcessStatsProto> processStatsProtoList =
424                 new ArrayList<ProcessStatsProto>();
425             android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
426                     ProcessStatsServiceDumpProto.parser(),
427                     String.join(" ", DUMP_PROCSTATS_CMD,
428                             "--proto")).getProcstatsNow();
429             for (android.service.procstats.ProcessStatsProto stats :
430                     sectionProto.getProcessStatsList()) {
431                 ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
432                     stats.toByteArray());
433                 processStatsProtoList.add(procStats);
434             }
435             LogUtil.CLog.d("Got procstats:\n ");
436             for (ProcessStatsProto processStatsProto : processStatsProtoList) {
437                 LogUtil.CLog.d(processStatsProto.toString());
438             }
439             return processStatsProtoList;
440         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
441             LogUtil.CLog.e("Failed to dump procstats proto");
442             throw (e);
443         }
444     }
445 
446     /*
447      * Get all procstats package data in proto
448      */
getAllProcStatsProto()449     protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
450         try {
451             android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
452                     ProcessStatsServiceDumpProto.parser(),
453                     String.join(" ", DUMP_PROCSTATS_CMD,
454                             "--proto")).getProcstatsOver24Hrs();
455             List<ProcessStatsPackageProto> processStatsProtoList =
456                 new ArrayList<ProcessStatsPackageProto>();
457             for (android.service.procstats.ProcessStatsPackageProto pkgStast :
458                 sectionProto.getPackageStatsList()) {
459               ProcessStatsPackageProto pkgAtom =
460                   ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
461                 processStatsProtoList.add(pkgAtom);
462             }
463             LogUtil.CLog.d("Got procstats:\n ");
464             for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
465                 LogUtil.CLog.d(processStatsProto.toString());
466             }
467             return processStatsProtoList;
468         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
469             LogUtil.CLog.e("Failed to dump procstats proto");
470             throw (e);
471         }
472     }
473 
hasBattery()474     protected boolean hasBattery() throws Exception {
475         try {
476             BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(),
477                     String.join(" ", DUMP_BATTERY_CMD, "--proto"));
478             LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
479             return batteryProto.getIsPresent();
480         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
481             LogUtil.CLog.e("Failed to dump batteryservice proto");
482             throw (e);
483         }
484     }
485 
486     /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
createFvm(int field)487     protected static FieldValueMatcher.Builder createFvm(int field) {
488         return FieldValueMatcher.newBuilder().setField(field);
489     }
490 
addAtomEvent(StatsdConfig.Builder conf, int atomTag)491     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
492         addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
493     }
494 
495     /**
496      * Adds an event to the config for an atom that matches the given key.
497      *
498      * @param conf    configuration
499      * @param atomTag atom tag (from atoms.proto)
500      * @param fvm     FieldValueMatcher.Builder for the relevant key
501      */
addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)502     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
503             FieldValueMatcher.Builder fvm)
504             throws Exception {
505         addAtomEvent(conf, atomTag, Arrays.asList(fvm));
506     }
507 
508     /**
509      * Adds an event to the config for an atom that matches the given keys.
510      *
511      * @param conf   configuration
512      * @param atomId atom tag (from atoms.proto)
513      * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
514      */
addAtomEvent(StatsdConfig.Builder conf, int atomId, List<FieldValueMatcher.Builder> fvms)515     protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
516             List<FieldValueMatcher.Builder> fvms) throws Exception {
517 
518         final String atomName = "Atom" + System.nanoTime();
519         final String eventName = "Event" + System.nanoTime();
520 
521         SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
522         if (fvms != null) {
523             for (FieldValueMatcher.Builder fvm : fvms) {
524                 sam.addFieldValueMatcher(fvm);
525             }
526         }
527         conf.addAtomMatcher(AtomMatcher.newBuilder()
528                 .setId(atomName.hashCode())
529                 .setSimpleAtomMatcher(sam));
530         conf.addEventMetric(EventMetric.newBuilder()
531                 .setId(eventName.hashCode())
532                 .setWhat(atomName.hashCode()));
533     }
534 
535     /**
536      * Adds an atom to a gauge metric of a config
537      *
538      * @param conf        configuration
539      * @param atomId      atom id (from atoms.proto)
540      * @param gaugeMetric the gauge metric to add
541      */
addGaugeAtom(StatsdConfig.Builder conf, int atomId, GaugeMetric.Builder gaugeMetric)542     protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
543             GaugeMetric.Builder gaugeMetric) throws Exception {
544         final String atomName = "Atom" + System.nanoTime();
545         final String gaugeName = "Gauge" + System.nanoTime();
546         final String predicateName = "APP_BREADCRUMB";
547         SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
548         conf.addAtomMatcher(AtomMatcher.newBuilder()
549                 .setId(atomName.hashCode())
550                 .setSimpleAtomMatcher(sam));
551         final String predicateTrueName = "APP_BREADCRUMB_1";
552         final String predicateFalseName = "APP_BREADCRUMB_2";
553         conf.addAtomMatcher(AtomMatcher.newBuilder()
554                 .setId(predicateTrueName.hashCode())
555                 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
556                         .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
557                         .addFieldValueMatcher(FieldValueMatcher.newBuilder()
558                                 .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
559                                 .setEqInt(1)
560                         )
561                 )
562         )
563                 // Used to trigger predicate
564                 .addAtomMatcher(AtomMatcher.newBuilder()
565                         .setId(predicateFalseName.hashCode())
566                         .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
567                                 .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
568                                 .addFieldValueMatcher(FieldValueMatcher.newBuilder()
569                                         .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
570                                         .setEqInt(2)
571                                 )
572                         )
573                 );
574         conf.addPredicate(Predicate.newBuilder()
575                 .setId(predicateName.hashCode())
576                 .setSimplePredicate(SimplePredicate.newBuilder()
577                         .setStart(predicateTrueName.hashCode())
578                         .setStop(predicateFalseName.hashCode())
579                         .setCountNesting(false)
580                 )
581         );
582         gaugeMetric
583                 .setId(gaugeName.hashCode())
584                 .setWhat(atomName.hashCode())
585                 .setCondition(predicateName.hashCode());
586         conf.addGaugeMetric(gaugeMetric.build());
587     }
588 
589     /**
590      * Adds an atom to a gauge metric of a config
591      *
592      * @param conf      configuration
593      * @param atomId    atom id (from atoms.proto)
594      * @param dimension dimension is needed for most pulled atoms
595      */
addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId, @Nullable FieldMatcher.Builder dimension)596     protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
597             @Nullable FieldMatcher.Builder dimension) throws Exception {
598         GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
599                 .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
600                 .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
601                 .setMaxNumGaugeAtomsPerBucket(10000)
602                 .setBucket(TimeUnit.CTS);
603         if (dimension != null) {
604             gaugeMetric.setDimensionsInWhat(dimension.build());
605         }
606         addGaugeAtom(conf, atomId, gaugeMetric);
607     }
608 
609     /**
610      * Asserts that each set of states in stateSets occurs at least once in data.
611      * Asserts that the states in data occur in the same order as the sets in stateSets.
612      *
613      * @param stateSets        A list of set of states, where each set represents an equivalent
614      *                         state of the device for the purpose of CTS.
615      * @param data             list of EventMetricData from statsd, produced by
616      *                         getReportMetricListData()
617      * @param wait             expected duration (in ms) between state changes; asserts that the
618      *                         actual wait
619      *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
620      *                         assertion.
621      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
622      */
assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data, int wait, Function<Atom, Integer> getStateFromAtom)623     public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
624             int wait, Function<Atom, Integer> getStateFromAtom) {
625         // Sometimes, there are more events than there are states.
626         // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
627         assertTrue("Too few states found (" + data.size() + ")", data.size() >= stateSets.size());
628         int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
629         for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
630             Atom atom = data.get(dataIndex).getAtom();
631             int state = getStateFromAtom.apply(atom);
632             // If state is in the current state set, we do not assert anything.
633             // If it is not, we expect to have transitioned to the next state set.
634             if (stateSets.get(stateSetIndex).contains(state)) {
635                 // No need to assert anything. Just log it.
636                 LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
637                         + "in stateSetIndex " + stateSetIndex + ":\n"
638                         + data.get(dataIndex).getAtom().toString());
639             } else {
640                 stateSetIndex += 1;
641                 LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
642                         + " in stateSetIndex " + stateSetIndex + ":\n"
643                         + data.get(dataIndex).getAtom().toString());
644                 assertTrue("Missed first state", dataIndex != 0); // should not be on first data
645                 assertTrue("Too many states (" + (stateSetIndex + 1) + ")",
646                         stateSetIndex < stateSets.size());
647                 assertTrue("Is in wrong state (" + state + ")",
648                         stateSets.get(stateSetIndex).contains(state));
649                 if (wait > 0) {
650                     assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
651                             wait / 2, wait * 5);
652                 }
653             }
654         }
655         assertTrue("Too few states (" + (stateSetIndex + 1) + ")",
656                 stateSetIndex == stateSets.size() - 1);
657     }
658 
659     /**
660      * Removes all elements from data prior to the first occurrence of an element of state. After
661      * this method is called, the first element of data (if non-empty) is guaranteed to be an
662      * element in state.
663      *
664      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
665      */
popUntilFind(List<EventMetricData> data, Set<Integer> state, Function<Atom, Integer> getStateFromAtom)666     public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
667             Function<Atom, Integer> getStateFromAtom) {
668         int firstStateIdx;
669         for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
670             Atom atom = data.get(firstStateIdx).getAtom();
671             if (state.contains(getStateFromAtom.apply(atom))) {
672                 break;
673             }
674         }
675         if (firstStateIdx == 0) {
676             // First first element already is in state, so there's nothing to do.
677             return;
678         }
679         data.subList(0, firstStateIdx).clear();
680     }
681 
682     /**
683      * Removes all elements from data after to the last occurrence of an element of state. After
684      * this method is called, the last element of data (if non-empty) is guaranteed to be an
685      * element in state.
686      *
687      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
688      */
popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state, Function<Atom, Integer> getStateFromAtom)689     public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state,
690         Function<Atom, Integer> getStateFromAtom) {
691         int lastStateIdx;
692         for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
693             Atom atom = data.get(lastStateIdx).getAtom();
694             if (state.contains(getStateFromAtom.apply(atom))) {
695                 break;
696             }
697         }
698         if (lastStateIdx == data.size()-1) {
699             // Last element already is in state, so there's nothing to do.
700             return;
701         }
702         data.subList(lastStateIdx+1, data.size()).clear();
703     }
704 
705     /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */
getHostUid()706     protected int getHostUid() throws DeviceNotAvailableException {
707         String strUid = "";
708         try {
709             strUid = getDevice().executeShellCommand("id -u");
710             return Integer.parseInt(strUid.trim());
711         } catch (NumberFormatException e) {
712             LogUtil.CLog.e("Failed to get host's uid via shell command. Found " + strUid);
713             // Fall back to alternative method...
714             if (getDevice().isAdbRoot()) {
715                 return 0; // ROOT
716             } else {
717                 return 2000; // SHELL
718             }
719         }
720     }
721 
getProperty(String prop)722     protected String getProperty(String prop) throws Exception {
723         return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
724     }
725 
turnScreenOn()726     protected void turnScreenOn() throws Exception {
727         getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
728         getDevice().executeShellCommand("wm dismiss-keyguard");
729     }
730 
turnScreenOff()731     protected void turnScreenOff() throws Exception {
732         getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
733     }
734 
setChargingState(int state)735     protected void setChargingState(int state) throws Exception {
736         getDevice().executeShellCommand("cmd battery set status " + state);
737     }
738 
unplugDevice()739     protected void unplugDevice() throws Exception {
740         // On batteryless devices on Android P or above, the 'unplug' command
741         // alone does not simulate the really unplugged state.
742         //
743         // This is because charging state is left as "unknown". Unless a valid
744         // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
745         // framework does not consider the device as running on battery.
746         setChargingState(3);
747 
748         getDevice().executeShellCommand("cmd battery unplug");
749     }
750 
plugInAc()751     protected void plugInAc() throws Exception {
752         getDevice().executeShellCommand("cmd battery set ac 1");
753     }
754 
plugInUsb()755     protected void plugInUsb() throws Exception {
756         getDevice().executeShellCommand("cmd battery set usb 1");
757     }
758 
plugInWireless()759     protected void plugInWireless() throws Exception {
760         getDevice().executeShellCommand("cmd battery set wireless 1");
761     }
762 
enableLooperStats()763     protected void enableLooperStats() throws Exception {
764         getDevice().executeShellCommand("cmd looper_stats enable");
765     }
766 
resetLooperStats()767     protected void resetLooperStats() throws Exception {
768         getDevice().executeShellCommand("cmd looper_stats reset");
769     }
770 
disableLooperStats()771     protected void disableLooperStats() throws Exception {
772         getDevice().executeShellCommand("cmd looper_stats disable");
773     }
774 
enableBinderStats()775     protected void enableBinderStats() throws Exception {
776         getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
777     }
778 
resetBinderStats()779     protected void resetBinderStats() throws Exception {
780         getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
781     }
782 
disableBinderStats()783     protected void disableBinderStats() throws Exception {
784         getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
785     }
786 
binderStatsNoSampling()787     protected void binderStatsNoSampling() throws Exception {
788         getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
789     }
790 
setUpLooperStats()791     protected void setUpLooperStats() throws Exception {
792         getDevice().executeShellCommand("cmd looper_stats enable");
793         getDevice().executeShellCommand("cmd looper_stats sampling_interval 1");
794         getDevice().executeShellCommand("cmd looper_stats reset");
795     }
796 
cleanUpLooperStats()797     protected void cleanUpLooperStats() throws Exception {
798         getDevice().executeShellCommand("cmd looper_stats disable");
799     }
800 
setAppBreadcrumbPredicate()801     public void setAppBreadcrumbPredicate() throws Exception {
802         doAppBreadcrumbReportedStart(1);
803     }
804 
clearAppBreadcrumbPredicate()805     public void clearAppBreadcrumbPredicate() throws Exception {
806         doAppBreadcrumbReportedStart(2);
807     }
808 
doAppBreadcrumbReportedStart(int label)809     public void doAppBreadcrumbReportedStart(int label) throws Exception {
810         doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
811     }
812 
doAppBreadcrumbReportedStop(int label)813     public void doAppBreadcrumbReportedStop(int label) throws Exception {
814         doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
815     }
816 
doAppBreadcrumbReported(int label)817     public void doAppBreadcrumbReported(int label) throws Exception {
818         doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
819     }
820 
doAppBreadcrumbReported(int label, int state)821     public void doAppBreadcrumbReported(int label, int state) throws Exception {
822         getDevice().executeShellCommand(String.format(
823                 "cmd stats log-app-breadcrumb %d %d", label, state));
824     }
825 
setBatteryLevel(int level)826     protected void setBatteryLevel(int level) throws Exception {
827         getDevice().executeShellCommand("cmd battery set level " + level);
828     }
829 
resetBatteryStatus()830     protected void resetBatteryStatus() throws Exception {
831         getDevice().executeShellCommand("cmd battery reset");
832     }
833 
getScreenBrightness()834     protected int getScreenBrightness() throws Exception {
835         return Integer.parseInt(
836                 getDevice().executeShellCommand("settings get system screen_brightness").trim());
837     }
838 
setScreenBrightness(int brightness)839     protected void setScreenBrightness(int brightness) throws Exception {
840         getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
841     }
842 
843     // Gets whether "Always on Display" setting is enabled.
844     // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE.
getAodState()845     protected String getAodState() throws Exception {
846         return getDevice().executeShellCommand("settings get secure doze_always_on");
847     }
848 
setAodState(String state)849     protected void setAodState(String state) throws Exception {
850         getDevice().executeShellCommand("settings put secure doze_always_on " + state);
851     }
852 
isScreenBrightnessModeManual()853     protected boolean isScreenBrightnessModeManual() throws Exception {
854         String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
855         return Integer.parseInt(mode.trim()) == 0;
856     }
857 
setScreenBrightnessMode(boolean manual)858     protected void setScreenBrightnessMode(boolean manual) throws Exception {
859         getDevice().executeShellCommand(
860                 "settings put system screen_brightness_mode " + (manual ? 0 : 1));
861     }
862 
enterDozeModeLight()863     protected void enterDozeModeLight() throws Exception {
864         getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
865     }
866 
enterDozeModeDeep()867     protected void enterDozeModeDeep() throws Exception {
868         getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
869     }
870 
leaveDozeMode()871     protected void leaveDozeMode() throws Exception {
872         getDevice().executeShellCommand("dumpsys deviceidle unforce");
873         getDevice().executeShellCommand("dumpsys deviceidle disable");
874         getDevice().executeShellCommand("dumpsys deviceidle enable");
875     }
876 
turnBatterySaverOn()877     protected void turnBatterySaverOn() throws Exception {
878         unplugDevice();
879         getDevice().executeShellCommand("settings put global low_power 1");
880     }
881 
turnBatterySaverOff()882     protected void turnBatterySaverOff() throws Exception {
883         getDevice().executeShellCommand("settings put global low_power 0");
884         getDevice().executeShellCommand("cmd battery reset");
885     }
886 
rebootDevice()887     protected void rebootDevice() throws Exception {
888         getDevice().rebootUntilOnline();
889     }
890 
891     /**
892      * Asserts that the two events are within the specified range of each other.
893      *
894      * @param d0        the event that should occur first
895      * @param d1        the event that should occur second
896      * @param minDiffMs d0 should precede d1 by at least this amount
897      * @param maxDiffMs d0 should precede d1 by at most this amount
898      */
assertTimeDiffBetween(EventMetricData d0, EventMetricData d1, int minDiffMs, int maxDiffMs)899     public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
900             int minDiffMs, int maxDiffMs) {
901         long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
902         assertTrue("Illegal time difference (" + diffMs + "ms)", minDiffMs <= diffMs);
903         assertTrue("Illegal time difference (" + diffMs + "ms)", diffMs <= maxDiffMs);
904     }
905 
getCurrentLogcatDate()906     protected String getCurrentLogcatDate() throws Exception {
907         // TODO: Do something more robust than this for getting logcat markers.
908         long timestampMs = getDevice().getDeviceDate();
909         return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
910                 .format(new Date(timestampMs));
911     }
912 
getLogcatSince(String date, String logcatParams)913     protected String getLogcatSince(String date, String logcatParams) throws Exception {
914         return getDevice().executeShellCommand(String.format(
915                 "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
916     }
917 
918     /**
919      * Pulled atoms should have a better way of constructing the config.
920      * Remove this config when that happens.
921      */
getPulledConfig()922     protected StatsdConfig.Builder getPulledConfig() {
923         return StatsdConfig.newBuilder().setId(CONFIG_ID)
924                 .addAllowedLogSource("AID_SYSTEM")
925                 .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE);
926     }
927 
928     /**
929      * Determines if the device has the given feature.
930      * Prints a warning if its value differs from requiredAnswer.
931      */
hasFeature(String featureName, boolean requiredAnswer)932     protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
933         final String features = getDevice().executeShellCommand("pm list features");
934         boolean hasIt = features.contains(featureName);
935         if (hasIt != requiredAnswer) {
936             LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
937                     + featureName);
938         }
939         return hasIt == requiredAnswer;
940     }
941 
942     /**
943      * Determines if the device has |file|.
944      */
doesFileExist(String file)945     protected boolean doesFileExist(String file) throws Exception {
946         return getDevice().doesFileExist(file);
947     }
948 
turnOnAirplaneMode()949     protected void turnOnAirplaneMode() throws Exception {
950         getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
951     }
952 
turnOffAirplaneMode()953     protected void turnOffAirplaneMode() throws Exception {
954         getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
955     }
956 }
957