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