• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 import static com.google.common.truth.Truth.assertThat;
21 import static com.google.common.truth.Truth.assertWithMessage;
22 
23 import android.os.BatteryStatsProto;
24 import android.os.StatsDataDumpProto;
25 import android.service.battery.BatteryServiceDumpProto;
26 import android.service.batterystats.BatteryStatsServiceDumpProto;
27 import android.service.procstats.ProcessStatsServiceDumpProto;
28 
29 import com.android.annotations.Nullable;
30 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
31 import com.android.internal.os.StatsdConfigProto.EventMetric;
32 import com.android.internal.os.StatsdConfigProto.FieldFilter;
33 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
34 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
35 import com.android.internal.os.StatsdConfigProto.GaugeMetric;
36 import com.android.internal.os.StatsdConfigProto.Predicate;
37 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
38 import com.android.internal.os.StatsdConfigProto.SimplePredicate;
39 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
40 import com.android.internal.os.StatsdConfigProto.TimeUnit;
41 import com.android.os.AtomsProto.AppBreadcrumbReported;
42 import com.android.os.AtomsProto.Atom;
43 import com.android.os.AtomsProto.ProcessStatsPackageProto;
44 import com.android.os.AtomsProto.ProcessStatsProto;
45 import com.android.os.AtomsProto.ProcessStatsStateProto;
46 import com.android.os.StatsLog;
47 import com.android.os.StatsLog.ConfigMetricsReport;
48 import com.android.os.StatsLog.ConfigMetricsReportList;
49 import com.android.os.StatsLog.CountMetricData;
50 import com.android.os.StatsLog.DurationMetricData;
51 import com.android.os.StatsLog.EventMetricData;
52 import com.android.os.StatsLog.GaugeBucketInfo;
53 import com.android.os.StatsLog.GaugeMetricData;
54 import com.android.os.StatsLog.StatsLogReport;
55 import com.android.os.StatsLog.StatsLogReport.GaugeMetricDataWrapper;
56 import com.android.os.StatsLog.ValueMetricData;
57 import com.android.tradefed.device.DeviceNotAvailableException;
58 import com.android.tradefed.log.LogUtil;
59 import com.android.tradefed.util.CommandResult;
60 import com.android.tradefed.util.CommandStatus;
61 import com.android.tradefed.util.Pair;
62 
63 import com.google.common.collect.Range;
64 import com.google.common.io.Files;
65 import com.google.protobuf.ByteString;
66 import java.io.File;
67 import java.text.SimpleDateFormat;
68 import java.util.ArrayList;
69 import java.util.Arrays;
70 import java.util.Collections;
71 import java.util.Comparator;
72 import java.util.Date;
73 import java.util.HashMap;
74 import java.util.LinkedList;
75 import java.util.List;
76 import java.util.Map;
77 import java.util.Queue;
78 import java.util.Random;
79 import java.util.Set;
80 import java.util.StringTokenizer;
81 import java.util.function.Function;
82 import java.util.regex.Matcher;
83 import java.util.regex.Pattern;
84 import java.util.stream.Collectors;
85 import perfetto.protos.PerfettoConfig.DataSourceConfig;
86 import perfetto.protos.PerfettoConfig.FtraceConfig;
87 import perfetto.protos.PerfettoConfig.TraceConfig;
88 
89 /**
90  * Base class for testing Statsd atoms.
91  * Validates reporting of statsd logging based on different events
92  */
93 public class AtomTestCase extends BaseTestCase {
94 
95     /**
96      * Run tests that are optional; they are not valid CTS tests per se, since not all devices can
97      * be expected to pass them, but can be run, if desired, to ensure they work when appropriate.
98      */
99     public static final boolean OPTIONAL_TESTS_ENABLED = false;
100 
101     public static final String UPDATE_CONFIG_CMD = "cmd stats config update";
102     public static final String DUMP_REPORT_CMD = "cmd stats dump-report";
103     public static final String DUMP_BATTERY_CMD = "dumpsys battery";
104     public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats";
105     public static final String DUMPSYS_STATS_CMD = "dumpsys stats";
106     public static final String DUMP_PROCSTATS_CMD = "dumpsys procstats";
107     public static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
108     /** ID of the config, which evaluates to -1572883457. */
109     public static final long CONFIG_ID = "cts_config".hashCode();
110 
111     public static final String FEATURE_AUDIO_OUTPUT = "android.hardware.audio.output";
112     public static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
113     public static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
114     public static final String FEATURE_BLUETOOTH_LE = "android.hardware.bluetooth_le";
115     public static final String FEATURE_CAMERA = "android.hardware.camera";
116     public static final String FEATURE_CAMERA_FLASH = "android.hardware.camera.flash";
117     public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front";
118     public static final String FEATURE_LEANBACK_ONLY = "android.software.leanback_only";
119     public static final String FEATURE_LOCATION_GPS = "android.hardware.location.gps";
120     public static final String FEATURE_PC = "android.hardware.type.pc";
121     public static final String FEATURE_PICTURE_IN_PICTURE = "android.software.picture_in_picture";
122     public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
123     public static final String FEATURE_WATCH = "android.hardware.type.watch";
124     public static final String FEATURE_WIFI = "android.hardware.wifi";
125     public static final String FEATURE_INCREMENTAL_DELIVERY =
126             "android.software.incremental_delivery";
127 
128     public static final int SHELL_UID = 2000;
129 
130     // Telephony phone types
131     public static final int PHONE_TYPE_GSM = 1;
132     public static final int PHONE_TYPE_CDMA = 2;
133     public static final int PHONE_TYPE_CDMA_LTE = 6;
134 
135     protected static final int WAIT_TIME_SHORT = 500;
136     protected static final int WAIT_TIME_LONG = 2_000;
137 
138     protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000;
139     protected static final long SCREEN_STATE_POLLING_INTERVAL = 500;
140 
141     protected static final long NS_PER_SEC = (long) 1E+9;
142 
143     @Override
setUp()144     protected void setUp() throws Exception {
145         super.setUp();
146 
147         // Uninstall to clear the history in case it's still on the device.
148         removeConfig(CONFIG_ID);
149         getReportList(); // Clears data.
150     }
151 
152     @Override
tearDown()153     protected void tearDown() throws Exception {
154         removeConfig(CONFIG_ID);
155         getDevice().uninstallPackage(DEVICE_SIDE_TEST_PACKAGE);
156         super.tearDown();
157     }
158 
159     /**
160      * Determines whether logcat indicates that incidentd fired since the given device date.
161      */
didIncidentdFireSince(String date)162     protected boolean didIncidentdFireSince(String date) throws Exception {
163         final String INCIDENTD_TAG = "incidentd";
164         final String INCIDENTD_STARTED_STRING = "reportIncident";
165         // TODO: Do something more robust than this in case of delayed logging.
166         Thread.sleep(1000);
167         String log = getLogcatSince(date, String.format(
168                 "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING));
169         return log.contains(INCIDENTD_STARTED_STRING);
170     }
171 
checkDeviceFor(String methodName)172     protected boolean checkDeviceFor(String methodName) throws Exception {
173         try {
174             installPackage(DEVICE_SIDE_TEST_APK, true);
175             runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".Checkers", methodName);
176             // Test passes, meaning that the answer is true.
177             LogUtil.CLog.d(methodName + "() indicates true.");
178             return true;
179         } catch (AssertionError e) {
180             // Method is designed to fail if the answer is false.
181             LogUtil.CLog.d(methodName + "() indicates false.");
182             return false;
183         }
184     }
185 
186     /**
187      * Returns a protobuf-encoded perfetto config that enables the kernel
188      * ftrace tracer with sched_switch for 10 seconds.
189      */
getPerfettoConfig()190     protected ByteString getPerfettoConfig() {
191         TraceConfig.Builder builder = TraceConfig.newBuilder();
192 
193         TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig
194             .newBuilder()
195             .setSizeKb(128)
196             .build();
197         builder.addBuffers(buffer);
198 
199         FtraceConfig ftraceConfig = FtraceConfig.newBuilder()
200             .addFtraceEvents("sched/sched_switch")
201             .build();
202         DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
203             .setName("linux.ftrace")
204             .setTargetBuffer(0)
205             .setFtraceConfig(ftraceConfig)
206             .build();
207         TraceConfig.DataSource dataSource = TraceConfig.DataSource
208             .newBuilder()
209             .setConfig(dataSourceConfig)
210             .build();
211         builder.addDataSources(dataSource);
212 
213         builder.setDurationMs(10000);
214         builder.setAllowUserBuildTracing(true);
215 
216         TraceConfig.IncidentReportConfig incident = TraceConfig.IncidentReportConfig
217             .newBuilder()
218             .setDestinationPackage("foo.bar.baz")
219             .build();
220         builder.setIncidentReportConfig(incident);
221 
222         // To avoid being hit with guardrails firing in multiple test runs back
223         // to back, we set a unique session key for each config.
224         Random random = new Random();
225         StringBuilder sessionNameBuilder = new StringBuilder("statsd-cts-");
226         sessionNameBuilder.append(random.nextInt() & Integer.MAX_VALUE);
227         builder.setUniqueSessionName(sessionNameBuilder.toString());
228 
229         return builder.build().toByteString();
230     }
231 
232     /**
233      * Resets the state of the Perfetto guardrails. This avoids that the test fails if it's
234      * run too close of for too many times and hits the upload limit.
235      */
resetPerfettoGuardrails()236     protected void resetPerfettoGuardrails() throws Exception {
237         final String cmd = "perfetto --reset-guardrails";
238         CommandResult cr = getDevice().executeShellV2Command(cmd);
239         if (cr.getStatus() != CommandStatus.SUCCESS)
240             throw new Exception(String.format("Error while executing %s: %s %s", cmd, cr.getStdout(), cr.getStderr()));
241     }
242 
probe(String path)243     private String probe(String path) throws Exception {
244         return getDevice().executeShellCommand("if [ -e " + path + " ] ; then"
245                 + " cat " + path + " ; else echo -1 ; fi");
246     }
247 
enableSystemTracing()248     protected void enableSystemTracing() throws Exception {
249         getDevice().executeShellCommand("setprop persist.traced.enable 1");
250     }
251 
disableSystemTracing()252     protected void disableSystemTracing() throws Exception {
253         getDevice().executeShellCommand("setprop persist.traced.enable 0");
254     }
255 
256     /**
257      * Determines whether perfetto enabled the kernel ftrace tracer.
258      */
isSystemTracingEnabled()259     protected boolean isSystemTracingEnabled() throws Exception {
260         final String traceFsPath = "/sys/kernel/tracing/tracing_on";
261         String tracing_on = probe(traceFsPath);
262         if (tracing_on.startsWith("0")) return false;
263         if (tracing_on.startsWith("1")) return true;
264 
265         // fallback to debugfs
266         LogUtil.CLog.d("Unexpected state for %s = %s. Falling back to debugfs", traceFsPath,
267                 tracing_on);
268 
269         final String debugFsPath = "/sys/kernel/debug/tracing/tracing_on";
270         tracing_on = probe(debugFsPath);
271         if (tracing_on.startsWith("0")) return false;
272         if (tracing_on.startsWith("1")) return true;
273         throw new Exception(String.format("Unexpected state for %s = %s", traceFsPath, tracing_on));
274     }
275 
createConfigBuilder()276     protected static StatsdConfig.Builder createConfigBuilder() {
277       return StatsdConfig.newBuilder()
278           .setId(CONFIG_ID)
279           .addAllowedLogSource("AID_SYSTEM")
280           .addAllowedLogSource("AID_BLUETOOTH")
281           // TODO(b/134091167): Fix bluetooth source name issue in Auto platform.
282           .addAllowedLogSource("com.android.bluetooth")
283           .addAllowedLogSource("AID_LMKD")
284           .addAllowedLogSource("AID_RADIO")
285           .addAllowedLogSource("AID_ROOT")
286           .addAllowedLogSource("AID_STATSD")
287           .addAllowedLogSource("com.android.systemui")
288           .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE)
289           .addDefaultPullPackages("AID_RADIO")
290           .addDefaultPullPackages("AID_SYSTEM")
291           .addWhitelistedAtomIds(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
292     }
293 
createAndUploadConfig(int atomTag)294     protected void createAndUploadConfig(int atomTag) throws Exception {
295         StatsdConfig.Builder conf = createConfigBuilder();
296         addAtomEvent(conf, atomTag);
297         uploadConfig(conf);
298     }
299 
uploadConfig(StatsdConfig.Builder config)300     protected void uploadConfig(StatsdConfig.Builder config) throws Exception {
301         uploadConfig(config.build());
302     }
303 
uploadConfig(StatsdConfig config)304     protected void uploadConfig(StatsdConfig config) throws Exception {
305         LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
306         File configFile = File.createTempFile("statsdconfig", ".config");
307         try {
308             Files.write(config.toByteArray(), configFile);
309             String remotePath = "/data/local/tmp/" + configFile.getName();
310             getDevice().pushFile(configFile, remotePath);
311             getDevice().executeShellCommand(String.join(" ", "cat", remotePath, "|",
312                     UPDATE_CONFIG_CMD, String.valueOf(SHELL_UID), String.valueOf(CONFIG_ID)));
313             getDevice().executeShellCommand("rm " + remotePath);
314         } finally {
315             configFile.delete();
316         }
317     }
318 
removeConfig(long configId)319     protected void removeConfig(long configId) throws Exception {
320         getDevice().executeShellCommand(
321                 String.join(" ", REMOVE_CONFIG_CMD,
322                         String.valueOf(SHELL_UID), String.valueOf(configId)));
323     }
324 
325     /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */
getEventMetricDataList()326     protected List<EventMetricData> getEventMetricDataList() throws Exception {
327         ConfigMetricsReportList reportList = getReportList();
328         return getEventMetricDataList(reportList);
329     }
330 
331     /**
332      *  Gets a List of sorted ConfigMetricsReports from ConfigMetricsReportList.
333      */
getSortedConfigMetricsReports( ConfigMetricsReportList configMetricsReportList)334     protected List<ConfigMetricsReport> getSortedConfigMetricsReports(
335             ConfigMetricsReportList configMetricsReportList) {
336         return configMetricsReportList.getReportsList().stream()
337                 .sorted(Comparator.comparing(ConfigMetricsReport::getCurrentReportWallClockNanos))
338                 .collect(Collectors.toList());
339     }
340 
341     /**
342      * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must
343      * contain a single report).
344      */
getEventMetricDataList(ConfigMetricsReportList reportList)345     protected List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList)
346             throws Exception {
347         assertThat(reportList.getReportsCount()).isEqualTo(1);
348         ConfigMetricsReport report = reportList.getReports(0);
349 
350         List<EventMetricData> data = new ArrayList<>();
351         for (StatsLogReport metric : report.getMetricsList()) {
352           for (EventMetricData metricData :
353                metric.getEventMetrics().getDataList()) {
354             if (metricData.hasAtom()) {
355               data.add(metricData);
356             } else {
357               data.addAll(backfillAggregatedAtomsInEventMetric(metricData));
358             }
359           }
360         }
361         data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
362 
363         LogUtil.CLog.d("Get EventMetricDataList as following:\n");
364         for (EventMetricData d : data) {
365             LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString());
366         }
367         return data;
368     }
369 
getGaugeMetricDataList()370     protected List<Atom> getGaugeMetricDataList() throws Exception {
371         return getGaugeMetricDataList(/*checkTimestampTruncated=*/false);
372     }
373 
getGaugeMetricDataList(boolean checkTimestampTruncated)374     protected List<Atom> getGaugeMetricDataList(boolean checkTimestampTruncated) throws Exception {
375         ConfigMetricsReportList reportList = getReportList();
376         assertThat(reportList.getReportsCount()).isEqualTo(1);
377 
378         // only config
379         ConfigMetricsReport report = reportList.getReports(0);
380         assertThat(report.getMetricsCount()).isEqualTo(1);
381 
382         List<Atom> data = new ArrayList<>();
383         for (GaugeMetricData gaugeMetricData :
384                 report.getMetrics(0).getGaugeMetrics().getDataList()) {
385             assertThat(gaugeMetricData.getBucketInfoCount()).isEqualTo(1);
386             GaugeBucketInfo bucketInfo = gaugeMetricData.getBucketInfo(0);
387             if (bucketInfo.getAtomCount() != 0) {
388                 for (Atom atom : bucketInfo.getAtomList()) {
389                     data.add(atom);
390                 }
391             } else {
392                 backFillGaugeBucketAtoms(bucketInfo.getAggregatedAtomInfoList());
393             }
394             if (checkTimestampTruncated) {
395                 for (long timestampNs : bucketInfo.getElapsedTimestampNanosList()) {
396                     assertTimestampIsTruncated(timestampNs);
397                 }
398             }
399         }
400 
401         LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
402         for (Atom d : data) {
403             LogUtil.CLog.d("Atom:\n" + d.toString());
404         }
405         return data;
406     }
407 
backFillGaugeBucketAtoms( List<StatsLog.AggregatedAtomInfo> atomInfoList)408     private List<Atom> backFillGaugeBucketAtoms(
409             List<StatsLog.AggregatedAtomInfo> atomInfoList) {
410         List<Pair<Atom, Long>> atomTimestamp = new ArrayList<>();
411         for (StatsLog.AggregatedAtomInfo atomInfo : atomInfoList) {
412             for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) {
413                 atomTimestamp.add(Pair.create(atomInfo.getAtom(), timestampNs));
414             }
415         }
416         atomTimestamp.sort(Comparator.comparing(o -> o.second));
417         return atomTimestamp.stream().map(p -> p.first).collect(Collectors.toList());
418     }
419 
backfillGaugeMetricData(GaugeMetricDataWrapper dataWrapper)420     protected GaugeMetricDataWrapper backfillGaugeMetricData(GaugeMetricDataWrapper dataWrapper) {
421         GaugeMetricDataWrapper.Builder dataWrapperBuilder = dataWrapper.toBuilder();
422         List<GaugeMetricData> backfilledMetricData = new ArrayList<>();
423         for (GaugeMetricData gaugeMetricData : dataWrapperBuilder.getDataList()) {
424             GaugeMetricData.Builder gaugeMetricDataBuilder = gaugeMetricData.toBuilder();
425             List<GaugeBucketInfo> backfilledBuckets = new ArrayList<>();
426             for (GaugeBucketInfo bucketInfo : gaugeMetricData.getBucketInfoList()) {
427                 backfilledBuckets.add(backfillGaugeBucket(bucketInfo.toBuilder()));
428             }
429             gaugeMetricDataBuilder.clearBucketInfo();
430             gaugeMetricDataBuilder.addAllBucketInfo(backfilledBuckets);
431             backfilledMetricData.add(gaugeMetricDataBuilder.build());
432         }
433         dataWrapperBuilder.clearData();
434         dataWrapperBuilder.addAllData(backfilledMetricData);
435         return dataWrapperBuilder.build();
436     }
437 
backfillGaugeBucket(GaugeBucketInfo.Builder bucketInfoBuilder)438     private GaugeBucketInfo backfillGaugeBucket(GaugeBucketInfo.Builder bucketInfoBuilder) {
439         if (bucketInfoBuilder.getAtomCount() != 0) {
440             return bucketInfoBuilder.build();
441         }
442         List<Pair<Atom, Long>> atomTimestampData = new ArrayList<>();
443         for (StatsLog.AggregatedAtomInfo atomInfo : bucketInfoBuilder.getAggregatedAtomInfoList()) {
444             for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) {
445                 atomTimestampData.add(Pair.create(atomInfo.getAtom(), timestampNs));
446             }
447         }
448         atomTimestampData.sort(Comparator.comparing(o -> o.second));
449         bucketInfoBuilder.clearAggregatedAtomInfo();
450         for (Pair<Atom, Long> atomTimestamp : atomTimestampData) {
451             bucketInfoBuilder.addAtom(atomTimestamp.first);
452             bucketInfoBuilder.addElapsedTimestampNanos(atomTimestamp.second);
453         }
454         return bucketInfoBuilder.build();
455     }
456 
457     /**
458      * Gets the statsd report and extract duration metric data.
459      * Note that this also deletes that report from statsd.
460      */
getDurationMetricDataList()461     protected List<DurationMetricData> getDurationMetricDataList() throws Exception {
462         ConfigMetricsReportList reportList = getReportList();
463         assertThat(reportList.getReportsCount()).isEqualTo(1);
464         ConfigMetricsReport report = reportList.getReports(0);
465 
466         List<DurationMetricData> data = new ArrayList<>();
467         for (StatsLogReport metric : report.getMetricsList()) {
468             data.addAll(metric.getDurationMetrics().getDataList());
469         }
470 
471         LogUtil.CLog.d("Got DurationMetricDataList as following:\n");
472         for (DurationMetricData d : data) {
473             LogUtil.CLog.d("Duration " + d);
474         }
475         return data;
476     }
477 
478     /**
479      * Gets the statsd report and extract count metric data.
480      * Note that this also deletes that report from statsd.
481      */
getCountMetricDataList()482     protected List<CountMetricData> getCountMetricDataList() throws Exception {
483         ConfigMetricsReportList reportList = getReportList();
484         assertThat(reportList.getReportsCount()).isEqualTo(1);
485         ConfigMetricsReport report = reportList.getReports(0);
486 
487         List<CountMetricData> data = new ArrayList<>();
488         for (StatsLogReport metric : report.getMetricsList()) {
489             data.addAll(metric.getCountMetrics().getDataList());
490         }
491 
492         LogUtil.CLog.d("Got CountMetricDataList as following:\n");
493         for (CountMetricData d : data) {
494             LogUtil.CLog.d("Count " + d);
495         }
496         return data;
497     }
498 
499     /**
500      * Gets the statsd report and extract value metric data.
501      * Note that this also deletes that report from statsd.
502      */
getValueMetricDataList()503     protected List<ValueMetricData> getValueMetricDataList() throws Exception {
504         ConfigMetricsReportList reportList = getReportList();
505         assertThat(reportList.getReportsCount()).isEqualTo(1);
506         ConfigMetricsReport report = reportList.getReports(0);
507 
508         List<ValueMetricData> data = new ArrayList<>();
509         for (StatsLogReport metric : report.getMetricsList()) {
510             data.addAll(metric.getValueMetrics().getDataList());
511         }
512 
513         LogUtil.CLog.d("Got ValueMetricDataList as following:\n");
514         for (ValueMetricData d : data) {
515             LogUtil.CLog.d("Value " + d);
516         }
517         return data;
518     }
519 
getStatsLogReport()520     protected StatsLogReport getStatsLogReport() throws Exception {
521         ConfigMetricsReport report = getConfigMetricsReport();
522         assertThat(report.hasUidMap()).isTrue();
523         assertThat(report.getMetricsCount()).isEqualTo(1);
524         return report.getMetrics(0);
525     }
526 
getConfigMetricsReport()527     protected ConfigMetricsReport getConfigMetricsReport() throws Exception {
528         ConfigMetricsReportList reportList = getReportList();
529         assertThat(reportList.getReportsCount()).isEqualTo(1);
530         return reportList.getReports(0);
531     }
532 
533     /** Gets the statsd report. Note that this also deletes that report from statsd. */
getReportList()534     protected ConfigMetricsReportList getReportList() throws Exception {
535         try {
536             ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
537                     String.join(" ", DUMP_REPORT_CMD, String.valueOf(SHELL_UID),
538                             String.valueOf(CONFIG_ID), "--include_current_bucket", "--proto"));
539             return reportList;
540         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
541             LogUtil.CLog.e("Failed to fetch and parse the statsd output report. "
542                     + "Perhaps there is not a valid statsd config for the requested "
543                     + "uid=" + getHostUid() + ", id=" + CONFIG_ID + ".");
544             throw (e);
545         }
546     }
547 
getBatteryStatsProto()548     protected BatteryStatsProto getBatteryStatsProto() throws Exception {
549         try {
550             BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(),
551                     String.join(" ", DUMP_BATTERYSTATS_CMD,
552                             "--proto")).getBatterystats();
553             LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString());
554             return batteryStatsProto;
555         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
556             LogUtil.CLog.e("Failed to dump batterystats proto");
557             throw (e);
558         }
559     }
560 
561     /** Gets reports from the statsd data incident section from the stats dumpsys. */
getReportsFromStatsDataDumpProto()562     protected List<ConfigMetricsReportList> getReportsFromStatsDataDumpProto() throws Exception {
563         try {
564             StatsDataDumpProto statsProto = getDump(StatsDataDumpProto.parser(),
565                     String.join(" ", DUMPSYS_STATS_CMD, "--proto"));
566             // statsProto holds repeated bytes, which we must parse into ConfigMetricsReportLists.
567             List<ConfigMetricsReportList> reports
568                     = new ArrayList<>(statsProto.getConfigMetricsReportListCount());
569             for (ByteString reportListBytes : statsProto.getConfigMetricsReportListList()) {
570                 reports.add(ConfigMetricsReportList.parseFrom(reportListBytes));
571             }
572             LogUtil.CLog.d("Got dumpsys stats output:\n " + reports.toString());
573             return reports;
574         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
575             LogUtil.CLog.e("Failed to dumpsys stats proto");
576             throw (e);
577         }
578     }
579 
getProcStatsProto()580     protected List<ProcessStatsProto> getProcStatsProto() throws Exception {
581         try {
582 
583             List<ProcessStatsProto> processStatsProtoList =
584                 new ArrayList<ProcessStatsProto>();
585             android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
586                     ProcessStatsServiceDumpProto.parser(),
587                     String.join(" ", DUMP_PROCSTATS_CMD,
588                             "--proto")).getProcstatsNow();
589             for (android.service.procstats.ProcessStatsProto stats :
590                     sectionProto.getProcessStatsList()) {
591                 ProcessStatsProto procStats = ProcessStatsProto.parser().parseFrom(
592                     stats.toByteArray());
593                 processStatsProtoList.add(procStats);
594             }
595             LogUtil.CLog.d("Got procstats:\n ");
596             for (ProcessStatsProto processStatsProto : processStatsProtoList) {
597                 LogUtil.CLog.d(processStatsProto.toString());
598             }
599             return processStatsProtoList;
600         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
601             LogUtil.CLog.e("Failed to dump procstats proto");
602             throw (e);
603         }
604     }
605 
606     /*
607      * Get all procstats package data in proto
608      */
getAllProcStatsProto()609     protected List<ProcessStatsPackageProto> getAllProcStatsProto() throws Exception {
610         try {
611             android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
612                     ProcessStatsServiceDumpProto.parser(),
613                     String.join(" ", DUMP_PROCSTATS_CMD,
614                             "--proto")).getProcstatsOver24Hrs();
615             List<ProcessStatsPackageProto> processStatsProtoList =
616                 new ArrayList<ProcessStatsPackageProto>();
617             for (android.service.procstats.ProcessStatsPackageProto pkgStast :
618                 sectionProto.getPackageStatsList()) {
619               ProcessStatsPackageProto pkgAtom =
620                   ProcessStatsPackageProto.parser().parseFrom(pkgStast.toByteArray());
621                 processStatsProtoList.add(pkgAtom);
622             }
623             LogUtil.CLog.d("Got procstats:\n ");
624             for (ProcessStatsPackageProto processStatsProto : processStatsProtoList) {
625                 LogUtil.CLog.d(processStatsProto.toString());
626             }
627             return processStatsProtoList;
628         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
629             LogUtil.CLog.e("Failed to dump procstats proto");
630             throw (e);
631         }
632     }
633 
634     /*
635      * Get all processes' procstats statsd data in proto
636      */
getAllProcStatsProtoForStatsd()637     protected List<android.service.procstats.ProcessStatsProto> getAllProcStatsProtoForStatsd()
638             throws Exception {
639         try {
640             android.service.procstats.ProcessStatsSectionProto sectionProto = getDump(
641                     android.service.procstats.ProcessStatsSectionProto.parser(),
642                     String.join(" ", DUMP_PROCSTATS_CMD,
643                             "--statsd"));
644             List<android.service.procstats.ProcessStatsProto> processStatsProtoList
645                     = sectionProto.getProcessStatsList();
646             LogUtil.CLog.d("Got procstats:\n ");
647             for (android.service.procstats.ProcessStatsProto processStatsProto
648                     : processStatsProtoList) {
649                 LogUtil.CLog.d(processStatsProto.toString());
650             }
651             return processStatsProtoList;
652         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
653             LogUtil.CLog.e("Failed to dump procstats proto");
654             throw (e);
655         }
656     }
657 
hasBattery()658     protected boolean hasBattery() throws Exception {
659         try {
660             BatteryServiceDumpProto batteryProto = getDump(BatteryServiceDumpProto.parser(),
661                     String.join(" ", DUMP_BATTERY_CMD, "--proto"));
662             LogUtil.CLog.d("Got battery service dump:\n " + batteryProto.toString());
663             return batteryProto.getIsPresent();
664         } catch (com.google.protobuf.InvalidProtocolBufferException e) {
665             LogUtil.CLog.e("Failed to dump batteryservice proto");
666             throw (e);
667         }
668     }
669 
670     /** Creates a FieldValueMatcher.Builder corresponding to the given field. */
createFvm(int field)671     protected static FieldValueMatcher.Builder createFvm(int field) {
672         return FieldValueMatcher.newBuilder().setField(field);
673     }
674 
addAtomEvent(StatsdConfig.Builder conf, int atomTag)675     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception {
676         addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>());
677     }
678 
679     /**
680      * Adds an event to the config for an atom that matches the given key.
681      *
682      * @param conf    configuration
683      * @param atomTag atom tag (from atoms.proto)
684      * @param fvm     FieldValueMatcher.Builder for the relevant key
685      */
addAtomEvent(StatsdConfig.Builder conf, int atomTag, FieldValueMatcher.Builder fvm)686     protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag,
687             FieldValueMatcher.Builder fvm)
688             throws Exception {
689         addAtomEvent(conf, atomTag, Arrays.asList(fvm));
690     }
691 
692     /**
693      * Adds an event to the config for an atom that matches the given keys.
694      *
695      * @param conf   configuration
696      * @param atomId atom tag (from atoms.proto)
697      * @param fvms   list of FieldValueMatcher.Builders to attach to the atom. May be null.
698      */
addAtomEvent(StatsdConfig.Builder conf, int atomId, List<FieldValueMatcher.Builder> fvms)699     protected void addAtomEvent(StatsdConfig.Builder conf, int atomId,
700             List<FieldValueMatcher.Builder> fvms) throws Exception {
701 
702         final String atomName = "Atom" + System.nanoTime();
703         final String eventName = "Event" + System.nanoTime();
704 
705         SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
706         if (fvms != null) {
707             for (FieldValueMatcher.Builder fvm : fvms) {
708                 sam.addFieldValueMatcher(fvm);
709             }
710         }
711         conf.addAtomMatcher(AtomMatcher.newBuilder()
712                 .setId(atomName.hashCode())
713                 .setSimpleAtomMatcher(sam));
714         conf.addEventMetric(EventMetric.newBuilder()
715                 .setId(eventName.hashCode())
716                 .setWhat(atomName.hashCode()));
717     }
718 
719     /**
720      * Adds an atom to a gauge metric of a config
721      *
722      * @param conf        configuration
723      * @param atomId      atom id (from atoms.proto)
724      * @param gaugeMetric the gauge metric to add
725      */
addGaugeAtom(StatsdConfig.Builder conf, int atomId, GaugeMetric.Builder gaugeMetric)726     protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId,
727             GaugeMetric.Builder gaugeMetric) throws Exception {
728         final String atomName = "Atom" + System.nanoTime();
729         final String gaugeName = "Gauge" + System.nanoTime();
730         final String predicateName = "APP_BREADCRUMB";
731         SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId);
732         conf.addAtomMatcher(AtomMatcher.newBuilder()
733                 .setId(atomName.hashCode())
734                 .setSimpleAtomMatcher(sam));
735         final String predicateTrueName = "APP_BREADCRUMB_1";
736         final String predicateFalseName = "APP_BREADCRUMB_2";
737         conf.addAtomMatcher(AtomMatcher.newBuilder()
738                 .setId(predicateTrueName.hashCode())
739                 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
740                         .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
741                         .addFieldValueMatcher(FieldValueMatcher.newBuilder()
742                                 .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
743                                 .setEqInt(1)
744                         )
745                 )
746         )
747                 // Used to trigger predicate
748                 .addAtomMatcher(AtomMatcher.newBuilder()
749                         .setId(predicateFalseName.hashCode())
750                         .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder()
751                                 .setAtomId(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
752                                 .addFieldValueMatcher(FieldValueMatcher.newBuilder()
753                                         .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER)
754                                         .setEqInt(2)
755                                 )
756                         )
757                 );
758         conf.addPredicate(Predicate.newBuilder()
759                 .setId(predicateName.hashCode())
760                 .setSimplePredicate(SimplePredicate.newBuilder()
761                         .setStart(predicateTrueName.hashCode())
762                         .setStop(predicateFalseName.hashCode())
763                         .setCountNesting(false)
764                 )
765         );
766         gaugeMetric
767                 .setId(gaugeName.hashCode())
768                 .setWhat(atomName.hashCode())
769                 .setCondition(predicateName.hashCode());
770         conf.addGaugeMetric(gaugeMetric.build());
771     }
772 
773     /**
774      * Adds an atom to a gauge metric of a config
775      *
776      * @param conf      configuration
777      * @param atomId    atom id (from atoms.proto)
778      * @param dimension dimension is needed for most pulled atoms
779      */
addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId, @Nullable FieldMatcher.Builder dimension)780     protected void addGaugeAtomWithDimensions(StatsdConfig.Builder conf, int atomId,
781             @Nullable FieldMatcher.Builder dimension) throws Exception {
782         GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder()
783                 .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build())
784                 .setSamplingType(GaugeMetric.SamplingType.CONDITION_CHANGE_TO_TRUE)
785                 .setMaxNumGaugeAtomsPerBucket(10000)
786                 .setBucket(TimeUnit.CTS);
787         if (dimension != null) {
788             gaugeMetric.setDimensionsInWhat(dimension.build());
789         }
790         addGaugeAtom(conf, atomId, gaugeMetric);
791     }
792 
793     /**
794      * Asserts that each set of states in stateSets occurs at least once in data.
795      * Asserts that the states in data occur in the same order as the sets in stateSets.
796      *
797      * @param stateSets        A list of set of states, where each set represents an equivalent
798      *                         state of the device for the purpose of CTS.
799      * @param data             list of EventMetricData from statsd, produced by
800      *                         getReportMetricListData()
801      * @param wait             expected duration (in ms) between state changes; asserts that the
802      *                         actual wait
803      *                         time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
804      *                         assertion.
805      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
806      */
assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data, int wait, Function<Atom, Integer> getStateFromAtom)807     public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data,
808             int wait, Function<Atom, Integer> getStateFromAtom) {
809         // Sometimes, there are more events than there are states.
810         // Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
811         assertWithMessage("Too few states found").that(data.size()).isAtLeast(stateSets.size());
812         int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
813         for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
814             Atom atom = data.get(dataIndex).getAtom();
815             int state = getStateFromAtom.apply(atom);
816             // If state is in the current state set, we do not assert anything.
817             // If it is not, we expect to have transitioned to the next state set.
818             if (stateSets.get(stateSetIndex).contains(state)) {
819                 // No need to assert anything. Just log it.
820                 LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
821                         + "in stateSetIndex " + stateSetIndex + ":\n"
822                         + data.get(dataIndex).getAtom().toString());
823             } else {
824                 stateSetIndex += 1;
825                 LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
826                         + " in stateSetIndex " + stateSetIndex + ":\n"
827                         + data.get(dataIndex).getAtom().toString());
828                 assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
829                 assertWithMessage("Too many states").that(stateSetIndex)
830                     .isLessThan(stateSets.size());
831                 assertWithMessage(String.format("Is in wrong state (%d)", state))
832                     .that(stateSets.get(stateSetIndex)).contains(state);
833                 if (wait > 0) {
834                     assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
835                             wait / 2, wait * 5);
836                 }
837             }
838         }
839         assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
840     }
841 
842     /**
843      * Removes all elements from data prior to the first occurrence of an element of state. After
844      * this method is called, the first element of data (if non-empty) is guaranteed to be an
845      * element in state.
846      *
847      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
848      */
popUntilFind(List<EventMetricData> data, Set<Integer> state, Function<Atom, Integer> getStateFromAtom)849     public void popUntilFind(List<EventMetricData> data, Set<Integer> state,
850             Function<Atom, Integer> getStateFromAtom) {
851         int firstStateIdx;
852         for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
853             Atom atom = data.get(firstStateIdx).getAtom();
854             if (state.contains(getStateFromAtom.apply(atom))) {
855                 break;
856             }
857         }
858         if (firstStateIdx == 0) {
859             // First first element already is in state, so there's nothing to do.
860             return;
861         }
862         data.subList(0, firstStateIdx).clear();
863     }
864 
865     /**
866      * Removes all elements from data after to the last occurrence of an element of state. After
867      * this method is called, the last element of data (if non-empty) is guaranteed to be an
868      * element in state.
869      *
870      * @param getStateFromAtom expression that takes in an Atom and returns the state it contains
871      */
popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state, Function<Atom, Integer> getStateFromAtom)872     public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state,
873         Function<Atom, Integer> getStateFromAtom) {
874         int lastStateIdx;
875         for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
876             Atom atom = data.get(lastStateIdx).getAtom();
877             if (state.contains(getStateFromAtom.apply(atom))) {
878                 break;
879             }
880         }
881         if (lastStateIdx == data.size()-1) {
882             // Last element already is in state, so there's nothing to do.
883             return;
884         }
885         data.subList(lastStateIdx+1, data.size()).clear();
886     }
887 
888     /** Returns the UID of the host, which should always either be SHELL (2000). */
getHostUid()889     protected int getHostUid() throws DeviceNotAvailableException {
890         return SHELL_UID;
891     }
892 
getProperty(String prop)893     protected String getProperty(String prop) throws Exception {
894         return getDevice().executeShellCommand("getprop " + prop).replace("\n", "");
895     }
896 
turnScreenOn()897     protected void turnScreenOn() throws Exception {
898         getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
899         getDevice().executeShellCommand("wm dismiss-keyguard");
900     }
901 
turnScreenOff()902     protected void turnScreenOff() throws Exception {
903         getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP");
904     }
905 
setChargingState(int state)906     protected void setChargingState(int state) throws Exception {
907         getDevice().executeShellCommand("cmd battery set status " + state);
908     }
909 
unplugDevice()910     protected void unplugDevice() throws Exception {
911         // On batteryless devices on Android P or above, the 'unplug' command
912         // alone does not simulate the really unplugged state.
913         //
914         // This is because charging state is left as "unknown". Unless a valid
915         // state like 3 = BatteryManager.BATTERY_STATUS_DISCHARGING is set,
916         // framework does not consider the device as running on battery.
917         setChargingState(3);
918 
919         getDevice().executeShellCommand("cmd battery unplug");
920     }
921 
plugInAc()922     protected void plugInAc() throws Exception {
923         getDevice().executeShellCommand("cmd battery set ac 1");
924     }
925 
plugInUsb()926     protected void plugInUsb() throws Exception {
927         getDevice().executeShellCommand("cmd battery set usb 1");
928     }
929 
plugInWireless()930     protected void plugInWireless() throws Exception {
931         getDevice().executeShellCommand("cmd battery set wireless 1");
932     }
933 
enableLooperStats()934     protected void enableLooperStats() throws Exception {
935         getDevice().executeShellCommand("cmd looper_stats enable");
936     }
937 
resetLooperStats()938     protected void resetLooperStats() throws Exception {
939         getDevice().executeShellCommand("cmd looper_stats reset");
940     }
941 
disableLooperStats()942     protected void disableLooperStats() throws Exception {
943         getDevice().executeShellCommand("cmd looper_stats disable");
944     }
945 
enableBinderStats()946     protected void enableBinderStats() throws Exception {
947         getDevice().executeShellCommand("dumpsys binder_calls_stats --enable");
948     }
949 
resetBinderStats()950     protected void resetBinderStats() throws Exception {
951         getDevice().executeShellCommand("dumpsys binder_calls_stats --reset");
952     }
953 
disableBinderStats()954     protected void disableBinderStats() throws Exception {
955         getDevice().executeShellCommand("dumpsys binder_calls_stats --disable");
956     }
957 
binderStatsNoSampling()958     protected void binderStatsNoSampling() throws Exception {
959         getDevice().executeShellCommand("dumpsys binder_calls_stats --no-sampling");
960     }
961 
setUpLooperStats()962     protected void setUpLooperStats() throws Exception {
963         getDevice().executeShellCommand("cmd looper_stats enable");
964         getDevice().executeShellCommand("cmd looper_stats sampling_interval 1");
965         getDevice().executeShellCommand("cmd looper_stats reset");
966     }
967 
cleanUpLooperStats()968     protected void cleanUpLooperStats() throws Exception {
969         getDevice().executeShellCommand("cmd looper_stats disable");
970     }
971 
setAppBreadcrumbPredicate()972     public void setAppBreadcrumbPredicate() throws Exception {
973         doAppBreadcrumbReportedStart(1);
974     }
975 
clearAppBreadcrumbPredicate()976     public void clearAppBreadcrumbPredicate() throws Exception {
977         doAppBreadcrumbReportedStart(2);
978     }
979 
doAppBreadcrumbReportedStart(int label)980     public void doAppBreadcrumbReportedStart(int label) throws Exception {
981         doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal());
982     }
983 
doAppBreadcrumbReportedStop(int label)984     public void doAppBreadcrumbReportedStop(int label) throws Exception {
985         doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal());
986     }
987 
doAppBreadcrumbReported(int label)988     public void doAppBreadcrumbReported(int label) throws Exception {
989         doAppBreadcrumbReported(label, AppBreadcrumbReported.State.UNSPECIFIED.ordinal());
990     }
991 
doAppBreadcrumbReported(int label, int state)992     public void doAppBreadcrumbReported(int label, int state) throws Exception {
993         getDevice().executeShellCommand(String.format(
994                 "cmd stats log-app-breadcrumb %d %d %d", SHELL_UID, label, state));
995     }
996 
setBatteryLevel(int level)997     protected void setBatteryLevel(int level) throws Exception {
998         getDevice().executeShellCommand("cmd battery set level " + level);
999     }
1000 
resetBatteryStatus()1001     protected void resetBatteryStatus() throws Exception {
1002         getDevice().executeShellCommand("cmd battery reset");
1003     }
1004 
getScreenBrightness()1005     protected int getScreenBrightness() throws Exception {
1006         return Integer.parseInt(
1007                 getDevice().executeShellCommand("settings get system screen_brightness").trim());
1008     }
1009 
setScreenBrightness(int brightness)1010     protected void setScreenBrightness(int brightness) throws Exception {
1011         getDevice().executeShellCommand("settings put system screen_brightness " + brightness);
1012     }
1013 
1014     // Gets whether "Always on Display" setting is enabled.
1015     // In rare cases, this is different from whether the device can enter SCREEN_STATE_DOZE.
getAodState()1016     protected String getAodState() throws Exception {
1017         return getDevice().executeShellCommand("settings get secure doze_always_on");
1018     }
1019 
setAodState(String state)1020     protected void setAodState(String state) throws Exception {
1021         getDevice().executeShellCommand("settings put secure doze_always_on " + state);
1022     }
1023 
isScreenBrightnessModeManual()1024     protected boolean isScreenBrightnessModeManual() throws Exception {
1025         String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode");
1026         return Integer.parseInt(mode.trim()) == 0;
1027     }
1028 
setScreenBrightnessMode(boolean manual)1029     protected void setScreenBrightnessMode(boolean manual) throws Exception {
1030         getDevice().executeShellCommand(
1031                 "settings put system screen_brightness_mode " + (manual ? 0 : 1));
1032     }
1033 
enterDozeModeLight()1034     protected void enterDozeModeLight() throws Exception {
1035         getDevice().executeShellCommand("dumpsys deviceidle force-idle light");
1036     }
1037 
enterDozeModeDeep()1038     protected void enterDozeModeDeep() throws Exception {
1039         getDevice().executeShellCommand("dumpsys deviceidle force-idle deep");
1040     }
1041 
leaveDozeMode()1042     protected void leaveDozeMode() throws Exception {
1043         getDevice().executeShellCommand("dumpsys deviceidle unforce");
1044         getDevice().executeShellCommand("dumpsys deviceidle disable");
1045         getDevice().executeShellCommand("dumpsys deviceidle enable");
1046     }
1047 
turnBatterySaverOn()1048     protected void turnBatterySaverOn() throws Exception {
1049         unplugDevice();
1050         getDevice().executeShellCommand("settings put global low_power 1");
1051     }
1052 
turnBatterySaverOff()1053     protected void turnBatterySaverOff() throws Exception {
1054         getDevice().executeShellCommand("settings put global low_power 0");
1055         getDevice().executeShellCommand("cmd battery reset");
1056     }
1057 
turnBatteryStatsAutoResetOn()1058     protected void turnBatteryStatsAutoResetOn() throws Exception {
1059         getDevice().executeShellCommand("dumpsys batterystats enable no-auto-reset");
1060     }
1061 
turnBatteryStatsAutoResetOff()1062     protected void turnBatteryStatsAutoResetOff() throws Exception {
1063         getDevice().executeShellCommand("dumpsys batterystats enable no-auto-reset");
1064     }
1065 
flushBatteryStatsHandlers()1066     protected void flushBatteryStatsHandlers() throws Exception {
1067         // Dumping batterystats will flush everything in the batterystats handler threads.
1068         getDevice().executeShellCommand(DUMP_BATTERYSTATS_CMD);
1069     }
1070 
rebootDevice()1071     protected void rebootDevice() throws Exception {
1072         getDevice().rebootUntilOnline();
1073     }
1074 
1075     /**
1076      * Asserts that the two events are within the specified range of each other.
1077      *
1078      * @param d0        the event that should occur first
1079      * @param d1        the event that should occur second
1080      * @param minDiffMs d0 should precede d1 by at least this amount
1081      * @param maxDiffMs d0 should precede d1 by at most this amount
1082      */
assertTimeDiffBetween(EventMetricData d0, EventMetricData d1, int minDiffMs, int maxDiffMs)1083     public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1,
1084             int minDiffMs, int maxDiffMs) {
1085         long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
1086         assertWithMessage("Illegal time difference")
1087             .that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
1088     }
1089 
getCurrentLogcatDate()1090     protected String getCurrentLogcatDate() throws Exception {
1091         // TODO: Do something more robust than this for getting logcat markers.
1092         long timestampMs = getDevice().getDeviceDate();
1093         return new SimpleDateFormat("MM-dd HH:mm:ss.SSS")
1094                 .format(new Date(timestampMs));
1095     }
1096 
getLogcatSince(String date, String logcatParams)1097     protected String getLogcatSince(String date, String logcatParams) throws Exception {
1098         return getDevice().executeShellCommand(String.format(
1099                 "logcat -v threadtime -t '%s' -d %s", date, logcatParams));
1100     }
1101 
1102     // TODO: Remove this and migrate all usages to createConfigBuilder()
getPulledConfig()1103     protected StatsdConfig.Builder getPulledConfig() {
1104         return createConfigBuilder();
1105     }
1106     /**
1107      * Determines if the device has the given feature.
1108      * Prints a warning if its value differs from requiredAnswer.
1109      */
hasFeature(String featureName, boolean requiredAnswer)1110     protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception {
1111         final String features = getDevice().executeShellCommand("pm list features");
1112         StringTokenizer featureToken = new StringTokenizer(features, "\n");
1113         boolean hasIt = false;
1114 
1115         while (featureToken.hasMoreTokens()) {
1116             if (("feature:" + featureName).equals(featureToken.nextToken())) {
1117                  hasIt = true;
1118                  break;
1119             }
1120         }
1121 
1122         if (hasIt != requiredAnswer) {
1123             LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature "
1124                     + featureName);
1125         }
1126         return hasIt == requiredAnswer;
1127     }
1128 
1129     /**
1130      * Determines if the device has |file|.
1131      */
doesFileExist(String file)1132     protected boolean doesFileExist(String file) throws Exception {
1133         return getDevice().doesFileExist(file);
1134     }
1135 
turnOnAirplaneMode()1136     protected void turnOnAirplaneMode() throws Exception {
1137         getDevice().executeShellCommand("cmd connectivity airplane-mode enable");
1138     }
1139 
turnOffAirplaneMode()1140     protected void turnOffAirplaneMode() throws Exception {
1141         getDevice().executeShellCommand("cmd connectivity airplane-mode disable");
1142     }
1143 
1144     /**
1145      * Returns a list of fields and values for {@code className} from {@link TelephonyDebugService}
1146      * output.
1147      *
1148      * <p>Telephony dumpsys output does not support proto at the moment. This method provides
1149      * limited support for parsing its output. Specifically, it does not support arrays or
1150      * multi-line values.
1151      */
getTelephonyDumpEntries(String className)1152     private List<Map<String, String>> getTelephonyDumpEntries(String className) throws Exception {
1153         // Matches any line with indentation, except for lines with only spaces
1154         Pattern indentPattern = Pattern.compile("^(\\s*)[^ ].*$");
1155         // Matches pattern for class, e.g. "    Phone:"
1156         Pattern classNamePattern = Pattern.compile("^(\\s*)" + Pattern.quote(className) + ":.*$");
1157         // Matches pattern for key-value pairs, e.g. "     mPhoneId=1"
1158         Pattern keyValuePattern = Pattern.compile("^(\\s*)([a-zA-Z]+[a-zA-Z0-9_]*)\\=(.+)$");
1159         String response =
1160                 getDevice().executeShellCommand("dumpsys activity service TelephonyDebugService");
1161         Queue<String> responseLines = new LinkedList<>(Arrays.asList(response.split("[\\r\\n]+")));
1162 
1163         List<Map<String, String>> results = new ArrayList<>();
1164         while (responseLines.peek() != null) {
1165             Matcher matcher = classNamePattern.matcher(responseLines.poll());
1166             if (matcher.matches()) {
1167                 final int classIndentLevel = matcher.group(1).length();
1168                 final Map<String, String> instanceEntries = new HashMap<>();
1169                 while (responseLines.peek() != null) {
1170                     // Skip blank lines
1171                     matcher = indentPattern.matcher(responseLines.peek());
1172                     if (responseLines.peek().length() == 0 || !matcher.matches()) {
1173                         responseLines.poll();
1174                         continue;
1175                     }
1176                     // Finish (without consuming the line) if already parsed past this instance
1177                     final int indentLevel = matcher.group(1).length();
1178                     if (indentLevel <= classIndentLevel) {
1179                         break;
1180                     }
1181                     // Parse key-value pair if it belongs to the instance directly
1182                     matcher = keyValuePattern.matcher(responseLines.poll());
1183                     if (indentLevel == classIndentLevel + 1 && matcher.matches()) {
1184                         instanceEntries.put(matcher.group(2), matcher.group(3));
1185                     }
1186                 }
1187                 results.add(instanceEntries);
1188             }
1189         }
1190         return results;
1191     }
1192 
getActiveSimSlotCount()1193     protected int getActiveSimSlotCount() throws Exception {
1194         List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
1195         long count = slots.stream().filter(slot -> "true".equals(slot.get("mActive"))).count();
1196         return Math.toIntExact(count);
1197     }
1198 
1199     /**
1200      * Returns the upper bound of active SIM profile count.
1201      *
1202      * <p>The value is an upper bound as eSIMs without profiles are also counted in.
1203      */
getActiveSimCountUpperBound()1204     protected int getActiveSimCountUpperBound() throws Exception {
1205         List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
1206         long count = slots.stream().filter(slot ->
1207                 "true".equals(slot.get("mActive"))
1208                 && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))).count();
1209         return Math.toIntExact(count);
1210     }
1211 
1212     /**
1213      * Returns the upper bound of active eSIM profile count.
1214      *
1215      * <p>The value is an upper bound as eSIMs without profiles are also counted in.
1216      */
getActiveEsimCountUpperBound()1217     protected int getActiveEsimCountUpperBound() throws Exception {
1218         List<Map<String, String>> slots = getTelephonyDumpEntries("UiccSlot");
1219         long count = slots.stream().filter(slot ->
1220                 "true".equals(slot.get("mActive"))
1221                 && "CARDSTATE_PRESENT".equals(slot.get("mCardState"))
1222                 && "true".equals(slot.get("mIsEuicc"))).count();
1223         return Math.toIntExact(count);
1224     }
1225 
hasGsmPhone()1226     protected boolean hasGsmPhone() throws Exception {
1227         // Not using log entries or ServiceState in the dump since they may or may not be present,
1228         // which can make the test flaky
1229         return getTelephonyDumpEntries("Phone").stream()
1230                 .anyMatch(phone ->
1231                         String.format("%d", PHONE_TYPE_GSM).equals(phone.get("getPhoneType()")));
1232     }
1233 
hasCdmaPhone()1234     protected boolean hasCdmaPhone() throws Exception {
1235         // Not using log entries or ServiceState in the dump due to the same reason as hasGsmPhone()
1236         return getTelephonyDumpEntries("Phone").stream()
1237                 .anyMatch(phone ->
1238                         String.format("%d", PHONE_TYPE_CDMA).equals(phone.get("getPhoneType()"))
1239                         || String.format("%d", PHONE_TYPE_CDMA_LTE)
1240                                 .equals(phone.get("getPhoneType()")));
1241     }
1242 
1243     // Checks that a timestamp has been truncated to be a multiple of 5 min
assertTimestampIsTruncated(long timestampNs)1244     protected void assertTimestampIsTruncated(long timestampNs) {
1245         long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
1246         assertWithMessage("Timestamp is not truncated")
1247                 .that(timestampNs % fiveMinutesInNs).isEqualTo(0);
1248     }
1249 
backfillAggregatedAtomsInEventMetric( EventMetricData metricData)1250     protected List<EventMetricData> backfillAggregatedAtomsInEventMetric(
1251             EventMetricData metricData) {
1252       if (!metricData.hasAggregatedAtomInfo()) {
1253         return Collections.singletonList(metricData);
1254       }
1255       List<EventMetricData> data = new ArrayList<>();
1256       StatsLog.AggregatedAtomInfo atomInfo = metricData.getAggregatedAtomInfo();
1257       for (long timestamp : atomInfo.getElapsedTimestampNanosList()) {
1258         data.add(EventMetricData.newBuilder()
1259                      .setAtom(atomInfo.getAtom())
1260                      .setElapsedTimestampNanos(timestamp)
1261                      .build());
1262       }
1263       return data;
1264     }
1265 }
1266