android_commands.py revision 116680a4aac90f2aa7413d9095a592090648e557
1# Copyright (c) 2012 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 an interface to communicate with the device via the adb command.
6
7Assumes adb binary is currently on system path.
8"""
9# pylint: disable-all
10
11import collections
12import datetime
13import inspect
14import logging
15import os
16import random
17import re
18import shlex
19import signal
20import subprocess
21import sys
22import tempfile
23import time
24
25import cmd_helper
26import constants
27import system_properties
28from utils import host_utils
29
30try:
31  from pylib import pexpect
32except ImportError:
33  pexpect = None
34
35sys.path.append(os.path.join(
36    constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner'))
37import adb_interface
38import am_instrument_parser
39import errors
40
41from pylib.device import device_blacklist
42from pylib.device import device_errors
43
44# Pattern to search for the next whole line of pexpect output and capture it
45# into a match group. We can't use ^ and $ for line start end with pexpect,
46# see http://www.noah.org/python/pexpect/#doc for explanation why.
47PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r')
48
49# Set the adb shell prompt to be a unique marker that will [hopefully] not
50# appear at the start of any line of a command's output.
51SHELL_PROMPT = '~+~PQ\x17RS~+~'
52
53# Java properties file
54LOCAL_PROPERTIES_PATH = constants.DEVICE_LOCAL_PROPERTIES_PATH
55
56# Property in /data/local.prop that controls Java assertions.
57JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
58
59# Keycode "enum" suitable for passing to AndroidCommands.SendKey().
60KEYCODE_HOME = 3
61KEYCODE_BACK = 4
62KEYCODE_DPAD_UP = 19
63KEYCODE_DPAD_DOWN = 20
64KEYCODE_DPAD_RIGHT = 22
65KEYCODE_ENTER = 66
66KEYCODE_MENU = 82
67
68MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/'
69MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin'
70
71PIE_WRAPPER_PATH = constants.TEST_EXECUTABLE_DIR + '/run_pie'
72
73CONTROL_USB_CHARGING_COMMANDS = [
74  {
75    # Nexus 4
76    'witness_file': '/sys/module/pm8921_charger/parameters/disabled',
77    'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled',
78    'disable_command':
79        'echo 1 > /sys/module/pm8921_charger/parameters/disabled',
80  },
81]
82
83class DeviceTempFile(object):
84  def __init__(self, android_commands, prefix='temp_file', suffix=''):
85    """Find an unused temporary file path in the devices external directory.
86
87    When this object is closed, the file will be deleted on the device.
88    """
89    self.android_commands = android_commands
90    while True:
91      # TODO(cjhopman): This could actually return the same file in multiple
92      # calls if the caller doesn't write to the files immediately. This is
93      # expected to never happen.
94      i = random.randint(0, 1000000)
95      self.name = '%s/%s-%d-%010d%s' % (
96          android_commands.GetExternalStorage(),
97          prefix, int(time.time()), i, suffix)
98      if not android_commands.FileExistsOnDevice(self.name):
99        break
100
101  def __enter__(self):
102    return self
103
104  def __exit__(self, type, value, traceback):
105    self.close()
106
107  def close(self):
108    self.android_commands.RunShellCommand('rm ' + self.name)
109
110
111def GetAVDs():
112  """Returns a list of AVDs."""
113  re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE)
114  avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd']))
115  return avds
116
117def ResetBadDevices():
118  """Removes the blacklist that keeps track of bad devices for a current
119     build.
120  """
121  device_blacklist.ResetBlacklist()
122
123def ExtendBadDevices(devices):
124  """Adds devices to the blacklist that keeps track of bad devices for a
125     current build.
126
127  The devices listed in the bad devices file will not be returned by
128  GetAttachedDevices.
129
130  Args:
131    devices: list of bad devices to be added to the bad devices file.
132  """
133  device_blacklist.ExtendBlacklist(devices)
134
135
136def GetAttachedDevices(hardware=True, emulator=True, offline=False):
137  """Returns a list of attached, android devices and emulators.
138
139  If a preferred device has been set with ANDROID_SERIAL, it will be first in
140  the returned list. The arguments specify what devices to include in the list.
141
142  Example output:
143
144    * daemon not running. starting it now on port 5037 *
145    * daemon started successfully *
146    List of devices attached
147    027c10494100b4d7        device
148    emulator-5554   offline
149
150  Args:
151    hardware: Include attached actual devices that are online.
152    emulator: Include emulators (i.e. AVD's) currently on host.
153    offline: Include devices and emulators that are offline.
154
155  Returns: List of devices.
156  """
157  adb_devices_output = cmd_helper.GetCmdOutput([constants.GetAdbPath(),
158                                                'devices'])
159
160  re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
161  online_devices = re_device.findall(adb_devices_output)
162
163  re_device = re.compile('^(emulator-[0-9]+)\tdevice', re.MULTILINE)
164  emulator_devices = re_device.findall(adb_devices_output)
165
166  re_device = re.compile('^([a-zA-Z0-9_:.-]+)\toffline$', re.MULTILINE)
167  offline_devices = re_device.findall(adb_devices_output)
168
169  devices = []
170  # First determine list of online devices (e.g. hardware and/or emulator).
171  if hardware and emulator:
172    devices = online_devices
173  elif hardware:
174    devices = [device for device in online_devices
175               if device not in emulator_devices]
176  elif emulator:
177    devices = emulator_devices
178
179  # Now add offline devices if offline is true
180  if offline:
181    devices = devices + offline_devices
182
183  # Remove any devices in the blacklist.
184  blacklist = device_blacklist.ReadBlacklist()
185  if len(blacklist):
186    logging.info('Avoiding bad devices %s', ' '.join(blacklist))
187    devices = [device for device in devices if device not in blacklist]
188
189  preferred_device = os.environ.get('ANDROID_SERIAL')
190  if preferred_device in devices:
191    devices.remove(preferred_device)
192    devices.insert(0, preferred_device)
193  return devices
194
195
196def IsDeviceAttached(device):
197  """Return true if the device is attached and online."""
198  return device in GetAttachedDevices()
199
200
201def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
202  """Gets a list of files from `ls` command output.
203
204  Python's os.walk isn't used because it doesn't work over adb shell.
205
206  Args:
207    path: The path to list.
208    ls_output: A list of lines returned by an `ls -lR` command.
209    re_file: A compiled regular expression which parses a line into named groups
210        consisting of at minimum "filename", "date", "time", "size" and
211        optionally "timezone".
212    utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a
213        2-digit string giving the number of UTC offset hours, and MM is a
214        2-digit string giving the number of UTC offset minutes. If the input
215        utc_offset is None, will try to look for the value of "timezone" if it
216        is specified in re_file.
217
218  Returns:
219    A dict of {"name": (size, lastmod), ...} where:
220      name: The file name relative to |path|'s directory.
221      size: The file size in bytes (0 for directories).
222      lastmod: The file last modification date in UTC.
223  """
224  re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path))
225  path_dir = os.path.dirname(path)
226
227  current_dir = ''
228  files = {}
229  for line in ls_output:
230    directory_match = re_directory.match(line)
231    if directory_match:
232      current_dir = directory_match.group('dir')
233      continue
234    file_match = re_file.match(line)
235    if file_match:
236      filename = os.path.join(current_dir, file_match.group('filename'))
237      if filename.startswith(path_dir):
238        filename = filename[len(path_dir) + 1:]
239      lastmod = datetime.datetime.strptime(
240          file_match.group('date') + ' ' + file_match.group('time')[:5],
241          '%Y-%m-%d %H:%M')
242      if not utc_offset and 'timezone' in re_file.groupindex:
243        utc_offset = file_match.group('timezone')
244      if isinstance(utc_offset, str) and len(utc_offset) == 5:
245        utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
246                                       minutes=int(utc_offset[3:5]))
247        if utc_offset[0:1] == '-':
248          utc_delta = -utc_delta
249        lastmod -= utc_delta
250      files[filename] = (int(file_match.group('size')), lastmod)
251  return files
252
253
254def _ParseMd5SumOutput(md5sum_output):
255  """Returns a list of tuples from the provided md5sum output.
256
257  Args:
258    md5sum_output: output directly from md5sum binary.
259
260  Returns:
261    List of namedtuples with attributes |hash| and |path|, where |path| is the
262    absolute path to the file with an Md5Sum of |hash|.
263  """
264  HashAndPath = collections.namedtuple('HashAndPath', ['hash', 'path'])
265  split_lines = [line.split('  ') for line in md5sum_output]
266  return [HashAndPath._make(s) for s in split_lines if len(s) == 2]
267
268
269def _HasAdbPushSucceeded(command_output):
270  """Returns whether adb push has succeeded from the provided output."""
271  # TODO(frankf): We should look at the return code instead of the command
272  # output for many of the commands in this file.
273  if not command_output:
274    return True
275  # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)"
276  # Errors look like this: "failed to copy  ... "
277  if not re.search('^[0-9]', command_output.splitlines()[-1]):
278    logging.critical('PUSH FAILED: ' + command_output)
279    return False
280  return True
281
282
283def GetLogTimestamp(log_line, year):
284  """Returns the timestamp of the given |log_line| in the given year."""
285  try:
286    return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]),
287                                      '%Y-%m-%d %H:%M:%S.%f')
288  except (ValueError, IndexError):
289    logging.critical('Error reading timestamp from ' + log_line)
290    return None
291
292
293class AndroidCommands(object):
294  """Helper class for communicating with Android device via adb."""
295
296  def __init__(self, device=None):
297    """Constructor.
298
299    Args:
300      device: If given, adb commands are only send to the device of this ID.
301          Otherwise commands are sent to all attached devices.
302    """
303    adb_dir = os.path.dirname(constants.GetAdbPath())
304    if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep):
305      # Required by third_party/android_testrunner to call directly 'adb'.
306      os.environ['PATH'] += os.pathsep + adb_dir
307    self._adb = adb_interface.AdbInterface()
308    if device:
309      self._adb.SetTargetSerial(device)
310    self._device = device
311    self._logcat = None
312    self.logcat_process = None
313    self._logcat_tmpoutfile = None
314    self._pushed_files = []
315    self._device_utc_offset = None
316    self._potential_push_size = 0
317    self._actual_push_size = 0
318    self._external_storage = ''
319    self._util_wrapper = ''
320    self._system_properties = system_properties.SystemProperties(self.Adb())
321    self._push_if_needed_cache = {}
322    self._control_usb_charging_command = {
323        'command': None,
324        'cached': False,
325    }
326    self._protected_file_access_method_initialized = None
327    self._privileged_command_runner = None
328    self._pie_wrapper = None
329
330  @property
331  def system_properties(self):
332    return self._system_properties
333
334  def _LogShell(self, cmd):
335    """Logs the adb shell command."""
336    if self._device:
337      device_repr = self._device[-4:]
338    else:
339      device_repr = '????'
340    logging.info('[%s]> %s', device_repr, cmd)
341
342  def Adb(self):
343    """Returns our AdbInterface to avoid us wrapping all its methods."""
344    # TODO(tonyg): Goal should be to git rid of this method by making this API
345    # complete and alleviating the need.
346    return self._adb
347
348  def GetDevice(self):
349    """Returns the device serial."""
350    return self._device
351
352  def IsOnline(self):
353    """Checks whether the device is online.
354
355    Returns:
356      True if device is in 'device' mode, False otherwise.
357    """
358    # TODO(aurimas): revert to using adb get-state when android L adb is fixed.
359    #out = self._adb.SendCommand('get-state')
360    #return out.strip() == 'device'
361
362    out = self._adb.SendCommand('devices')
363    for line in out.split('\n'):
364      if self._device in line and 'device' in line:
365        return True
366    return False
367
368  def IsRootEnabled(self):
369    """Checks if root is enabled on the device."""
370    root_test_output = self.RunShellCommand('ls /root') or ['']
371    return not 'Permission denied' in root_test_output[0]
372
373  def EnableAdbRoot(self):
374    """Enables adb root on the device.
375
376    Returns:
377      True: if output from executing adb root was as expected.
378      False: otherwise.
379    """
380    if self.GetBuildType() == 'user':
381      logging.warning("Can't enable root in production builds with type user")
382      return False
383    else:
384      return_value = self._adb.EnableAdbRoot()
385      # EnableAdbRoot inserts a call for wait-for-device only when adb logcat
386      # output matches what is expected. Just to be safe add a call to
387      # wait-for-device.
388      self._adb.SendCommand('wait-for-device')
389      return return_value
390
391  def GetDeviceYear(self):
392    """Returns the year information of the date on device."""
393    return self.RunShellCommand('date +%Y')[0]
394
395  def GetExternalStorage(self):
396    if not self._external_storage:
397      self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0]
398      if not self._external_storage:
399        raise device_errors.CommandFailedError(
400            ['shell', "'echo $EXTERNAL_STORAGE'"],
401            'Unable to find $EXTERNAL_STORAGE')
402    return self._external_storage
403
404  def WaitForDevicePm(self, timeout=120):
405    """Blocks until the device's package manager is available.
406
407    To workaround http://b/5201039, we restart the shell and retry if the
408    package manager isn't back after 120 seconds.
409
410    Raises:
411      errors.WaitForResponseTimedOutError after max retries reached.
412    """
413    last_err = None
414    retries = 3
415    while retries:
416      try:
417        self._adb.WaitForDevicePm(wait_time=timeout)
418        return  # Success
419      except errors.WaitForResponseTimedOutError as e:
420        last_err = e
421        logging.warning('Restarting and retrying after timeout: %s', e)
422        retries -= 1
423        self.RestartShell()
424    raise last_err # Only reached after max retries, re-raise the last error.
425
426  def RestartShell(self):
427    """Restarts the shell on the device. Does not block for it to return."""
428    self.RunShellCommand('stop')
429    self.RunShellCommand('start')
430
431  def Reboot(self, full_reboot=True):
432    """Reboots the device and waits for the package manager to return.
433
434    Args:
435      full_reboot: Whether to fully reboot the device or just restart the shell.
436    """
437    # TODO(torne): hive can't reboot the device either way without breaking the
438    # connection; work out if we can handle this better
439    if os.environ.get('USING_HIVE'):
440      logging.warning('Ignoring reboot request as we are on hive')
441      return
442    if full_reboot or not self.IsRootEnabled():
443      self._adb.SendCommand('reboot')
444      self._system_properties = system_properties.SystemProperties(self.Adb())
445      timeout = 300
446      retries = 1
447      # Wait for the device to disappear.
448      while retries < 10 and self.IsOnline():
449        time.sleep(1)
450        retries += 1
451    else:
452      self.RestartShell()
453      timeout = 120
454    # To run tests we need at least the package manager and the sd card (or
455    # other external storage) to be ready.
456    self.WaitForDevicePm(timeout)
457    self.WaitForSdCardReady(timeout)
458
459  def Shutdown(self):
460    """Shuts down the device."""
461    self._adb.SendCommand('reboot -p')
462    self._system_properties = system_properties.SystemProperties(self.Adb())
463
464  def Uninstall(self, package):
465    """Uninstalls the specified package from the device.
466
467    Args:
468      package: Name of the package to remove.
469
470    Returns:
471      A status string returned by adb uninstall
472    """
473    uninstall_command = 'uninstall %s' % package
474
475    self._LogShell(uninstall_command)
476    return self._adb.SendCommand(uninstall_command, timeout_time=60)
477
478  def Install(self, package_file_path, reinstall=False):
479    """Installs the specified package to the device.
480
481    Args:
482      package_file_path: Path to .apk file to install.
483      reinstall: Reinstall an existing apk, keeping the data.
484
485    Returns:
486      A status string returned by adb install
487    """
488    assert os.path.isfile(package_file_path), ('<%s> is not file' %
489                                               package_file_path)
490
491    install_cmd = ['install']
492
493    if reinstall:
494      install_cmd.append('-r')
495
496    install_cmd.append(package_file_path)
497    install_cmd = ' '.join(install_cmd)
498
499    self._LogShell(install_cmd)
500    return self._adb.SendCommand(install_cmd,
501                                 timeout_time=2 * 60,
502                                 retry_count=0)
503
504  def ManagedInstall(self, apk_path, keep_data=False, package_name=None,
505                     reboots_on_timeout=2):
506    """Installs specified package and reboots device on timeouts.
507
508    If package_name is supplied, checks if the package is already installed and
509    doesn't reinstall if the apk md5sums match.
510
511    Args:
512      apk_path: Path to .apk file to install.
513      keep_data: Reinstalls instead of uninstalling first, preserving the
514        application data.
515      package_name: Package name (only needed if keep_data=False).
516      reboots_on_timeout: number of time to reboot if package manager is frozen.
517    """
518    # Check if package is already installed and up to date.
519    if package_name:
520      installed_apk_path = self.GetApplicationPath(package_name)
521      if (installed_apk_path and
522          not self.GetFilesChanged(apk_path, installed_apk_path,
523                                   ignore_filenames=True)):
524        logging.info('Skipped install: identical %s APK already installed' %
525            package_name)
526        return
527    # Install.
528    reboots_left = reboots_on_timeout
529    while True:
530      try:
531        if not keep_data:
532          assert package_name
533          self.Uninstall(package_name)
534        install_status = self.Install(apk_path, reinstall=keep_data)
535        if 'Success' in install_status:
536          return
537        else:
538          raise Exception('Install failure: %s' % install_status)
539      except errors.WaitForResponseTimedOutError:
540        print '@@@STEP_WARNINGS@@@'
541        logging.info('Timeout on installing %s on device %s', apk_path,
542                     self._device)
543
544        if reboots_left <= 0:
545          raise Exception('Install timed out')
546
547        # Force a hard reboot on last attempt
548        self.Reboot(full_reboot=(reboots_left == 1))
549        reboots_left -= 1
550
551  def MakeSystemFolderWritable(self):
552    """Remounts the /system folder rw."""
553    out = self._adb.SendCommand('remount')
554    if out.strip() != 'remount succeeded':
555      raise errors.MsgException('Remount failed: %s' % out)
556
557  def RestartAdbdOnDevice(self):
558    logging.info('Restarting adbd on the device...')
559    with DeviceTempFile(self, suffix=".sh") as temp_script_file:
560      host_script_path = os.path.join(constants.DIR_SOURCE_ROOT,
561                                      'build',
562                                      'android',
563                                      'pylib',
564                                      'restart_adbd.sh')
565      self._adb.Push(host_script_path, temp_script_file.name)
566      self.RunShellCommand('. %s' % temp_script_file.name)
567      self._adb.SendCommand('wait-for-device')
568
569  def RestartAdbServer(self):
570    """Restart the adb server."""
571    ret = self.KillAdbServer()
572    if ret != 0:
573      raise errors.MsgException('KillAdbServer: %d' % ret)
574
575    ret = self.StartAdbServer()
576    if ret != 0:
577      raise errors.MsgException('StartAdbServer: %d' % ret)
578
579  @staticmethod
580  def KillAdbServer():
581    """Kill adb server."""
582    adb_cmd = [constants.GetAdbPath(), 'kill-server']
583    ret = cmd_helper.RunCmd(adb_cmd)
584    retry = 0
585    while retry < 3:
586      ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
587      if ret != 0:
588        # pgrep didn't find adb, kill-server succeeded.
589        return 0
590      retry += 1
591      time.sleep(retry)
592    return ret
593
594  def StartAdbServer(self):
595    """Start adb server."""
596    adb_cmd = ['taskset', '-c', '0', constants.GetAdbPath(), 'start-server']
597    ret, _ = cmd_helper.GetCmdStatusAndOutput(adb_cmd)
598    retry = 0
599    while retry < 3:
600      ret, _ = cmd_helper.GetCmdStatusAndOutput(['pgrep', 'adb'])
601      if ret == 0:
602        # pgrep found adb, start-server succeeded.
603        # Waiting for device to reconnect before returning success.
604        self._adb.SendCommand('wait-for-device')
605        return 0
606      retry += 1
607      time.sleep(retry)
608    return ret
609
610  def WaitForSystemBootCompleted(self, wait_time):
611    """Waits for targeted system's boot_completed flag to be set.
612
613    Args:
614      wait_time: time in seconds to wait
615
616    Raises:
617      WaitForResponseTimedOutError if wait_time elapses and flag still not
618      set.
619    """
620    logging.info('Waiting for system boot completed...')
621    self._adb.SendCommand('wait-for-device')
622    # Now the device is there, but system not boot completed.
623    # Query the sys.boot_completed flag with a basic command
624    boot_completed = False
625    attempts = 0
626    wait_period = 5
627    while not boot_completed and (attempts * wait_period) < wait_time:
628      output = self.system_properties['sys.boot_completed']
629      output = output.strip()
630      if output == '1':
631        boot_completed = True
632      else:
633        # If 'error: xxx' returned when querying the flag, it means
634        # adb server lost the connection to the emulator, so restart the adb
635        # server.
636        if 'error:' in output:
637          self.RestartAdbServer()
638        time.sleep(wait_period)
639        attempts += 1
640    if not boot_completed:
641      raise errors.WaitForResponseTimedOutError(
642          'sys.boot_completed flag was not set after %s seconds' % wait_time)
643
644  def WaitForSdCardReady(self, timeout_time):
645    """Wait for the SD card ready before pushing data into it."""
646    logging.info('Waiting for SD card ready...')
647    sdcard_ready = False
648    attempts = 0
649    wait_period = 5
650    external_storage = self.GetExternalStorage()
651    while not sdcard_ready and attempts * wait_period < timeout_time:
652      output = self.RunShellCommand('ls ' + external_storage)
653      if output:
654        sdcard_ready = True
655      else:
656        time.sleep(wait_period)
657        attempts += 1
658    if not sdcard_ready:
659      raise errors.WaitForResponseTimedOutError(
660          'SD card not ready after %s seconds' % timeout_time)
661
662  def GetAndroidToolStatusAndOutput(self, command, lib_path=None, *args, **kw):
663    """Runs a native Android binary, wrapping the command as necessary.
664
665    This is a specialization of GetShellCommandStatusAndOutput, which is meant
666    for running tools/android/ binaries and handle properly: (1) setting the
667    lib path (for component=shared_library), (2) using the PIE wrapper on ICS.
668    See crbug.com/373219 for more context.
669
670    Args:
671      command: String containing the command to send.
672      lib_path: (optional) path to the folder containing the dependent libs.
673      Same other arguments of GetCmdStatusAndOutput.
674    """
675    # The first time this command is run the device is inspected to check
676    # whether a wrapper for running PIE executable is needed (only Android ICS)
677    # or not. The results is cached, so the wrapper is pushed only once.
678    if self._pie_wrapper is None:
679      # None: did not check; '': did check and not needed; '/path': use /path.
680      self._pie_wrapper = ''
681      if self.GetBuildId().startswith('I'):  # Ixxxx = Android ICS.
682        run_pie_dist_path = os.path.join(constants.GetOutDirectory(), 'run_pie')
683        assert os.path.exists(run_pie_dist_path), 'Please build run_pie'
684        # The PIE loader must be pushed manually (i.e. no PushIfNeeded) because
685        # PushIfNeeded requires md5sum and md5sum requires the wrapper as well.
686        command = 'push %s %s' % (run_pie_dist_path, PIE_WRAPPER_PATH)
687        assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
688        self._pie_wrapper = PIE_WRAPPER_PATH
689
690    if self._pie_wrapper:
691      command = '%s %s' % (self._pie_wrapper, command)
692    if lib_path:
693      command = 'LD_LIBRARY_PATH=%s %s' % (lib_path, command)
694    return self.GetShellCommandStatusAndOutput(command, *args, **kw)
695
696  # It is tempting to turn this function into a generator, however this is not
697  # possible without using a private (local) adb_shell instance (to ensure no
698  # other command interleaves usage of it), which would defeat the main aim of
699  # being able to reuse the adb shell instance across commands.
700  def RunShellCommand(self, command, timeout_time=20, log_result=False):
701    """Send a command to the adb shell and return the result.
702
703    Args:
704      command: String containing the shell command to send. Must not include
705               the single quotes as we use them to escape the whole command.
706      timeout_time: Number of seconds to wait for command to respond before
707        retrying, used by AdbInterface.SendShellCommand.
708      log_result: Boolean to indicate whether we should log the result of the
709                  shell command.
710
711    Returns:
712      list containing the lines of output received from running the command
713    """
714    self._LogShell(command)
715    if "'" in command:
716      logging.warning(command + " contains ' quotes")
717    result = self._adb.SendShellCommand(
718        "'%s'" % command, timeout_time).splitlines()
719    # TODO(b.kelemen): we should really be able to drop the stderr of the
720    # command or raise an exception based on what the caller wants.
721    result = [ l for l in result if not l.startswith('WARNING') ]
722    if ['error: device not found'] == result:
723      raise errors.DeviceUnresponsiveError('device not found')
724    if log_result:
725      self._LogShell('\n'.join(result))
726    return result
727
728  def GetShellCommandStatusAndOutput(self, command, timeout_time=20,
729                                     log_result=False):
730    """See RunShellCommand() above.
731
732    Returns:
733      The tuple (exit code, list of output lines).
734    """
735    lines = self.RunShellCommand(
736        command + '; echo %$?', timeout_time, log_result)
737    last_line = lines[-1]
738    status_pos = last_line.rfind('%')
739    assert status_pos >= 0
740    status = int(last_line[status_pos + 1:])
741    if status_pos == 0:
742      lines = lines[:-1]
743    else:
744      lines = lines[:-1] + [last_line[:status_pos]]
745    return (status, lines)
746
747  def KillAll(self, process, signum=9, with_su=False):
748    """Android version of killall, connected via adb.
749
750    Args:
751      process: name of the process to kill off.
752      signum: signal to use, 9 (SIGKILL) by default.
753      with_su: wether or not to use su to kill the processes.
754
755    Returns:
756      the number of processes killed
757    """
758    pids = self.ExtractPid(process)
759    if pids:
760      cmd = 'kill -%d %s' % (signum, ' '.join(pids))
761      if with_su:
762        self.RunShellCommandWithSU(cmd)
763      else:
764        self.RunShellCommand(cmd)
765    return len(pids)
766
767  def KillAllBlocking(self, process, timeout_sec, signum=9, with_su=False):
768    """Blocking version of killall, connected via adb.
769
770    This waits until no process matching the corresponding name appears in ps'
771    output anymore.
772
773    Args:
774      process: name of the process to kill off
775      timeout_sec: the timeout in seconds
776      signum: same as |KillAll|
777      with_su: same as |KillAll|
778    Returns:
779      the number of processes killed
780    """
781    processes_killed = self.KillAll(process, signum=signum, with_su=with_su)
782    if processes_killed:
783      elapsed = 0
784      wait_period = 0.1
785      # Note that this doesn't take into account the time spent in ExtractPid().
786      while self.ExtractPid(process) and elapsed < timeout_sec:
787        time.sleep(wait_period)
788        elapsed += wait_period
789      if elapsed >= timeout_sec:
790        return processes_killed - self.ExtractPid(process)
791    return processes_killed
792
793  @staticmethod
794  def _GetActivityCommand(package, activity, wait_for_completion, action,
795                          category, data, extras, trace_file_name, force_stop,
796                          flags):
797    """Creates command to start |package|'s activity on the device.
798
799    Args - as for StartActivity
800
801    Returns:
802      the command to run on the target to start the activity
803    """
804    cmd = 'am start -a %s' % action
805    if force_stop:
806      cmd += ' -S'
807    if wait_for_completion:
808      cmd += ' -W'
809    if category:
810      cmd += ' -c %s' % category
811    if package and activity:
812      cmd += ' -n %s/%s' % (package, activity)
813    if data:
814      cmd += ' -d "%s"' % data
815    if extras:
816      for key in extras:
817        value = extras[key]
818        if isinstance(value, str):
819          cmd += ' --es'
820        elif isinstance(value, bool):
821          cmd += ' --ez'
822        elif isinstance(value, int):
823          cmd += ' --ei'
824        else:
825          raise NotImplementedError(
826              'Need to teach StartActivity how to pass %s extras' % type(value))
827        cmd += ' %s %s' % (key, value)
828    if trace_file_name:
829      cmd += ' --start-profiler ' + trace_file_name
830    if flags:
831      cmd += ' -f %s' % flags
832    return cmd
833
834  def StartActivity(self, package, activity, wait_for_completion=False,
835                    action='android.intent.action.VIEW',
836                    category=None, data=None,
837                    extras=None, trace_file_name=None,
838                    force_stop=False, flags=None):
839    """Starts |package|'s activity on the device.
840
841    Args:
842      package: Name of package to start (e.g. 'com.google.android.apps.chrome').
843      activity: Name of activity (e.g. '.Main' or
844        'com.google.android.apps.chrome.Main').
845      wait_for_completion: wait for the activity to finish launching (-W flag).
846      action: string (e.g. "android.intent.action.MAIN"). Default is VIEW.
847      category: string (e.g. "android.intent.category.HOME")
848      data: Data string to pass to activity (e.g. 'http://www.example.com/').
849      extras: Dict of extras to pass to activity. Values are significant.
850      trace_file_name: If used, turns on and saves the trace to this file name.
851      force_stop: force stop the target app before starting the activity (-S
852        flag).
853    Returns:
854      The output of the underlying command as a list of lines.
855    """
856    cmd = self._GetActivityCommand(package, activity, wait_for_completion,
857                                   action, category, data, extras,
858                                   trace_file_name, force_stop, flags)
859    return self.RunShellCommand(cmd)
860
861  def StartActivityTimed(self, package, activity, wait_for_completion=False,
862                         action='android.intent.action.VIEW',
863                         category=None, data=None,
864                         extras=None, trace_file_name=None,
865                         force_stop=False, flags=None):
866    """Starts |package|'s activity on the device, returning the start time
867
868    Args - as for StartActivity
869
870    Returns:
871      A tuple containing:
872        - the output of the underlying command as a list of lines, and
873        - a timestamp string for the time at which the activity started
874    """
875    cmd = self._GetActivityCommand(package, activity, wait_for_completion,
876                                   action, category, data, extras,
877                                   trace_file_name, force_stop, flags)
878    self.StartMonitoringLogcat()
879    out = self.RunShellCommand('log starting activity; ' + cmd)
880    activity_started_re = re.compile('.*starting activity.*')
881    m = self.WaitForLogMatch(activity_started_re, None)
882    assert m
883    start_line = m.group(0)
884    return (out, GetLogTimestamp(start_line, self.GetDeviceYear()))
885
886  def StartCrashUploadService(self, package):
887    # TODO(frankf): We really need a python wrapper around Intent
888    # to be shared with StartActivity/BroadcastIntent.
889    cmd = (
890      'am startservice -a %s.crash.ACTION_FIND_ALL -n '
891      '%s/%s.crash.MinidumpUploadService' %
892      (constants.PACKAGE_INFO['chrome'].package,
893       package,
894       constants.PACKAGE_INFO['chrome'].package))
895    am_output = self.RunShellCommandWithSU(cmd)
896    assert am_output and 'Starting' in am_output[-1], (
897        'Service failed to start: %s' % am_output)
898    time.sleep(15)
899
900  def BroadcastIntent(self, package, intent, *args):
901    """Send a broadcast intent.
902
903    Args:
904      package: Name of package containing the intent.
905      intent: Name of the intent.
906      args: Optional extra arguments for the intent.
907    """
908    cmd = 'am broadcast -a %s.%s %s' % (package, intent, ' '.join(args))
909    self.RunShellCommand(cmd)
910
911  def GoHome(self):
912    """Tell the device to return to the home screen. Blocks until completion."""
913    self.RunShellCommand('am start -W '
914        '-a android.intent.action.MAIN -c android.intent.category.HOME')
915
916  def CloseApplication(self, package):
917    """Attempt to close down the application, using increasing violence.
918
919    Args:
920      package: Name of the process to kill off, e.g.
921      com.google.android.apps.chrome
922    """
923    self.RunShellCommand('am force-stop ' + package)
924
925  def GetApplicationPath(self, package):
926    """Get the installed apk path on the device for the given package.
927
928    Args:
929      package: Name of the package.
930
931    Returns:
932      Path to the apk on the device if it exists, None otherwise.
933    """
934    pm_path_output  = self.RunShellCommand('pm path ' + package)
935    # The path output contains anything if and only if the package
936    # exists.
937    if pm_path_output:
938      # pm_path_output is of the form: "package:/path/to/foo.apk"
939      return pm_path_output[0].split(':')[1]
940    else:
941      return None
942
943  def ClearApplicationState(self, package):
944    """Closes and clears all state for the given |package|."""
945    # Check that the package exists before clearing it. Necessary because
946    # calling pm clear on a package that doesn't exist may never return.
947    pm_path_output  = self.RunShellCommand('pm path ' + package)
948    # The path output only contains anything if and only if the package exists.
949    if pm_path_output:
950      self.RunShellCommand('pm clear ' + package)
951
952  def SendKeyEvent(self, keycode):
953    """Sends keycode to the device.
954
955    Args:
956      keycode: Numeric keycode to send (see "enum" at top of file).
957    """
958    self.RunShellCommand('input keyevent %d' % keycode)
959
960  def _RunMd5Sum(self, host_path, device_path):
961    """Gets the md5sum of a host path and device path.
962
963    Args:
964      host_path: Path (file or directory) on the host.
965      device_path: Path on the device.
966
967    Returns:
968      A tuple containing lists of the host and device md5sum results as
969      created by _ParseMd5SumOutput().
970    """
971    md5sum_dist_path = os.path.join(constants.GetOutDirectory(),
972                                    'md5sum_dist')
973    assert os.path.exists(md5sum_dist_path), 'Please build md5sum.'
974    md5sum_dist_mtime = os.stat(md5sum_dist_path).st_mtime
975    if (md5sum_dist_path not in self._push_if_needed_cache or
976        self._push_if_needed_cache[md5sum_dist_path] != md5sum_dist_mtime):
977      command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER)
978      assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
979      self._push_if_needed_cache[md5sum_dist_path] = md5sum_dist_mtime
980
981    (_, md5_device_output) = self.GetAndroidToolStatusAndOutput(
982        self._util_wrapper + ' ' + MD5SUM_DEVICE_PATH + ' ' + device_path,
983        lib_path=MD5SUM_DEVICE_FOLDER,
984        timeout_time=2 * 60)
985    device_hash_tuples = _ParseMd5SumOutput(md5_device_output)
986    assert os.path.exists(host_path), 'Local path not found %s' % host_path
987    md5sum_output = cmd_helper.GetCmdOutput(
988        [os.path.join(constants.GetOutDirectory(), 'md5sum_bin_host'),
989         host_path])
990    host_hash_tuples = _ParseMd5SumOutput(md5sum_output.splitlines())
991    return (host_hash_tuples, device_hash_tuples)
992
993  def GetFilesChanged(self, host_path, device_path, ignore_filenames=False):
994    """Compares the md5sum of a host path against a device path.
995
996    Note: Ignores extra files on the device.
997
998    Args:
999      host_path: Path (file or directory) on the host.
1000      device_path: Path on the device.
1001      ignore_filenames: If True only the file contents are considered when
1002          checking whether a file has changed, otherwise the relative path
1003          must also match.
1004
1005    Returns:
1006      A list of tuples of the form (host_path, device_path) for files whose
1007      md5sums do not match.
1008    """
1009
1010    # Md5Sum resolves symbolic links in path names so the calculation of
1011    # relative path names from its output will need the real path names of the
1012    # base directories. Having calculated these they are used throughout the
1013    # function since this makes us less subject to any future changes to Md5Sum.
1014    real_host_path = os.path.realpath(host_path)
1015    real_device_path = self.RunShellCommand('realpath "%s"' % device_path)[0]
1016
1017    host_hash_tuples, device_hash_tuples = self._RunMd5Sum(
1018        real_host_path, real_device_path)
1019
1020    if len(host_hash_tuples) > len(device_hash_tuples):
1021      logging.info('%s files do not exist on the device' %
1022                   (len(host_hash_tuples) - len(device_hash_tuples)))
1023
1024    host_rel = [(os.path.relpath(os.path.normpath(t.path), real_host_path),
1025                 t.hash)
1026                for t in host_hash_tuples]
1027
1028    if os.path.isdir(real_host_path):
1029      def RelToRealPaths(rel_path):
1030        return (os.path.join(real_host_path, rel_path),
1031                os.path.join(real_device_path, rel_path))
1032    else:
1033      assert len(host_rel) == 1
1034      def RelToRealPaths(_):
1035        return (real_host_path, real_device_path)
1036
1037    if ignore_filenames:
1038      # If we are ignoring file names, then we want to push any file for which
1039      # a file with an equivalent MD5 sum does not exist on the device.
1040      device_hashes = set([h.hash for h in device_hash_tuples])
1041      ShouldPush = lambda p, h: h not in device_hashes
1042    else:
1043      # Otherwise, we want to push any file on the host for which a file with
1044      # an equivalent MD5 sum does not exist at the same relative path on the
1045      # device.
1046      device_rel = dict([(os.path.relpath(os.path.normpath(t.path),
1047                                          real_device_path),
1048                          t.hash)
1049                         for t in device_hash_tuples])
1050      ShouldPush = lambda p, h: p not in device_rel or h != device_rel[p]
1051
1052    return [RelToRealPaths(path) for path, host_hash in host_rel
1053            if ShouldPush(path, host_hash)]
1054
1055  def PushIfNeeded(self, host_path, device_path):
1056    """Pushes |host_path| to |device_path|.
1057
1058    Works for files and directories. This method skips copying any paths in
1059    |test_data_paths| that already exist on the device with the same hash.
1060
1061    All pushed files can be removed by calling RemovePushedFiles().
1062    """
1063    MAX_INDIVIDUAL_PUSHES = 50
1064    if not os.path.exists(host_path):
1065      raise device_errors.CommandFailedError(
1066          'Local path not found %s' % host_path, device=str(self))
1067
1068    # See if the file on the host changed since the last push (if any) and
1069    # return early if it didn't. Note that this shortcut assumes that the tests
1070    # on the device don't modify the files.
1071    if not os.path.isdir(host_path):
1072      if host_path in self._push_if_needed_cache:
1073        host_path_mtime = self._push_if_needed_cache[host_path]
1074        if host_path_mtime == os.stat(host_path).st_mtime:
1075          return
1076
1077    size = host_utils.GetRecursiveDiskUsage(host_path)
1078    self._pushed_files.append(device_path)
1079    self._potential_push_size += size
1080
1081    if os.path.isdir(host_path):
1082      self.RunShellCommand('mkdir -p "%s"' % device_path)
1083
1084    changed_files = self.GetFilesChanged(host_path, device_path)
1085    logging.info('Found %d files that need to be pushed to %s',
1086        len(changed_files), device_path)
1087    if not changed_files:
1088      return
1089
1090    def Push(host, device):
1091      # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout
1092      # of 60 seconds which isn't sufficient for a lot of users of this method.
1093      push_command = 'push %s %s' % (host, device)
1094      self._LogShell(push_command)
1095
1096      # Retry push with increasing backoff if the device is busy.
1097      retry = 0
1098      while True:
1099        output = self._adb.SendCommand(push_command, timeout_time=30 * 60)
1100        if _HasAdbPushSucceeded(output):
1101          if not os.path.isdir(host_path):
1102            self._push_if_needed_cache[host] = os.stat(host).st_mtime
1103          return
1104        if retry < 3:
1105          retry += 1
1106          wait_time = 5 * retry
1107          logging.error('Push failed, retrying in %d seconds: %s' %
1108                        (wait_time, output))
1109          time.sleep(wait_time)
1110        else:
1111          raise Exception('Push failed: %s' % output)
1112
1113    diff_size = 0
1114    if len(changed_files) <= MAX_INDIVIDUAL_PUSHES:
1115      diff_size = sum(host_utils.GetRecursiveDiskUsage(f[0])
1116                      for f in changed_files)
1117
1118    # TODO(craigdh): Replace this educated guess with a heuristic that
1119    # approximates the push time for each method.
1120    if len(changed_files) > MAX_INDIVIDUAL_PUSHES or diff_size > 0.5 * size:
1121      self._actual_push_size += size
1122      Push(host_path, device_path)
1123    else:
1124      for f in changed_files:
1125        Push(f[0], f[1])
1126      self._actual_push_size += diff_size
1127
1128  def GetPushSizeInfo(self):
1129    """Get total size of pushes to the device done via PushIfNeeded()
1130
1131    Returns:
1132      A tuple:
1133        1. Total size of push requests to PushIfNeeded (MB)
1134        2. Total size that was actually pushed (MB)
1135    """
1136    return (self._potential_push_size, self._actual_push_size)
1137
1138  def GetFileContents(self, filename, log_result=False):
1139    """Gets contents from the file specified by |filename|."""
1140    return self.RunShellCommand('cat "%s" 2>/dev/null' % filename,
1141                                log_result=log_result)
1142
1143  def SetFileContents(self, filename, contents):
1144    """Writes |contents| to the file specified by |filename|."""
1145    with tempfile.NamedTemporaryFile() as f:
1146      f.write(contents)
1147      f.flush()
1148      self._adb.Push(f.name, filename)
1149
1150  def RunShellCommandWithSU(self, command, timeout_time=20, log_result=False):
1151    return self.RunShellCommand('su -c %s' % command, timeout_time, log_result)
1152
1153  def CanAccessProtectedFileContents(self):
1154    """Returns True if Get/SetProtectedFileContents would work via "su" or adb
1155    shell running as root.
1156
1157    Devices running user builds don't have adb root, but may provide "su" which
1158    can be used for accessing protected files.
1159    """
1160    return (self._GetProtectedFileCommandRunner() != None)
1161
1162  def _GetProtectedFileCommandRunner(self):
1163    """Finds the best method to access protected files on the device.
1164
1165    Returns:
1166      1. None when privileged files cannot be accessed on the device.
1167      2. Otherwise: A function taking a single parameter: a string with command
1168         line arguments. Running that function executes the command with
1169         the appropriate method.
1170    """
1171    if self._protected_file_access_method_initialized:
1172      return self._privileged_command_runner
1173
1174    self._privileged_command_runner = None
1175    self._protected_file_access_method_initialized = True
1176
1177    for cmd in [self.RunShellCommand, self.RunShellCommandWithSU]:
1178      # Get contents of the auxv vector for the init(8) process from a small
1179      # binary file that always exists on linux and is always read-protected.
1180      contents = cmd('cat /proc/1/auxv')
1181      # The leading 4 or 8-bytes of auxv vector is a_type. There are not many
1182      # reserved a_type values, hence byte 2 must always be '\0' for a realistic
1183      # auxv. See /usr/include/elf.h.
1184      if len(contents) > 0 and (contents[0][2] == '\0'):
1185        self._privileged_command_runner = cmd
1186        break
1187    return self._privileged_command_runner
1188
1189  def GetProtectedFileContents(self, filename):
1190    """Gets contents from the protected file specified by |filename|.
1191
1192    This is potentially less efficient than GetFileContents.
1193    """
1194    command = 'cat "%s" 2> /dev/null' % filename
1195    command_runner = self._GetProtectedFileCommandRunner()
1196    if command_runner:
1197      return command_runner(command)
1198    else:
1199      logging.warning('Could not access protected file: %s' % filename)
1200      return []
1201
1202  def SetProtectedFileContents(self, filename, contents):
1203    """Writes |contents| to the protected file specified by |filename|.
1204
1205    This is less efficient than SetFileContents.
1206    """
1207    with DeviceTempFile(self) as temp_file:
1208      with DeviceTempFile(self, suffix=".sh") as temp_script:
1209        # Put the contents in a temporary file
1210        self.SetFileContents(temp_file.name, contents)
1211        # Create a script to copy the file contents to its final destination
1212        self.SetFileContents(temp_script.name,
1213                             'cat %s > %s' % (temp_file.name, filename))
1214
1215        command = 'sh %s' % temp_script.name
1216        command_runner = self._GetProtectedFileCommandRunner()
1217        if command_runner:
1218          return command_runner(command)
1219        else:
1220          logging.warning(
1221              'Could not set contents of protected file: %s' % filename)
1222
1223
1224  def RemovePushedFiles(self):
1225    """Removes all files pushed with PushIfNeeded() from the device."""
1226    for p in self._pushed_files:
1227      self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60)
1228
1229  def ListPathContents(self, path):
1230    """Lists files in all subdirectories of |path|.
1231
1232    Args:
1233      path: The path to list.
1234
1235    Returns:
1236      A dict of {"name": (size, lastmod), ...}.
1237    """
1238    # Example output:
1239    # /foo/bar:
1240    # -rw-r----- user group   102 2011-05-12 12:29:54.131623387 +0100 baz.txt
1241    re_file = re.compile('^-(?P<perms>[^\s]+)\s+'
1242                         '(?P<user>[^\s]+)\s+'
1243                         '(?P<group>[^\s]+)\s+'
1244                         '(?P<size>[^\s]+)\s+'
1245                         '(?P<date>[^\s]+)\s+'
1246                         '(?P<time>[^\s]+)\s+'
1247                         '(?P<filename>[^\s]+)$')
1248    return _GetFilesFromRecursiveLsOutput(
1249        path, self.RunShellCommand('ls -lR %s' % path), re_file,
1250        self.GetUtcOffset())
1251
1252  def GetUtcOffset(self):
1253    if not self._device_utc_offset:
1254      self._device_utc_offset = self.RunShellCommand('date +%z')[0]
1255    return self._device_utc_offset
1256
1257  def SetJavaAssertsEnabled(self, enable):
1258    """Sets or removes the device java assertions property.
1259
1260    Args:
1261      enable: If True the property will be set.
1262
1263    Returns:
1264      True if the file was modified (reboot is required for it to take effect).
1265    """
1266    # First ensure the desired property is persisted.
1267    temp_props_file = tempfile.NamedTemporaryFile()
1268    properties = ''
1269    if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name):
1270      with open(temp_props_file.name) as f:
1271        properties = f.read()
1272    re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
1273                           r'\s*=\s*all\s*$', re.MULTILINE)
1274    if enable != bool(re.search(re_search, properties)):
1275      re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
1276                              r'\s*=\s*\w+\s*$', re.MULTILINE)
1277      properties = re.sub(re_replace, '', properties)
1278      if enable:
1279        properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY
1280
1281      file(temp_props_file.name, 'w').write(properties)
1282      self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH)
1283
1284    # Next, check the current runtime value is what we need, and
1285    # if not, set it and report that a reboot is required.
1286    was_set = 'all' in self.system_properties[JAVA_ASSERT_PROPERTY]
1287    if was_set == enable:
1288      return False
1289    self.system_properties[JAVA_ASSERT_PROPERTY] = enable and 'all' or ''
1290    return True
1291
1292  def GetBuildId(self):
1293    """Returns the build ID of the system (e.g. JRM79C)."""
1294    build_id = self.system_properties['ro.build.id']
1295    assert build_id
1296    return build_id
1297
1298  def GetBuildType(self):
1299    """Returns the build type of the system (e.g. eng)."""
1300    build_type = self.system_properties['ro.build.type']
1301    assert build_type
1302    return build_type
1303
1304  def GetBuildProduct(self):
1305    """Returns the build product of the device (e.g. maguro)."""
1306    build_product = self.system_properties['ro.build.product']
1307    assert build_product
1308    return build_product
1309
1310  def GetProductName(self):
1311    """Returns the product name of the device (e.g. takju)."""
1312    name = self.system_properties['ro.product.name']
1313    assert name
1314    return name
1315
1316  def GetBuildFingerprint(self):
1317    """Returns the build fingerprint of the device."""
1318    build_fingerprint = self.system_properties['ro.build.fingerprint']
1319    assert build_fingerprint
1320    return build_fingerprint
1321
1322  def GetDescription(self):
1323    """Returns the description of the system.
1324
1325    For example, "yakju-userdebug 4.1 JRN54F 364167 dev-keys".
1326    """
1327    description = self.system_properties['ro.build.description']
1328    assert description
1329    return description
1330
1331  def GetProductModel(self):
1332    """Returns the name of the product model (e.g. "Galaxy Nexus") """
1333    model = self.system_properties['ro.product.model']
1334    assert model
1335    return model
1336
1337  def GetWifiIP(self):
1338    """Returns the wifi IP on the device."""
1339    wifi_ip = self.system_properties['dhcp.wlan0.ipaddress']
1340    # Do not assert here. Devices (e.g. emulators) may not have a WifiIP.
1341    return wifi_ip
1342
1343  def GetSubscriberInfo(self):
1344    """Returns the device subscriber info (e.g. GSM and device ID) as string."""
1345    iphone_sub = self.RunShellCommand('dumpsys iphonesubinfo')
1346    # Do not assert here. Devices (e.g. Nakasi on K) may not have iphonesubinfo.
1347    return '\n'.join(iphone_sub)
1348
1349  def GetBatteryInfo(self):
1350    """Returns a {str: str} dict of battery info (e.g. status, level, etc)."""
1351    battery = self.RunShellCommand('dumpsys battery')
1352    assert battery
1353    battery_info = {}
1354    for line in battery[1:]:
1355      k, _, v = line.partition(': ')
1356      battery_info[k.strip()] = v.strip()
1357    return battery_info
1358
1359  def GetSetupWizardStatus(self):
1360    """Returns the status of the device setup wizard (e.g. DISABLED)."""
1361    status = self.system_properties['ro.setupwizard.mode']
1362    # On some devices, the status is empty if not otherwise set. In such cases
1363    # the caller should expect an empty string to be returned.
1364    return status
1365
1366  def StartMonitoringLogcat(self, clear=True, logfile=None, filters=None):
1367    """Starts monitoring the output of logcat, for use with WaitForLogMatch.
1368
1369    Args:
1370      clear: If True the existing logcat output will be cleared, to avoiding
1371             matching historical output lurking in the log.
1372      filters: A list of logcat filters to be used.
1373    """
1374    if clear:
1375      self.RunShellCommand('logcat -c')
1376    args = []
1377    if self._adb._target_arg:
1378      args += shlex.split(self._adb._target_arg)
1379    args += ['logcat', '-v', 'threadtime']
1380    if filters:
1381      args.extend(filters)
1382    else:
1383      args.append('*:v')
1384
1385    if logfile:
1386      logfile = NewLineNormalizer(logfile)
1387
1388    # Spawn logcat and synchronize with it.
1389    for _ in range(4):
1390      self._logcat = pexpect.spawn(constants.GetAdbPath(), args, timeout=10,
1391                                   logfile=logfile)
1392      if not clear or self.SyncLogCat():
1393        break
1394      self._logcat.close(force=True)
1395    else:
1396      logging.critical('Error reading from logcat: ' + str(self._logcat.match))
1397      sys.exit(1)
1398
1399  def SyncLogCat(self):
1400    """Synchronize with logcat.
1401
1402    Synchronize with the monitored logcat so that WaitForLogMatch will only
1403    consider new message that are received after this point in time.
1404
1405    Returns:
1406      True if the synchronization succeeded.
1407    """
1408    assert self._logcat
1409    tag = 'logcat_sync_%s' % time.time()
1410    self.RunShellCommand('log ' + tag)
1411    return self._logcat.expect([tag, pexpect.EOF, pexpect.TIMEOUT]) == 0
1412
1413  def GetMonitoredLogCat(self):
1414    """Returns an "adb logcat" command as created by pexpected.spawn."""
1415    if not self._logcat:
1416      self.StartMonitoringLogcat(clear=False)
1417    return self._logcat
1418
1419  def WaitForLogMatch(self, success_re, error_re, clear=False, timeout=10):
1420    """Blocks until a matching line is logged or a timeout occurs.
1421
1422    Args:
1423      success_re: A compiled re to search each line for.
1424      error_re: A compiled re which, if found, terminates the search for
1425          |success_re|. If None is given, no error condition will be detected.
1426      clear: If True the existing logcat output will be cleared, defaults to
1427          false.
1428      timeout: Timeout in seconds to wait for a log match.
1429
1430    Raises:
1431      pexpect.TIMEOUT after |timeout| seconds without a match for |success_re|
1432      or |error_re|.
1433
1434    Returns:
1435      The re match object if |success_re| is matched first or None if |error_re|
1436      is matched first.
1437    """
1438    logging.info('<<< Waiting for logcat:' + str(success_re.pattern))
1439    t0 = time.time()
1440    while True:
1441      if not self._logcat:
1442        self.StartMonitoringLogcat(clear)
1443      try:
1444        while True:
1445          # Note this will block for upto the timeout _per log line_, so we need
1446          # to calculate the overall timeout remaining since t0.
1447          time_remaining = t0 + timeout - time.time()
1448          if time_remaining < 0:
1449            raise pexpect.TIMEOUT(self._logcat)
1450          self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining)
1451          line = self._logcat.match.group(1)
1452          if error_re:
1453            error_match = error_re.search(line)
1454            if error_match:
1455              return None
1456          success_match = success_re.search(line)
1457          if success_match:
1458            return success_match
1459          logging.info('<<< Skipped Logcat Line:' + str(line))
1460      except pexpect.TIMEOUT:
1461        raise pexpect.TIMEOUT(
1462            'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv '
1463            'to debug)' %
1464            (timeout, success_re.pattern))
1465      except pexpect.EOF:
1466        # It seems that sometimes logcat can end unexpectedly. This seems
1467        # to happen during Chrome startup after a reboot followed by a cache
1468        # clean. I don't understand why this happens, but this code deals with
1469        # getting EOF in logcat.
1470        logging.critical('Found EOF in adb logcat. Restarting...')
1471        # Rerun spawn with original arguments. Note that self._logcat.args[0] is
1472        # the path of adb, so we don't want it in the arguments.
1473        self._logcat = pexpect.spawn(constants.GetAdbPath(),
1474                                     self._logcat.args[1:],
1475                                     timeout=self._logcat.timeout,
1476                                     logfile=self._logcat.logfile)
1477
1478  def StartRecordingLogcat(self, clear=True, filters=None):
1479    """Starts recording logcat output to eventually be saved as a string.
1480
1481    This call should come before some series of tests are run, with either
1482    StopRecordingLogcat or SearchLogcatRecord following the tests.
1483
1484    Args:
1485      clear: True if existing log output should be cleared.
1486      filters: A list of logcat filters to be used.
1487    """
1488    if not filters:
1489      filters = ['*:v']
1490    if clear:
1491      self._adb.SendCommand('logcat -c')
1492    logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg,
1493                                                         ' '.join(filters))
1494    self._logcat_tmpoutfile = tempfile.NamedTemporaryFile(bufsize=0)
1495    self.logcat_process = subprocess.Popen(logcat_command, shell=True,
1496                                           stdout=self._logcat_tmpoutfile)
1497
1498  def GetCurrentRecordedLogcat(self):
1499    """Return the current content of the logcat being recorded.
1500       Call this after StartRecordingLogcat() and before StopRecordingLogcat().
1501       This can be useful to perform timed polling/parsing.
1502    Returns:
1503       Current logcat output as a single string, or None if
1504       StopRecordingLogcat() was already called.
1505    """
1506    if not self._logcat_tmpoutfile:
1507      return None
1508
1509    with open(self._logcat_tmpoutfile.name) as f:
1510      return f.read()
1511
1512  def StopRecordingLogcat(self):
1513    """Stops an existing logcat recording subprocess and returns output.
1514
1515    Returns:
1516      The logcat output as a string or an empty string if logcat was not
1517      being recorded at the time.
1518    """
1519    if not self.logcat_process:
1520      return ''
1521    # Cannot evaluate directly as 0 is a possible value.
1522    # Better to read the self.logcat_process.stdout before killing it,
1523    # Otherwise the communicate may return incomplete output due to pipe break.
1524    if self.logcat_process.poll() is None:
1525      self.logcat_process.kill()
1526    self.logcat_process.wait()
1527    self.logcat_process = None
1528    self._logcat_tmpoutfile.seek(0)
1529    output = self._logcat_tmpoutfile.read()
1530    self._logcat_tmpoutfile.close()
1531    self._logcat_tmpoutfile = None
1532    return output
1533
1534  @staticmethod
1535  def SearchLogcatRecord(record, message, thread_id=None, proc_id=None,
1536                         log_level=None, component=None):
1537    """Searches the specified logcat output and returns results.
1538
1539    This method searches through the logcat output specified by record for a
1540    certain message, narrowing results by matching them against any other
1541    specified criteria.  It returns all matching lines as described below.
1542
1543    Args:
1544      record: A string generated by Start/StopRecordingLogcat to search.
1545      message: An output string to search for.
1546      thread_id: The thread id that is the origin of the message.
1547      proc_id: The process that is the origin of the message.
1548      log_level: The log level of the message.
1549      component: The name of the component that would create the message.
1550
1551    Returns:
1552      A list of dictionaries represeting matching entries, each containing keys
1553      thread_id, proc_id, log_level, component, and message.
1554    """
1555    if thread_id:
1556      thread_id = str(thread_id)
1557    if proc_id:
1558      proc_id = str(proc_id)
1559    results = []
1560    reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$',
1561                     re.MULTILINE)
1562    log_list = reg.findall(record)
1563    for (tid, pid, log_lev, comp, msg) in log_list:
1564      if ((not thread_id or thread_id == tid) and
1565          (not proc_id or proc_id == pid) and
1566          (not log_level or log_level == log_lev) and
1567          (not component or component == comp) and msg.find(message) > -1):
1568        match = dict({'thread_id': tid, 'proc_id': pid,
1569                      'log_level': log_lev, 'component': comp,
1570                      'message': msg})
1571        results.append(match)
1572    return results
1573
1574  def ExtractPid(self, process_name):
1575    """Extracts Process Ids for a given process name from Android Shell.
1576
1577    Args:
1578      process_name: name of the process on the device.
1579
1580    Returns:
1581      List of all the process ids (as strings) that match the given name.
1582      If the name of a process exactly matches the given name, the pid of
1583      that process will be inserted to the front of the pid list.
1584    """
1585    pids = []
1586    for line in self.RunShellCommand('ps', log_result=False):
1587      data = line.split()
1588      try:
1589        if process_name in data[-1]:  # name is in the last column
1590          if process_name == data[-1]:
1591            pids.insert(0, data[1])  # PID is in the second column
1592          else:
1593            pids.append(data[1])
1594      except IndexError:
1595        pass
1596    return pids
1597
1598  def GetIoStats(self):
1599    """Gets cumulative disk IO stats since boot (for all processes).
1600
1601    Returns:
1602      Dict of {num_reads, num_writes, read_ms, write_ms} or None if there
1603      was an error.
1604    """
1605    IoStats = collections.namedtuple(
1606        'IoStats',
1607        ['device',
1608         'num_reads_issued',
1609         'num_reads_merged',
1610         'num_sectors_read',
1611         'ms_spent_reading',
1612         'num_writes_completed',
1613         'num_writes_merged',
1614         'num_sectors_written',
1615         'ms_spent_writing',
1616         'num_ios_in_progress',
1617         'ms_spent_doing_io',
1618         'ms_spent_doing_io_weighted',
1619        ])
1620
1621    for line in self.GetFileContents('/proc/diskstats', log_result=False):
1622      fields = line.split()
1623      stats = IoStats._make([fields[2]] + [int(f) for f in fields[3:]])
1624      if stats.device == 'mmcblk0':
1625        return {
1626            'num_reads': stats.num_reads_issued,
1627            'num_writes': stats.num_writes_completed,
1628            'read_ms': stats.ms_spent_reading,
1629            'write_ms': stats.ms_spent_writing,
1630        }
1631    logging.warning('Could not find disk IO stats.')
1632    return None
1633
1634  def GetMemoryUsageForPid(self, pid):
1635    """Returns the memory usage for given pid.
1636
1637    Args:
1638      pid: The pid number of the specific process running on device.
1639
1640    Returns:
1641      Dict of {metric:usage_kb}, for the process which has specified pid.
1642      The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
1643      Shared_Dirty, Private_Clean, Private_Dirty, VmHWM.
1644    """
1645    showmap = self.RunShellCommand('showmap %d' % pid)
1646    if not showmap or not showmap[-1].endswith('TOTAL'):
1647      logging.warning('Invalid output for showmap %s', str(showmap))
1648      return {}
1649    items = showmap[-1].split()
1650    if len(items) != 9:
1651      logging.warning('Invalid TOTAL for showmap %s', str(items))
1652      return {}
1653    usage_dict = collections.defaultdict(int)
1654    usage_dict.update({
1655        'Size': int(items[0].strip()),
1656        'Rss': int(items[1].strip()),
1657        'Pss': int(items[2].strip()),
1658        'Shared_Clean': int(items[3].strip()),
1659        'Shared_Dirty': int(items[4].strip()),
1660        'Private_Clean': int(items[5].strip()),
1661        'Private_Dirty': int(items[6].strip()),
1662    })
1663    peak_value_kb = 0
1664    for line in self.GetProtectedFileContents('/proc/%s/status' % pid):
1665      if not line.startswith('VmHWM:'):  # Format: 'VmHWM: +[0-9]+ kB'
1666        continue
1667      peak_value_kb = int(line.split(':')[1].strip().split(' ')[0])
1668      break
1669    usage_dict['VmHWM'] = peak_value_kb
1670    if not peak_value_kb:
1671      logging.warning('Could not find memory peak value for pid ' + str(pid))
1672
1673    return usage_dict
1674
1675  def ProcessesUsingDevicePort(self, device_port):
1676    """Lists processes using the specified device port on loopback interface.
1677
1678    Args:
1679      device_port: Port on device we want to check.
1680
1681    Returns:
1682      A list of (pid, process_name) tuples using the specified port.
1683    """
1684    tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False)
1685    tcp_address = '0100007F:%04X' % device_port
1686    pids = []
1687    for single_connect in tcp_results:
1688      connect_results = single_connect.split()
1689      # Column 1 is the TCP port, and Column 9 is the inode of the socket
1690      if connect_results[1] == tcp_address:
1691        socket_inode = connect_results[9]
1692        socket_name = 'socket:[%s]' % socket_inode
1693        lsof_results = self.RunShellCommand('lsof', log_result=False)
1694        for single_process in lsof_results:
1695          process_results = single_process.split()
1696          # Ignore the line if it has less than nine columns in it, which may
1697          # be the case when a process stops while lsof is executing.
1698          if len(process_results) <= 8:
1699            continue
1700          # Column 0 is the executable name
1701          # Column 1 is the pid
1702          # Column 8 is the Inode in use
1703          if process_results[8] == socket_name:
1704            pids.append((int(process_results[1]), process_results[0]))
1705        break
1706    logging.info('PidsUsingDevicePort: %s', pids)
1707    return pids
1708
1709  def FileExistsOnDevice(self, file_name):
1710    """Checks whether the given file exists on the device.
1711
1712    Args:
1713      file_name: Full path of file to check.
1714
1715    Returns:
1716      True if the file exists, False otherwise.
1717    """
1718    assert '"' not in file_name, 'file_name cannot contain double quotes'
1719    try:
1720      status = self._adb.SendShellCommand(
1721          '\'test -e "%s"; echo $?\'' % (file_name))
1722      if 'test: not found' not in status:
1723        return int(status) == 0
1724
1725      status = self._adb.SendShellCommand(
1726          '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name))
1727      return int(status) == 0
1728    except ValueError:
1729      if IsDeviceAttached(self._device):
1730        raise errors.DeviceUnresponsiveError('Device may be offline.')
1731
1732      return False
1733
1734  def IsFileWritableOnDevice(self, file_name):
1735    """Checks whether the given file (or directory) is writable on the device.
1736
1737    Args:
1738      file_name: Full path of file/directory to check.
1739
1740    Returns:
1741      True if writable, False otherwise.
1742    """
1743    assert '"' not in file_name, 'file_name cannot contain double quotes'
1744    try:
1745      status = self._adb.SendShellCommand(
1746          '\'test -w "%s"; echo $?\'' % (file_name))
1747      if 'test: not found' not in status:
1748        return int(status) == 0
1749      raise errors.AbortError('"test" binary not found. OS too old.')
1750
1751    except ValueError:
1752      if IsDeviceAttached(self._device):
1753        raise errors.DeviceUnresponsiveError('Device may be offline.')
1754
1755      return False
1756
1757  @staticmethod
1758  def GetTimestamp():
1759    return time.strftime('%Y-%m-%d-%H%M%S', time.localtime())
1760
1761  @staticmethod
1762  def EnsureHostDirectory(host_file):
1763    host_dir = os.path.dirname(os.path.abspath(host_file))
1764    if not os.path.exists(host_dir):
1765      os.makedirs(host_dir)
1766
1767  def TakeScreenshot(self, host_file=None):
1768    """Saves a screenshot image to |host_file| on the host.
1769
1770    Args:
1771      host_file: Absolute path to the image file to store on the host or None to
1772                 use an autogenerated file name.
1773
1774    Returns:
1775      Resulting host file name of the screenshot.
1776    """
1777    host_file = os.path.abspath(host_file or
1778                                'screenshot-%s.png' % self.GetTimestamp())
1779    self.EnsureHostDirectory(host_file)
1780    device_file = '%s/screenshot.png' % self.GetExternalStorage()
1781    self.RunShellCommand(
1782        '/system/bin/screencap -p %s' % device_file)
1783    self.PullFileFromDevice(device_file, host_file)
1784    self.RunShellCommand('rm -f "%s"' % device_file)
1785    return host_file
1786
1787  def PullFileFromDevice(self, device_file, host_file):
1788    """Download |device_file| on the device from to |host_file| on the host.
1789
1790    Args:
1791      device_file: Absolute path to the file to retrieve from the device.
1792      host_file: Absolute path to the file to store on the host.
1793    """
1794    if not self._adb.Pull(device_file, host_file):
1795      raise device_errors.AdbCommandFailedError(
1796          ['pull', device_file, host_file], 'Failed to pull file from device.')
1797    assert os.path.exists(host_file)
1798
1799  def SetUtilWrapper(self, util_wrapper):
1800    """Sets a wrapper prefix to be used when running a locally-built
1801    binary on the device (ex.: md5sum_bin).
1802    """
1803    self._util_wrapper = util_wrapper
1804
1805  def RunInstrumentationTest(self, test, test_package, instr_args, timeout):
1806    """Runs a single instrumentation test.
1807
1808    Args:
1809      test: Test class/method.
1810      test_package: Package name of test apk.
1811      instr_args: Extra key/value to pass to am instrument.
1812      timeout: Timeout time in seconds.
1813
1814    Returns:
1815      An instance of am_instrument_parser.TestResult object.
1816    """
1817    instrumentation_path = ('%s/android.test.InstrumentationTestRunner' %
1818                            test_package)
1819    args_with_filter = dict(instr_args)
1820    args_with_filter['class'] = test
1821    logging.info(args_with_filter)
1822    (raw_results, _) = self._adb.StartInstrumentation(
1823        instrumentation_path=instrumentation_path,
1824        instrumentation_args=args_with_filter,
1825        timeout_time=timeout)
1826    assert len(raw_results) == 1
1827    return raw_results[0]
1828
1829  def RunUIAutomatorTest(self, test, test_package, timeout):
1830    """Runs a single uiautomator test.
1831
1832    Args:
1833      test: Test class/method.
1834      test_package: Name of the test jar.
1835      timeout: Timeout time in seconds.
1836
1837    Returns:
1838      An instance of am_instrument_parser.TestResult object.
1839    """
1840    cmd = 'uiautomator runtest %s -e class %s' % (test_package, test)
1841    self._LogShell(cmd)
1842    output = self._adb.SendShellCommand(cmd, timeout_time=timeout)
1843    # uiautomator doesn't fully conform to the instrumenation test runner
1844    # convention and doesn't terminate with INSTRUMENTATION_CODE.
1845    # Just assume the first result is valid.
1846    (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output)
1847    if not test_results:
1848      raise errors.InstrumentationError(
1849          'no test results... device setup correctly?')
1850    return test_results[0]
1851
1852  def DismissCrashDialogIfNeeded(self):
1853    """Dismiss the error/ANR dialog if present.
1854
1855    Returns: Name of the crashed package if a dialog is focused,
1856             None otherwise.
1857    """
1858    re_focus = re.compile(
1859        r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
1860
1861    def _FindFocusedWindow():
1862      match = None
1863      for line in self.RunShellCommand('dumpsys window windows'):
1864        match = re.match(re_focus, line)
1865        if match:
1866          break
1867      return match
1868
1869    match = _FindFocusedWindow()
1870    if not match:
1871      return
1872    package = match.group(2)
1873    logging.warning('Trying to dismiss %s dialog for %s' % match.groups())
1874    self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
1875    self.SendKeyEvent(KEYCODE_DPAD_RIGHT)
1876    self.SendKeyEvent(KEYCODE_ENTER)
1877    match = _FindFocusedWindow()
1878    if match:
1879      logging.error('Still showing a %s dialog for %s' % match.groups())
1880    return package
1881
1882  def EfficientDeviceDirectoryCopy(self, source, dest):
1883    """ Copy a directory efficiently on the device
1884
1885    Uses a shell script running on the target to copy new and changed files the
1886    source directory to the destination directory and remove added files. This
1887    is in some cases much faster than cp -r.
1888
1889    Args:
1890      source: absolute path of source directory
1891      dest: absolute path of destination directory
1892    """
1893    logging.info('In EfficientDeviceDirectoryCopy %s %s', source, dest)
1894    with DeviceTempFile(self, suffix=".sh") as temp_script_file:
1895      host_script_path = os.path.join(constants.DIR_SOURCE_ROOT,
1896                                      'build',
1897                                      'android',
1898                                      'pylib',
1899                                      'efficient_android_directory_copy.sh')
1900      self._adb.Push(host_script_path, temp_script_file.name)
1901      out = self.RunShellCommand(
1902          'sh %s %s %s' % (temp_script_file.name, source, dest),
1903          timeout_time=120)
1904      if self._device:
1905        device_repr = self._device[-4:]
1906      else:
1907        device_repr = '????'
1908      for line in out:
1909        logging.info('[%s]> %s', device_repr, line)
1910
1911  def _GetControlUsbChargingCommand(self):
1912    if self._control_usb_charging_command['cached']:
1913      return self._control_usb_charging_command['command']
1914    self._control_usb_charging_command['cached'] = True
1915    if not self.IsRootEnabled():
1916      return None
1917    for command in CONTROL_USB_CHARGING_COMMANDS:
1918      # Assert command is valid.
1919      assert 'disable_command' in command
1920      assert 'enable_command' in command
1921      assert 'witness_file' in command
1922      witness_file = command['witness_file']
1923      if self.FileExistsOnDevice(witness_file):
1924        self._control_usb_charging_command['command'] = command
1925        return command
1926    return None
1927
1928  def CanControlUsbCharging(self):
1929    return self._GetControlUsbChargingCommand() is not None
1930
1931  def DisableUsbCharging(self, timeout=10):
1932    command = self._GetControlUsbChargingCommand()
1933    if not command:
1934      raise Exception('Unable to act on usb charging.')
1935    disable_command = command['disable_command']
1936    t0 = time.time()
1937    # Do not loop directly on self.IsDeviceCharging to cut the number of calls
1938    # to the device.
1939    while True:
1940      if t0 + timeout - time.time() < 0:
1941        raise pexpect.TIMEOUT('Unable to enable USB charging in time.')
1942      self.RunShellCommand(disable_command)
1943      if not self.IsDeviceCharging():
1944        break
1945
1946  def EnableUsbCharging(self, timeout=10):
1947    command = self._GetControlUsbChargingCommand()
1948    if not command:
1949      raise Exception('Unable to act on usb charging.')
1950    disable_command = command['enable_command']
1951    t0 = time.time()
1952    # Do not loop directly on self.IsDeviceCharging to cut the number of calls
1953    # to the device.
1954    while True:
1955      if t0 + timeout - time.time() < 0:
1956        raise pexpect.TIMEOUT('Unable to enable USB charging in time.')
1957      self.RunShellCommand(disable_command)
1958      if self.IsDeviceCharging():
1959        break
1960
1961  def IsDeviceCharging(self):
1962    for line in self.RunShellCommand('dumpsys battery'):
1963      if 'powered: ' in line:
1964        if line.split('powered: ')[1] == 'true':
1965          return True
1966
1967
1968class NewLineNormalizer(object):
1969  """A file-like object to normalize EOLs to '\n'.
1970
1971  Pexpect runs adb within a pseudo-tty device (see
1972  http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written
1973  as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate
1974  lines, the log ends up having '\r\r\n' at the end of each line. This
1975  filter replaces the above with a single '\n' in the data stream.
1976  """
1977  def __init__(self, output):
1978    self._output = output
1979
1980  def write(self, data):
1981    data = data.replace('\r\r\n', '\n')
1982    self._output.write(data)
1983
1984  def flush(self):
1985    self._output.flush()
1986