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