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