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