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