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 android.os.BatteryStatsProto; 19 import android.service.batterystats.BatteryStatsServiceDumpProto; 20 import android.view.DisplayStateEnum; 21 22 import com.android.annotations.Nullable; 23 import com.android.internal.os.StatsdConfigProto.AtomMatcher; 24 import com.android.internal.os.StatsdConfigProto.EventMetric; 25 import com.android.internal.os.StatsdConfigProto.FieldFilter; 26 import com.android.internal.os.StatsdConfigProto.FieldMatcher; 27 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher; 28 import com.android.internal.os.StatsdConfigProto.GaugeMetric; 29 import com.android.internal.os.StatsdConfigProto.Predicate; 30 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher; 31 import com.android.internal.os.StatsdConfigProto.SimplePredicate; 32 import com.android.internal.os.StatsdConfigProto.StatsdConfig; 33 import com.android.internal.os.StatsdConfigProto.TimeUnit; 34 import com.android.os.AtomsProto.AppBreadcrumbReported; 35 import com.android.os.AtomsProto.Atom; 36 import com.android.os.AtomsProto.ScreenStateChanged; 37 import com.android.os.StatsLog.ConfigMetricsReport; 38 import com.android.os.StatsLog.ConfigMetricsReportList; 39 import com.android.os.StatsLog.EventMetricData; 40 import com.android.os.StatsLog.GaugeMetricData; 41 import com.android.os.StatsLog.StatsLogReport; 42 import com.android.tradefed.device.DeviceNotAvailableException; 43 import com.android.tradefed.log.LogUtil; 44 45 import com.google.common.io.Files; 46 47 import java.io.File; 48 import java.text.SimpleDateFormat; 49 import java.util.ArrayList; 50 import java.util.Arrays; 51 import java.util.Comparator; 52 import java.util.Date; 53 import java.util.List; 54 import java.util.Set; 55 import java.util.function.Function; 56 57 import perfetto.protos.PerfettoConfig.DataSourceConfig; 58 import perfetto.protos.PerfettoConfig.TraceConfig; 59 import perfetto.protos.PerfettoConfig.TraceConfig.BufferConfig; 60 import perfetto.protos.PerfettoConfig.TraceConfig.DataSource; 61 62 /** 63 * Base class for testing Statsd atoms. 64 * Validates reporting of statsd logging based on different events 65 */ 66 public class AtomTestCase extends BaseTestCase { 67 68 public static final String UPDATE_CONFIG_CMD = "cmd stats config update"; 69 public static final String DUMP_REPORT_CMD = "cmd stats dump-report"; 70 public static final String DUMP_BATTERYSTATS_CMD = "dumpsys batterystats"; 71 public static final String REMOVE_CONFIG_CMD = "cmd stats config remove"; 72 public static final String CONFIG_UID = "1000"; 73 /** ID of the config, which evaluates to -1572883457. */ 74 public static final long CONFIG_ID = "cts_config".hashCode(); 75 76 protected static final int WAIT_TIME_SHORT = 500; 77 protected static final int WAIT_TIME_LONG = 2_000; 78 79 protected static final long SCREEN_STATE_CHANGE_TIMEOUT = 4000; 80 protected static final long SCREEN_STATE_POLLING_INTERVAL = 500; 81 82 @Override 83 protected void setUp() throws Exception { 84 super.setUp(); 85 86 if (statsdDisabled()) { 87 return; 88 } 89 // TODO: need to do these before running real test: 90 // 1. compile statsd and push to device 91 // 2. make sure StatsCompanionService and incidentd is running 92 // 3. start statsd 93 // These should go away once we have statsd properly set up. 94 95 // Uninstall to clear the history in case it's still on the device. 96 removeConfig(CONFIG_ID); 97 getReportList(); // Clears data. 98 } 99 100 @Override 101 protected void tearDown() throws Exception { 102 removeConfig(CONFIG_ID); 103 super.tearDown(); 104 } 105 106 /** 107 * Determines whether logcat indicates that incidentd fired since the given device date. 108 */ 109 protected boolean didIncidentdFireSince(String date) throws Exception { 110 final String INCIDENTD_TAG = "incidentd"; 111 final String INCIDENTD_STARTED_STRING = "reportIncident"; 112 // TODO: Do something more robust than this in case of delayed logging. 113 Thread.sleep(1000); 114 String log = getLogcatSince(date, String.format( 115 "-s %s -e %s", INCIDENTD_TAG, INCIDENTD_STARTED_STRING)); 116 return log.contains(INCIDENTD_STARTED_STRING); 117 } 118 119 /** 120 * Determines whether logcat indicates that perfetto fired since the given device date. 121 */ 122 protected boolean didPerfettoStartSince(String date) throws Exception { 123 final String PERFETTO_TAG = "perfetto"; 124 final String PERFETTO_STARTED_STRING = "Enabled tracing"; 125 final String PERFETTO_STARTED_REGEX = ".*" + PERFETTO_STARTED_STRING + ".*"; 126 // TODO: Do something more robust than this in case of delayed logging. 127 Thread.sleep(1000); 128 String log = getLogcatSince(date, String.format( 129 "-s %s -e %s", PERFETTO_TAG, PERFETTO_STARTED_REGEX)); 130 return log.contains(PERFETTO_STARTED_STRING); 131 } 132 133 protected static StatsdConfig.Builder createConfigBuilder() { 134 return StatsdConfig.newBuilder().setId(CONFIG_ID) 135 .addAllowedLogSource("AID_SYSTEM") 136 .addAllowedLogSource("AID_BLUETOOTH") 137 .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE); 138 } 139 140 protected void createAndUploadConfig(int atomTag) throws Exception { 141 StatsdConfig.Builder conf = createConfigBuilder(); 142 addAtomEvent(conf, atomTag); 143 uploadConfig(conf); 144 } 145 146 protected void uploadConfig(StatsdConfig.Builder config) throws Exception { 147 uploadConfig(config.build()); 148 } 149 150 protected void uploadConfig(StatsdConfig config) throws Exception { 151 LogUtil.CLog.d("Uploading the following config:\n" + config.toString()); 152 File configFile = File.createTempFile("statsdconfig", ".config"); 153 configFile.deleteOnExit(); 154 Files.write(config.toByteArray(), configFile); 155 String remotePath = "/data/local/tmp/" + configFile.getName(); 156 getDevice().pushFile(configFile, remotePath); 157 getDevice().executeShellCommand( 158 String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD, 159 String.valueOf(CONFIG_ID))); 160 getDevice().executeShellCommand("rm " + remotePath); 161 } 162 163 protected void removeConfig(long configId) throws Exception { 164 getDevice().executeShellCommand( 165 String.join(" ", REMOVE_CONFIG_CMD, String.valueOf(configId))); 166 } 167 168 /** Gets the statsd report and sorts it. Note that this also deletes that report from statsd. */ 169 protected List<EventMetricData> getEventMetricDataList() throws Exception { 170 ConfigMetricsReportList reportList = getReportList(); 171 assertTrue("Expected one report", reportList.getReportsCount() == 1); 172 ConfigMetricsReport report = reportList.getReports(0); 173 174 List<EventMetricData> data = new ArrayList<>(); 175 for (StatsLogReport metric : report.getMetricsList()) { 176 data.addAll(metric.getEventMetrics().getDataList()); 177 } 178 data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos)); 179 180 LogUtil.CLog.d("Get EventMetricDataList as following:\n"); 181 for (EventMetricData d : data) { 182 LogUtil.CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString()); 183 } 184 return data; 185 } 186 187 protected List<Atom> getGaugeMetricDataList() throws Exception { 188 ConfigMetricsReportList reportList = getReportList(); 189 assertTrue(reportList.getReportsCount() == 1); 190 // only config 191 ConfigMetricsReport report = reportList.getReports(0); 192 193 List<Atom> data = new ArrayList<>(); 194 for (GaugeMetricData gaugeMetricData : 195 report.getMetrics(0).getGaugeMetrics().getDataList()) { 196 for (Atom atom : gaugeMetricData.getBucketInfo(0).getAtomList()) { 197 data.add(atom); 198 } 199 } 200 201 LogUtil.CLog.d("Get GaugeMetricDataList as following:\n"); 202 for (Atom d : data) { 203 LogUtil.CLog.d("Atom:\n" + d.toString()); 204 } 205 return data; 206 } 207 208 protected StatsLogReport getStatsLogReport() throws Exception { 209 ConfigMetricsReportList reportList = getReportList(); 210 assertTrue(reportList.getReportsCount() == 1); 211 ConfigMetricsReport report = reportList.getReports(0); 212 assertTrue(report.hasUidMap()); 213 assertEquals(1, report.getMetricsCount()); 214 return report.getMetrics(0); 215 } 216 217 /** Gets the statsd report. Note that this also deletes that report from statsd. */ 218 protected ConfigMetricsReportList getReportList() throws Exception { 219 try { 220 ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(), 221 String.join(" ", DUMP_REPORT_CMD, String.valueOf(CONFIG_ID), 222 "--proto")); 223 return reportList; 224 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 225 LogUtil.CLog.e("Failed to fetch and parse the statsd output report. " 226 + "Perhaps there is not a valid statsd config for the requested " 227 + "uid=" + CONFIG_UID + ", id=" + CONFIG_ID + "."); 228 throw (e); 229 } 230 } 231 232 protected BatteryStatsProto getBatteryStatsProto() throws Exception { 233 try { 234 BatteryStatsProto batteryStatsProto = getDump(BatteryStatsServiceDumpProto.parser(), 235 String.join(" ", DUMP_BATTERYSTATS_CMD, 236 "--proto")).getBatterystats(); 237 LogUtil.CLog.d("Got batterystats:\n " + batteryStatsProto.toString()); 238 return batteryStatsProto; 239 } catch (com.google.protobuf.InvalidProtocolBufferException e) { 240 LogUtil.CLog.e("Failed to dump batterystats proto"); 241 throw (e); 242 } 243 } 244 245 /** Creates a FieldValueMatcher.Builder corresponding to the given field. */ 246 protected static FieldValueMatcher.Builder createFvm(int field) { 247 return FieldValueMatcher.newBuilder().setField(field); 248 } 249 250 protected static TraceConfig createPerfettoTraceConfig() { 251 return TraceConfig.newBuilder() 252 .addBuffers(BufferConfig.newBuilder().setSizeKb(32)) 253 .addDataSources(DataSource.newBuilder() 254 .setConfig(DataSourceConfig.newBuilder() 255 .setName("linux.ftrace") 256 .setTargetBuffer(0) 257 .build() 258 ) 259 ) 260 .build(); 261 } 262 263 protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag) throws Exception { 264 addAtomEvent(conf, atomTag, new ArrayList<FieldValueMatcher.Builder>()); 265 } 266 267 /** 268 * Adds an event to the config for an atom that matches the given key. 269 * 270 * @param conf configuration 271 * @param atomTag atom tag (from atoms.proto) 272 * @param fvm FieldValueMatcher.Builder for the relevant key 273 */ 274 protected void addAtomEvent(StatsdConfig.Builder conf, int atomTag, 275 FieldValueMatcher.Builder fvm) 276 throws Exception { 277 addAtomEvent(conf, atomTag, Arrays.asList(fvm)); 278 } 279 280 /** 281 * Adds an event to the config for an atom that matches the given keys. 282 * 283 * @param conf configuration 284 * @param atomId atom tag (from atoms.proto) 285 * @param fvms list of FieldValueMatcher.Builders to attach to the atom. May be null. 286 */ 287 protected void addAtomEvent(StatsdConfig.Builder conf, int atomId, 288 List<FieldValueMatcher.Builder> fvms) throws Exception { 289 290 final String atomName = "Atom" + System.nanoTime(); 291 final String eventName = "Event" + System.nanoTime(); 292 293 SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId); 294 if (fvms != null) { 295 for (FieldValueMatcher.Builder fvm : fvms) { 296 sam.addFieldValueMatcher(fvm); 297 } 298 } 299 conf.addAtomMatcher(AtomMatcher.newBuilder() 300 .setId(atomName.hashCode()) 301 .setSimpleAtomMatcher(sam)); 302 conf.addEventMetric(EventMetric.newBuilder() 303 .setId(eventName.hashCode()) 304 .setWhat(atomName.hashCode())); 305 } 306 307 /** 308 * Adds an atom to a gauge metric of a config 309 * 310 * @param conf configuration 311 * @param atomId atom id (from atoms.proto) 312 * @param dimension dimension is needed for most pulled atoms 313 */ 314 protected void addGaugeAtom(StatsdConfig.Builder conf, int atomId, 315 @Nullable FieldMatcher.Builder dimension) throws Exception { 316 final String atomName = "Atom" + System.nanoTime(); 317 final String gaugeName = "Gauge" + System.nanoTime(); 318 final String predicateName = "SCREEN_IS_ON"; 319 SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(atomId); 320 conf.addAtomMatcher(AtomMatcher.newBuilder() 321 .setId(atomName.hashCode()) 322 .setSimpleAtomMatcher(sam)); 323 // TODO: change this predicate to something simpler and easier 324 final String predicateTrueName = "SCREEN_TURNED_ON"; 325 final String predicateFalseName = "SCREEN_TURNED_OFF"; 326 conf.addAtomMatcher(AtomMatcher.newBuilder() 327 .setId(predicateTrueName.hashCode()) 328 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder() 329 .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER) 330 .addFieldValueMatcher(FieldValueMatcher.newBuilder() 331 .setField(ScreenStateChanged.STATE_FIELD_NUMBER) 332 .setEqInt(DisplayStateEnum.DISPLAY_STATE_ON_VALUE) 333 ) 334 ) 335 ) 336 // Used to trigger predicate 337 .addAtomMatcher(AtomMatcher.newBuilder() 338 .setId(predicateFalseName.hashCode()) 339 .setSimpleAtomMatcher(SimpleAtomMatcher.newBuilder() 340 .setAtomId(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER) 341 .addFieldValueMatcher(FieldValueMatcher.newBuilder() 342 .setField(ScreenStateChanged.STATE_FIELD_NUMBER) 343 .setEqInt(DisplayStateEnum.DISPLAY_STATE_OFF_VALUE) 344 ) 345 ) 346 ); 347 conf.addPredicate(Predicate.newBuilder() 348 .setId(predicateName.hashCode()) 349 .setSimplePredicate(SimplePredicate.newBuilder() 350 .setStart(predicateTrueName.hashCode()) 351 .setStop(predicateFalseName.hashCode()) 352 .setCountNesting(false) 353 ) 354 ); 355 GaugeMetric.Builder gaugeMetric = GaugeMetric.newBuilder() 356 .setId(gaugeName.hashCode()) 357 .setWhat(atomName.hashCode()) 358 .setGaugeFieldsFilter(FieldFilter.newBuilder().setIncludeAll(true).build()) 359 .setSamplingType(GaugeMetric.SamplingType.ALL_CONDITION_CHANGES) 360 .setBucket(TimeUnit.CTS) 361 .setCondition(predicateName.hashCode()); 362 if (dimension != null) { 363 gaugeMetric.setDimensionsInWhat(dimension.build()); 364 } 365 conf.addGaugeMetric(gaugeMetric.build()); 366 } 367 368 /** 369 * Asserts that each set of states in stateSets occurs at least once in data. 370 * Asserts that the states in data occur in the same order as the sets in stateSets. 371 * 372 * @param stateSets A list of set of states, where each set represents an equivalent 373 * state of the device for the purpose of CTS. 374 * @param data list of EventMetricData from statsd, produced by 375 * getReportMetricListData() 376 * @param wait expected duration (in ms) between state changes; asserts that the 377 * actual wait 378 * time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this 379 * assertion. 380 * @param getStateFromAtom expression that takes in an Atom and returns the state it contains 381 */ 382 public void assertStatesOccurred(List<Set<Integer>> stateSets, List<EventMetricData> data, 383 int wait, Function<Atom, Integer> getStateFromAtom) { 384 // Sometimes, there are more events than there are states. 385 // Eg: When the screen turns off, it may go into OFF and then DOZE immediately. 386 assertTrue("Too few states found (" + data.size() + ")", data.size() >= stateSets.size()); 387 int stateSetIndex = 0; // Tracks which state set we expect the data to be in. 388 for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) { 389 Atom atom = data.get(dataIndex).getAtom(); 390 int state = getStateFromAtom.apply(atom); 391 // If state is in the current state set, we do not assert anything. 392 // If it is not, we expect to have transitioned to the next state set. 393 if (stateSets.get(stateSetIndex).contains(state)) { 394 // No need to assert anything. Just log it. 395 LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is " 396 + "in stateSetIndex " + stateSetIndex + ":\n" 397 + data.get(dataIndex).getAtom().toString()); 398 } else { 399 stateSetIndex += 1; 400 LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is" 401 + " in stateSetIndex " + stateSetIndex + ":\n" 402 + data.get(dataIndex).getAtom().toString()); 403 assertTrue("Missed first state", dataIndex != 0); // should not be on first data 404 assertTrue("Too many states (" + (stateSetIndex + 1) + ")", 405 stateSetIndex < stateSets.size()); 406 assertTrue("Is in wrong state (" + state + ")", 407 stateSets.get(stateSetIndex).contains(state)); 408 if (wait > 0) { 409 assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex), 410 wait / 2, wait * 5); 411 } 412 } 413 } 414 assertTrue("Too few states (" + (stateSetIndex + 1) + ")", 415 stateSetIndex == stateSets.size() - 1); 416 } 417 418 /** 419 * Removes all elements from data prior to the first occurrence of an element of state. After 420 * this method is called, the first element of data (if non-empty) is guaranteed to be an 421 * element in state. 422 * 423 * @param getStateFromAtom expression that takes in an Atom and returns the state it contains 424 */ 425 public void popUntilFind(List<EventMetricData> data, Set<Integer> state, 426 Function<Atom, Integer> getStateFromAtom) { 427 int firstStateIdx; 428 for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) { 429 Atom atom = data.get(firstStateIdx).getAtom(); 430 if (state.contains(getStateFromAtom.apply(atom))) { 431 break; 432 } 433 } 434 if (firstStateIdx == 0) { 435 // First first element already is in state, so there's nothing to do. 436 return; 437 } 438 data.subList(0, firstStateIdx).clear(); 439 } 440 441 /** 442 * Removes all elements from data after to the last occurrence of an element of state. After 443 * this method is called, the last element of data (if non-empty) is guaranteed to be an 444 * element in state. 445 * 446 * @param getStateFromAtom expression that takes in an Atom and returns the state it contains 447 */ 448 public void popUntilFindFromEnd(List<EventMetricData> data, Set<Integer> state, 449 Function<Atom, Integer> getStateFromAtom) { 450 int lastStateIdx; 451 for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) { 452 Atom atom = data.get(lastStateIdx).getAtom(); 453 if (state.contains(getStateFromAtom.apply(atom))) { 454 break; 455 } 456 } 457 if (lastStateIdx == data.size()-1) { 458 // Last element already is in state, so there's nothing to do. 459 return; 460 } 461 data.subList(lastStateIdx+1, data.size()).clear(); 462 } 463 464 /** Returns the UID of the host, which should always either be SHELL (2000) or ROOT (0). */ 465 protected int getHostUid() throws DeviceNotAvailableException { 466 String strUid = ""; 467 try { 468 strUid = getDevice().executeShellCommand("id -u"); 469 return Integer.parseInt(strUid.trim()); 470 } catch (NumberFormatException e) { 471 LogUtil.CLog.e("Failed to get host's uid via shell command. Found " + strUid); 472 // Fall back to alternative method... 473 if (getDevice().isAdbRoot()) { 474 return 0; // ROOT 475 } else { 476 return 2000; // SHELL 477 } 478 } 479 } 480 481 protected void turnScreenOn() throws Exception { 482 getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP"); 483 getDevice().executeShellCommand("wm dismiss-keyguard"); 484 } 485 486 protected void turnScreenOff() throws Exception { 487 getDevice().executeShellCommand("input keyevent KEYCODE_SLEEP"); 488 } 489 490 protected void setChargingState(int state) throws Exception { 491 getDevice().executeShellCommand("cmd battery set status " + state); 492 } 493 494 protected void unplugDevice() throws Exception { 495 getDevice().executeShellCommand("cmd battery unplug"); 496 } 497 498 protected void plugInAc() throws Exception { 499 getDevice().executeShellCommand("cmd battery set ac 1"); 500 } 501 502 protected void plugInUsb() throws Exception { 503 getDevice().executeShellCommand("cmd battery set usb 1"); 504 } 505 506 protected void plugInWireless() throws Exception { 507 getDevice().executeShellCommand("cmd battery set wireless 1"); 508 } 509 510 public void doAppBreadcrumbReportedStart(int label) throws Exception { 511 doAppBreadcrumbReported(label, AppBreadcrumbReported.State.START.ordinal()); 512 } 513 514 public void doAppBreadcrumbReportedStop(int label) throws Exception { 515 doAppBreadcrumbReported(label, AppBreadcrumbReported.State.STOP.ordinal()); 516 } 517 518 public void doAppBreadcrumbReported(int label, int state) throws Exception { 519 getDevice().executeShellCommand(String.format( 520 "cmd stats log-app-breadcrumb %d %d", label, state)); 521 } 522 523 protected void setBatteryLevel(int level) throws Exception { 524 getDevice().executeShellCommand("cmd battery set level " + level); 525 } 526 527 protected void resetBatteryStatus() throws Exception { 528 getDevice().executeShellCommand("cmd battery reset"); 529 } 530 531 protected int getScreenBrightness() throws Exception { 532 return Integer.parseInt( 533 getDevice().executeShellCommand("settings get system screen_brightness").trim()); 534 } 535 536 protected void setScreenBrightness(int brightness) throws Exception { 537 getDevice().executeShellCommand("settings put system screen_brightness " + brightness); 538 } 539 540 protected boolean isScreenBrightnessModeManual() throws Exception { 541 String mode = getDevice().executeShellCommand("settings get system screen_brightness_mode"); 542 return Integer.parseInt(mode.trim()) == 0; 543 } 544 545 protected void setScreenBrightnessMode(boolean manual) throws Exception { 546 getDevice().executeShellCommand( 547 "settings put system screen_brightness_mode " + (manual ? 0 : 1)); 548 } 549 550 protected void enterDozeModeLight() throws Exception { 551 getDevice().executeShellCommand("dumpsys deviceidle force-idle light"); 552 } 553 554 protected void enterDozeModeDeep() throws Exception { 555 getDevice().executeShellCommand("dumpsys deviceidle force-idle deep"); 556 } 557 558 protected void leaveDozeMode() throws Exception { 559 getDevice().executeShellCommand("dumpsys deviceidle unforce"); 560 getDevice().executeShellCommand("dumpsys deviceidle disable"); 561 getDevice().executeShellCommand("dumpsys deviceidle enable"); 562 } 563 564 protected void turnBatterySaverOn() throws Exception { 565 getDevice().executeShellCommand("cmd battery unplug"); 566 getDevice().executeShellCommand("settings put global low_power 1"); 567 } 568 569 protected void turnBatterySaverOff() throws Exception { 570 getDevice().executeShellCommand("settings put global low_power 0"); 571 getDevice().executeShellCommand("cmd battery reset"); 572 } 573 574 protected void rebootDevice() throws Exception { 575 getDevice().rebootUntilOnline(); 576 } 577 578 /** 579 * Asserts that the two events are within the specified range of each other. 580 * 581 * @param d0 the event that should occur first 582 * @param d1 the event that should occur second 583 * @param minDiffMs d0 should precede d1 by at least this amount 584 * @param maxDiffMs d0 should precede d1 by at most this amount 585 */ 586 public static void assertTimeDiffBetween(EventMetricData d0, EventMetricData d1, 587 int minDiffMs, int maxDiffMs) { 588 long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000; 589 assertTrue("Illegal time difference (" + diffMs + "ms)", minDiffMs <= diffMs); 590 assertTrue("Illegal time difference (" + diffMs + "ms)", diffMs <= maxDiffMs); 591 } 592 593 protected String getCurrentLogcatDate() throws Exception { 594 // TODO: Do something more robust than this for getting logcat markers. 595 long timestampMs = getDevice().getDeviceDate(); 596 return new SimpleDateFormat("MM-dd HH:mm:ss.SSS") 597 .format(new Date(timestampMs)); 598 } 599 600 protected String getLogcatSince(String date, String logcatParams) throws Exception { 601 return getDevice().executeShellCommand(String.format( 602 "logcat -v threadtime -t '%s' -d %s", date, logcatParams)); 603 } 604 605 /** 606 * Pulled atoms should have a better way of constructing the config. 607 * Remove this config when that happens. 608 */ 609 protected StatsdConfig.Builder getPulledConfig() { 610 return StatsdConfig.newBuilder().setId(CONFIG_ID) 611 .addAllowedLogSource("AID_SYSTEM") 612 .addAllowedLogSource(DeviceAtomTestCase.DEVICE_SIDE_TEST_PACKAGE); 613 } 614 615 /** 616 * Determines if the device has the given feature. 617 * Prints a warning if its value differs from requiredAnswer. 618 */ 619 protected boolean hasFeature(String featureName, boolean requiredAnswer) throws Exception { 620 final String features = getDevice().executeShellCommand("pm list features"); 621 boolean hasIt = features.contains(featureName); 622 if (hasIt != requiredAnswer) { 623 LogUtil.CLog.w("Device does " + (requiredAnswer ? "not " : "") + "have feature " 624 + featureName); 625 } 626 return hasIt == requiredAnswer; 627 } 628 629 } 630