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