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"""Provides a variety of device interactions with power. 5""" 6# pylint: disable=unused-argument 7 8import collections 9import contextlib 10import csv 11import logging 12 13from devil.android import crash_handler 14from devil.android import decorators 15from devil.android import device_errors 16from devil.android import device_utils 17from devil.android.sdk import version_codes 18from devil.utils import timeout_retry 19 20logger = 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 { # Cherry Mobile One 144 'name': ['W6210 (4560MMX_b fingerprint)'], 145 'enable_command': ( 146 'echo "0 0" > /proc/mtk_battery_cmd/current_cmd && ' 147 'dumpsys battery reset'), 148 'disable_command': ( 149 'echo "0 1" > /proc/mtk_battery_cmd/current_cmd && ' 150 'dumpsys battery set ac 0 && dumpsys battery set usb 0'), 151 'charge_counter': None, 152 'voltage': None, 153 'current': None, 154 }, 155] 156 157# The list of useful dumpsys columns. 158# Index of the column containing the format version. 159_DUMP_VERSION_INDEX = 0 160# Index of the column containing the type of the row. 161_ROW_TYPE_INDEX = 3 162# Index of the column containing the uid. 163_PACKAGE_UID_INDEX = 4 164# Index of the column containing the application package. 165_PACKAGE_NAME_INDEX = 5 166# The column containing the uid of the power data. 167_PWI_UID_INDEX = 1 168# The column containing the type of consumption. Only consumption since last 169# charge are of interest here. 170_PWI_AGGREGATION_INDEX = 2 171_PWS_AGGREGATION_INDEX = _PWI_AGGREGATION_INDEX 172# The column containing the amount of power used, in mah. 173_PWI_POWER_CONSUMPTION_INDEX = 5 174_PWS_POWER_CONSUMPTION_INDEX = _PWI_POWER_CONSUMPTION_INDEX 175 176_MAX_CHARGE_ERROR = 20 177 178 179class BatteryUtils(object): 180 def __init__(self, 181 device, 182 default_timeout=_DEFAULT_TIMEOUT, 183 default_retries=_DEFAULT_RETRIES): 184 """BatteryUtils constructor. 185 186 Args: 187 device: A DeviceUtils instance. 188 default_timeout: An integer containing the default number of seconds to 189 wait for an operation to complete if no explicit value 190 is provided. 191 default_retries: An integer containing the default number or times an 192 operation should be retried on failure if no explicit 193 value is provided. 194 Raises: 195 TypeError: If it is not passed a DeviceUtils instance. 196 """ 197 if not isinstance(device, device_utils.DeviceUtils): 198 raise TypeError('Must be initialized with DeviceUtils object.') 199 self._device = device 200 self._cache = device.GetClientCache(self.__class__.__name__) 201 self._default_timeout = default_timeout 202 self._default_retries = default_retries 203 204 @decorators.WithTimeoutAndRetriesFromInstance() 205 def SupportsFuelGauge(self, timeout=None, retries=None): 206 """Detect if fuel gauge chip is present. 207 208 Args: 209 timeout: timeout in seconds 210 retries: number of retries 211 212 Returns: 213 True if known fuel gauge files are present. 214 False otherwise. 215 """ 216 self._DiscoverDeviceProfile() 217 return (self._cache['profile']['enable_command'] != None 218 and self._cache['profile']['charge_counter'] != None) 219 220 @decorators.WithTimeoutAndRetriesFromInstance() 221 def GetFuelGaugeChargeCounter(self, timeout=None, retries=None): 222 """Get value of charge_counter on fuel gauge chip. 223 224 Device must have charging disabled for this, not just battery updates 225 disabled. The only device that this currently works with is the nexus 5. 226 227 Args: 228 timeout: timeout in seconds 229 retries: number of retries 230 231 Returns: 232 value of charge_counter for fuel gauge chip in units of nAh. 233 234 Raises: 235 device_errors.CommandFailedError: If fuel gauge chip not found. 236 """ 237 if self.SupportsFuelGauge(): 238 return int( 239 self._device.ReadFile(self._cache['profile']['charge_counter'])) 240 raise device_errors.CommandFailedError('Unable to find fuel gauge.') 241 242 @decorators.WithTimeoutAndRetriesFromInstance() 243 def GetPowerData(self, timeout=None, retries=None): 244 """Get power data for device. 245 246 Args: 247 timeout: timeout in seconds 248 retries: number of retries 249 250 Returns: 251 Dict containing system power, and a per-package power dict keyed on 252 package names. 253 { 254 'system_total': 23.1, 255 'per_package' : { 256 package_name: { 257 'uid': uid, 258 'data': [1,2,3] 259 }, 260 } 261 } 262 """ 263 if 'uids' not in self._cache: 264 self._cache['uids'] = {} 265 dumpsys_output = self._device.RunShellCommand( 266 ['dumpsys', 'batterystats', '-c'], check_return=True, large_output=True) 267 csvreader = csv.reader(dumpsys_output) 268 pwi_entries = collections.defaultdict(list) 269 system_total = None 270 for entry in csvreader: 271 if entry[_DUMP_VERSION_INDEX] not in ['8', '9']: 272 # Wrong dumpsys version. 273 raise device_errors.DeviceVersionError( 274 'Dumpsys version must be 8 or 9. "%s" found.' % 275 entry[_DUMP_VERSION_INDEX]) 276 if _ROW_TYPE_INDEX < len(entry) and entry[_ROW_TYPE_INDEX] == 'uid': 277 current_package = entry[_PACKAGE_NAME_INDEX] 278 if (self._cache['uids'].get(current_package) 279 and self._cache['uids'].get(current_package) != 280 entry[_PACKAGE_UID_INDEX]): 281 raise device_errors.CommandFailedError( 282 'Package %s found multiple times with different UIDs %s and %s' % 283 (current_package, self._cache['uids'][current_package], 284 entry[_PACKAGE_UID_INDEX])) 285 self._cache['uids'][current_package] = entry[_PACKAGE_UID_INDEX] 286 elif (_PWI_POWER_CONSUMPTION_INDEX < len(entry) 287 and entry[_ROW_TYPE_INDEX] == 'pwi' 288 and entry[_PWI_AGGREGATION_INDEX] == 'l'): 289 pwi_entries[entry[_PWI_UID_INDEX]].append( 290 float(entry[_PWI_POWER_CONSUMPTION_INDEX])) 291 elif (_PWS_POWER_CONSUMPTION_INDEX < len(entry) 292 and entry[_ROW_TYPE_INDEX] == 'pws' 293 and entry[_PWS_AGGREGATION_INDEX] == 'l'): 294 # This entry should only appear once. 295 assert system_total is None 296 system_total = float(entry[_PWS_POWER_CONSUMPTION_INDEX]) 297 298 per_package = { 299 p: { 300 'uid': uid, 301 'data': pwi_entries[uid] 302 } 303 for p, uid in self._cache['uids'].items() 304 } 305 return {'system_total': system_total, 'per_package': per_package} 306 307 @decorators.WithTimeoutAndRetriesFromInstance() 308 def GetBatteryInfo(self, timeout=None, retries=None): 309 """Gets battery info for the device. 310 311 Args: 312 timeout: timeout in seconds 313 retries: number of retries 314 Returns: 315 A dict containing various battery information as reported by dumpsys 316 battery. 317 """ 318 result = {} 319 # Skip the first line, which is just a header. 320 for line in self._device.RunShellCommand(['dumpsys', 'battery'], 321 check_return=True)[1:]: 322 # If usb charging has been disabled, an extra line of header exists. 323 if 'UPDATES STOPPED' in line: 324 logger.warning('Dumpsys battery not receiving updates. ' 325 'Run dumpsys battery reset if this is in error.') 326 elif ':' not in line: 327 logger.warning('Unknown line found in dumpsys battery: "%s"', line) 328 else: 329 k, v = line.split(':', 1) 330 result[k.strip()] = v.strip() 331 return result 332 333 @decorators.WithTimeoutAndRetriesFromInstance() 334 def GetCharging(self, timeout=None, retries=None): 335 """Gets the charging state of the device. 336 337 Args: 338 timeout: timeout in seconds 339 retries: number of retries 340 Returns: 341 True if the device is charging, false otherwise. 342 """ 343 344 # Wrapper function so that we can use `RetryOnSystemCrash`. 345 def GetBatteryInfoHelper(device): 346 return self.GetBatteryInfo() 347 348 battery_info = crash_handler.RetryOnSystemCrash(GetBatteryInfoHelper, 349 self._device) 350 for k in ('AC powered', 'USB powered', 'Wireless powered'): 351 if (k in battery_info 352 and battery_info[k].lower() in ('true', '1', 'yes')): 353 return True 354 return False 355 356 # TODO(rnephew): Make private when all use cases can use the context manager. 357 @decorators.WithTimeoutAndRetriesFromInstance() 358 def DisableBatteryUpdates(self, timeout=None, retries=None): 359 """Resets battery data and makes device appear like it is not 360 charging so that it will collect power data since last charge. 361 362 Args: 363 timeout: timeout in seconds 364 retries: number of retries 365 366 Raises: 367 device_errors.CommandFailedError: When resetting batterystats fails to 368 reset power values. 369 device_errors.DeviceVersionError: If device is not L or higher. 370 """ 371 372 def battery_updates_disabled(): 373 return self.GetCharging() is False 374 375 self._ClearPowerData() 376 self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'ac', '0'], 377 check_return=True) 378 self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'usb', '0'], 379 check_return=True) 380 timeout_retry.WaitFor(battery_updates_disabled, wait_period=1) 381 382 # TODO(rnephew): Make private when all use cases can use the context manager. 383 @decorators.WithTimeoutAndRetriesFromInstance() 384 def EnableBatteryUpdates(self, timeout=None, retries=None): 385 """Restarts device charging so that dumpsys no longer collects power data. 386 387 Args: 388 timeout: timeout in seconds 389 retries: number of retries 390 391 Raises: 392 device_errors.DeviceVersionError: If device is not L or higher. 393 """ 394 395 def battery_updates_enabled(): 396 return (self.GetCharging() 397 or not bool('UPDATES STOPPED' in self._device.RunShellCommand( 398 ['dumpsys', 'battery'], check_return=True))) 399 400 self._device.RunShellCommand(['dumpsys', 'battery', 'reset'], 401 check_return=True) 402 timeout_retry.WaitFor(battery_updates_enabled, wait_period=1) 403 404 @contextlib.contextmanager 405 def BatteryMeasurement(self, timeout=None, retries=None): 406 """Context manager that enables battery data collection. It makes 407 the device appear to stop charging so that dumpsys will start collecting 408 power data since last charge. Once the with block is exited, charging is 409 resumed and power data since last charge is no longer collected. 410 411 Only for devices L and higher. 412 413 Example usage: 414 with BatteryMeasurement(): 415 browser_actions() 416 get_power_data() # report usage within this block 417 after_measurements() # Anything that runs after power 418 # measurements are collected 419 420 Args: 421 timeout: timeout in seconds 422 retries: number of retries 423 424 Raises: 425 device_errors.DeviceVersionError: If device is not L or higher. 426 """ 427 if self._device.build_version_sdk < version_codes.LOLLIPOP: 428 raise device_errors.DeviceVersionError('Device must be L or higher.') 429 try: 430 self.DisableBatteryUpdates(timeout=timeout, retries=retries) 431 yield 432 finally: 433 self.EnableBatteryUpdates(timeout=timeout, retries=retries) 434 435 def _DischargeDevice(self, percent, wait_period=120): 436 """Disables charging and waits for device to discharge given amount 437 438 Args: 439 percent: level of charge to discharge. 440 441 Raises: 442 ValueError: If percent is not between 1 and 99. 443 """ 444 battery_level = int(self.GetBatteryInfo().get('level')) 445 if not 0 < percent < 100: 446 raise ValueError( 447 'Discharge amount(%s) must be between 1 and 99' % percent) 448 if battery_level is None: 449 logger.warning('Unable to find current battery level. Cannot discharge.') 450 return 451 # Do not discharge if it would make battery level too low. 452 if percent >= battery_level - 10: 453 logger.warning( 454 'Battery is too low or discharge amount requested is too ' 455 'high. Cannot discharge phone %s percent.', percent) 456 return 457 458 self._HardwareSetCharging(False) 459 460 def device_discharged(): 461 self._HardwareSetCharging(True) 462 current_level = int(self.GetBatteryInfo().get('level')) 463 logger.info('current battery level: %s', current_level) 464 if battery_level - current_level >= percent: 465 return True 466 self._HardwareSetCharging(False) 467 return False 468 469 timeout_retry.WaitFor(device_discharged, wait_period=wait_period) 470 471 def ChargeDeviceToLevel(self, level, wait_period=60): 472 """Enables charging and waits for device to be charged to given level. 473 474 Args: 475 level: level of charge to wait for. 476 wait_period: time in seconds to wait between checking. 477 Raises: 478 device_errors.DeviceChargingError: If error while charging is detected. 479 """ 480 self.SetCharging(True) 481 charge_status = {'charge_failure_count': 0, 'last_charge_value': 0} 482 483 def device_charged(): 484 battery_level = self.GetBatteryInfo().get('level') 485 if battery_level is None: 486 logger.warning('Unable to find current battery level.') 487 battery_level = 100 488 else: 489 logger.info('current battery level: %s', battery_level) 490 battery_level = int(battery_level) 491 492 # Use > so that it will not reset if charge is going down. 493 if battery_level > charge_status['last_charge_value']: 494 charge_status['last_charge_value'] = battery_level 495 charge_status['charge_failure_count'] = 0 496 else: 497 charge_status['charge_failure_count'] += 1 498 499 if (not battery_level >= level 500 and charge_status['charge_failure_count'] >= _MAX_CHARGE_ERROR): 501 raise device_errors.DeviceChargingError( 502 'Device not charging properly. Current level:%s Previous level:%s' % 503 (battery_level, charge_status['last_charge_value'])) 504 return battery_level >= level 505 506 timeout_retry.WaitFor(device_charged, wait_period=wait_period) 507 508 def LetBatteryCoolToTemperature(self, target_temp, wait_period=180): 509 """Lets device sit to give battery time to cool down 510 Args: 511 temp: maximum temperature to allow in tenths of degrees c. 512 wait_period: time in seconds to wait between checking. 513 """ 514 515 def cool_device(): 516 temp = self.GetBatteryInfo().get('temperature') 517 if temp is None: 518 logger.warning('Unable to find current battery temperature.') 519 temp = 0 520 else: 521 logger.info('Current battery temperature: %s', temp) 522 if int(temp) <= target_temp: 523 return True 524 else: 525 if 'Nexus 5' in self._cache['profile']['name']: 526 self._DischargeDevice(1) 527 return False 528 529 self._DiscoverDeviceProfile() 530 self.EnableBatteryUpdates() 531 logger.info('Waiting for the device to cool down to %s (0.1 C)', 532 target_temp) 533 timeout_retry.WaitFor(cool_device, wait_period=wait_period) 534 535 @decorators.WithTimeoutAndRetriesFromInstance() 536 def SetCharging(self, enabled, timeout=None, retries=None): 537 """Enables or disables charging on the device. 538 539 Args: 540 enabled: A boolean indicating whether charging should be enabled or 541 disabled. 542 timeout: timeout in seconds 543 retries: number of retries 544 """ 545 if self.GetCharging() == enabled: 546 logger.warning('Device charging already in expected state: %s', enabled) 547 return 548 549 self._DiscoverDeviceProfile() 550 if enabled: 551 if self._cache['profile']['enable_command']: 552 self._HardwareSetCharging(enabled) 553 else: 554 logger.info('Unable to enable charging via hardware. ' 555 'Falling back to software enabling.') 556 self.EnableBatteryUpdates() 557 else: 558 if self._cache['profile']['enable_command']: 559 self._ClearPowerData() 560 self._HardwareSetCharging(enabled) 561 else: 562 logger.info('Unable to disable charging via hardware. ' 563 'Falling back to software disabling.') 564 self.DisableBatteryUpdates() 565 566 def _HardwareSetCharging(self, enabled, timeout=None, retries=None): 567 """Enables or disables charging on the device. 568 569 Args: 570 enabled: A boolean indicating whether charging should be enabled or 571 disabled. 572 timeout: timeout in seconds 573 retries: number of retries 574 575 Raises: 576 device_errors.CommandFailedError: If method of disabling charging cannot 577 be determined. 578 """ 579 self._DiscoverDeviceProfile() 580 if not self._cache['profile']['enable_command']: 581 raise device_errors.CommandFailedError( 582 'Unable to find charging commands.') 583 584 command = (self._cache['profile']['enable_command'] 585 if enabled else self._cache['profile']['disable_command']) 586 587 def verify_charging(): 588 return self.GetCharging() == enabled 589 590 self._device.RunShellCommand( 591 command, shell=True, check_return=True, as_root=True, large_output=True) 592 timeout_retry.WaitFor(verify_charging, wait_period=1) 593 594 @contextlib.contextmanager 595 def PowerMeasurement(self, timeout=None, retries=None): 596 """Context manager that enables battery power collection. 597 598 Once the with block is exited, charging is resumed. Will attempt to disable 599 charging at the hardware level, and if that fails will fall back to software 600 disabling of battery updates. 601 602 Only for devices L and higher. 603 604 Example usage: 605 with PowerMeasurement(): 606 browser_actions() 607 get_power_data() # report usage within this block 608 after_measurements() # Anything that runs after power 609 # measurements are collected 610 611 Args: 612 timeout: timeout in seconds 613 retries: number of retries 614 """ 615 try: 616 self.SetCharging(False, timeout=timeout, retries=retries) 617 yield 618 finally: 619 self.SetCharging(True, timeout=timeout, retries=retries) 620 621 def _ClearPowerData(self): 622 """Resets battery data and makes device appear like it is not 623 charging so that it will collect power data since last charge. 624 625 Returns: 626 True if power data cleared. 627 False if power data clearing is not supported (pre-L) 628 629 Raises: 630 device_errors.DeviceVersionError: If power clearing is supported, 631 but fails. 632 """ 633 if self._device.build_version_sdk < version_codes.LOLLIPOP: 634 logger.warning('Dumpsys power data only available on 5.0 and above. ' 635 'Cannot clear power data.') 636 return False 637 638 self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'usb', '1'], 639 check_return=True) 640 self._device.RunShellCommand(['dumpsys', 'battery', 'set', 'ac', '1'], 641 check_return=True) 642 643 def test_if_clear(): 644 self._device.RunShellCommand(['dumpsys', 'batterystats', '--reset'], 645 check_return=True) 646 battery_data = self._device.RunShellCommand( 647 ['dumpsys', 'batterystats', '--charged', '-c'], 648 check_return=True, 649 large_output=True) 650 for line in battery_data: 651 l = line.split(',') 652 if (len(l) > _PWI_POWER_CONSUMPTION_INDEX 653 and l[_ROW_TYPE_INDEX] == 'pwi' 654 and float(l[_PWI_POWER_CONSUMPTION_INDEX]) != 0.0): 655 return False 656 return True 657 658 try: 659 timeout_retry.WaitFor(test_if_clear, wait_period=1) 660 return True 661 finally: 662 self._device.RunShellCommand(['dumpsys', 'battery', 'reset'], 663 check_return=True) 664 665 def _DiscoverDeviceProfile(self): 666 """Checks and caches device information. 667 668 Returns: 669 True if profile is found, false otherwise. 670 """ 671 672 if 'profile' in self._cache: 673 return True 674 for profile in _DEVICE_PROFILES: 675 if self._device.product_model in profile['name']: 676 self._cache['profile'] = profile 677 return True 678 self._cache['profile'] = { 679 'name': [], 680 'enable_command': None, 681 'disable_command': None, 682 'charge_counter': None, 683 'voltage': None, 684 'current': None, 685 } 686 return False 687