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