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