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