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