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