• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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]
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
167class 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