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