1 # Copyright 2015 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 """Provides a variety of device interactions with power. 6 """ 7 # pylint: disable=unused-argument 8 9 import collections 10 import contextlib 11 import csv 12 import logging 13 14 from devil.android import decorators 15 from devil.android import device_errors 16 from devil.android import device_utils 17 from devil.android.sdk import version_codes 18 from devil.utils import timeout_retry 19 20 logger = logging.getLogger(__name__) 21 22 _DEFAULT_TIMEOUT = 30 23 _DEFAULT_RETRIES = 3 24 25 26 _DEVICE_PROFILES = [ 27 { 28 'name': ['Nexus 4'], 29 'enable_command': ( 30 'echo 0 > /sys/module/pm8921_charger/parameters/disabled && ' 31 'dumpsys battery reset'), 32 'disable_command': ( 33 'echo 1 > /sys/module/pm8921_charger/parameters/disabled && ' 34 'dumpsys battery set ac 0 && dumpsys battery set usb 0'), 35 'charge_counter': None, 36 'voltage': None, 37 'current': None, 38 }, 39 { 40 'name': ['Nexus 5'], 41 # Nexus 5 42 # Setting the HIZ bit of the bq24192 causes the charger to actually ignore 43 # energy coming from USB. Setting the power_supply offline just updates the 44 # Android system to reflect that. 45 'enable_command': ( 46 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' 47 'chmod 644 /sys/class/power_supply/usb/online && ' 48 'echo 1 > /sys/class/power_supply/usb/online && ' 49 'dumpsys battery reset'), 50 'disable_command': ( 51 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' 52 'chmod 644 /sys/class/power_supply/usb/online && ' 53 'echo 0 > /sys/class/power_supply/usb/online && ' 54 'dumpsys battery set ac 0 && dumpsys battery set usb 0'), 55 'charge_counter': None, 56 'voltage': None, 57 'current': None, 58 }, 59 { 60 'name': ['Nexus 6'], 61 'enable_command': ( 62 'echo 1 > /sys/class/power_supply/battery/charging_enabled && ' 63 'dumpsys battery reset'), 64 'disable_command': ( 65 'echo 0 > /sys/class/power_supply/battery/charging_enabled && ' 66 'dumpsys battery set ac 0 && dumpsys battery set usb 0'), 67 'charge_counter': ( 68 '/sys/class/power_supply/max170xx_battery/charge_counter_ext'), 69 'voltage': '/sys/class/power_supply/max170xx_battery/voltage_now', 70 'current': '/sys/class/power_supply/max170xx_battery/current_now', 71 }, 72 { 73 'name': ['Nexus 9'], 74 'enable_command': ( 75 'echo Disconnected > ' 76 '/sys/bus/i2c/drivers/bq2419x/0-006b/input_cable_state && ' 77 'dumpsys battery reset'), 78 'disable_command': ( 79 'echo Connected > ' 80 '/sys/bus/i2c/drivers/bq2419x/0-006b/input_cable_state && ' 81 'dumpsys battery set ac 0 && dumpsys battery set usb 0'), 82 'charge_counter': '/sys/class/power_supply/battery/charge_counter_ext', 83 'voltage': '/sys/class/power_supply/battery/voltage_now', 84 'current': '/sys/class/power_supply/battery/current_now', 85 }, 86 { 87 'name': ['Nexus 10'], 88 'enable_command': None, 89 'disable_command': None, 90 'charge_counter': None, 91 'voltage': '/sys/class/power_supply/ds2784-fuelgauge/voltage_now', 92 'current': '/sys/class/power_supply/ds2784-fuelgauge/current_now', 93 94 }, 95 { 96 'name': ['Nexus 5X'], 97 'enable_command': ( 98 'echo 1 > /sys/class/power_supply/battery/charging_enabled && ' 99 'dumpsys battery reset'), 100 'disable_command': ( 101 'echo 0 > /sys/class/power_supply/battery/charging_enabled && ' 102 'dumpsys battery set ac 0 && dumpsys battery set usb 0'), 103 'charge_counter': None, 104 'voltage': None, 105 'current': None, 106 }, 107 { # Galaxy s5 108 'name': ['SM-G900H'], 109 'enable_command': ( 110 'chmod 644 /sys/class/power_supply/battery/test_mode && ' 111 'chmod 644 /sys/class/power_supply/sec-charger/current_now && ' 112 'echo 0 > /sys/class/power_supply/battery/test_mode && ' 113 'echo 9999 > /sys/class/power_supply/sec-charger/current_now &&' 114 'dumpsys battery reset'), 115 'disable_command': ( 116 'chmod 644 /sys/class/power_supply/battery/test_mode && ' 117 'chmod 644 /sys/class/power_supply/sec-charger/current_now && ' 118 'echo 1 > /sys/class/power_supply/battery/test_mode && ' 119 'echo 0 > /sys/class/power_supply/sec-charger/current_now && ' 120 'dumpsys battery set ac 0 && dumpsys battery set usb 0'), 121 'charge_counter': None, 122 'voltage': '/sys/class/power_supply/sec-fuelgauge/voltage_now', 123 'current': '/sys/class/power_supply/sec-charger/current_now', 124 }, 125 { # Galaxy s6, Galaxy s6, Galaxy s6 edge 126 'name': ['SM-G920F', 'SM-G920V', 'SM-G925V'], 127 'enable_command': ( 128 'chmod 644 /sys/class/power_supply/battery/test_mode && ' 129 'chmod 644 /sys/class/power_supply/max77843-charger/current_now && ' 130 'echo 0 > /sys/class/power_supply/battery/test_mode && ' 131 'echo 9999 > /sys/class/power_supply/max77843-charger/current_now &&' 132 'dumpsys battery reset'), 133 'disable_command': ( 134 'chmod 644 /sys/class/power_supply/battery/test_mode && ' 135 'chmod 644 /sys/class/power_supply/max77843-charger/current_now && ' 136 'echo 1 > /sys/class/power_supply/battery/test_mode && ' 137 'echo 0 > /sys/class/power_supply/max77843-charger/current_now && ' 138 'dumpsys battery set ac 0 && dumpsys battery set usb 0'), 139 'charge_counter': None, 140 'voltage': '/sys/class/power_supply/max77843-fuelgauge/voltage_now', 141 'current': '/sys/class/power_supply/max77843-charger/current_now', 142 }, 143 ] 144 145 # The list of useful dumpsys columns. 146 # Index of the column containing the format version. 147 _DUMP_VERSION_INDEX = 0 148 # Index of the column containing the type of the row. 149 _ROW_TYPE_INDEX = 3 150 # Index of the column containing the uid. 151 _PACKAGE_UID_INDEX = 4 152 # Index of the column containing the application package. 153 _PACKAGE_NAME_INDEX = 5 154 # The column containing the uid of the power data. 155 _PWI_UID_INDEX = 1 156 # The column containing the type of consumption. Only consumption since last 157 # charge are of interest here. 158 _PWI_AGGREGATION_INDEX = 2 159 _PWS_AGGREGATION_INDEX = _PWI_AGGREGATION_INDEX 160 # The column containing the amount of power used, in mah. 161 _PWI_POWER_CONSUMPTION_INDEX = 5 162 _PWS_POWER_CONSUMPTION_INDEX = _PWI_POWER_CONSUMPTION_INDEX 163 164 _MAX_CHARGE_ERROR = 20 165 166 167 class BatteryUtils(object): 168 169 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, 170 default_retries=_DEFAULT_RETRIES): 171 """BatteryUtils constructor. 172 173 Args: 174 device: A DeviceUtils instance. 175 default_timeout: An integer containing the default number of seconds to 176 wait for an operation to complete if no explicit value 177 is provided. 178 default_retries: An integer containing the default number or times an 179 operation should be retried on failure if no explicit 180 value is provided. 181 Raises: 182 TypeError: If it is not passed a DeviceUtils instance. 183 """ 184 if not isinstance(device, device_utils.DeviceUtils): 185 raise TypeError('Must be initialized with DeviceUtils object.') 186 self._device = device 187 self._cache = device.GetClientCache(self.__class__.__name__) 188 self._default_timeout = default_timeout 189 self._default_retries = default_retries 190 191 @decorators.WithTimeoutAndRetriesFromInstance() 192 def SupportsFuelGauge(self, timeout=None, retries=None): 193 """Detect if fuel gauge chip is present. 194 195 Args: 196 timeout: timeout in seconds 197 retries: number of retries 198 199 Returns: 200 True if known fuel gauge files are present. 201 False otherwise. 202 """ 203 self._DiscoverDeviceProfile() 204 return (self._cache['profile']['enable_command'] != None 205 and self._cache['profile']['charge_counter'] != None) 206 207 @decorators.WithTimeoutAndRetriesFromInstance() 208 def GetFuelGaugeChargeCounter(self, timeout=None, retries=None): 209 """Get value of charge_counter on fuel gauge chip. 210 211 Device must have charging disabled for this, not just battery updates 212 disabled. The only device that this currently works with is the nexus 5. 213 214 Args: 215 timeout: timeout in seconds 216 retries: number of retries 217 218 Returns: 219 value of charge_counter for fuel gauge chip in units of nAh. 220 221 Raises: 222 device_errors.CommandFailedError: If fuel gauge chip not found. 223 """ 224 if self.SupportsFuelGauge(): 225 return int(self._device.ReadFile( 226 self._cache['profile']['charge_counter'])) 227 raise device_errors.CommandFailedError( 228 'Unable to find fuel gauge.') 229 230 @decorators.WithTimeoutAndRetriesFromInstance() 231 def GetNetworkData(self, package, timeout=None, retries=None): 232 """Get network data for specific package. 233 234 Args: 235 package: package name you want network data for. 236 timeout: timeout in seconds 237 retries: number of retries 238 239 Returns: 240 Tuple of (sent_data, recieved_data) 241 None if no network data found 242 """ 243 # If device_utils clears cache, cache['uids'] doesn't exist 244 if 'uids' not in self._cache: 245 self._cache['uids'] = {} 246 if package not in self._cache['uids']: 247 self.GetPowerData() 248 if package not in self._cache['uids']: 249 logger.warning('No UID found for %s. Can\'t get network data.', 250 package) 251 return None 252 253 network_data_path = '/proc/uid_stat/%s/' % self._cache['uids'][package] 254 try: 255 send_data = int(self._device.ReadFile(network_data_path + 'tcp_snd')) 256 # If ReadFile throws exception, it means no network data usage file for 257 # package has been recorded. Return 0 sent and 0 received. 258 except device_errors.AdbShellCommandFailedError: 259 logger.warning('No sent data found for package %s', package) 260 send_data = 0 261 try: 262 recv_data = int(self._device.ReadFile(network_data_path + 'tcp_rcv')) 263 except device_errors.AdbShellCommandFailedError: 264 logger.warning('No received data found for package %s', package) 265 recv_data = 0 266 return (send_data, recv_data) 267 268 @decorators.WithTimeoutAndRetriesFromInstance() 269 def GetPowerData(self, timeout=None, retries=None): 270 """Get power data for device. 271 272 Args: 273 timeout: timeout in seconds 274 retries: number of retries 275 276 Returns: 277 Dict containing system power, and a per-package power dict keyed on 278 package names. 279 { 280 'system_total': 23.1, 281 'per_package' : { 282 package_name: { 283 'uid': uid, 284 'data': [1,2,3] 285 }, 286 } 287 } 288 """ 289 if 'uids' not in self._cache: 290 self._cache['uids'] = {} 291 dumpsys_output = self._device.RunShellCommand( 292 ['dumpsys', 'batterystats', '-c'], 293 check_return=True, large_output=True) 294 csvreader = csv.reader(dumpsys_output) 295 pwi_entries = collections.defaultdict(list) 296 system_total = None 297 for entry in csvreader: 298 if entry[_DUMP_VERSION_INDEX] not in ['8', '9']: 299 # Wrong dumpsys version. 300 raise device_errors.DeviceVersionError( 301 'Dumpsys version must be 8 or 9. "%s" found.' 302 % entry[_DUMP_VERSION_INDEX]) 303 if _ROW_TYPE_INDEX < len(entry) and entry[_ROW_TYPE_INDEX] == 'uid': 304 current_package = entry[_PACKAGE_NAME_INDEX] 305 if (self._cache['uids'].get(current_package) 306 and self._cache['uids'].get(current_package) 307 != entry[_PACKAGE_UID_INDEX]): 308 raise device_errors.CommandFailedError( 309 'Package %s found multiple times with different UIDs %s and %s' 310 % (current_package, self._cache['uids'][current_package], 311 entry[_PACKAGE_UID_INDEX])) 312 self._cache['uids'][current_package] = entry[_PACKAGE_UID_INDEX] 313 elif (_PWI_POWER_CONSUMPTION_INDEX < len(entry) 314 and entry[_ROW_TYPE_INDEX] == 'pwi' 315 and entry[_PWI_AGGREGATION_INDEX] == 'l'): 316 pwi_entries[entry[_PWI_UID_INDEX]].append( 317 float(entry[_PWI_POWER_CONSUMPTION_INDEX])) 318 elif (_PWS_POWER_CONSUMPTION_INDEX < len(entry) 319 and entry[_ROW_TYPE_INDEX] == 'pws' 320 and entry[_PWS_AGGREGATION_INDEX] == 'l'): 321 # This entry should only appear once. 322 assert system_total is None 323 system_total = float(entry[_PWS_POWER_CONSUMPTION_INDEX]) 324 325 per_package = {p: {'uid': uid, 'data': pwi_entries[uid]} 326 for p, uid in self._cache['uids'].iteritems()} 327 return {'system_total': system_total, 'per_package': per_package} 328 329 @decorators.WithTimeoutAndRetriesFromInstance() 330 def GetBatteryInfo(self, timeout=None, retries=None): 331 """Gets battery info for the device. 332 333 Args: 334 timeout: timeout in seconds 335 retries: number of retries 336 Returns: 337 A dict containing various battery information as reported by dumpsys 338 battery. 339 """ 340 result = {} 341 # Skip the first line, which is just a header. 342 for line in self._device.RunShellCommand( 343 ['dumpsys', 'battery'], check_return=True)[1:]: 344 # If usb charging has been disabled, an extra line of header exists. 345 if 'UPDATES STOPPED' in line: 346 logger.warning('Dumpsys battery not receiving updates. ' 347 'Run dumpsys battery reset if this is in error.') 348 elif ':' not in line: 349 logger.warning('Unknown line found in dumpsys battery: "%s"', line) 350 else: 351 k, v = line.split(':', 1) 352 result[k.strip()] = v.strip() 353 return result 354 355 @decorators.WithTimeoutAndRetriesFromInstance() 356 def GetCharging(self, timeout=None, retries=None): 357 """Gets the charging state of the device. 358 359 Args: 360 timeout: timeout in seconds 361 retries: number of retries 362 Returns: 363 True if the device is charging, false otherwise. 364 """ 365 battery_info = self.GetBatteryInfo() 366 for k in ('AC powered', 'USB powered', 'Wireless powered'): 367 if (k in battery_info and 368 battery_info[k].lower() in ('true', '1', 'yes')): 369 return True 370 return False 371 372 # TODO(rnephew): Make private when all use cases can use the context manager. 373 @decorators.WithTimeoutAndRetriesFromInstance() 374 def DisableBatteryUpdates(self, timeout=None, retries=None): 375 """Resets battery data and makes device appear like it is not 376 charging so that it will collect power data since last charge. 377 378 Args: 379 timeout: timeout in seconds 380 retries: number of retries 381 382 Raises: 383 device_errors.CommandFailedError: When resetting batterystats fails to 384 reset power values. 385 device_errors.DeviceVersionError: If device is not L or higher. 386 """ 387 def battery_updates_disabled(): 388 return self.GetCharging() is False 389 390 self._ClearPowerData() 391 self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'ac', '0'], 392 check_return=True) 393 self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'usb', '0'], 394 check_return=True) 395 timeout_retry.WaitFor(battery_updates_disabled, wait_period=1) 396 397 # TODO(rnephew): Make private when all use cases can use the context manager. 398 @decorators.WithTimeoutAndRetriesFromInstance() 399 def EnableBatteryUpdates(self, timeout=None, retries=None): 400 """Restarts device charging so that dumpsys no longer collects power data. 401 402 Args: 403 timeout: timeout in seconds 404 retries: number of retries 405 406 Raises: 407 device_errors.DeviceVersionError: If device is not L or higher. 408 """ 409 def battery_updates_enabled(): 410 return (self.GetCharging() 411 or not bool('UPDATES STOPPED' in self._device.RunShellCommand( 412 ['dumpsys', 'battery'], check_return=True))) 413 414 self._device.RunShellCommand(['dumpsys', 'battery', 'reset'], 415 check_return=True) 416 timeout_retry.WaitFor(battery_updates_enabled, wait_period=1) 417 418 @contextlib.contextmanager 419 def BatteryMeasurement(self, timeout=None, retries=None): 420 """Context manager that enables battery data collection. It makes 421 the device appear to stop charging so that dumpsys will start collecting 422 power data since last charge. Once the with block is exited, charging is 423 resumed and power data since last charge is no longer collected. 424 425 Only for devices L and higher. 426 427 Example usage: 428 with BatteryMeasurement(): 429 browser_actions() 430 get_power_data() # report usage within this block 431 after_measurements() # Anything that runs after power 432 # measurements are collected 433 434 Args: 435 timeout: timeout in seconds 436 retries: number of retries 437 438 Raises: 439 device_errors.DeviceVersionError: If device is not L or higher. 440 """ 441 if self._device.build_version_sdk < version_codes.LOLLIPOP: 442 raise device_errors.DeviceVersionError('Device must be L or higher.') 443 try: 444 self.DisableBatteryUpdates(timeout=timeout, retries=retries) 445 yield 446 finally: 447 self.EnableBatteryUpdates(timeout=timeout, retries=retries) 448 449 def _DischargeDevice(self, percent, wait_period=120): 450 """Disables charging and waits for device to discharge given amount 451 452 Args: 453 percent: level of charge to discharge. 454 455 Raises: 456 ValueError: If percent is not between 1 and 99. 457 """ 458 battery_level = int(self.GetBatteryInfo().get('level')) 459 if not 0 < percent < 100: 460 raise ValueError('Discharge amount(%s) must be between 1 and 99' 461 % percent) 462 if battery_level is None: 463 logger.warning('Unable to find current battery level. Cannot discharge.') 464 return 465 # Do not discharge if it would make battery level too low. 466 if percent >= battery_level - 10: 467 logger.warning('Battery is too low or discharge amount requested is too ' 468 'high. Cannot discharge phone %s percent.', percent) 469 return 470 471 self._HardwareSetCharging(False) 472 473 def device_discharged(): 474 self._HardwareSetCharging(True) 475 current_level = int(self.GetBatteryInfo().get('level')) 476 logger.info('current battery level: %s', current_level) 477 if battery_level - current_level >= percent: 478 return True 479 self._HardwareSetCharging(False) 480 return False 481 482 timeout_retry.WaitFor(device_discharged, wait_period=wait_period) 483 484 def ChargeDeviceToLevel(self, level, wait_period=60): 485 """Enables charging and waits for device to be charged to given level. 486 487 Args: 488 level: level of charge to wait for. 489 wait_period: time in seconds to wait between checking. 490 Raises: 491 device_errors.DeviceChargingError: If error while charging is detected. 492 """ 493 self.SetCharging(True) 494 charge_status = { 495 'charge_failure_count': 0, 496 'last_charge_value': 0 497 } 498 def device_charged(): 499 battery_level = self.GetBatteryInfo().get('level') 500 if battery_level is None: 501 logger.warning('Unable to find current battery level.') 502 battery_level = 100 503 else: 504 logger.info('current battery level: %s', battery_level) 505 battery_level = int(battery_level) 506 507 # Use > so that it will not reset if charge is going down. 508 if battery_level > charge_status['last_charge_value']: 509 charge_status['last_charge_value'] = battery_level 510 charge_status['charge_failure_count'] = 0 511 else: 512 charge_status['charge_failure_count'] += 1 513 514 if (not battery_level >= level 515 and charge_status['charge_failure_count'] >= _MAX_CHARGE_ERROR): 516 raise device_errors.DeviceChargingError( 517 'Device not charging properly. Current level:%s Previous level:%s' 518 % (battery_level, charge_status['last_charge_value'])) 519 return battery_level >= level 520 521 timeout_retry.WaitFor(device_charged, wait_period=wait_period) 522 523 def LetBatteryCoolToTemperature(self, target_temp, wait_period=180): 524 """Lets device sit to give battery time to cool down 525 Args: 526 temp: maximum temperature to allow in tenths of degrees c. 527 wait_period: time in seconds to wait between checking. 528 """ 529 def cool_device(): 530 temp = self.GetBatteryInfo().get('temperature') 531 if temp is None: 532 logger.warning('Unable to find current battery temperature.') 533 temp = 0 534 else: 535 logger.info('Current battery temperature: %s', temp) 536 if int(temp) <= target_temp: 537 return True 538 else: 539 if 'Nexus 5' in self._cache['profile']['name']: 540 self._DischargeDevice(1) 541 return False 542 543 self._DiscoverDeviceProfile() 544 self.EnableBatteryUpdates() 545 logger.info('Waiting for the device to cool down to %s (0.1 C)', 546 target_temp) 547 timeout_retry.WaitFor(cool_device, wait_period=wait_period) 548 549 @decorators.WithTimeoutAndRetriesFromInstance() 550 def SetCharging(self, enabled, timeout=None, retries=None): 551 """Enables or disables charging on the device. 552 553 Args: 554 enabled: A boolean indicating whether charging should be enabled or 555 disabled. 556 timeout: timeout in seconds 557 retries: number of retries 558 """ 559 if self.GetCharging() == enabled: 560 logger.warning('Device charging already in expected state: %s', enabled) 561 return 562 563 self._DiscoverDeviceProfile() 564 if enabled: 565 if self._cache['profile']['enable_command']: 566 self._HardwareSetCharging(enabled) 567 else: 568 logger.info('Unable to enable charging via hardware. ' 569 'Falling back to software enabling.') 570 self.EnableBatteryUpdates() 571 else: 572 if self._cache['profile']['enable_command']: 573 self._ClearPowerData() 574 self._HardwareSetCharging(enabled) 575 else: 576 logger.info('Unable to disable charging via hardware. ' 577 'Falling back to software disabling.') 578 self.DisableBatteryUpdates() 579 580 def _HardwareSetCharging(self, enabled, timeout=None, retries=None): 581 """Enables or disables charging on the device. 582 583 Args: 584 enabled: A boolean indicating whether charging should be enabled or 585 disabled. 586 timeout: timeout in seconds 587 retries: number of retries 588 589 Raises: 590 device_errors.CommandFailedError: If method of disabling charging cannot 591 be determined. 592 """ 593 self._DiscoverDeviceProfile() 594 if not self._cache['profile']['enable_command']: 595 raise device_errors.CommandFailedError( 596 'Unable to find charging commands.') 597 598 command = (self._cache['profile']['enable_command'] if enabled 599 else self._cache['profile']['disable_command']) 600 601 def verify_charging(): 602 return self.GetCharging() == enabled 603 604 self._device.RunShellCommand( 605 command, check_return=True, as_root=True, large_output=True) 606 timeout_retry.WaitFor(verify_charging, wait_period=1) 607 608 @contextlib.contextmanager 609 def PowerMeasurement(self, timeout=None, retries=None): 610 """Context manager that enables battery power collection. 611 612 Once the with block is exited, charging is resumed. Will attempt to disable 613 charging at the hardware level, and if that fails will fall back to software 614 disabling of battery updates. 615 616 Only for devices L and higher. 617 618 Example usage: 619 with PowerMeasurement(): 620 browser_actions() 621 get_power_data() # report usage within this block 622 after_measurements() # Anything that runs after power 623 # measurements are collected 624 625 Args: 626 timeout: timeout in seconds 627 retries: number of retries 628 """ 629 try: 630 self.SetCharging(False, timeout=timeout, retries=retries) 631 yield 632 finally: 633 self.SetCharging(True, timeout=timeout, retries=retries) 634 635 def _ClearPowerData(self): 636 """Resets battery data and makes device appear like it is not 637 charging so that it will collect power data since last charge. 638 639 Returns: 640 True if power data cleared. 641 False if power data clearing is not supported (pre-L) 642 643 Raises: 644 device_errors.DeviceVersionError: If power clearing is supported, 645 but fails. 646 """ 647 if self._device.build_version_sdk < version_codes.LOLLIPOP: 648 logger.warning('Dumpsys power data only available on 5.0 and above. ' 649 'Cannot clear power data.') 650 return False 651 652 self._device.RunShellCommand( 653 ['dumpsys', 'battery', 'set', 'usb', '1'], check_return=True) 654 self._device.RunShellCommand( 655 ['dumpsys', 'battery', 'set', 'ac', '1'], check_return=True) 656 657 def test_if_clear(): 658 self._device.RunShellCommand( 659 ['dumpsys', 'batterystats', '--reset'], check_return=True) 660 battery_data = self._device.RunShellCommand( 661 ['dumpsys', 'batterystats', '--charged', '-c'], 662 check_return=True, large_output=True) 663 for line in battery_data: 664 l = line.split(',') 665 if (len(l) > _PWI_POWER_CONSUMPTION_INDEX 666 and l[_ROW_TYPE_INDEX] == 'pwi' 667 and float(l[_PWI_POWER_CONSUMPTION_INDEX]) != 0.0): 668 return False 669 return True 670 671 try: 672 timeout_retry.WaitFor(test_if_clear, wait_period=1) 673 return True 674 finally: 675 self._device.RunShellCommand( 676 ['dumpsys', 'battery', 'reset'], check_return=True) 677 678 def _DiscoverDeviceProfile(self): 679 """Checks and caches device information. 680 681 Returns: 682 True if profile is found, false otherwise. 683 """ 684 685 if 'profile' in self._cache: 686 return True 687 for profile in _DEVICE_PROFILES: 688 if self._device.product_model in profile['name']: 689 self._cache['profile'] = profile 690 return True 691 self._cache['profile'] = { 692 'name': [], 693 'enable_command': None, 694 'disable_command': None, 695 'charge_counter': None, 696 'voltage': None, 697 'current': None, 698 } 699 return False 700