• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2014 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"""Provides a variety of device interactions based on adb."""
5# pylint: disable=unused-argument
6
7import calendar
8import collections
9import contextlib
10import fnmatch
11import json
12import logging
13import math
14import os
15import posixpath
16import pprint
17import random
18import re
19import shutil
20import stat
21import sys
22import tempfile
23import time
24import threading
25import uuid
26
27import six
28
29from devil import base_error
30from devil import devil_env
31from devil.utils import cmd_helper
32from devil.android import apk_helper
33from devil.android import device_signal
34from devil.android import decorators
35from devil.android import device_errors
36from devil.android import device_temp_file
37from devil.android import install_commands
38from devil.android import logcat_monitor
39from devil.android import md5sum
40from devil.android.sdk import adb_wrapper
41from devil.android.sdk import intent
42from devil.android.sdk import keyevent
43from devil.android.sdk import version_codes
44from devil.utils import host_utils
45from devil.utils import parallelizer
46from devil.utils import reraiser_thread
47from devil.utils import timeout_retry
48from devil.utils import zip_utils
49
50with devil_env.SysPath(devil_env.PY_UTILS_PATH):
51  from py_utils import tempfile_ext
52
53try:
54  from devil.utils import reset_usb
55except ImportError:
56  # Fail silently if we can't import reset_usb. We're likely on windows.
57  reset_usb = None
58
59logger = logging.getLogger(__name__)
60
61_DEFAULT_TIMEOUT = 30
62_DEFAULT_RETRIES = 3
63
64# A sentinel object for default values
65# TODO(jbudorick): revisit how default values are handled by
66# the timeout_retry decorators.
67DEFAULT = object()
68
69# A sentinel object to require that calls to RunShellCommand force running the
70# command with su even if the device has been rooted. To use, pass into the
71# as_root param.
72_FORCE_SU = object()
73
74# Lists all files for the specified directories.
75# In order to minimize data transfer, prints directories as absolute paths
76# followed by files within that directory without their path.
77_FILE_LIST_SCRIPT = """
78  function list_files() {
79    for f in "$1"/{.,}*
80    do
81      if [ "$f" == "." ] || [ "$f" == ".." ] || [ "$f" == "${1}/.*" ] \
82          || [ "$f" == "${1}/*" ]
83      then
84        continue
85      fi
86      base=${f##*/} # Get the basename for the file, dropping the path.
87      echo "$base"
88    done
89  }
90  for dir in %s
91  do
92    if [ -d "$dir" ]; then
93      echo "$dir"
94      list_files "$dir"
95    fi
96  done
97"""
98
99_RESTART_ADBD_SCRIPT = """
100  trap '' HUP
101  trap '' TERM
102  trap '' PIPE
103  function restart() {
104    stop adbd
105    start adbd
106  }
107  restart &
108"""
109
110_UNZIP_AND_CHMOD_SCRIPT = """
111  {bin_dir}/unzip {zip_file} && (for dir in {dirs}
112  do
113    chmod -R 777 "$dir" || exit 1
114  done)
115"""
116
117# Not all permissions can be set.
118_PERMISSIONS_DENYLIST_RE = re.compile('|'.join(
119    fnmatch.translate(p) for p in [
120        'android.permission.ACCESS_LOCATION_EXTRA_COMMANDS',
121        'android.permission.ACCESS_MOCK_LOCATION',
122        'android.permission.ACCESS_NETWORK_STATE',
123        'android.permission.ACCESS_NOTIFICATION_POLICY',
124        'android.permission.ACCESS_VR_STATE',
125        'android.permission.ACCESS_WIFI_STATE',
126        'android.permission.AUTHENTICATE_ACCOUNTS',
127        'android.permission.BLUETOOTH',
128        'android.permission.BLUETOOTH_ADMIN',
129        'android.permission.BROADCAST_STICKY',
130        'android.permission.CHANGE_NETWORK_STATE',
131        'android.permission.CHANGE_WIFI_MULTICAST_STATE',
132        'android.permission.CHANGE_WIFI_STATE',
133        'android.permission.DISABLE_KEYGUARD',
134        'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION',
135        'android.permission.EXPAND_STATUS_BAR',
136        'android.permission.FOREGROUND_SERVICE',
137        'android.permission.GET_PACKAGE_SIZE',
138        'android.permission.INSTALL_SHORTCUT',
139        'android.permission.INJECT_EVENTS',
140        'android.permission.INTERNET',
141        'android.permission.KILL_BACKGROUND_PROCESSES',
142        'android.permission.MANAGE_ACCOUNTS',
143        'android.permission.MANAGE_EXTERNAL_STORAGE',
144        'android.permission.MODIFY_AUDIO_SETTINGS',
145        'android.permission.NFC',
146        'android.permission.QUERY_ALL_PACKAGES',
147        'android.permission.READ_SYNC_SETTINGS',
148        'android.permission.READ_SYNC_STATS',
149        'android.permission.RECEIVE_BOOT_COMPLETED',
150        'android.permission.RECORD_VIDEO',
151        'android.permission.REORDER_TASKS',
152        'android.permission.REQUEST_INSTALL_PACKAGES',
153        'android.permission.RESTRICTED_VR_ACCESS',
154        'android.permission.RUN_INSTRUMENTATION',
155        'android.permission.SET_ALARM',
156        'android.permission.SET_TIME_ZONE',
157        'android.permission.SET_WALLPAPER',
158        'android.permission.SET_WALLPAPER_HINTS',
159        'android.permission.TRANSMIT_IR',
160        'android.permission.USE_CREDENTIALS',
161        'android.permission.USE_FINGERPRINT',
162        'android.permission.VIBRATE',
163        'android.permission.WAKE_LOCK',
164        'android.permission.WRITE_SYNC_SETTINGS',
165        'com.android.browser.permission.READ_HISTORY_BOOKMARKS',
166        'com.android.browser.permission.WRITE_HISTORY_BOOKMARKS',
167        'com.android.launcher.permission.INSTALL_SHORTCUT',
168        'com.chrome.permission.DEVICE_EXTRAS',
169        'com.google.android.apps.now.CURRENT_ACCOUNT_ACCESS',
170        'com.google.android.c2dm.permission.RECEIVE',
171        'com.google.android.providers.gsf.permission.READ_GSERVICES',
172        'com.google.vr.vrcore.permission.VRCORE_INTERNAL',
173        'com.sec.enterprise.knox.MDM_CONTENT_PROVIDER',
174        '*.permission.C2D_MESSAGE',
175        '*.permission.READ_WRITE_BOOKMARK_FOLDERS',
176        '*.TOS_ACKED',
177    ]))
178_SHELL_OUTPUT_SEPARATOR = '~X~'
179_PERMISSIONS_EXCEPTION_RE = re.compile(r'java\.lang\.\w+Exception: .*$',
180                                       re.MULTILINE)
181
182_CURRENT_FOCUS_CRASH_RE = re.compile(
183    r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
184
185_GETPROP_RE = re.compile(r'\[(.*?)\]: \[(.*?)\]')
186_VERSION_CODE_SDK_RE = re.compile(
187    r'\s*versionCode=(\d+).*minSdk=(\d+).*targetSdk=(.*)\s*')
188
189# Regex to parse the long (-l) output of 'ls' command, c.f.
190# https://github.com/landley/toybox/blob/master/toys/posix/ls.c#L446
191# yapf: disable
192_LONG_LS_OUTPUT_RE = re.compile(
193    r'(?P<st_mode>[\w-]{10})\s+'                  # File permissions
194    r'(?:(?P<st_nlink>\d+)\s+)?'                  # Number of links (optional)
195    r'(?P<st_owner>\w+)\s+'                       # Name of owner
196    r'(?P<st_group>\w+)\s+'                       # Group of owner
197    r'(?:'                                        # Either ...
198      r'(?P<st_rdev_major>\d+),\s+'                 # Device major, and
199      r'(?P<st_rdev_minor>\d+)\s+'                  # Device minor
200    r'|'                                          # .. or
201      r'(?P<st_size>\d+)\s+'                        # Size in bytes
202    r')?'                                         # .. or nothing
203    r'(?P<st_mtime>\d{4}-\d\d-\d\d \d\d:\d\d)\s+' # Modification date/time
204    r'(?P<filename>.+?)'                          # File name
205    r'(?: -> (?P<symbolic_link_to>.+))?'          # Symbolic link (optional)
206    r'$'                                          # End of string
207)
208# yapf: enable
209
210_LS_DATE_FORMAT = '%Y-%m-%d %H:%M'
211_FILE_MODE_RE = re.compile(r'[dbclps-](?:[r-][w-][xSs-]){2}[r-][w-][xTt-]$')
212_FILE_MODE_KIND = {
213    'd': stat.S_IFDIR,
214    'b': stat.S_IFBLK,
215    'c': stat.S_IFCHR,
216    'l': stat.S_IFLNK,
217    'p': stat.S_IFIFO,
218    's': stat.S_IFSOCK,
219    '-': stat.S_IFREG
220}
221_FILE_MODE_PERMS = [
222    stat.S_IRUSR,
223    stat.S_IWUSR,
224    stat.S_IXUSR,
225    stat.S_IRGRP,
226    stat.S_IWGRP,
227    stat.S_IXGRP,
228    stat.S_IROTH,
229    stat.S_IWOTH,
230    stat.S_IXOTH,
231]
232_FILE_MODE_SPECIAL = [
233    ('s', stat.S_ISUID),
234    ('s', stat.S_ISGID),
235    ('t', stat.S_ISVTX),
236]
237_PS_COLUMNS = {'pid': 1, 'ppid': 2, 'name': -1}
238_SELINUX_MODE = {'enforcing': True, 'permissive': False, 'disabled': None}
239# Some devices require different logic for checking if root is necessary
240_SPECIAL_ROOT_DEVICE_LIST = [
241    'marlin',  # Pixel XL
242    'sailfish',  # Pixel
243    'taimen',  # Pixel 2 XL
244    'vega',  # Lenovo Mirage Solo
245    'walleye',  # Pixel 2
246    'crosshatch',  # Pixel 3 XL
247    'blueline',  # Pixel 3
248    'sargo',  # Pixel 3a
249    'bonito',  # Pixel 3a XL
250    'sdk_goog3_x86',  # Crow emulator
251]
252_SPECIAL_ROOT_DEVICE_LIST += [
253    'aosp_%s' % _d for _d in _SPECIAL_ROOT_DEVICE_LIST
254]
255
256# Somce devices are slow/timeout when using default install.
257# Devices listed here will perform no_streaming app installation.
258_NO_STREAMING_DEVICE_LIST = [
259    'flounder',  # Nexus 9
260    'volantis',  # Another product name for Nexus 9
261]
262
263_IMEI_RE = re.compile(r'  Device ID = (.+)$')
264# The following regex is used to match result parcels like:
265"""
266Result: Parcel(
267  0x00000000: 00000000 0000000f 00350033 00360033 '........3.5.3.6.'
268  0x00000010: 00360032 00370030 00300032 00300039 '2.6.0.7.2.0.9.0.'
269  0x00000020: 00380033 00000039                   '3.8.9...        ')
270"""
271_PARCEL_RESULT_RE = re.compile(
272    r'0x[0-9a-f]{8}\: (?:[0-9a-f]{8}\s+){1,4}\'(.{16})\'')
273
274# http://bit.ly/2WLZhUF added a timeout to adb wait-for-device. We sometimes
275# want to wait longer than the implicit call within adb root allows.
276_WAIT_FOR_DEVICE_TIMEOUT_STR = 'timeout expired while waiting for device'
277
278_WEBVIEW_SYSUPDATE_CURRENT_PKG_RE = re.compile(
279    r'Current WebView package.*:.*\(([a-z.]*),')
280_WEBVIEW_SYSUPDATE_NULL_PKG_RE = re.compile(r'Current WebView package is null')
281_WEBVIEW_SYSUPDATE_FALLBACK_LOGIC_RE = re.compile(
282    r'Fallback logic enabled: (true|false)')
283_WEBVIEW_SYSUPDATE_PACKAGE_INSTALLED_RE = re.compile(
284    r'(?:Valid|Invalid) package\s+(\S+)\s+\(.*\),?\s+(.*)$')
285_WEBVIEW_SYSUPDATE_PACKAGE_NOT_INSTALLED_RE = re.compile(
286    r'(\S+)\s+(is NOT installed\.)')
287_WEBVIEW_SYSUPDATE_MIN_VERSION_CODE = re.compile(
288    r'Minimum WebView version code: (\d+)')
289
290_GOOGLE_FEATURES_RE = re.compile(r'^\s*com\.google\.')
291
292_EMULATOR_RE = re.compile(r'^generic_.*$')
293
294# Regular expressions for determining if a package is installed using the
295# output of `dumpsys package`.
296# Matches lines like "Package [com.google.android.youtube] (c491050):".
297# or "Package [org.chromium.trichromelibrary_425300033] (e476383):"
298_DUMPSYS_PACKAGE_RE_STR =\
299    r'^\s*Package\s*\[%s(_(?P<version_code>\d*))?\]\s*\(\w*\):$'
300
301PS_COLUMNS = ('name', 'pid', 'ppid')
302ProcessInfo = collections.namedtuple('ProcessInfo', PS_COLUMNS)
303
304
305@decorators.WithExplicitTimeoutAndRetries(_DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
306def GetAVDs():
307  """Returns a list of Android Virtual Devices.
308
309  Returns:
310    A list containing the configured AVDs.
311  """
312  lines = cmd_helper.GetCmdOutput([
313      os.path.join(
314          devil_env.config.LocalPath('android_sdk'), 'tools', 'android'),
315      'list', 'avd'
316  ]).splitlines()
317  avds = []
318  for line in lines:
319    if 'Name:' not in line:
320      continue
321    key, value = (s.strip() for s in line.split(':', 1))
322    if key == 'Name':
323      avds.append(value)
324  return avds
325
326
327def _ParseModeString(mode_str):
328  """Parse a mode string, e.g. 'drwxrwxrwx', into a st_mode value.
329
330  Effectively the reverse of |mode_to_string| in, e.g.:
331  https://github.com/landley/toybox/blob/master/lib/lib.c#L896
332  """
333  if not _FILE_MODE_RE.match(mode_str):
334    raise ValueError('Unexpected file mode %r', mode_str)
335  mode = _FILE_MODE_KIND[mode_str[0]]
336  for c, flag in zip(mode_str[1:], _FILE_MODE_PERMS):
337    if c != '-' and c.islower():
338      mode |= flag
339  for c, (t, flag) in zip(mode_str[3::3], _FILE_MODE_SPECIAL):
340    if c.lower() == t:
341      mode |= flag
342  return mode
343
344
345def _GetTimeStamp():
346  """Return a basic ISO 8601 time stamp with the current local time."""
347  return time.strftime('%Y%m%dT%H%M%S', time.localtime())
348
349
350def _JoinLines(lines):
351  # makes sure that the last line is also terminated, and is more memory
352  # efficient than first appending an end-line to each line and then joining
353  # all of them together.
354  return ''.join(s for line in lines for s in (line, '\n'))
355
356
357def _CreateAdbWrapper(device):
358  if isinstance(device, adb_wrapper.AdbWrapper):
359    return device
360  else:
361    return adb_wrapper.AdbWrapper(device)
362
363
364def _FormatPartialOutputError(output):
365  lines = output.splitlines() if isinstance(output, six.string_types) else output
366  message = ['Partial output found:']
367  if len(lines) > 11:
368    message.extend('- %s' % line for line in lines[:5])
369    message.extend('<snip>')
370    message.extend('- %s' % line for line in lines[-5:])
371  else:
372    message.extend('- %s' % line for line in lines)
373  return '\n'.join(message)
374
375
376_PushableComponents = collections.namedtuple('_PushableComponents',
377                                             ('host', 'device', 'collapse'))
378
379
380def _IterPushableComponents(host_path, device_path):
381  """Yields a sequence of paths that can be pushed directly via adb push.
382
383  `adb push` doesn't currently handle pushing directories that contain
384  symlinks: https://bit.ly/2pMBlW5
385
386  To circumvent this issue, we get the smallest set of files and/or
387  directories that can be pushed without attempting to push a directory
388  that contains a symlink.
389
390  This function does so by recursing through |host_path|. Each call
391  yields 3-tuples that include the smallest set of (host, device) path pairs
392  that can be passed to adb push and a bool indicating whether the parent
393  directory can be pushed -- i.e., if True, the host path is neither a
394  symlink nor a directory that contains a symlink.
395
396  Args:
397    host_path: an absolute path of a file or directory on the host
398    device_path: an absolute path of a file or directory on the device
399  Yields:
400    3-tuples containing
401      host (str): the host path, with symlinks dereferenced
402      device (str): the device path
403      collapse (bool): whether this entity permits its parent to be pushed
404        in its entirety. (Parents need permission from all child entities
405        in order to be pushed in their entirety.)
406  """
407  if os.path.isfile(host_path):
408    yield _PushableComponents(
409        os.path.realpath(host_path), device_path, not os.path.islink(host_path))
410  else:
411    components = []
412    for child in os.listdir(host_path):
413      components.extend(
414          _IterPushableComponents(
415              os.path.join(host_path, child), posixpath.join(
416                  device_path, child)))
417
418    if all(c.collapse for c in components):
419      yield _PushableComponents(
420          os.path.realpath(host_path), device_path,
421          not os.path.islink(host_path))
422    else:
423      for c in components:
424        yield c
425
426
427class DeviceUtils(object):
428
429  _MAX_ADB_COMMAND_LENGTH = 512
430  _MAX_ADB_OUTPUT_LENGTH = 32768
431  _LAUNCHER_FOCUSED_RE = re.compile(r'\s*mCurrentFocus.*(Launcher|launcher).*')
432  _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
433
434  LOCAL_PROPERTIES_PATH = posixpath.join('/', 'data', 'local.prop')
435
436  # Property in /data/local.prop that controls Java assertions.
437  JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
438
439  def __init__(self,
440               device,
441               enable_device_files_cache=False,
442               default_timeout=_DEFAULT_TIMEOUT,
443               default_retries=_DEFAULT_RETRIES):
444    """DeviceUtils constructor.
445
446    Args:
447      device: Either a device serial, an existing AdbWrapper instance, or an
448        an existing AndroidCommands instance.
449      enable_device_files_cache: For PushChangedFiles(), cache checksums of
450        pushed files rather than recomputing them on a subsequent call.
451      default_timeout: An integer containing the default number of seconds to
452        wait for an operation to complete if no explicit value is provided.
453      default_retries: An integer containing the default number or times an
454        operation should be retried on failure if no explicit value is provided.
455    """
456    self.adb = None
457    if isinstance(device, six.string_types):
458      self.adb = _CreateAdbWrapper(device)
459    elif isinstance(device, adb_wrapper.AdbWrapper):
460      self.adb = device
461    else:
462      raise ValueError('Unsupported device value: %r' % device)
463    self._commands_installed = None
464    self._default_timeout = default_timeout
465    self._default_retries = default_retries
466    self._enable_device_files_cache = enable_device_files_cache
467    self._cache = {}
468    self._client_caches = {}
469    self._cache_lock = threading.RLock()
470    assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)
471    assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR)
472
473    self.ClearCache()
474
475  @property
476  def serial(self):
477    """Returns the device serial."""
478    return self.adb.GetDeviceSerial()
479
480  def __eq__(self, other):
481    """Checks whether |other| refers to the same device as |self|.
482
483    Args:
484      other: The object to compare to. This can be a basestring, an instance
485        of adb_wrapper.AdbWrapper, or an instance of DeviceUtils.
486    Returns:
487      Whether |other| refers to the same device as |self|.
488    """
489    return self.serial == str(other)
490
491  def __lt__(self, other):
492    """Compares two instances of DeviceUtils.
493
494    This merely compares their serial numbers.
495
496    Args:
497      other: The instance of DeviceUtils to compare to.
498    Returns:
499      Whether |self| is less than |other|.
500    """
501    return self.serial < other.serial
502
503  def __str__(self):
504    """Returns the device serial."""
505    return self.serial
506
507  @decorators.WithTimeoutAndRetriesFromInstance()
508  def IsOnline(self, timeout=None, retries=None):
509    """Checks whether the device is online.
510
511    Args:
512      timeout: timeout in seconds
513      retries: number of retries
514
515    Returns:
516      True if the device is online, False otherwise.
517
518    Raises:
519      CommandTimeoutError on timeout.
520    """
521    try:
522      return self.adb.GetState() == 'device'
523    except base_error.BaseError as exc:
524      logger.info('Failed to get state: %s', exc)
525      return False
526
527  @decorators.WithTimeoutAndRetriesFromInstance()
528  def HasRoot(self, timeout=None, retries=None):
529    """Checks whether or not adbd has root privileges.
530
531    A device is considered to have root if all commands are implicitly run
532    with elevated privileges, i.e. without having to use "su" to run them.
533
534    Note that some devices do not allow this implicit privilige elevation,
535    but _can_ run commands as root just fine when done explicitly with "su".
536    To check if your device can run commands with elevated privileges at all
537    use:
538
539      device.HasRoot() or device.NeedsSU()
540
541    Luckily, for the most part you don't need to worry about this and using
542    RunShellCommand(cmd, as_root=True) will figure out for you the right
543    command incantation to run with elevated privileges.
544
545    Args:
546      timeout: timeout in seconds
547      retries: number of retries
548
549    Returns:
550      True if adbd has root privileges, False otherwise.
551
552    Raises:
553      CommandTimeoutError on timeout.
554      DeviceUnreachableError on missing device.
555    """
556    if self.build_type == 'eng':
557      # 'eng' builds have root enabled by default and the adb session cannot
558      # be unrooted.
559      return True
560    # Check if uid is 0. Such behavior has remained unchanged since
561    # android 2.2.3 (https://bit.ly/2QQzg67)
562    output = self.RunShellCommand(['id'], single_line=True)
563    return output.startswith('uid=0(root)')
564
565  def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT):
566    """Checks whether 'su' is needed to access protected resources.
567
568    Args:
569      timeout: timeout in seconds
570      retries: number of retries
571
572    Returns:
573      True if 'su' is available on the device and is needed to to access
574        protected resources; False otherwise if either 'su' is not available
575        (e.g. because the device has a user build), or not needed (because adbd
576        already has root privileges).
577
578    Raises:
579      CommandTimeoutError on timeout.
580      DeviceUnreachableError on missing device.
581    """
582    if 'needs_su' not in self._cache:
583      cmd = '%s && ! ls /root' % self._Su('ls /root')
584      # Devices using the system-as-root partition layout appear to not have
585      # a /root directory. See http://bit.ly/37F34sx for more context.
586      if (self.build_system_root_image == 'true'
587          or self.build_version_sdk >= version_codes.Q
588          # This may be redundant with the checks above.
589          or self.product_name in _SPECIAL_ROOT_DEVICE_LIST):
590        if self.HasRoot():
591          self._cache['needs_su'] = False
592          return False
593        cmd = 'which which && which su'
594      try:
595        self.RunShellCommand(
596            cmd,
597            shell=True,
598            check_return=True,
599            timeout=self._default_timeout if timeout is DEFAULT else timeout,
600            retries=self._default_retries if retries is DEFAULT else retries)
601        self._cache['needs_su'] = True
602      except device_errors.AdbCommandFailedError:
603        self._cache['needs_su'] = False
604    return self._cache['needs_su']
605
606  def _Su(self, command):
607    if self.build_version_sdk >= version_codes.MARSHMALLOW:
608      return 'su 0 %s' % command
609    return 'su -c %s' % command
610
611  @decorators.WithTimeoutAndRetriesFromInstance()
612  def EnableRoot(self, timeout=None, retries=None):
613    """Restarts adbd with root privileges.
614
615    Args:
616      timeout: timeout in seconds
617      retries: number of retries
618
619    Raises:
620      CommandFailedError if root could not be enabled.
621      CommandTimeoutError on timeout.
622    """
623    if 'needs_su' in self._cache:
624      del self._cache['needs_su']
625
626    try:
627      self.adb.Root()
628    except device_errors.AdbCommandFailedError as e:
629      if self.IsUserBuild():
630        raise device_errors.RootUserBuildError(device_serial=str(self))
631      elif e.output and _WAIT_FOR_DEVICE_TIMEOUT_STR in e.output:
632        # adb 1.0.41 added a call to wait-for-device *inside* root
633        # with a timeout that can be too short in some cases.
634        # If we hit that timeout, ignore it & do our own wait below.
635        pass
636      else:
637        raise  # Failed probably due to some other reason.
638
639    def device_online_with_root():
640      try:
641        self.adb.WaitForDevice()
642        return self.HasRoot()
643      except (device_errors.AdbCommandFailedError,
644              device_errors.DeviceUnreachableError):
645        return False
646
647    timeout_retry.WaitFor(device_online_with_root, wait_period=1)
648
649  @decorators.WithTimeoutAndRetriesFromInstance()
650  def IsUserBuild(self, timeout=None, retries=None):
651    """Checks whether or not the device is running a user build.
652
653    Args:
654      timeout: timeout in seconds
655      retries: number of retries
656
657    Returns:
658      True if the device is running a user build, False otherwise (i.e. if
659        it's running a userdebug build).
660
661    Raises:
662      CommandTimeoutError on timeout.
663      DeviceUnreachableError on missing device.
664    """
665    return self.build_type == 'user'
666
667  @decorators.WithTimeoutAndRetriesFromInstance()
668  def GetExternalStoragePath(self, timeout=None, retries=None):
669    """Get the device's path to its SD card.
670
671    Note: this path is read-only by apps in R+. Use GetAppWritablePath() to
672    obtain a path writable by apps.
673
674    Args:
675      timeout: timeout in seconds
676      retries: number of retries
677
678    Returns:
679      The device's path to its SD card.
680
681    Raises:
682      CommandFailedError if the external storage path could not be determined.
683      CommandTimeoutError on timeout.
684      DeviceUnreachableError on missing device.
685    """
686    self._EnsureCacheInitialized()
687    if not self._cache['external_storage']:
688      raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set',
689                                             str(self))
690    return self._cache['external_storage']
691
692  def GetAppWritablePath(self, timeout=None, retries=None):
693    """Get a path that on the device's SD card that apps can write.
694
695    Args:
696      timeout: timeout in seconds
697      retries: number of retries
698
699    Returns:
700      A app-writeable path on the device's SD card.
701
702    Raises:
703      CommandFailedError if the external storage path could not be determined.
704      CommandTimeoutError on timeout.
705      DeviceUnreachableError on missing device.
706    """
707    if self.build_version_sdk >= version_codes.Q:
708      # On Q+ apps don't require permissions to access well-defined media
709      # locations like /sdcard/Download. On R+ the WRITE_EXTERNAL_STORAGE
710      # permission no longer provides access to the external storage root. See
711      # https://developer.android.com/preview/privacy/storage#permissions-target-11
712      # So use /sdcard/Download for the app-writable path on those versions.
713      return posixpath.join(self.GetExternalStoragePath(), 'Download')
714    return self.GetExternalStoragePath()
715
716  @decorators.WithTimeoutAndRetriesFromInstance()
717  def GetIMEI(self, timeout=None, retries=None):
718    """Get the device's IMEI.
719
720    Args:
721      timeout: timeout in seconds
722      retries: number of retries
723
724    Returns:
725      The device's IMEI.
726
727    Raises:
728      AdbCommandFailedError on error
729    """
730    if self._cache.get('imei') is not None:
731      return self._cache.get('imei')
732
733    if self.build_version_sdk < 21:
734      out = self.RunShellCommand(['dumpsys', 'iphonesubinfo'],
735                                 raw_output=True,
736                                 check_return=True)
737      if out:
738        match = re.search(_IMEI_RE, out)
739        if match:
740          self._cache['imei'] = match.group(1)
741          return self._cache['imei']
742    else:
743      out = self.RunShellCommand(['service', 'call', 'iphonesubinfo', '1'],
744                                 check_return=True)
745      if out:
746        imei = ''
747        for line in out:
748          match = re.search(_PARCEL_RESULT_RE, line)
749          if match:
750            imei = imei + match.group(1)
751        imei = imei.replace('.', '').strip()
752        if imei:
753          self._cache['imei'] = imei
754          return self._cache['imei']
755
756    raise device_errors.CommandFailedError('Unable to fetch IMEI.')
757
758  @decorators.WithTimeoutAndRetriesFromInstance()
759  def IsApplicationInstalled(
760      self, package, version_code=None, timeout=None, retries=None):
761    """Determines whether a particular package is installed on the device.
762
763    Args:
764      package: Name of the package.
765      version_code: The version of the package to check for as an int, if
766          applicable. Only used for static shared libraries, otherwise ignored.
767
768    Returns:
769      True if the application is installed, False otherwise.
770    """
771    # `pm list packages` doesn't include the version code, so if it was
772    # provided, skip this since we can't guarantee that the installed
773    # version is the requested version.
774    if version_code is None:
775      # `pm list packages` allows matching substrings, but we want exact matches
776      # only.
777      matching_packages = self.RunShellCommand(
778          ['pm', 'list', 'packages', package], check_return=True)
779      desired_line = 'package:' + package
780      found_package = desired_line in matching_packages
781      if found_package:
782        return True
783
784    # Some packages do not properly show up via `pm list packages`, so fall back
785    # to checking via `dumpsys package`.
786    matcher = re.compile(_DUMPSYS_PACKAGE_RE_STR % package)
787    dumpsys_output = self.RunShellCommand(
788        ['dumpsys', 'package'], check_return=True, large_output=True)
789    for line in dumpsys_output:
790      match = matcher.match(line)
791      # We should have one of these cases:
792      # 1. The package is a regular app, in which case it will show up without
793      #    its version code in the line we're filtering for.
794      # 2. The package is a static shared library, in which case one or more
795      #    entries with the version code can show up, but not one without the
796      #    version code.
797      if match:
798        installed_version_code = match.groupdict().get('version_code')
799        if (installed_version_code is None
800            or installed_version_code == str(version_code)):
801          return True
802    return False
803
804  @decorators.WithTimeoutAndRetriesFromInstance()
805  def GetApplicationPaths(self, package, timeout=None, retries=None):
806    """Get the paths of the installed apks on the device for the given package.
807
808    Args:
809      package: Name of the package.
810
811    Returns:
812      List of paths to the apks on the device for the given package.
813    """
814    return self._GetApplicationPathsInternal(package)
815
816  def _GetApplicationPathsInternal(self, package, skip_cache=False):
817    cached_result = self._cache['package_apk_paths'].get(package)
818    if cached_result is not None and not skip_cache:
819      if package in self._cache['package_apk_paths_to_verify']:
820        self._cache['package_apk_paths_to_verify'].remove(package)
821        # Don't verify an app that is not thought to be installed. We are
822        # concerned only with apps we think are installed having been
823        # uninstalled manually.
824        if cached_result and not self.PathExists(cached_result):
825          cached_result = None
826          self._cache['package_apk_checksums'].pop(package, 0)
827      if cached_result is not None:
828        return list(cached_result)
829    # 'pm path' is liable to incorrectly exit with a nonzero number starting
830    # in Lollipop.
831    # TODO(jbudorick): Check if this is fixed as new Android versions are
832    # released to put an upper bound on this.
833    should_check_return = (self.build_version_sdk < version_codes.LOLLIPOP)
834    output = self.RunShellCommand(['pm', 'path', package],
835                                  check_return=should_check_return)
836    apks = []
837    bad_output = False
838    for line in output:
839      if line.startswith('package:'):
840        apks.append(line[len('package:'):])
841      elif line.startswith('WARNING:'):
842        continue
843      else:
844        bad_output = True  # Unexpected line in output.
845    if not apks and output:
846      if bad_output:
847        raise device_errors.CommandFailedError(
848            'Unexpected pm path output: %r' % '\n'.join(output), str(self))
849      else:
850        logger.warning('pm returned no paths but the following warnings:')
851        for line in output:
852          logger.warning('- %s', line)
853    self._cache['package_apk_paths'][package] = list(apks)
854    return apks
855
856  @decorators.WithTimeoutAndRetriesFromInstance()
857  def GetApplicationVersion(self, package, timeout=None, retries=None):
858    """Get the version name of a package installed on the device.
859
860    Args:
861      package: Name of the package.
862
863    Returns:
864      A string with the version name or None if the package is not found
865      on the device.
866    """
867    output = self.RunShellCommand(['dumpsys', 'package', package],
868                                  check_return=True)
869    if not output:
870      return None
871    for line in output:
872      line = line.strip()
873      if line.startswith('versionName='):
874        return line[len('versionName='):]
875    raise device_errors.CommandFailedError(
876        'Version name for %s not found on dumpsys output' % package, str(self))
877
878  @decorators.WithTimeoutAndRetriesFromInstance()
879  def GetApplicationTargetSdk(self, package, timeout=None, retries=None):
880    """Get the targetSdkVersion of a package installed on the device.
881
882    Args:
883      package: Name of the package.
884
885    Returns:
886      A string with the targetSdkVersion or None if the package is not found on
887      the device. Note: this cannot always be cast to an integer. If this
888      application targets a pre-release SDK, this returns the version codename
889      instead (ex. "R").
890    """
891    if not self.IsApplicationInstalled(package):
892      return None
893    lines = self._GetDumpsysOutput(['package', package], 'targetSdk=')
894    for line in lines:
895      m = _VERSION_CODE_SDK_RE.match(line)
896      if m:
897        value = m.group(3)
898        # 10000 is the code used by Android for a pre-finalized SDK.
899        if value == '10000':
900          return self.GetProp('ro.build.version.codename', cache=True)
901        else:
902          return value
903    raise device_errors.CommandFailedError(
904        'targetSdkVersion for %s not found on dumpsys output' % package,
905        str(self))
906
907  @decorators.WithTimeoutAndRetriesFromInstance()
908  def GetPackageArchitecture(self, package, timeout=None, retries=None):
909    """Get the architecture of a package installed on the device.
910
911    Args:
912      package: Name of the package.
913
914    Returns:
915      A string with the architecture, or None if the package is missing.
916    """
917    lines = self._GetDumpsysOutput(['package', package], 'primaryCpuAbi')
918    if lines:
919      _, _, package_arch = lines[-1].partition('=')
920      return package_arch.strip()
921    return None
922
923  @decorators.WithTimeoutAndRetriesFromInstance()
924  def GetApplicationDataDirectory(self, package, timeout=None, retries=None):
925    """Get the data directory on the device for the given package.
926
927    Args:
928      package: Name of the package.
929
930    Returns:
931      The package's data directory.
932    Raises:
933      CommandFailedError if the package's data directory can't be found,
934        whether because it's not installed or otherwise.
935    """
936    if not self.IsApplicationInstalled(package):
937      raise device_errors.CommandFailedError('%s is not installed' % package,
938                                             str(self))
939    output = self._RunPipedShellCommand(
940        'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package))
941    for line in output:
942      _, _, dataDir = line.partition('dataDir=')
943      if dataDir:
944        return dataDir
945    raise device_errors.CommandFailedError(
946        'Could not find data directory for %s' % package, str(self))
947
948  @decorators.WithTimeoutAndRetriesFromInstance()
949  def GetSecurityContextForPackage(self,
950                                   package,
951                                   encrypted=False,
952                                   timeout=None,
953                                   retries=None):
954    """Gets the SELinux security context for the given package.
955
956    Args:
957      package: Name of the package.
958      encrypted: Whether to check in the encrypted data directory
959          (/data/user_de/0/) or the unencrypted data directory (/data/data/).
960
961    Returns:
962      The package's security context as a string, or None if not found.
963    """
964    directory = '/data/user_de/0/' if encrypted else '/data/data/'
965    for line in self.RunShellCommand(['ls', '-Z', directory],
966                                     as_root=True,
967                                     check_return=True):
968      split_line = line.split()
969      # ls -Z output differs between Android versions, but the package is
970      # always last and the context always starts with "u:object"
971      if split_line[-1] == package:
972        for column in split_line:
973          if column.startswith('u:object'):
974            return column
975    return None
976
977  def TakeBugReport(self, path, timeout=60 * 5, retries=None):
978    """Takes a bug report and dumps it to the specified path.
979
980    This doesn't use adb's bugreport option since its behavior is dependent on
981    both adb version and device OS version. To make it simpler, this directly
982    runs the bugreport command on the device itself and dumps the stdout to a
983    file.
984
985    Args:
986      path: Path on the host to drop the bug report.
987      timeout: (optional) Timeout per try in seconds.
988      retries: (optional) Number of retries to attempt.
989    """
990    with device_temp_file.DeviceTempFile(self.adb) as device_tmp_file:
991      cmd = '( bugreport )>%s 2>&1' % device_tmp_file.name
992      self.RunShellCommand(
993          cmd, check_return=True, shell=True, timeout=timeout, retries=retries)
994      self.PullFile(device_tmp_file.name, path)
995
996  @decorators.WithTimeoutAndRetriesFromInstance()
997  def WaitUntilFullyBooted(self,
998                           wifi=False,
999                           decrypt=False,
1000                           timeout=None,
1001                           retries=None):
1002    """Wait for the device to fully boot.
1003
1004    This means waiting for the device to boot, the package manager to be
1005    available, and the SD card to be ready.
1006    It can optionally wait the following:
1007     - Wait for wifi to come up.
1008     - Wait for full-disk decryption to complete.
1009
1010    Args:
1011      wifi: A boolean indicating if we should wait for wifi to come up or not.
1012      decrypt: A boolean indicating if we should wait for full-disk decryption
1013        to complete.
1014      timeout: timeout in seconds
1015      retries: number of retries
1016
1017    Raises:
1018      CommandFailedError on failure.
1019      CommandTimeoutError if one of the component waits times out.
1020      DeviceUnreachableError if the device becomes unresponsive.
1021    """
1022
1023    def sd_card_ready():
1024      try:
1025        self.RunShellCommand(
1026            ['test', '-d', self.GetExternalStoragePath()], check_return=True)
1027        return True
1028      except device_errors.AdbCommandFailedError:
1029        return False
1030
1031    def pm_ready():
1032      try:
1033        return self._GetApplicationPathsInternal('android', skip_cache=True)
1034      except device_errors.CommandFailedError:
1035        return False
1036
1037    def boot_completed():
1038      try:
1039        return self.GetProp('sys.boot_completed', cache=False) == '1'
1040      except device_errors.CommandFailedError:
1041        return False
1042
1043    def wifi_enabled():
1044      return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'],
1045                                                        check_return=False)
1046
1047    def decryption_completed():
1048      try:
1049        decrypt = self.GetProp('vold.decrypt', cache=False)
1050        # The prop "void.decrypt" will only be set when the device uses
1051        # full-disk encryption (FDE).
1052        # Return true when:
1053        #  - The prop is empty, which means the device is unencrypted or uses
1054        #    file-based encryption (FBE).
1055        #  - or the prop has value "trigger_restart_framework", which means
1056        #    the decription is finished.
1057        return decrypt == '' or decrypt == 'trigger_restart_framework'
1058      except device_errors.CommandFailedError:
1059        return False
1060
1061    self.adb.WaitForDevice()
1062    timeout_retry.WaitFor(sd_card_ready)
1063    timeout_retry.WaitFor(pm_ready)
1064    timeout_retry.WaitFor(boot_completed)
1065    if wifi:
1066      timeout_retry.WaitFor(wifi_enabled)
1067    if decrypt:
1068      timeout_retry.WaitFor(decryption_completed)
1069
1070  REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
1071
1072  @decorators.WithTimeoutAndRetriesFromInstance(
1073      min_default_timeout=REBOOT_DEFAULT_TIMEOUT)
1074  def Reboot(self,
1075             block=True,
1076             wifi=False,
1077             decrypt=False,
1078             timeout=None,
1079             retries=None):
1080    """Reboot the device.
1081
1082    Note if the device has the root privilege, it will likely lose it after the
1083    reboot. When |block| is True, it will try to restore the root status if
1084    applicable.
1085
1086    Args:
1087      block: A boolean indicating if we should wait for the reboot to complete.
1088      wifi: A boolean indicating if we should wait for wifi to be enabled after
1089        the reboot.
1090        The option has no effect unless |block| is also True.
1091      decrypt: A boolean indicating if we should wait for full-disk decryption
1092        to complete after the reboot.
1093        The option has no effect unless |block| is also True.
1094      timeout: timeout in seconds
1095      retries: number of retries
1096
1097    Raises:
1098      CommandTimeoutError on timeout.
1099      DeviceUnreachableError on missing device.
1100    """
1101
1102    def device_offline():
1103      return not self.IsOnline()
1104
1105    # Only check the root when block is True
1106    should_restore_root = self.HasRoot() if block else False
1107    self.adb.Reboot()
1108    self.ClearCache()
1109    timeout_retry.WaitFor(device_offline, wait_period=1)
1110    if block:
1111      self.WaitUntilFullyBooted(wifi=wifi, decrypt=decrypt)
1112      if should_restore_root:
1113        self.EnableRoot()
1114
1115  INSTALL_DEFAULT_TIMEOUT = 8 * _DEFAULT_TIMEOUT
1116  MODULES_SRC_DIRECTORY_PATH = '/data/local/tmp/modules'
1117
1118  @decorators.WithTimeoutAndRetriesFromInstance(
1119      min_default_timeout=INSTALL_DEFAULT_TIMEOUT)
1120  def Install(self,
1121              apk,
1122              allow_downgrade=False,
1123              reinstall=False,
1124              permissions=None,
1125              timeout=None,
1126              retries=None,
1127              modules=None,
1128              fake_modules=None,
1129              additional_locales=None):
1130    """Install an APK or app bundle.
1131
1132    Noop if an identical APK is already installed. If installing a bundle, the
1133    bundletools helper script (bin/*_bundle) should be used rather than the .aab
1134    file.
1135
1136    Args:
1137      apk: An ApkHelper instance or string containing the path to the APK or
1138        bundle.
1139      allow_downgrade: A boolean indicating if we should allow downgrades.
1140      reinstall: A boolean indicating if we should keep any existing app data.
1141        Ignored if |apk| is a bundle.
1142      permissions: Set of permissions to set. If not set, finds permissions with
1143          apk helper. To set no permissions, pass [].
1144      timeout: timeout in seconds
1145      retries: number of retries
1146      modules: An iterable containing specific bundle modules to install.
1147          Error if set and |apk| points to an APK instead of a bundle.
1148      fake_modules: An iterable containing specific bundle modules that should
1149          have their apks copied to |MODULES_SRC_DIRECTORY_PATH| subdirectory
1150          rather than installed. Thus the app can emulate SplitCompat while
1151          running. This should not have any overlap with |modules|.
1152      additional_locales: An iterable with additional locales to install for a
1153        bundle.
1154
1155    Raises:
1156      CommandFailedError if the installation fails.
1157      CommandTimeoutError if the installation times out.
1158      DeviceUnreachableError on missing device.
1159    """
1160    apk = apk_helper.ToHelper(apk)
1161    modules_set = set(modules or [])
1162    fake_modules_set = set(fake_modules or [])
1163    assert modules_set.isdisjoint(fake_modules_set), (
1164        'These modules overlap: %s' % (modules_set & fake_modules_set))
1165    all_modules = modules_set | fake_modules_set
1166    package_name = apk.GetPackageName()
1167
1168    with apk.GetApkPaths(self,
1169                         modules=all_modules,
1170                         additional_locales=additional_locales) as apk_paths:
1171      if apk.SupportsSplits():
1172        fake_apk_paths = self._GetFakeInstallPaths(apk_paths, fake_modules)
1173        self._FakeInstall(fake_apk_paths, fake_modules, package_name)
1174        apk_paths_to_install = [p for p in apk_paths if p not in fake_apk_paths]
1175      else:
1176        apk_paths_to_install = apk_paths
1177      self._InstallInternal(
1178          apk,
1179          apk_paths_to_install,
1180          allow_downgrade=allow_downgrade,
1181          reinstall=reinstall,
1182          permissions=permissions)
1183
1184  @staticmethod
1185  def _GetFakeInstallPaths(apk_paths, fake_modules):
1186    def IsFakeModulePath(path):
1187      filename = os.path.basename(path)
1188      return any(filename.startswith(f + '-') for f in fake_modules)
1189
1190    if not fake_modules:
1191      return set()
1192    return set(p for p in apk_paths if IsFakeModulePath(p))
1193
1194  def _FakeInstall(self, fake_apk_paths, fake_modules, package_name):
1195    with tempfile_ext.NamedTemporaryDirectory() as modules_dir:
1196      device_dir = posixpath.join(self.MODULES_SRC_DIRECTORY_PATH, package_name)
1197      if not fake_modules:
1198        # Push empty module dir to clear device dir and update the cache.
1199        self.PushChangedFiles([(modules_dir, device_dir)],
1200                              delete_device_stale=True)
1201        return
1202
1203      still_need_master = set(fake_modules)
1204      for path in fake_apk_paths:
1205        filename = os.path.basename(path)
1206        # Example names: base-en.apk, test_dummy-master.apk.
1207        module_name, suffix = filename.split('-', 1)
1208        if 'master' in suffix:
1209          assert module_name in still_need_master, (
1210              'Duplicate master apk file for %s' % module_name)
1211          still_need_master.remove(module_name)
1212          new_filename = '%s.apk' % module_name
1213        else:
1214          # |suffix| includes .apk extension.
1215          new_filename = '%s.config.%s' % (module_name, suffix)
1216        new_path = os.path.join(modules_dir, new_filename)
1217        os.rename(path, new_path)
1218
1219      assert not still_need_master, (
1220          'Missing master apk file for %s' % still_need_master)
1221      self.PushChangedFiles([(modules_dir, device_dir)],
1222                            delete_device_stale=True)
1223
1224  @decorators.WithTimeoutAndRetriesFromInstance(
1225      min_default_timeout=INSTALL_DEFAULT_TIMEOUT)
1226  def InstallSplitApk(self,
1227                      base_apk,
1228                      split_apks,
1229                      allow_downgrade=False,
1230                      reinstall=False,
1231                      allow_cached_props=False,
1232                      permissions=None,
1233                      timeout=None,
1234                      retries=None):
1235    """Install a split APK.
1236
1237    Noop if all of the APK splits are already installed.
1238
1239    Args:
1240      base_apk: An ApkHelper instance or string containing the path to the base
1241          APK.
1242      split_apks: A list of strings of paths of all of the APK splits.
1243      allow_downgrade: A boolean indicating if we should allow downgrades.
1244      reinstall: A boolean indicating if we should keep any existing app data.
1245      allow_cached_props: Whether to use cached values for device properties.
1246      permissions: Set of permissions to set. If not set, finds permissions with
1247          apk helper. To set no permissions, pass [].
1248      timeout: timeout in seconds
1249      retries: number of retries
1250
1251    Raises:
1252      CommandFailedError if the installation fails.
1253      CommandTimeoutError if the installation times out.
1254      DeviceUnreachableError on missing device.
1255      DeviceVersionError if device SDK is less than Android L.
1256    """
1257    apk = apk_helper.ToSplitHelper(base_apk, split_apks)
1258    with apk.GetApkPaths(
1259        self, allow_cached_props=allow_cached_props) as apk_paths:
1260      self._InstallInternal(
1261          apk,
1262          apk_paths,
1263          reinstall=reinstall,
1264          permissions=permissions,
1265          allow_downgrade=allow_downgrade)
1266
1267  def _InstallInternal(self,
1268                       apk,
1269                       apk_paths,
1270                       allow_downgrade=False,
1271                       reinstall=False,
1272                       permissions=None):
1273    if not apk_paths:
1274      raise device_errors.CommandFailedError('Did not get any APKs to install')
1275
1276    if len(apk_paths) > 1:
1277      self._CheckSdkLevel(version_codes.LOLLIPOP)
1278
1279    missing_apks = [a for a in apk_paths if not os.path.exists(a)]
1280    if missing_apks:
1281      raise device_errors.CommandFailedError(
1282          'Attempted to install non-existent apks: %s' %
1283          pprint.pformat(missing_apks))
1284
1285    package_name = apk.GetPackageName()
1286    device_apk_paths = self._GetApplicationPathsInternal(package_name)
1287
1288    host_checksums = None
1289    if not device_apk_paths:
1290      apks_to_install = apk_paths
1291    elif len(device_apk_paths) > 1 and len(apk_paths) == 1:
1292      logger.warning(
1293          'Installing non-split APK when split APK was previously installed')
1294      apks_to_install = apk_paths
1295    elif len(device_apk_paths) == 1 and len(apk_paths) > 1:
1296      logger.warning(
1297          'Installing split APK when non-split APK was previously installed')
1298      apks_to_install = apk_paths
1299    else:
1300      try:
1301        apks_to_install, host_checksums = (self._ComputeStaleApks(
1302            package_name, apk_paths))
1303      except device_errors.CommandFailedError as e:
1304        logger.warning('Error calculating md5: %s', e)
1305        apks_to_install, host_checksums = apk_paths, None
1306      if apks_to_install and not reinstall:
1307        apks_to_install = apk_paths
1308
1309    if device_apk_paths and apks_to_install and not reinstall:
1310      logger.info('Uninstalling package %s', package_name)
1311      self.Uninstall(package_name)
1312
1313    if apks_to_install:
1314      # Assume that we won't know the resulting device state.
1315      self._cache['package_apk_paths'].pop(package_name, 0)
1316      self._cache['package_apk_checksums'].pop(package_name, 0)
1317      partial = package_name if len(apks_to_install) < len(apk_paths) else None
1318      streaming = None
1319      if self.product_name in _NO_STREAMING_DEVICE_LIST:
1320        streaming = False
1321      logger.info('Installing package %s using APKs %s',
1322                  package_name, apks_to_install)
1323      if len(apks_to_install) > 1 or partial:
1324        self.adb.InstallMultiple(
1325            apks_to_install,
1326            partial=partial,
1327            reinstall=reinstall,
1328            streaming=streaming,
1329            allow_downgrade=allow_downgrade)
1330      else:
1331        self.adb.Install(
1332            apks_to_install[0],
1333            reinstall=reinstall,
1334            streaming=streaming,
1335            allow_downgrade=allow_downgrade)
1336    else:
1337      logger.info('Skipping installation of package %s', package_name)
1338      # Running adb install terminates running instances of the app, so to be
1339      # consistent, we explicitly terminate it when skipping the install.
1340      self.ForceStop(package_name)
1341
1342    # There have been cases of APKs not being detected after being explicitly
1343    # installed, so perform a sanity check now and fail early if the
1344    # installation somehow failed.
1345    apk_version = apk.GetVersionCode()
1346    if not self.IsApplicationInstalled(package_name, apk_version):
1347      raise device_errors.CommandFailedError(
1348          'Package %s with version %s not installed on device after explicit '
1349          'install attempt.' % (package_name, apk_version))
1350
1351    if (permissions is None
1352        and self.build_version_sdk >= version_codes.MARSHMALLOW):
1353      permissions = apk.GetPermissions()
1354    self.GrantPermissions(package_name, permissions)
1355    # Upon success, we know the device checksums, but not their paths.
1356    if host_checksums is not None:
1357      self._cache['package_apk_checksums'][package_name] = host_checksums
1358
1359  @decorators.WithTimeoutAndRetriesFromInstance()
1360  def Uninstall(self, package_name, keep_data=False, timeout=None,
1361                retries=None):
1362    """Remove the app |package_name| from the device.
1363
1364    This is a no-op if the app is not already installed.
1365
1366    Args:
1367      package_name: The package to uninstall.
1368      keep_data: (optional) Whether to keep the data and cache directories.
1369      timeout: Timeout in seconds.
1370      retries: Number of retries.
1371
1372    Raises:
1373      CommandFailedError if the uninstallation fails.
1374      CommandTimeoutError if the uninstallation times out.
1375      DeviceUnreachableError on missing device.
1376    """
1377    installed = self._GetApplicationPathsInternal(package_name)
1378    if not installed:
1379      return
1380    # cached package paths are indeterminate due to system apps taking over
1381    # user apps after uninstall, so clear it
1382    self._cache['package_apk_paths'].pop(package_name, 0)
1383    self._cache['package_apk_checksums'].pop(package_name, 0)
1384    self.adb.Uninstall(package_name, keep_data)
1385
1386  def _CheckSdkLevel(self, required_sdk_level):
1387    """Raises an exception if the device does not have the required SDK level.
1388    """
1389    if self.build_version_sdk < required_sdk_level:
1390      raise device_errors.DeviceVersionError(
1391          ('Requires SDK level %s, device is SDK level %s' %
1392           (required_sdk_level, self.build_version_sdk)),
1393          device_serial=self.serial)
1394
1395  @decorators.WithTimeoutAndRetriesFromInstance()
1396  def RunShellCommand(self,
1397                      cmd,
1398                      shell=False,
1399                      check_return=False,
1400                      cwd=None,
1401                      env=None,
1402                      run_as=None,
1403                      as_root=False,
1404                      single_line=False,
1405                      large_output=False,
1406                      raw_output=False,
1407                      timeout=None,
1408                      retries=None):
1409    """Run an ADB shell command.
1410
1411    The command to run |cmd| should be a sequence of program arguments
1412    (preferred) or a single string with a shell script to run.
1413
1414    When |cmd| is a sequence, it is assumed to contain the name of the command
1415    to run followed by its arguments. In this case, arguments are passed to the
1416    command exactly as given, preventing any further processing by the shell.
1417    This allows callers to easily pass arguments with spaces or special
1418    characters without having to worry about quoting rules. Whenever possible,
1419    it is recomended to pass |cmd| as a sequence.
1420
1421    When |cmd| is passed as a single string, |shell| should be set to True.
1422    The command will be interpreted and run by the shell on the device,
1423    allowing the use of shell features such as pipes, wildcards, or variables.
1424    Failing to set shell=True will issue a warning, but this will be changed
1425    to a hard failure in the future (see: catapult:#3242).
1426
1427    This behaviour is consistent with that of command runners in cmd_helper as
1428    well as Python's own subprocess.Popen.
1429
1430    TODO(crbug.com/1029769) Change the default of |check_return| to True when
1431    callers have switched to the new behaviour.
1432
1433    Args:
1434      cmd: A sequence containing the command to run and its arguments, or a
1435        string with a shell script to run (should also set shell=True).
1436      shell: A boolean indicating whether shell features may be used in |cmd|.
1437      check_return: A boolean indicating whether or not the return code should
1438        be checked.
1439      cwd: The device directory in which the command should be run.
1440      env: The environment variables with which the command should be run.
1441      run_as: A string containing the package as which the command should be
1442        run.
1443      as_root: A boolean indicating whether the shell command should be run
1444        with root privileges.
1445      single_line: A boolean indicating if only a single line of output is
1446        expected.
1447      large_output: Uses a work-around for large shell command output. Without
1448        this large output will be truncated.
1449      raw_output: Whether to only return the raw output
1450          (no splitting into lines).
1451      timeout: timeout in seconds
1452      retries: number of retries
1453
1454    Returns:
1455      If single_line is False, the output of the command as a list of lines,
1456      otherwise, a string with the unique line of output emmited by the command
1457      (with the optional newline at the end stripped).
1458
1459    Raises:
1460      AdbCommandFailedError if check_return is True and the exit code of
1461        the command run on the device is non-zero.
1462      CommandFailedError if single_line is True but the output contains two or
1463        more lines.
1464      CommandTimeoutError on timeout.
1465      DeviceUnreachableError on missing device.
1466    """
1467
1468    def env_quote(key, value):
1469      if not DeviceUtils._VALID_SHELL_VARIABLE.match(key):
1470        raise KeyError('Invalid shell variable name %r' % key)
1471      # using double quotes here to allow interpolation of shell variables
1472      return '%s=%s' % (key, cmd_helper.DoubleQuote(value))
1473
1474    def run(cmd):
1475      return self.adb.Shell(cmd)
1476
1477    def handle_check_return(cmd):
1478      try:
1479        return run(cmd)
1480      except device_errors.AdbCommandFailedError as exc:
1481        if check_return:
1482          raise
1483        else:
1484          return exc.output
1485
1486    def handle_large_command(cmd):
1487      if len(cmd) < self._MAX_ADB_COMMAND_LENGTH:
1488        return handle_check_return(cmd)
1489      else:
1490        with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
1491          self._WriteFileWithPush(script.name, cmd)
1492          logger.debug('Large shell command will be run from file: %s ...',
1493                       cmd[:self._MAX_ADB_COMMAND_LENGTH])
1494          return handle_check_return('sh %s' % script.name_quoted)
1495
1496    def handle_large_output(cmd, large_output_mode):
1497      if large_output_mode:
1498        with device_temp_file.DeviceTempFile(self.adb) as large_output_file:
1499          large_output_cmd = '( %s )>%s 2>&1' % (cmd, large_output_file.name)
1500          logger.debug('Large output mode enabled. Will write output to '
1501                       'device and read results from file.')
1502          try:
1503            handle_large_command(large_output_cmd)
1504            return self.ReadFile(large_output_file.name, force_pull=True)
1505          except device_errors.AdbShellCommandFailedError as exc:
1506            output = self.ReadFile(large_output_file.name, force_pull=True)
1507            raise device_errors.AdbShellCommandFailedError(
1508                cmd, output, exc.status, exc.device_serial)
1509      else:
1510        try:
1511          return handle_large_command(cmd)
1512        except device_errors.AdbCommandFailedError as exc:
1513          if exc.status is None:
1514            logger.error(_FormatPartialOutputError(exc.output))
1515            logger.warning('Attempting to run in large_output mode.')
1516            logger.warning('Use RunShellCommand(..., large_output=True) for '
1517                           'shell commands that expect a lot of output.')
1518            return handle_large_output(cmd, True)
1519          else:
1520            raise
1521
1522    if isinstance(cmd, six.string_types):
1523      if not shell:
1524        # TODO(crbug.com/1029769): Make this an error instead.
1525        logger.warning(
1526            'The command to run should preferably be passed as a sequence of'
1527            ' args. If shell features are needed (pipes, wildcards, variables)'
1528            ' clients should explicitly set shell=True.')
1529    else:
1530      cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
1531    if env:
1532      env = ' '.join(env_quote(k, v) for k, v in env.items())
1533      cmd = '%s %s' % (env, cmd)
1534    if cwd:
1535      cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd)
1536    if run_as:
1537      cmd = 'run-as %s sh -c %s' % (cmd_helper.SingleQuote(run_as),
1538                                    cmd_helper.SingleQuote(cmd))
1539    if (as_root is _FORCE_SU) or (as_root and self.NeedsSU()):
1540      # "su -c sh -c" allows using shell features in |cmd|
1541      cmd = self._Su('sh -c %s' % cmd_helper.SingleQuote(cmd))
1542
1543    output = handle_large_output(cmd, large_output)
1544
1545    if raw_output:
1546      return output
1547
1548    output = output.splitlines()
1549    if single_line:
1550      if not output:
1551        return ''
1552      elif len(output) == 1:
1553        return output[0]
1554      else:
1555        msg = 'one line of output was expected, but got: %s'
1556        raise device_errors.CommandFailedError(msg % output, str(self))
1557    else:
1558      return output
1559
1560  def _RunPipedShellCommand(self, script, **kwargs):
1561    PIPESTATUS_LEADER = 'PIPESTATUS: '
1562
1563    script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER
1564    kwargs.update(shell=True, check_return=True)
1565    output = self.RunShellCommand(script, **kwargs)
1566    pipestatus_line = output[-1]
1567
1568    if not pipestatus_line.startswith(PIPESTATUS_LEADER):
1569      logger.error('Pipe exit statuses of shell script missing.')
1570      raise device_errors.AdbShellCommandFailedError(
1571          script, output, status=None, device_serial=self.serial)
1572
1573    output = output[:-1]
1574    statuses = [
1575        int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split()
1576    ]
1577    if any(statuses):
1578      raise device_errors.AdbShellCommandFailedError(
1579          script, output, status=statuses, device_serial=self.serial)
1580    return output
1581
1582  @decorators.WithTimeoutAndRetriesFromInstance()
1583  def KillAll(self,
1584              process_name,
1585              exact=False,
1586              signum=device_signal.SIGKILL,
1587              as_root=False,
1588              blocking=False,
1589              quiet=False,
1590              timeout=None,
1591              retries=None):
1592    """Kill all processes with the given name on the device.
1593
1594    Args:
1595      process_name: A string containing the name of the process to kill.
1596      exact: A boolean indicating whether to kill all processes matching
1597             the string |process_name| exactly, or all of those which contain
1598             |process_name| as a substring. Defaults to False.
1599      signum: An integer containing the signal number to send to kill. Defaults
1600              to SIGKILL (9).
1601      as_root: A boolean indicating whether the kill should be executed with
1602               root privileges.
1603      blocking: A boolean indicating whether we should wait until all processes
1604                with the given |process_name| are dead.
1605      quiet: A boolean indicating whether to ignore the fact that no processes
1606             to kill were found.
1607      timeout: timeout in seconds
1608      retries: number of retries
1609
1610    Returns:
1611      The number of processes attempted to kill.
1612
1613    Raises:
1614      CommandFailedError if no process was killed and |quiet| is False.
1615      CommandTimeoutError on timeout.
1616      DeviceUnreachableError on missing device.
1617    """
1618    processes = self.ListProcesses(process_name)
1619    if exact:
1620      processes = [p for p in processes if p.name == process_name]
1621    if not processes:
1622      if quiet:
1623        return 0
1624      else:
1625        raise device_errors.CommandFailedError(
1626            'No processes matching %r (exact=%r)' % (process_name, exact),
1627            str(self))
1628
1629    logger.info('KillAll(%r, ...) attempting to kill the following:',
1630                process_name)
1631    for p in processes:
1632      logger.info('  %05d %s', p.pid, p.name)
1633
1634    pids = set(p.pid for p in processes)
1635    cmd = ['kill', '-%d' % signum] + sorted(str(p) for p in pids)
1636    self.RunShellCommand(cmd, as_root=as_root, check_return=True)
1637
1638    def all_pids_killed():
1639      pids_left = (p.pid for p in self.ListProcesses(process_name))
1640      return not pids.intersection(pids_left)
1641
1642    if blocking:
1643      timeout_retry.WaitFor(all_pids_killed, wait_period=0.1)
1644
1645    return len(pids)
1646
1647  @decorators.WithTimeoutAndRetriesFromInstance()
1648  def StartActivity(self,
1649                    intent_obj,
1650                    blocking=False,
1651                    trace_file_name=None,
1652                    force_stop=False,
1653                    timeout=None,
1654                    retries=None):
1655    """Start package's activity on the device.
1656
1657    Args:
1658      intent_obj: An Intent object to send.
1659      blocking: A boolean indicating whether we should wait for the activity to
1660                finish launching.
1661      trace_file_name: If present, a string that both indicates that we want to
1662                       profile the activity and contains the path to which the
1663                       trace should be saved.
1664      force_stop: A boolean indicating whether we should stop the activity
1665                  before starting it.
1666      timeout: timeout in seconds
1667      retries: number of retries
1668
1669    Raises:
1670      CommandFailedError if the activity could not be started.
1671      CommandTimeoutError on timeout.
1672      DeviceUnreachableError on missing device.
1673    """
1674    cmd = ['am', 'start']
1675    if blocking:
1676      cmd.append('-W')
1677    if trace_file_name:
1678      cmd.extend(['--start-profiler', trace_file_name])
1679    if force_stop:
1680      cmd.append('-S')
1681    cmd.extend(intent_obj.am_args)
1682    for line in self.RunShellCommand(cmd, check_return=True):
1683      if line.startswith('Error:'):
1684        raise device_errors.CommandFailedError(line, str(self))
1685
1686  @decorators.WithTimeoutAndRetriesFromInstance()
1687  def StartService(self, intent_obj, user_id=None, timeout=None, retries=None):
1688    """Start a service on the device.
1689
1690    Args:
1691      intent_obj: An Intent object to send describing the service to start.
1692      user_id: A specific user to start the service as, defaults to current.
1693      timeout: Timeout in seconds.
1694      retries: Number of retries
1695
1696    Raises:
1697      CommandFailedError if the service could not be started.
1698      CommandTimeoutError on timeout.
1699      DeviceUnreachableError on missing device.
1700    """
1701    # For whatever reason, startservice was changed to start-service on O and
1702    # above.
1703    cmd = ['am', 'startservice']
1704    if self.build_version_sdk >= version_codes.OREO:
1705      cmd[1] = 'start-service'
1706    if user_id:
1707      cmd.extend(['--user', str(user_id)])
1708    cmd.extend(intent_obj.am_args)
1709    for line in self.RunShellCommand(cmd, check_return=True):
1710      if line.startswith('Error:'):
1711        raise device_errors.CommandFailedError(line, str(self))
1712
1713  @decorators.WithTimeoutAndRetriesFromInstance()
1714  def StartInstrumentation(self,
1715                           component,
1716                           finish=True,
1717                           raw=False,
1718                           extras=None,
1719                           timeout=None,
1720                           retries=None):
1721    if extras is None:
1722      extras = {}
1723
1724    cmd = ['am', 'instrument']
1725    if finish:
1726      cmd.append('-w')
1727    if raw:
1728      cmd.append('-r')
1729    for k, v in extras.items():
1730      cmd.extend(['-e', str(k), str(v)])
1731    cmd.append(component)
1732
1733    # Store the package name in a shell variable to help the command stay under
1734    # the _MAX_ADB_COMMAND_LENGTH limit.
1735    package = component.split('/')[0]
1736    shell_snippet = 'p=%s;%s' % (package,
1737                                 cmd_helper.ShrinkToSnippet(cmd, 'p', package))
1738    return self.RunShellCommand(
1739        shell_snippet, shell=True, check_return=True, large_output=True)
1740
1741  @decorators.WithTimeoutAndRetriesFromInstance()
1742  def BroadcastIntent(self, intent_obj, timeout=None, retries=None):
1743    """Send a broadcast intent.
1744
1745    Args:
1746      intent: An Intent to broadcast.
1747      timeout: timeout in seconds
1748      retries: number of retries
1749
1750    Raises:
1751      CommandTimeoutError on timeout.
1752      DeviceUnreachableError on missing device.
1753    """
1754    cmd = ['am', 'broadcast'] + intent_obj.am_args
1755    self.RunShellCommand(cmd, check_return=True)
1756
1757  @decorators.WithTimeoutAndRetriesFromInstance()
1758  def GoHome(self, timeout=None, retries=None):
1759    """Return to the home screen and obtain launcher focus.
1760
1761    This command launches the home screen and attempts to obtain
1762    launcher focus until the timeout is reached.
1763
1764    Args:
1765      timeout: timeout in seconds
1766      retries: number of retries
1767
1768    Raises:
1769      CommandTimeoutError on timeout.
1770      DeviceUnreachableError on missing device.
1771    """
1772
1773    def is_launcher_focused():
1774      output = self.RunShellCommand(['dumpsys', 'window', 'windows'],
1775                                    check_return=True,
1776                                    large_output=True)
1777      return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output)
1778
1779    def dismiss_popups():
1780      # There is a dialog present; attempt to get rid of it.
1781      # Not all dialogs can be dismissed with back.
1782      self.SendKeyEvent(keyevent.KEYCODE_ENTER)
1783      self.SendKeyEvent(keyevent.KEYCODE_BACK)
1784      return is_launcher_focused()
1785
1786    # If Home is already focused, return early to avoid unnecessary work.
1787    if is_launcher_focused():
1788      return
1789
1790    self.StartActivity(
1791        intent.Intent(
1792            action='android.intent.action.MAIN',
1793            category='android.intent.category.HOME'),
1794        blocking=True)
1795
1796    if not is_launcher_focused():
1797      timeout_retry.WaitFor(dismiss_popups, wait_period=1)
1798
1799  @decorators.WithTimeoutAndRetriesFromInstance()
1800  def ForceStop(self, package, timeout=None, retries=None):
1801    """Close the application.
1802
1803    Args:
1804      package: A string containing the name of the package to stop.
1805      timeout: timeout in seconds
1806      retries: number of retries
1807
1808    Raises:
1809      CommandTimeoutError on timeout.
1810      DeviceUnreachableError on missing device.
1811    """
1812    if self.GetApplicationPids(package):
1813      self.RunShellCommand(['am', 'force-stop', package], check_return=True)
1814
1815  @decorators.WithTimeoutAndRetriesFromInstance()
1816  def ClearApplicationState(self,
1817                            package,
1818                            permissions=None,
1819                            timeout=None,
1820                            retries=None):
1821    """Clear all state for the given package.
1822
1823    Args:
1824      package: A string containing the name of the package to stop.
1825      permissions: List of permissions to set after clearing data.
1826      timeout: timeout in seconds
1827      retries: number of retries
1828
1829    Raises:
1830      CommandTimeoutError on timeout.
1831      DeviceUnreachableError on missing device.
1832    """
1833    # Check that the package exists before clearing it for android builds below
1834    # JB MR2. Necessary because calling pm clear on a package that doesn't exist
1835    # may never return.
1836    if ((self.build_version_sdk >= version_codes.JELLY_BEAN_MR2)
1837        or self._GetApplicationPathsInternal(package)):
1838      self.RunShellCommand(['pm', 'clear', package], check_return=True)
1839      self.GrantPermissions(package, permissions)
1840
1841  @decorators.WithTimeoutAndRetriesFromInstance()
1842  def SendKeyEvent(self, keycode, timeout=None, retries=None):
1843    """Sends a keycode to the device.
1844
1845    See the devil.android.sdk.keyevent module for suitable keycode values.
1846
1847    Args:
1848      keycode: A integer keycode to send to the device.
1849      timeout: timeout in seconds
1850      retries: number of retries
1851
1852    Raises:
1853      CommandTimeoutError on timeout.
1854      DeviceUnreachableError on missing device.
1855    """
1856    self.RunShellCommand(
1857        ['input', 'keyevent', format(keycode, 'd')], check_return=True)
1858
1859  PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
1860
1861  @decorators.WithTimeoutAndRetriesFromInstance(
1862      min_default_timeout=PUSH_CHANGED_FILES_DEFAULT_TIMEOUT)
1863  def PushChangedFiles(self,
1864                       host_device_tuples,
1865                       delete_device_stale=False,
1866                       timeout=None,
1867                       retries=None):
1868    """Push files to the device, skipping files that don't need updating.
1869
1870    When a directory is pushed, it is traversed recursively on the host and
1871    all files in it are pushed to the device as needed.
1872    Additionally, if delete_device_stale option is True,
1873    files that exist on the device but don't exist on the host are deleted.
1874
1875    Args:
1876      host_device_tuples: A list of (host_path, device_path) tuples, where
1877        |host_path| is an absolute path of a file or directory on the host
1878        that should be minimially pushed to the device, and |device_path| is
1879        an absolute path of the destination on the device.
1880      delete_device_stale: option to delete stale files on device
1881      timeout: timeout in seconds
1882      retries: number of retries
1883
1884    Raises:
1885      CommandFailedError on failure.
1886      CommandTimeoutError on timeout.
1887      DeviceUnreachableError on missing device.
1888    """
1889    # TODO(crbug.com/1005504): Experiment with this on physical devices after
1890    # upgrading devil's default adb beyond 1.0.39.
1891    # TODO(crbug.com/1020716): disabled as can result in extra directory.
1892    enable_push_sync = False
1893
1894    if enable_push_sync:
1895      try:
1896        self._PushChangedFilesSync(host_device_tuples)
1897        return
1898      except device_errors.AdbVersionError as e:
1899        # If we don't meet the adb requirements, fall back to the previous
1900        # sync-unaware implementation.
1901        logging.warning(str(e))
1902
1903    changed_files, missing_dirs, cache_commit_func = (self._GetChangedFiles(
1904        host_device_tuples, delete_device_stale))
1905
1906    if changed_files:
1907      if missing_dirs:
1908        self.RunShellCommand(['mkdir', '-p'] + list(missing_dirs),
1909                             check_return=True)
1910      self._PushFilesImpl(host_device_tuples, changed_files)
1911    cache_commit_func()
1912
1913  def _PushChangedFilesSync(self, host_device_tuples):
1914    """Push changed files via `adb sync`.
1915
1916    Args:
1917      host_device_tuples: Same as PushChangedFiles.
1918    """
1919    for h, d in host_device_tuples:
1920      for ph, pd, _ in _IterPushableComponents(h, d):
1921        self.adb.Push(ph, pd, sync=True)
1922
1923
1924  def _GetDeviceNodes(self, paths):
1925    """Get the set of all files and directories on the device contained within
1926    the provided list of paths, without recursively expanding directories.
1927
1928    Args:
1929      paths: The list of paths for which to list files and directories.
1930
1931    Returns:
1932      a set containing all files and directories contained within |paths| on the
1933      device.
1934    """
1935    nodes = set()
1936    paths = [p.replace(' ', r'\ ') for p in paths]
1937    command = _FILE_LIST_SCRIPT % ' '.join(paths)
1938    current_path = ""
1939    # We use shell=True to evaluate the command as a script through the shell,
1940    # otherwise RunShellCommand tries to interpret it as the name of a (non
1941    # existent) command to run.
1942    for line in self.RunShellCommand(command, shell=True, check_return=True):
1943      # If the line is an absolute path it's a directory, otherwise it's a file
1944      # within the most recent directory.
1945      if posixpath.isabs(line):
1946        current_path = line + '/'
1947      else:
1948        line = current_path + line
1949      nodes.add(line)
1950
1951    return nodes
1952
1953  def _GetChangedFiles(self, host_device_tuples, delete_stale=False):
1954    """Get files to push and delete.
1955
1956    Args:
1957      host_device_tuples: a list of (host_files_path, device_files_path) tuples
1958        to find changed files from
1959      delete_stale: Whether to delete stale files
1960
1961    Returns:
1962      a three-element tuple
1963      1st element: a list of (host_files_path, device_files_path) tuples to push
1964      2nd element: a list of missing device directories to mkdir
1965      3rd element: a cache commit function
1966    """
1967    # The fully expanded list of host/device tuples of files to push.
1968    file_tuples = []
1969    # All directories we're pushing files to.
1970    device_dirs_to_push_to = set()
1971    # All files and directories we expect to have on the device after pushing
1972    # files.
1973    expected_device_nodes = set()
1974
1975    for h, d in host_device_tuples:
1976      assert os.path.isabs(h) and posixpath.isabs(d)
1977      h = os.path.realpath(h)
1978      host_path = h.rstrip('/')
1979      device_dir = d.rstrip('/')
1980
1981      expected_device_nodes.add(device_dir)
1982
1983      # Add all parent directories to the directories we expect to have so we
1984      # don't delete empty nested directories.
1985      parent = posixpath.dirname(device_dir)
1986      while parent and parent != '/':
1987        expected_device_nodes.add(parent)
1988        parent = posixpath.dirname(parent)
1989
1990      if os.path.isdir(host_path):
1991        device_dirs_to_push_to.add(device_dir)
1992        for root, _, filenames in os.walk(host_path):
1993          # ignore hidden directories
1994          if os.path.sep + '.' in root:
1995            continue
1996          relative_dir = os.path.relpath(root, host_path).rstrip('.')
1997          device_path = posixpath.join(device_dir, relative_dir).rstrip('/')
1998          expected_device_nodes.add(device_path)
1999          device_dirs_to_push_to.add(device_path)
2000          files = (
2001            [posixpath.join(device_dir, relative_dir, f) for f in filenames])
2002          expected_device_nodes.update(files)
2003          file_tuples.extend(zip(
2004            (os.path.join(root, f) for f in filenames), files))
2005      else:
2006        device_dirs_to_push_to.add(posixpath.dirname(device_dir))
2007        file_tuples.append((host_path, device_dir))
2008
2009    if file_tuples or delete_stale:
2010      current_device_nodes = self._GetDeviceNodes(device_dirs_to_push_to)
2011      nodes_to_delete = current_device_nodes - expected_device_nodes
2012
2013    missing_dirs = device_dirs_to_push_to - current_device_nodes
2014
2015    if not file_tuples:
2016      if delete_stale and nodes_to_delete:
2017        self.RemovePath(nodes_to_delete, force=True, recursive=True)
2018      return (host_device_tuples, missing_dirs, lambda: 0)
2019
2020    possibly_stale_device_nodes = current_device_nodes - nodes_to_delete
2021    possibly_stale_tuples = (
2022      [t for t in file_tuples if t[1] in possibly_stale_device_nodes])
2023
2024    def calculate_host_checksums():
2025      # Need to compute all checksums when caching.
2026      if self._enable_device_files_cache:
2027        return md5sum.CalculateHostMd5Sums([t[0] for t in file_tuples])
2028      else:
2029        return md5sum.CalculateHostMd5Sums(
2030            [t[0] for t in possibly_stale_tuples])
2031
2032    def calculate_device_checksums():
2033      paths = set([t[1] for t in possibly_stale_tuples])
2034      if not paths:
2035        return dict()
2036      sums = dict()
2037      if self._enable_device_files_cache:
2038        paths_not_in_cache = set()
2039        for path in paths:
2040          cache_entry = self._cache['device_path_checksums'].get(path)
2041          if cache_entry:
2042            sums[path] = cache_entry
2043          else:
2044            paths_not_in_cache.add(path)
2045        paths = paths_not_in_cache
2046      sums.update(dict(md5sum.CalculateDeviceMd5Sums(paths, self)))
2047      if self._enable_device_files_cache:
2048        for path, checksum in sums.iteritems():
2049          self._cache['device_path_checksums'][path] = checksum
2050      return sums
2051    try:
2052      host_checksums, device_checksums = reraiser_thread.RunAsync(
2053          (calculate_host_checksums, calculate_device_checksums))
2054    except device_errors.CommandFailedError as e:
2055      logger.warning('Error calculating md5: %s', e)
2056      return (host_device_tuples, set(), lambda: 0)
2057
2058    up_to_date = set()
2059
2060    for host_path, device_path in possibly_stale_tuples:
2061      device_checksum = device_checksums.get(device_path, None)
2062      host_checksum = host_checksums.get(host_path, None)
2063      if device_checksum == host_checksum and device_checksum is not None:
2064        up_to_date.add(device_path)
2065      else:
2066        nodes_to_delete.add(device_path)
2067
2068    if delete_stale and nodes_to_delete:
2069      self.RemovePath(nodes_to_delete, force=True, recursive=True)
2070
2071    to_push = (
2072        [t for t in file_tuples if t[1] not in up_to_date])
2073
2074    def cache_commit_func():
2075      if not self._enable_device_files_cache:
2076        return
2077      for host_path, device_path in file_tuples:
2078        host_checksum = host_checksums.get(host_path, None)
2079        self._cache['device_path_checksums'][device_path] = host_checksum
2080
2081    return (to_push, missing_dirs, cache_commit_func)
2082
2083  def _ComputeDeviceChecksumsForApks(self, package_name):
2084    ret = self._cache['package_apk_checksums'].get(package_name)
2085    if ret is None:
2086      if self.PathExists('/data/data/' + package_name, as_root=True):
2087        device_paths = self._GetApplicationPathsInternal(package_name)
2088        file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self)
2089        ret = set(file_to_checksums.values())
2090      else:
2091        logger.info('Cannot reuse package %s (data directory missing)',
2092                    package_name)
2093        ret = set()
2094      self._cache['package_apk_checksums'][package_name] = ret
2095    return ret
2096
2097  def _ComputeStaleApks(self, package_name, host_apk_paths):
2098    def calculate_host_checksums():
2099      return md5sum.CalculateHostMd5Sums(host_apk_paths)
2100
2101    def calculate_device_checksums():
2102      return self._ComputeDeviceChecksumsForApks(package_name)
2103
2104    host_checksums, device_checksums = reraiser_thread.RunAsync(
2105        (calculate_host_checksums, calculate_device_checksums))
2106    stale_apks = [
2107        k for (k, v) in host_checksums.iteritems() if v not in device_checksums
2108    ]
2109    return stale_apks, set(host_checksums.values())
2110
2111  def _PushFilesImpl(self, host_device_tuples, files):
2112    if not files:
2113      return
2114
2115    size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files)
2116    file_count = len(files)
2117    dir_size = sum(
2118        host_utils.GetRecursiveDiskUsage(h) for h, _ in host_device_tuples)
2119    dir_file_count = 0
2120    for h, _ in host_device_tuples:
2121      if os.path.isdir(h):
2122        dir_file_count += sum(len(f) for _r, _d, f in os.walk(h))
2123      else:
2124        dir_file_count += 1
2125
2126    push_duration = self._ApproximateDuration(file_count, file_count, size,
2127                                              False)
2128    dir_push_duration = self._ApproximateDuration(
2129        len(host_device_tuples), dir_file_count, dir_size, False)
2130    zip_duration = self._ApproximateDuration(1, 1, size, True)
2131
2132    if (dir_push_duration < push_duration and dir_push_duration < zip_duration
2133        # TODO(jbudorick): Resume directory pushing once clients have switched
2134        # to 1.0.36-compatible syntax.
2135        and False):
2136      self._PushChangedFilesIndividually(host_device_tuples)
2137    elif push_duration < zip_duration:
2138      self._PushChangedFilesIndividually(files)
2139    elif self._commands_installed is False:
2140      # Already tried and failed to install unzip command.
2141      self._PushChangedFilesIndividually(files)
2142    elif not self._PushChangedFilesZipped(files,
2143                                          [d for _, d in host_device_tuples]):
2144      self._PushChangedFilesIndividually(files)
2145
2146  def _MaybeInstallCommands(self):
2147    if self._commands_installed is None:
2148      try:
2149        if not install_commands.Installed(self):
2150          install_commands.InstallCommands(self)
2151        self._commands_installed = True
2152      except device_errors.CommandFailedError as e:
2153        logger.warning('unzip not available: %s', str(e))
2154        self._commands_installed = False
2155    return self._commands_installed
2156
2157  @staticmethod
2158  def _ApproximateDuration(adb_calls, file_count, byte_count, is_zipping):
2159    # We approximate the time to push a set of files to a device as:
2160    #   t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where
2161    #     t: total time (sec)
2162    #     c1: adb call time delay (sec)
2163    #     a: number of times adb is called (unitless)
2164    #     c2: push time delay (sec)
2165    #     f: number of files pushed via adb (unitless)
2166    #     c3: zip time delay (sec)
2167    #     c4: zip rate (bytes/sec)
2168    #     b: total number of bytes (bytes)
2169    #     c5: transfer rate (bytes/sec)
2170    #     c6: compression ratio (unitless)
2171
2172    # All of these are approximations.
2173    ADB_CALL_PENALTY = 0.1  # seconds
2174    ADB_PUSH_PENALTY = 0.01  # seconds
2175    ZIP_PENALTY = 2.0  # seconds
2176    ZIP_RATE = 10000000.0  # bytes / second
2177    TRANSFER_RATE = 2000000.0  # bytes / second
2178    COMPRESSION_RATIO = 2.0  # unitless
2179
2180    adb_call_time = ADB_CALL_PENALTY * adb_calls
2181    adb_push_setup_time = ADB_PUSH_PENALTY * file_count
2182    if is_zipping:
2183      zip_time = ZIP_PENALTY + byte_count / ZIP_RATE
2184      transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO)
2185    else:
2186      zip_time = 0
2187      transfer_time = byte_count / TRANSFER_RATE
2188    return adb_call_time + adb_push_setup_time + zip_time + transfer_time
2189
2190  def _PushChangedFilesIndividually(self, files):
2191    for h, d in files:
2192      self.adb.Push(h, d)
2193
2194  def _PushChangedFilesZipped(self, files, dirs):
2195    if not self._MaybeInstallCommands():
2196      return False
2197
2198    with tempfile_ext.NamedTemporaryDirectory() as working_dir:
2199      zip_path = os.path.join(working_dir, 'tmp.zip')
2200      try:
2201        zip_utils.WriteZipFile(zip_path, files)
2202      except zip_utils.ZipFailedError:
2203        return False
2204
2205      logger.info('Pushing %d files via .zip of size %d', len(files),
2206                  os.path.getsize(zip_path))
2207      self.NeedsSU()
2208      with device_temp_file.DeviceTempFile(
2209          self.adb, suffix='.zip') as device_temp:
2210        self.adb.Push(zip_path, device_temp.name)
2211
2212        with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
2213          # Read dirs from temp file to avoid potential errors like
2214          # "Argument list too long" (crbug.com/1174331) when the list
2215          # is too long.
2216          self.WriteFile(
2217              script.name,
2218              _UNZIP_AND_CHMOD_SCRIPT.format(bin_dir=install_commands.BIN_DIR,
2219                                             zip_file=device_temp.name,
2220                                             dirs=' '.join(dirs)))
2221
2222          self.RunShellCommand(['source', script.name],
2223                               check_return=True,
2224                               as_root=True)
2225
2226    return True
2227
2228  # TODO(crbug.com/1111556): remove this and migrate the callsite to
2229  # PathExists().
2230  @decorators.WithTimeoutAndRetriesFromInstance()
2231  def FileExists(self, device_path, timeout=None, retries=None):
2232    """Checks whether the given file exists on the device.
2233
2234    Arguments are the same as PathExists.
2235    """
2236    return self.PathExists(device_path, timeout=timeout, retries=retries)
2237
2238  @decorators.WithTimeoutAndRetriesFromInstance()
2239  def PathExists(self, device_paths, as_root=False, timeout=None, retries=None):
2240    """Checks whether the given path(s) exists on the device.
2241
2242    Args:
2243      device_path: A string containing the absolute path to the file on the
2244                   device, or an iterable of paths to check.
2245      as_root: Whether root permissions should be use to check for the existence
2246               of the given path(s).
2247      timeout: timeout in seconds
2248      retries: number of retries
2249
2250    Returns:
2251      True if the all given paths exist on the device, False otherwise.
2252
2253    Raises:
2254      CommandTimeoutError on timeout.
2255      DeviceUnreachableError on missing device.
2256    """
2257    paths = device_paths
2258    if isinstance(paths, six.string_types):
2259      paths = (paths, )
2260    if not paths:
2261      return True
2262    cmd = ['test', '-e', paths[0]]
2263    for p in paths[1:]:
2264      cmd.extend(['-a', '-e', p])
2265    try:
2266      self.RunShellCommand(
2267          cmd,
2268          as_root=as_root,
2269          check_return=True,
2270          timeout=timeout,
2271          retries=retries)
2272      return True
2273    except device_errors.CommandFailedError:
2274      return False
2275
2276  @decorators.WithTimeoutAndRetriesFromInstance()
2277  def RemovePath(self,
2278                 device_path,
2279                 force=False,
2280                 recursive=False,
2281                 as_root=False,
2282                 rename=False,
2283                 timeout=None,
2284                 retries=None):
2285    """Removes the given path(s) from the device.
2286
2287    Args:
2288      device_path: A string containing the absolute path to the file on the
2289                   device, or an iterable of paths to check.
2290      force: Whether to remove the path(s) with force (-f).
2291      recursive: Whether to remove any directories in the path(s) recursively.
2292      as_root: Whether root permissions should be use to remove the given
2293               path(s).
2294      rename: Whether to rename the path(s) before removing to help avoid
2295            filesystem errors. See https://stackoverflow.com/questions/11539657
2296      timeout: timeout in seconds
2297      retries: number of retries
2298    """
2299
2300    def _RenamePath(path):
2301      random_suffix = hex(random.randint(2**12, 2**16 - 1))[2:]
2302      dest = '%s-%s' % (path, random_suffix)
2303      try:
2304        self.RunShellCommand(['mv', path, dest],
2305                             as_root=as_root,
2306                             check_return=True)
2307        return dest
2308      except device_errors.AdbShellCommandFailedError:
2309        # If it couldn't be moved, just try rm'ing the original path instead.
2310        return path
2311
2312    args = ['rm']
2313    if force:
2314      args.append('-f')
2315    if recursive:
2316      args.append('-r')
2317    if isinstance(device_path, six.string_types):
2318      args.append(device_path if not rename else _RenamePath(device_path))
2319    else:
2320      args.extend(
2321          device_path if not rename else [_RenamePath(p) for p in device_path])
2322    self.RunShellCommand(args, as_root=as_root, check_return=True)
2323
2324  @contextlib.contextmanager
2325  def _CopyToReadableLocation(self, device_path):
2326    """Context manager to copy a file to a globally readable temp file.
2327
2328    This uses root permission to copy a file to a globally readable named
2329    temporary file. The temp file is removed when this contextmanager is closed.
2330
2331    Args:
2332      device_path: A string containing the absolute path of the file (on the
2333        device) to copy.
2334    Yields:
2335      The globally readable file object.
2336    """
2337    with device_temp_file.DeviceTempFile(self.adb) as device_temp:
2338      cmd = 'SRC=%s DEST=%s;cp "$SRC" "$DEST" && chmod 666 "$DEST"' % (
2339          cmd_helper.SingleQuote(device_path),
2340          cmd_helper.SingleQuote(device_temp.name))
2341      self.RunShellCommand(cmd, shell=True, as_root=True, check_return=True)
2342      yield device_temp
2343
2344  @decorators.WithTimeoutAndRetriesFromInstance()
2345  def PullFile(self,
2346               device_path,
2347               host_path,
2348               as_root=False,
2349               timeout=None,
2350               retries=None):
2351    """Pull a file from the device.
2352
2353    Args:
2354      device_path: A string containing the absolute path of the file to pull
2355                   from the device.
2356      host_path: A string containing the absolute path of the destination on
2357                 the host.
2358      as_root: Whether root permissions should be used to pull the file.
2359      timeout: timeout in seconds
2360      retries: number of retries
2361
2362    Raises:
2363      CommandFailedError on failure.
2364      CommandTimeoutError on timeout.
2365    """
2366    # Create the base dir if it doesn't exist already
2367    dirname = os.path.dirname(host_path)
2368    if dirname and not os.path.exists(dirname):
2369      os.makedirs(dirname)
2370    if as_root and self.NeedsSU():
2371      if not self.PathExists(device_path, as_root=True):
2372        raise device_errors.CommandFailedError(
2373            '%r: No such file or directory' % device_path, str(self))
2374      with self._CopyToReadableLocation(device_path) as readable_temp_file:
2375        self.adb.Pull(readable_temp_file.name, host_path)
2376    else:
2377      self.adb.Pull(device_path, host_path)
2378
2379  def _ReadFileWithPull(self, device_path):
2380    try:
2381      d = tempfile.mkdtemp()
2382      host_temp_path = os.path.join(d, 'tmp_ReadFileWithPull')
2383      self.adb.Pull(device_path, host_temp_path)
2384      with open(host_temp_path, 'r') as host_temp:
2385        return host_temp.read()
2386    finally:
2387      if os.path.exists(d):
2388        shutil.rmtree(d)
2389
2390  @decorators.WithTimeoutAndRetriesFromInstance()
2391  def ReadFile(self,
2392               device_path,
2393               as_root=False,
2394               force_pull=False,
2395               timeout=None,
2396               retries=None):
2397    """Reads the contents of a file from the device.
2398
2399    Args:
2400      device_path: A string containing the absolute path of the file to read
2401                   from the device.
2402      as_root: A boolean indicating whether the read should be executed with
2403               root privileges.
2404      force_pull: A boolean indicating whether to force the operation to be
2405          performed by pulling a file from the device. The default is, when the
2406          contents are short, to retrieve the contents using cat instead.
2407      timeout: timeout in seconds
2408      retries: number of retries
2409
2410    Returns:
2411      The contents of |device_path| as a string. Contents are intepreted using
2412      universal newlines, so the caller will see them encoded as '\n'. Also,
2413      all lines will be terminated.
2414
2415    Raises:
2416      AdbCommandFailedError if the file can't be read.
2417      CommandTimeoutError on timeout.
2418      DeviceUnreachableError on missing device.
2419    """
2420
2421    def get_size(path):
2422      return self.FileSize(path, as_root=as_root)
2423
2424    # Reading by pulling is faster than first getting the file size and cat-ing,
2425    # so only read by cat when we need root.
2426    if as_root and self.NeedsSU():
2427      if (not force_pull
2428          and 0 < get_size(device_path) <= self._MAX_ADB_OUTPUT_LENGTH):
2429        return _JoinLines(
2430            self.RunShellCommand(['cat', device_path],
2431                                 as_root=as_root,
2432                                 check_return=True))
2433      else:
2434        with self._CopyToReadableLocation(device_path) as readable_temp_file:
2435          return self._ReadFileWithPull(readable_temp_file.name)
2436    else:
2437      return self._ReadFileWithPull(device_path)
2438
2439  def _WriteFileWithPush(self, device_path, contents):
2440    with tempfile.NamedTemporaryFile() as host_temp:
2441      host_temp.write(contents)
2442      host_temp.flush()
2443      self.adb.Push(host_temp.name, device_path)
2444
2445  @decorators.WithTimeoutAndRetriesFromInstance()
2446  def WriteFile(self,
2447                device_path,
2448                contents,
2449                as_root=False,
2450                force_push=False,
2451                timeout=None,
2452                retries=None):
2453    """Writes |contents| to a file on the device.
2454
2455    Args:
2456      device_path: A string containing the absolute path to the file to write
2457          on the device.
2458      contents: A string containing the data to write to the device.
2459      as_root: A boolean indicating whether the write should be executed with
2460          root privileges (if available).
2461      force_push: A boolean indicating whether to force the operation to be
2462          performed by pushing a file to the device. The default is, when the
2463          contents are short, to pass the contents using a shell script instead.
2464      timeout: timeout in seconds
2465      retries: number of retries
2466
2467    Raises:
2468      CommandFailedError if the file could not be written on the device.
2469      CommandTimeoutError on timeout.
2470      DeviceUnreachableError on missing device.
2471    """
2472    if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH:
2473      # If the contents are small, for efficieny we write the contents with
2474      # a shell command rather than pushing a file.
2475      cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents),
2476                                 cmd_helper.SingleQuote(device_path))
2477      self.RunShellCommand(cmd, shell=True, as_root=as_root, check_return=True)
2478    elif as_root and self.NeedsSU():
2479      # Adb does not allow to "push with su", so we first push to a temp file
2480      # on a safe location, and then copy it to the desired location with su.
2481      with device_temp_file.DeviceTempFile(self.adb) as device_temp:
2482        self._WriteFileWithPush(device_temp.name, contents)
2483        # Here we need 'cp' rather than 'mv' because the temp and
2484        # destination files might be on different file systems (e.g.
2485        # on internal storage and an external sd card).
2486        self.RunShellCommand(['cp', device_temp.name, device_path],
2487                             as_root=True,
2488                             check_return=True)
2489    else:
2490      # If root is not needed, we can push directly to the desired location.
2491      self._WriteFileWithPush(device_path, contents)
2492
2493  def _ParseLongLsOutput(self, device_path, as_root=False, **kwargs):
2494    """Run and scrape the output of 'ls -a -l' on a device directory."""
2495    device_path = posixpath.join(device_path, '')  # Force trailing '/'.
2496    output = self.RunShellCommand(['ls', '-a', '-l', device_path],
2497                                  as_root=as_root,
2498                                  check_return=True,
2499                                  env={'TZ': 'utc'},
2500                                  **kwargs)
2501    if output and output[0].startswith('total '):
2502      output.pop(0)  # pylint: disable=maybe-no-member
2503
2504    entries = []
2505    for line in output:
2506      m = _LONG_LS_OUTPUT_RE.match(line)
2507      if m:
2508        if m.group('filename') not in ['.', '..']:
2509          item = m.groupdict()
2510          # A change in toybox is causing recent Android versions to escape
2511          # spaces in file names. Here we just unquote those spaces. If we
2512          # later find more essoteric characters in file names, a more careful
2513          # unquoting mechanism may be needed. But hopefully not.
2514          # See: https://goo.gl/JAebZj
2515          item['filename'] = item['filename'].replace('\\ ', ' ')
2516          entries.append(item)
2517      else:
2518        logger.info('Skipping: %s', line)
2519
2520    return entries
2521
2522  def ListDirectory(self, device_path, as_root=False, **kwargs):
2523    """List all files on a device directory.
2524
2525    Mirroring os.listdir (and most client expectations) the resulting list
2526    does not include the special entries '.' and '..' even if they are present
2527    in the directory.
2528
2529    Args:
2530      device_path: A string containing the path of the directory on the device
2531                   to list.
2532      as_root: A boolean indicating whether the to use root privileges to list
2533               the directory contents.
2534      timeout: timeout in seconds
2535      retries: number of retries
2536
2537    Returns:
2538      A list of filenames for all entries contained in the directory.
2539
2540    Raises:
2541      AdbCommandFailedError if |device_path| does not specify a valid and
2542          accessible directory in the device.
2543      CommandTimeoutError on timeout.
2544      DeviceUnreachableError on missing device.
2545    """
2546    entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs)
2547    return [d['filename'] for d in entries]
2548
2549  def StatDirectory(self, device_path, as_root=False, **kwargs):
2550    """List file and stat info for all entries on a device directory.
2551
2552    Implementation notes: this is currently implemented by parsing the output
2553    of 'ls -a -l' on the device. Whether possible and convenient, we attempt to
2554    make parsing strict and return values mirroring those of the standard |os|
2555    and |stat| Python modules.
2556
2557    Mirroring os.listdir (and most client expectations) the resulting list
2558    does not include the special entries '.' and '..' even if they are present
2559    in the directory.
2560
2561    Args:
2562      device_path: A string containing the path of the directory on the device
2563                   to list.
2564      as_root: A boolean indicating whether the to use root privileges to list
2565               the directory contents.
2566      timeout: timeout in seconds
2567      retries: number of retries
2568
2569    Returns:
2570      A list of dictionaries, each containing the following keys:
2571        filename: A string with the file name.
2572        st_mode: File permissions, use the stat module to interpret these.
2573        st_nlink: Number of hard links (may be missing).
2574        st_owner: A string with the user name of the owner.
2575        st_group: A string with the group name of the owner.
2576        st_rdev_pair: Device type as (major, minior) (only if inode device).
2577        st_size: Size of file, in bytes (may be missing for non-regular files).
2578        st_mtime: Time of most recent modification, in seconds since epoch
2579          (although resolution is in minutes).
2580        symbolic_link_to: If entry is a symbolic link, path where it points to;
2581          missing otherwise.
2582
2583    Raises:
2584      AdbCommandFailedError if |device_path| does not specify a valid and
2585          accessible directory in the device.
2586      CommandTimeoutError on timeout.
2587      DeviceUnreachableError on missing device.
2588    """
2589    entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs)
2590    for d in entries:
2591      for key, value in list(d.items()):
2592        if value is None:
2593          del d[key]  # Remove missing fields.
2594      d['st_mode'] = _ParseModeString(d['st_mode'])
2595      d['st_mtime'] = calendar.timegm(
2596          time.strptime(d['st_mtime'], _LS_DATE_FORMAT))
2597      for key in ['st_nlink', 'st_size', 'st_rdev_major', 'st_rdev_minor']:
2598        if key in d:
2599          d[key] = int(d[key])
2600      if 'st_rdev_major' in d and 'st_rdev_minor' in d:
2601        d['st_rdev_pair'] = (d.pop('st_rdev_major'), d.pop('st_rdev_minor'))
2602    return entries
2603
2604  def StatPath(self, device_path, as_root=False, **kwargs):
2605    """Get the stat attributes of a file or directory on the device.
2606
2607    Args:
2608      device_path: A string containing the path of a file or directory from
2609                   which to get attributes.
2610      as_root: A boolean indicating whether the to use root privileges to
2611               access the file information.
2612      timeout: timeout in seconds
2613      retries: number of retries
2614
2615    Returns:
2616      A dictionary with the stat info collected; see StatDirectory for details.
2617
2618    Raises:
2619      CommandFailedError if device_path cannot be found on the device.
2620      CommandTimeoutError on timeout.
2621      DeviceUnreachableError on missing device.
2622    """
2623    dirname, filename = posixpath.split(posixpath.normpath(device_path))
2624    for entry in self.StatDirectory(dirname, as_root=as_root, **kwargs):
2625      if entry['filename'] == filename:
2626        return entry
2627    raise device_errors.CommandFailedError(
2628        'Cannot find file or directory: %r' % device_path, str(self))
2629
2630  def FileSize(self, device_path, as_root=False, **kwargs):
2631    """Get the size of a file on the device.
2632
2633    Note: This is implemented by parsing the output of the 'ls' command on
2634    the device. On some Android versions, when passing a directory or special
2635    file, the size is *not* reported and this function will throw an exception.
2636
2637    Args:
2638      device_path: A string containing the path of a file on the device.
2639      as_root: A boolean indicating whether the to use root privileges to
2640               access the file information.
2641      timeout: timeout in seconds
2642      retries: number of retries
2643
2644    Returns:
2645      The size of the file in bytes.
2646
2647    Raises:
2648      CommandFailedError if device_path cannot be found on the device, or
2649        its size cannot be determited for some reason.
2650      CommandTimeoutError on timeout.
2651      DeviceUnreachableError on missing device.
2652    """
2653    entry = self.StatPath(device_path, as_root=as_root, **kwargs)
2654    try:
2655      return entry['st_size']
2656    except KeyError:
2657      raise device_errors.CommandFailedError(
2658          'Could not determine the size of: %s' % device_path, str(self))
2659
2660  @decorators.WithTimeoutAndRetriesFromInstance()
2661  def SetJavaAsserts(self, enabled, timeout=None, retries=None):
2662    """Enables or disables Java asserts.
2663
2664    Args:
2665      enabled: A boolean indicating whether Java asserts should be enabled
2666               or disabled.
2667      timeout: timeout in seconds
2668      retries: number of retries
2669
2670    Returns:
2671      True if the device-side property changed and a restart is required as a
2672      result, False otherwise.
2673
2674    Raises:
2675      CommandTimeoutError on timeout.
2676    """
2677
2678    def find_property(lines, property_name):
2679      for index, line in enumerate(lines):
2680        if line.strip() == '':
2681          continue
2682        key_value = tuple(s.strip() for s in line.split('=', 1))
2683        if len(key_value) != 2:
2684          continue
2685        key, value = key_value
2686        if key == property_name:
2687          return index, value
2688      return None, ''
2689
2690    new_value = 'all' if enabled else ''
2691
2692    # First ensure the desired property is persisted.
2693    try:
2694      properties = self.ReadFile(self.LOCAL_PROPERTIES_PATH).splitlines()
2695    except device_errors.CommandFailedError:
2696      properties = []
2697    index, value = find_property(properties, self.JAVA_ASSERT_PROPERTY)
2698    if new_value != value:
2699      if new_value:
2700        new_line = '%s=%s' % (self.JAVA_ASSERT_PROPERTY, new_value)
2701        if index is None:
2702          properties.append(new_line)
2703        else:
2704          properties[index] = new_line
2705      else:
2706        assert index is not None  # since new_value == '' and new_value != value
2707        properties.pop(index)
2708      self.WriteFile(self.LOCAL_PROPERTIES_PATH, _JoinLines(properties))
2709
2710    # Next, check the current runtime value is what we need, and
2711    # if not, set it and report that a reboot is required.
2712    value = self.GetProp(self.JAVA_ASSERT_PROPERTY)
2713    if new_value != value:
2714      self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value)
2715      return True
2716    else:
2717      return False
2718
2719  def GetLocale(self, cache=False):
2720    """Returns the locale setting on the device.
2721
2722    Args:
2723      cache: Whether to use cached properties when available.
2724    Returns:
2725      A pair (language, country).
2726    """
2727    locale = self.GetProp('persist.sys.locale', cache=cache)
2728    if locale:
2729      if '-' not in locale:
2730        logging.error('Unparsable locale: %s', locale)
2731        return ('', '')  # Behave as if persist.sys.locale is undefined.
2732      return tuple(locale.split('-', 1))
2733    return (self.GetProp('persist.sys.language', cache=cache),
2734            self.GetProp('persist.sys.country', cache=cache))
2735
2736  def GetLanguage(self, cache=False):
2737    """Returns the language setting on the device.
2738
2739    DEPRECATED: Prefer GetLocale() instead.
2740
2741    Args:
2742      cache: Whether to use cached properties when available.
2743    """
2744    return self.GetLocale(cache=cache)[0]
2745
2746  def GetCountry(self, cache=False):
2747    """Returns the country setting on the device.
2748
2749    DEPRECATED: Prefer GetLocale() instead.
2750
2751    Args:
2752      cache: Whether to use cached properties when available.
2753    """
2754    return self.GetLocale(cache=cache)[1]
2755
2756  @property
2757  def screen_density(self):
2758    """Returns the screen density of the device."""
2759    DPI_TO_DENSITY = {
2760        120: 'ldpi',
2761        160: 'mdpi',
2762        240: 'hdpi',
2763        320: 'xhdpi',
2764        480: 'xxhdpi',
2765        640: 'xxxhdpi',
2766    }
2767    return DPI_TO_DENSITY.get(self.pixel_density, 'tvdpi')
2768
2769  @property
2770  def pixel_density(self):
2771    density = self.GetProp('ro.sf.lcd_density', cache=True)
2772    if not density:
2773      # It might be an emulator, try the qemu prop.
2774      density = self.GetProp('qemu.sf.lcd_density', cache=True)
2775    return int(density)
2776
2777  @property
2778  def is_emulator(self):
2779    return _EMULATOR_RE.match(self.GetProp('ro.product.device', cache=True))
2780
2781  @property
2782  def build_description(self):
2783    """Returns the build description of the system.
2784
2785    For example:
2786      nakasi-user 4.4.4 KTU84P 1227136 release-keys
2787    """
2788    return self.GetProp('ro.build.description', cache=True)
2789
2790  @property
2791  def build_fingerprint(self):
2792    """Returns the build fingerprint of the system.
2793
2794    For example:
2795      google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys
2796    """
2797    return self.GetProp('ro.build.fingerprint', cache=True)
2798
2799  @property
2800  def build_id(self):
2801    """Returns the build ID of the system (e.g. 'KTU84P')."""
2802    return self.GetProp('ro.build.id', cache=True)
2803
2804  @property
2805  def build_product(self):
2806    """Returns the build product of the system (e.g. 'grouper')."""
2807    return self.GetProp('ro.build.product', cache=True)
2808
2809  @property
2810  def build_system_root_image(self):
2811    """Returns the system_root_image property.
2812
2813    This seems to indicate whether the device is using a system-as-root
2814    partition layout. See http://bit.ly/37F34sx for more info.
2815    """
2816    return self.GetProp('ro.build.system_root_image', cache=True)
2817
2818  @property
2819  def build_type(self):
2820    """Returns the build type of the system (e.g. 'user')."""
2821    return self.GetProp('ro.build.type', cache=True)
2822
2823  @property
2824  def build_version_sdk(self):
2825    """Returns the build version sdk of the system as a number (e.g. 19).
2826
2827    For version code numbers see:
2828    http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
2829
2830    For named constants see devil.android.sdk.version_codes
2831
2832    Raises:
2833      CommandFailedError if the build version sdk is not a number.
2834    """
2835    value = self.GetProp('ro.build.version.sdk', cache=True)
2836    try:
2837      return int(value)
2838    except ValueError:
2839      raise device_errors.CommandFailedError(
2840          'Invalid build version sdk: %r' % value)
2841
2842  @property
2843  def tracing_path(self):
2844    """Returns the tracing path of the device for atrace."""
2845    return self.GetTracingPath()
2846
2847  @property
2848  def product_cpu_abi(self):
2849    """Returns the product cpu abi of the device (e.g. 'armeabi-v7a').
2850
2851    For supported ABIs, the return value will be one of the values defined in
2852    devil.android.ndk.abis.
2853    """
2854    return self.GetProp('ro.product.cpu.abi', cache=True)
2855
2856  @property
2857  def product_cpu_abis(self):
2858    """Returns all product cpu abi of the device."""
2859    return self.GetProp('ro.product.cpu.abilist', cache=True).split(',')
2860
2861  @property
2862  def product_model(self):
2863    """Returns the name of the product model (e.g. 'Nexus 7')."""
2864    return self.GetProp('ro.product.model', cache=True)
2865
2866  @property
2867  def product_name(self):
2868    """Returns the product name of the device (e.g. 'nakasi')."""
2869    return self.GetProp('ro.product.name', cache=True)
2870
2871  @property
2872  def product_board(self):
2873    """Returns the product board name of the device (e.g. 'shamu')."""
2874    return self.GetProp('ro.product.board', cache=True)
2875
2876  def _EnsureCacheInitialized(self):
2877    """Populates cache token, runs getprop and fetches $EXTERNAL_STORAGE."""
2878    if self._cache['token']:
2879      return
2880    with self._cache_lock:
2881      if self._cache['token']:
2882        return
2883      # Change the token every time to ensure that it will match only the
2884      # previously dumped cache.
2885      token = str(uuid.uuid1())
2886      cmd = ('c=/data/local/tmp/cache_token;'
2887             'echo $EXTERNAL_STORAGE;'
2888             'cat $c 2>/dev/null||echo;'
2889             'echo "%s">$c &&' % token + 'getprop')
2890      output = self.RunShellCommand(
2891          cmd, shell=True, check_return=True, large_output=True)
2892      # Error-checking for this existing is done in GetExternalStoragePath().
2893      self._cache['external_storage'] = output[0]
2894      self._cache['prev_token'] = output[1]
2895      output = output[2:]
2896
2897      prop_cache = self._cache['getprop']
2898      prop_cache.clear()
2899      for key, value in _GETPROP_RE.findall(''.join(output)):
2900        prop_cache[key] = value
2901      self._cache['token'] = token
2902
2903  @decorators.WithTimeoutAndRetriesFromInstance()
2904  def GetTracingPath(self, timeout=None, retries=None):
2905    """Gets tracing path from the device.
2906
2907    Args:
2908      timeout: timeout in seconds
2909      retries: number of retries
2910
2911    Returns:
2912      /sys/kernel/debug/tracing for device with debugfs mount support;
2913      /sys/kernel/tracing for device with tracefs support;
2914      /sys/kernel/debug/tracing if support can't be determined.
2915
2916    Raises:
2917      CommandTimeoutError on timeout.
2918    """
2919    tracing_path = self._cache['tracing_path']
2920    if tracing_path:
2921      return tracing_path
2922    with self._cache_lock:
2923      tracing_path = '/sys/kernel/debug/tracing'
2924      try:
2925        lines = self.RunShellCommand(['mount'],
2926                                     check_return=True,
2927                                     timeout=timeout,
2928                                     retries=retries)
2929        if not any('debugfs' in line for line in lines):
2930          tracing_path = '/sys/kernel/tracing'
2931      except device_errors.AdbCommandFailedError:
2932        pass
2933      self._cache['tracing_path'] = tracing_path
2934    return tracing_path
2935
2936  @decorators.WithTimeoutAndRetriesFromInstance()
2937  def GetProp(self, property_name, cache=False, timeout=None, retries=None):
2938    """Gets a property from the device.
2939
2940    Args:
2941      property_name: A string containing the name of the property to get from
2942                     the device.
2943      cache: Whether to use cached properties when available.
2944      timeout: timeout in seconds
2945      retries: number of retries
2946
2947    Returns:
2948      The value of the device's |property_name| property.
2949
2950    Raises:
2951      CommandTimeoutError on timeout.
2952    """
2953    assert isinstance(
2954        property_name,
2955        six.string_types), ("property_name is not a string: %r" % property_name)
2956
2957    if cache:
2958      # It takes ~120ms to query a single property, and ~130ms to query all
2959      # properties. So, when caching we always query all properties.
2960      self._EnsureCacheInitialized()
2961    else:
2962      # timeout and retries are handled down at run shell, because we don't
2963      # want to apply them in the other branch when reading from the cache
2964      value = self.RunShellCommand(['getprop', property_name],
2965                                   single_line=True,
2966                                   check_return=True,
2967                                   timeout=timeout,
2968                                   retries=retries)
2969      self._cache['getprop'][property_name] = value
2970    # Non-existent properties are treated as empty strings by getprop.
2971    return self._cache['getprop'].get(property_name, '')
2972
2973  @decorators.WithTimeoutAndRetriesFromInstance()
2974  def SetProp(self,
2975              property_name,
2976              value,
2977              check=False,
2978              timeout=None,
2979              retries=None):
2980    """Sets a property on the device.
2981
2982    Args:
2983      property_name: A string containing the name of the property to set on
2984                     the device.
2985      value: A string containing the value to set to the property on the
2986             device.
2987      check: A boolean indicating whether to check that the property was
2988             successfully set on the device.
2989      timeout: timeout in seconds
2990      retries: number of retries
2991
2992    Raises:
2993      CommandFailedError if check is true and the property was not correctly
2994        set on the device (e.g. because it is not rooted).
2995      CommandTimeoutError on timeout.
2996    """
2997    assert isinstance(
2998        property_name,
2999        six.string_types), ("property_name is not a string: %r" % property_name)
3000    assert isinstance(value, six.string_types), "value is not a string: %r" % value
3001
3002    self.RunShellCommand(['setprop', property_name, value], check_return=True)
3003    prop_cache = self._cache['getprop']
3004    if property_name in prop_cache:
3005      del prop_cache[property_name]
3006    # TODO(crbug.com/1029772) remove the option and make the check mandatory,
3007    # but using a single shell script to both set- and getprop.
3008    if check and value != self.GetProp(property_name, cache=False):
3009      raise device_errors.CommandFailedError(
3010          'Unable to set property %r on the device to %r' % (property_name,
3011                                                             value), str(self))
3012
3013  @decorators.WithTimeoutAndRetriesFromInstance()
3014  def GetABI(self, timeout=None, retries=None):
3015    """Gets the device main ABI.
3016
3017    Args:
3018      timeout: timeout in seconds
3019      retries: number of retries
3020
3021    Returns:
3022      The device's main ABI name. For supported ABIs, the return value will be
3023      one of the values defined in devil.android.ndk.abis.
3024
3025    Raises:
3026      CommandTimeoutError on timeout.
3027    """
3028    return self.GetProp('ro.product.cpu.abi', cache=True)
3029
3030  @decorators.WithTimeoutAndRetriesFromInstance()
3031  def GetFeatures(self, timeout=None, retries=None):
3032    """Returns the features supported on the device."""
3033    lines = self.RunShellCommand(['pm', 'list', 'features'], check_return=True)
3034    return [f[8:] for f in lines if f.startswith('feature:')]
3035
3036  def _GetPsOutput(self, pattern):
3037    """Runs |ps| command on the device and returns its output,
3038
3039    This private method abstracts away differences between Android verions for
3040    calling |ps|, and implements support for filtering the output by a given
3041    |pattern|, but does not do any output parsing.
3042    """
3043    try:
3044      ps_cmd = 'ps'
3045      # ps behavior was changed in Android O and above, http://crbug.com/686716
3046      if self.build_version_sdk >= version_codes.OREO:
3047        ps_cmd = 'ps -e'
3048      if pattern:
3049        return self._RunPipedShellCommand(
3050            '%s | grep -F %s' % (ps_cmd, cmd_helper.SingleQuote(pattern)))
3051      else:
3052        return self.RunShellCommand(
3053            ps_cmd.split(), check_return=True, large_output=True)
3054    except device_errors.AdbShellCommandFailedError as e:
3055      if e.status and isinstance(e.status, list) and not e.status[0]:
3056        # If ps succeeded but grep failed, there were no processes with the
3057        # given name.
3058        return []
3059      else:
3060        raise
3061
3062  @decorators.WithTimeoutAndRetriesFromInstance()
3063  def ListProcesses(self, process_name=None, timeout=None, retries=None):
3064    """Returns a list of tuples with info about processes on the device.
3065
3066    This essentially parses the output of the |ps| command into convenient
3067    ProcessInfo tuples.
3068
3069    Args:
3070      process_name: A string used to filter the returned processes. If given,
3071                    only processes whose name have this value as a substring
3072                    will be returned.
3073      timeout: timeout in seconds
3074      retries: number of retries
3075
3076    Returns:
3077      A list of ProcessInfo tuples with |name|, |pid|, and |ppid| fields.
3078    """
3079    # pylint: disable=broad-except
3080    process_name = process_name or ''
3081    processes = []
3082    for line in self._GetPsOutput(process_name):
3083      row = line.split()
3084      try:
3085        row = {k: row[i] for k, i in _PS_COLUMNS.items()}
3086        if row['pid'] == 'PID' or process_name not in row['name']:
3087          # Skip over header and non-matching processes.
3088          continue
3089        row['pid'] = int(row['pid'])
3090        row['ppid'] = int(row['ppid'])
3091      except Exception:  # e.g. IndexError, TypeError, ValueError.
3092        logging.warning('failed to parse ps line: %r', line)
3093        continue
3094      processes.append(ProcessInfo(**row))
3095    return processes
3096
3097  def _GetDumpsysOutput(self, extra_args, pattern=None):
3098    """Runs |dumpsys| command on the device and returns its output.
3099
3100    This private method implements support for filtering the output by a given
3101    |pattern|, but does not do any output parsing.
3102    """
3103    try:
3104      cmd = ['dumpsys'] + extra_args
3105      if pattern:
3106        cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
3107        return self._RunPipedShellCommand(
3108            '%s | grep -F %s' % (cmd, cmd_helper.SingleQuote(pattern)))
3109      else:
3110        cmd = ['dumpsys'] + extra_args
3111        return self.RunShellCommand(cmd, check_return=True, large_output=True)
3112    except device_errors.AdbShellCommandFailedError as e:
3113      if e.status and isinstance(e.status, list) and not e.status[0]:
3114        # If dumpsys succeeded but grep failed, there were no lines matching
3115        # the given pattern.
3116        return []
3117      else:
3118        raise
3119
3120  # TODO(#4103): Remove after migrating clients to ListProcesses.
3121  @decorators.WithTimeoutAndRetriesFromInstance()
3122  def GetPids(self, process_name=None, timeout=None, retries=None):
3123    """Returns the PIDs of processes containing the given name as substring.
3124
3125    DEPRECATED
3126
3127    Note that the |process_name| is often the package name.
3128
3129    Args:
3130      process_name: A string containing the process name to get the PIDs for.
3131                    If missing returns PIDs for all processes.
3132      timeout: timeout in seconds
3133      retries: number of retries
3134
3135    Returns:
3136      A dict mapping process name to a list of PIDs for each process that
3137      contained the provided |process_name|.
3138
3139    Raises:
3140      CommandTimeoutError on timeout.
3141      DeviceUnreachableError on missing device.
3142    """
3143    procs_pids = collections.defaultdict(list)
3144    for p in self.ListProcesses(process_name):
3145      procs_pids[p.name].append(str(p.pid))
3146    return procs_pids
3147
3148  @decorators.WithTimeoutAndRetriesFromInstance()
3149  def GetApplicationPids(self,
3150                         process_name,
3151                         at_most_one=False,
3152                         timeout=None,
3153                         retries=None):
3154    """Returns the PID or PIDs of a given process name.
3155
3156    Note that the |process_name|, often the package name, must match exactly.
3157
3158    Args:
3159      process_name: A string containing the process name to get the PIDs for.
3160      at_most_one: A boolean indicating that at most one PID is expected to
3161                   be found.
3162      timeout: timeout in seconds
3163      retries: number of retries
3164
3165    Returns:
3166      A list of the PIDs for the named process. If at_most_one=True returns
3167      the single PID found or None otherwise.
3168
3169    Raises:
3170      CommandFailedError if at_most_one=True and more than one PID is found
3171          for the named process.
3172      CommandTimeoutError on timeout.
3173      DeviceUnreachableError on missing device.
3174    """
3175    pids = [
3176        p.pid for p in self.ListProcesses(process_name)
3177        if p.name == process_name
3178    ]
3179    if at_most_one:
3180      if len(pids) > 1:
3181        raise device_errors.CommandFailedError(
3182            'Expected a single PID for %r but found: %r.' % (process_name,
3183                                                             pids),
3184            device_serial=str(self))
3185      return pids[0] if pids else None
3186    else:
3187      return pids
3188
3189  @decorators.WithTimeoutAndRetriesFromInstance()
3190  def GetEnforce(self, timeout=None, retries=None):
3191    """Get the current mode of SELinux.
3192
3193    Args:
3194      timeout: timeout in seconds
3195      retries: number of retries
3196
3197    Returns:
3198      True (enforcing), False (permissive), or None (disabled).
3199
3200    Raises:
3201      CommandFailedError on failure.
3202      CommandTimeoutError on timeout.
3203      DeviceUnreachableError on missing device.
3204    """
3205    output = self.RunShellCommand(['getenforce'],
3206                                  check_return=True,
3207                                  single_line=True).lower()
3208    if output not in _SELINUX_MODE:
3209      raise device_errors.CommandFailedError(
3210          'Unexpected getenforce output: %s' % output)
3211    return _SELINUX_MODE[output]
3212
3213  @decorators.WithTimeoutAndRetriesFromInstance()
3214  def SetEnforce(self, enabled, timeout=None, retries=None):
3215    """Modify the mode SELinux is running in.
3216
3217    Args:
3218      enabled: a boolean indicating whether to put SELinux in encorcing mode
3219               (if True), or permissive mode (otherwise).
3220      timeout: timeout in seconds
3221      retries: number of retries
3222
3223    Raises:
3224      CommandFailedError on failure.
3225      CommandTimeoutError on timeout.
3226      DeviceUnreachableError on missing device.
3227    """
3228    self.RunShellCommand(['setenforce', '1' if int(enabled) else '0'],
3229                         as_root=True,
3230                         check_return=True)
3231
3232  @decorators.WithTimeoutAndRetriesFromInstance()
3233  def GetWebViewUpdateServiceDump(self, timeout=None, retries=None):
3234    """Get the WebView update command sysdump on the device.
3235
3236    Returns:
3237      A dictionary with these possible entries:
3238        FallbackLogicEnabled: True|False
3239        CurrentWebViewPackage: "package name" or None
3240        MinimumWebViewVersionCode: int
3241        WebViewPackages: Dict of installed WebView providers, mapping "package
3242            name" to "reason it's valid/invalid."
3243
3244    The returned dictionary may not include all of the above keys: this depends
3245    on the support of the platform's underlying WebViewUpdateService. This may
3246    return an empty dictionary on OS versions which do not support querying the
3247    WebViewUpdateService.
3248
3249    Raises:
3250      CommandTimeoutError on timeout.
3251      DeviceUnreachableError on missing device.
3252    """
3253    result = {}
3254
3255    # Command was implemented starting in Oreo
3256    if self.build_version_sdk < version_codes.OREO:
3257      return result
3258
3259    output = self.RunShellCommand(['dumpsys', 'webviewupdate'],
3260                                  check_return=True)
3261    webview_packages = {}
3262    for line in output:
3263      match = re.search(_WEBVIEW_SYSUPDATE_CURRENT_PKG_RE, line)
3264      if match:
3265        result['CurrentWebViewPackage'] = match.group(1)
3266      match = re.search(_WEBVIEW_SYSUPDATE_NULL_PKG_RE, line)
3267      if match:
3268        result['CurrentWebViewPackage'] = None
3269      match = re.search(_WEBVIEW_SYSUPDATE_FALLBACK_LOGIC_RE, line)
3270      if match:
3271        result['FallbackLogicEnabled'] = \
3272            True if match.group(1) == 'true' else False
3273      match = re.search(_WEBVIEW_SYSUPDATE_PACKAGE_INSTALLED_RE, line)
3274      if match:
3275        package_name = match.group(1)
3276        reason = match.group(2)
3277        webview_packages[package_name] = reason
3278      match = re.search(_WEBVIEW_SYSUPDATE_PACKAGE_NOT_INSTALLED_RE, line)
3279      if match:
3280        package_name = match.group(1)
3281        reason = match.group(2)
3282        webview_packages[package_name] = reason
3283      match = re.search(_WEBVIEW_SYSUPDATE_MIN_VERSION_CODE, line)
3284      if match:
3285        result['MinimumWebViewVersionCode'] = int(match.group(1))
3286    if webview_packages:
3287      result['WebViewPackages'] = webview_packages
3288    return result
3289
3290  @decorators.WithTimeoutAndRetriesFromInstance()
3291  def SetWebViewImplementation(self, package_name, timeout=None, retries=None):
3292    """Select the WebView implementation to the specified package.
3293
3294    Args:
3295      package_name: The package name of a WebView implementation. The package
3296        must be already installed on the device.
3297      timeout: timeout in seconds
3298      retries: number of retries
3299
3300    Raises:
3301      CommandFailedError on failure.
3302      CommandTimeoutError on timeout.
3303      DeviceUnreachableError on missing device.
3304    """
3305    if not self.IsApplicationInstalled(package_name):
3306      raise device_errors.CommandFailedError(
3307          '%s is not installed' % package_name, str(self))
3308    output = self.RunShellCommand(
3309        ['cmd', 'webviewupdate', 'set-webview-implementation', package_name],
3310        single_line=True,
3311        check_return=False)
3312    if output == 'Success':
3313      logging.info('WebView provider set to: %s', package_name)
3314    else:
3315      dumpsys_output = self.GetWebViewUpdateServiceDump()
3316      webview_packages = dumpsys_output.get('WebViewPackages')
3317      if webview_packages:
3318        reason = webview_packages.get(package_name)
3319        if not reason:
3320          all_provider_package_names = webview_packages.keys()
3321          raise device_errors.CommandFailedError(
3322              '%s is not in the system WebView provider list. Must choose one '
3323              'of %r.' % (package_name, all_provider_package_names), str(self))
3324        if re.search(r'is\s+NOT\s+installed/enabled for all users', reason):
3325          raise device_errors.CommandFailedError(
3326              '%s is disabled, make sure to disable WebView fallback logic' %
3327              package_name, str(self))
3328        if re.search(r'No WebView-library manifest flag', reason):
3329          raise device_errors.CommandFailedError(
3330              '%s does not declare a WebView native library, so it cannot '
3331              'be a WebView provider' % package_name, str(self))
3332        if re.search(r'SDK version too low', reason):
3333          app_target_sdk_version = self.GetApplicationTargetSdk(package_name)
3334          is_preview_sdk = self.GetProp('ro.build.version.preview_sdk') == '1'
3335          if is_preview_sdk:
3336            codename = self.GetProp('ro.build.version.codename')
3337            raise device_errors.CommandFailedError(
3338                '%s targets a finalized SDK (%r), but valid WebView providers '
3339                'must target a pre-finalized SDK (%r) on this device' %
3340                (package_name, app_target_sdk_version, codename), str(self))
3341          else:
3342            raise device_errors.CommandFailedError(
3343                '%s has targetSdkVersion %r, but valid WebView providers must '
3344                'target >= %r on this device' %
3345                (package_name, app_target_sdk_version, self.build_version_sdk),
3346                str(self))
3347        if re.search(r'Version code too low', reason):
3348          raise device_errors.CommandFailedError(
3349              '%s needs a higher versionCode (must be >= %d)' %
3350              (package_name, dumpsys_output.get('MinimumWebViewVersionCode')),
3351              str(self))
3352        if re.search(r'Incorrect signature', reason):
3353          raise device_errors.CommandFailedError(
3354              '%s is not signed with release keys (but user builds require '
3355              'this for WebView providers)' % package_name, str(self))
3356      raise device_errors.CommandFailedError(
3357          'Error setting WebView provider: %s' % output, str(self))
3358
3359  @decorators.WithTimeoutAndRetriesFromInstance()
3360  def SetWebViewFallbackLogic(self, enabled, timeout=None, retries=None):
3361    """Set whether WebViewUpdateService's "fallback logic" should be enabled.
3362
3363    WebViewUpdateService has nonintuitive "fallback logic" for devices where
3364    Monochrome (Chrome Stable) is preinstalled as the WebView provider, with a
3365    "stub" (little-to-no code) implementation of standalone WebView.
3366
3367    "Fallback logic" (enabled by default) is designed, in the case where the
3368    user has disabled Chrome, to fall back to the stub standalone WebView by
3369    enabling the package. The implementation plumbs through the Chrome APK until
3370    Play Store installs an update with the full implementation.
3371
3372    A surprising side-effect of "fallback logic" is that, immediately after
3373    sideloading WebView, WebViewUpdateService re-disables the package and
3374    uninstalls the update. This can prevent successfully using standalone
3375    WebView for development, although "fallback logic" can be disabled on
3376    userdebug/eng devices.
3377
3378    Because this is only relevant for devices with the standalone WebView stub,
3379    this command is only relevant on N-P (inclusive).
3380
3381    You can determine if "fallback logic" is currently enabled by checking
3382    FallbackLogicEnabled in the dictionary returned by
3383    GetWebViewUpdateServiceDump.
3384
3385    Args:
3386      enabled: bool - True for enabled, False for disabled
3387      timeout: timeout in seconds
3388      retries: number of retries
3389
3390    Raises:
3391      CommandFailedError on failure.
3392      CommandTimeoutError on timeout.
3393      DeviceUnreachableError on missing device.
3394    """
3395
3396    # Command is only available on devices which preinstall stub WebView.
3397    if not version_codes.NOUGAT <= self.build_version_sdk <= version_codes.PIE:
3398      return
3399
3400    # redundant-packages is the opposite of fallback logic
3401    enable_string = 'disable' if enabled else 'enable'
3402    output = self.RunShellCommand(
3403        ['cmd', 'webviewupdate',
3404         '%s-redundant-packages' % enable_string],
3405        single_line=True,
3406        check_return=True)
3407    if output == 'Success':
3408      logging.info('WebView Fallback Logic is %s',
3409                   'enabled' if enabled else 'disabled')
3410    else:
3411      raise device_errors.CommandFailedError(
3412          'Error setting WebView Fallback Logic: %s' % output, str(self))
3413
3414  @decorators.WithTimeoutAndRetriesFromInstance()
3415  def TakeScreenshot(self, host_path=None, timeout=None, retries=None):
3416    """Takes a screenshot of the device.
3417
3418    Args:
3419      host_path: A string containing the path on the host to save the
3420                 screenshot to. If None, a file name in the current
3421                 directory will be generated.
3422      timeout: timeout in seconds
3423      retries: number of retries
3424
3425    Returns:
3426      The name of the file on the host to which the screenshot was saved.
3427
3428    Raises:
3429      CommandFailedError on failure.
3430      CommandTimeoutError on timeout.
3431      DeviceUnreachableError on missing device.
3432    """
3433    if not host_path:
3434      host_path = os.path.abspath(
3435          'screenshot-%s-%s.png' % (self.serial, _GetTimeStamp()))
3436    with device_temp_file.DeviceTempFile(self.adb, suffix='.png') as device_tmp:
3437      self.RunShellCommand(['/system/bin/screencap', '-p', device_tmp.name],
3438                           check_return=True)
3439      self.PullFile(device_tmp.name, host_path)
3440    return host_path
3441
3442  @decorators.WithTimeoutAndRetriesFromInstance()
3443  def DismissCrashDialogIfNeeded(self, timeout=None, retries=None):
3444    """Dismiss the error/ANR dialog if present.
3445
3446    Returns: Name of the crashed package if a dialog is focused,
3447             None otherwise.
3448    """
3449
3450    def _FindFocusedWindow():
3451      match = None
3452      # TODO(jbudorick): Try to grep the output on the device instead of using
3453      # large_output if/when DeviceUtils exposes a public interface for piped
3454      # shell command handling.
3455      for line in self.RunShellCommand(['dumpsys', 'window', 'windows'],
3456                                       check_return=True,
3457                                       large_output=True):
3458        match = re.match(_CURRENT_FOCUS_CRASH_RE, line)
3459        if match:
3460          break
3461      return match
3462
3463    match = _FindFocusedWindow()
3464    if not match:
3465      return None
3466    package = match.group(2)
3467    logger.warning('Trying to dismiss %s dialog for %s', *match.groups())
3468    self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT)
3469    self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT)
3470    self.SendKeyEvent(keyevent.KEYCODE_ENTER)
3471    match = _FindFocusedWindow()
3472    if match:
3473      logger.error('Still showing a %s dialog for %s', *match.groups())
3474    return package
3475
3476  def GetLogcatMonitor(self, *args, **kwargs):
3477    """Returns a new LogcatMonitor associated with this device.
3478
3479    Parameters passed to this function are passed directly to
3480    |logcat_monitor.LogcatMonitor| and are documented there.
3481    """
3482    return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs)
3483
3484  def GetClientCache(self, client_name):
3485    """Returns client cache."""
3486    if client_name not in self._client_caches:
3487      self._client_caches[client_name] = {}
3488    return self._client_caches[client_name]
3489
3490  def ClearCache(self):
3491    """Clears all caches."""
3492    for client in self._client_caches:
3493      self._client_caches[client].clear()
3494    self._cache = {
3495        # Map of packageId -> list of on-device .apk paths
3496        'package_apk_paths': {},
3497        # Set of packageId that were loaded from LoadCacheData and not yet
3498        # verified.
3499        'package_apk_paths_to_verify': set(),
3500        # Map of packageId -> set of on-device .apk checksums
3501        'package_apk_checksums': {},
3502        # Map of property_name -> value
3503        'getprop': {},
3504        # Map of device path -> checksum]
3505        'device_path_checksums': {},
3506        # Location of sdcard ($EXTERNAL_STORAGE).
3507        'external_storage': None,
3508        # Token used to detect when LoadCacheData is stale.
3509        'token': None,
3510        'prev_token': None,
3511        # Path for tracing.
3512        'tracing_path': None,
3513    }
3514
3515  @decorators.WithTimeoutAndRetriesFromInstance()
3516  def LoadCacheData(self, data, timeout=None, retries=None):
3517    """Initializes the cache from data created using DumpCacheData.
3518
3519    The cache is used only if its token matches the one found on the device.
3520    This prevents a stale cache from being used (which can happen when sharing
3521    devices).
3522
3523    Args:
3524      data: A previously serialized cache (string).
3525      timeout: timeout in seconds
3526      retries: number of retries
3527
3528    Returns:
3529      Whether the cache was loaded.
3530    """
3531    try:
3532      obj = json.loads(data)
3533    except ValueError:
3534      logger.error('Unable to parse cache file. Not using it.')
3535      return False
3536    self._EnsureCacheInitialized()
3537    given_token = obj.get('token')
3538    if not given_token or self._cache['prev_token'] != given_token:
3539      logger.warning('Stale cache detected. Not using it.')
3540      return False
3541
3542    self._cache['package_apk_paths'] = obj.get('package_apk_paths', {})
3543    # When using a cache across script invokations, verify that apps have
3544    # not been uninstalled.
3545    self._cache['package_apk_paths_to_verify'] = set(
3546        self._cache['package_apk_paths'])
3547
3548    package_apk_checksums = obj.get('package_apk_checksums', {})
3549    for k, v in package_apk_checksums.items():
3550      package_apk_checksums[k] = set(v)
3551    self._cache['package_apk_checksums'] = package_apk_checksums
3552    device_path_checksums = obj.get('device_path_checksums', {})
3553    self._cache['device_path_checksums'] = device_path_checksums
3554    return True
3555
3556  @decorators.WithTimeoutAndRetriesFromInstance()
3557  def DumpCacheData(self, timeout=None, retries=None):
3558    """Dumps the current cache state to a string.
3559
3560    Args:
3561      timeout: timeout in seconds
3562      retries: number of retries
3563
3564    Returns:
3565      A serialized cache as a string.
3566    """
3567    self._EnsureCacheInitialized()
3568    obj = {}
3569    obj['token'] = self._cache['token']
3570    obj['package_apk_paths'] = self._cache['package_apk_paths']
3571    obj['package_apk_checksums'] = self._cache['package_apk_checksums']
3572    # JSON can't handle sets.
3573    for k, v in obj['package_apk_checksums'].items():
3574      obj['package_apk_checksums'][k] = list(v)
3575    obj['device_path_checksums'] = self._cache['device_path_checksums']
3576    return json.dumps(obj, separators=(',', ':'))
3577
3578  @classmethod
3579  def parallel(cls, devices, asyn=False):
3580    """Creates a Parallelizer to operate over the provided list of devices.
3581
3582    Args:
3583      devices: A list of either DeviceUtils instances or objects from
3584               from which DeviceUtils instances can be constructed. If None,
3585               all attached devices will be used.
3586      asyn: If true, returns a Parallelizer that runs operations
3587             asynchronously.
3588
3589    Returns:
3590      A Parallelizer operating over |devices|.
3591    """
3592    devices = [d if isinstance(d, cls) else cls(d) for d in devices]
3593    if asyn:
3594      return parallelizer.Parallelizer(devices)
3595    else:
3596      return parallelizer.SyncParallelizer(devices)
3597
3598  @classmethod
3599  def HealthyDevices(cls,
3600                     denylist=None,
3601                     device_arg='default',
3602                     retries=1,
3603                     enable_usb_resets=False,
3604                     abis=None,
3605                     **kwargs):
3606    """Returns a list of DeviceUtils instances.
3607
3608    Returns a list of DeviceUtils instances that are attached, not denylisted,
3609    and optionally filtered by --device flags or ANDROID_SERIAL environment
3610    variable.
3611
3612    Args:
3613      denylist: A DeviceDenylist instance (optional). Device serials in this
3614          denylist will never be returned, but a warning will be logged if they
3615          otherwise would have been.
3616      device_arg: The value of the --device flag. This can be:
3617          'default' -> Same as [], but returns an empty list rather than raise a
3618              NoDevicesError.
3619          [] -> Returns all devices, unless $ANDROID_SERIAL is set.
3620          None -> Use $ANDROID_SERIAL if set, otherwise looks for a single
3621              attached device. Raises an exception if multiple devices are
3622              attached.
3623          'serial' -> Returns an instance for the given serial, if not
3624              denylisted.
3625          ['A', 'B', ...] -> Returns instances for the subset that is not
3626              denylisted.
3627      retries: Number of times to restart adb server and query it again if no
3628          devices are found on the previous attempts, with exponential backoffs
3629          up to 60s between each retry.
3630      enable_usb_resets: If true, will attempt to trigger a USB reset prior to
3631          the last attempt if there are no available devices. It will only reset
3632          those that appear to be android devices.
3633      abis: A list of ABIs for which the device needs to support at least one of
3634          (optional). See devil.android.ndk.abis for valid values.
3635      A device serial, or a list of device serials (optional).
3636
3637    Returns:
3638      A list of DeviceUtils instances.
3639
3640    Raises:
3641      NoDevicesError: Raised when no non-denylisted devices exist and
3642          device_arg is passed.
3643      MultipleDevicesError: Raise when multiple devices exist, but |device_arg|
3644          is None.
3645    """
3646    allow_no_devices = False
3647    if device_arg == 'default':
3648      allow_no_devices = True
3649      device_arg = ()
3650
3651    select_multiple = True
3652    if not (isinstance(device_arg, tuple) or isinstance(device_arg, list)):
3653      select_multiple = False
3654      if device_arg:
3655        device_arg = (device_arg, )
3656
3657    denylisted_devices = denylist.Read() if denylist else []
3658
3659    # adb looks for ANDROID_SERIAL, so support it as well.
3660    android_serial = os.environ.get('ANDROID_SERIAL')
3661    if not device_arg and android_serial:
3662      device_arg = (android_serial, )
3663
3664    def denylisted(serial):
3665      if serial in denylisted_devices:
3666        logger.warning('Device %s is denylisted.', serial)
3667        return True
3668      return False
3669
3670    def supports_abi(abi, serial):
3671      if abis and abi not in abis:
3672        logger.warning("Device %s doesn't support required ABIs.", serial)
3673        return False
3674      return True
3675
3676    def _get_devices():
3677      if device_arg:
3678        devices = [cls(x, **kwargs) for x in device_arg if not denylisted(x)]
3679      else:
3680        devices = []
3681        for adb in adb_wrapper.AdbWrapper.Devices():
3682          serial = adb.GetDeviceSerial()
3683          if not denylisted(serial):
3684            device = cls(_CreateAdbWrapper(adb), **kwargs)
3685            if supports_abi(device.GetABI(), serial):
3686              devices.append(device)
3687
3688      if len(devices) == 0 and not allow_no_devices:
3689        raise device_errors.NoDevicesError()
3690      if len(devices) > 1 and not select_multiple:
3691        raise device_errors.MultipleDevicesError(devices)
3692      return sorted(devices)
3693
3694    def _reset_devices():
3695      if not reset_usb:
3696        logging.error(
3697            'reset_usb.py not supported on this platform (%s). Skipping usb '
3698            'resets.', sys.platform)
3699        return
3700      if device_arg:
3701        for serial in device_arg:
3702          reset_usb.reset_android_usb(serial)
3703      else:
3704        reset_usb.reset_all_android_devices()
3705
3706    for attempt in range(retries + 1):
3707      try:
3708        return _get_devices()
3709      except device_errors.NoDevicesError:
3710        if attempt == retries:
3711          logging.error('No devices found after exhausting all retries.')
3712          raise
3713        elif attempt == retries - 1 and enable_usb_resets:
3714          logging.warning(
3715              'Attempting to reset relevant USB devices prior to the last '
3716              'attempt.')
3717          _reset_devices()
3718        # math.pow returns floats, so cast to int for easier testing
3719        sleep_s = min(int(math.pow(2, attempt + 1)), 60)
3720        logger.warning(
3721            'No devices found. Will try again after restarting adb server '
3722            'and a short nap of %d s.', sleep_s)
3723        time.sleep(sleep_s)
3724        adb_wrapper.RestartServer()
3725
3726  @decorators.WithTimeoutAndRetriesFromInstance()
3727  def RestartAdbd(self, timeout=None, retries=None):
3728    logger.info('Restarting adbd on device.')
3729    with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
3730      self.WriteFile(script.name, _RESTART_ADBD_SCRIPT)
3731      self.RunShellCommand(['source', script.name],
3732                           check_return=True,
3733                           as_root=True)
3734      self.adb.WaitForDevice()
3735
3736  @decorators.WithTimeoutAndRetriesFromInstance()
3737  def GrantPermissions(self, package, permissions, timeout=None, retries=None):
3738    if not permissions:
3739      return
3740
3741    # For Andorid-11(R), enable MANAGE_EXTERNAL_STORAGE for testing.
3742    # See https://bit.ly/2MBjBIM for details.
3743    if ('android.permission.MANAGE_EXTERNAL_STORAGE' in permissions
3744        and self.build_version_sdk >= version_codes.R):
3745      script_manage_ext_storage = [
3746          'appops set {package} MANAGE_EXTERNAL_STORAGE allow',
3747          'echo "{sep}MANAGE_EXTERNAL_STORAGE{sep}$?{sep}"',
3748      ]
3749    else:
3750      script_manage_ext_storage = []
3751
3752    permissions = set(p for p in permissions
3753                      if not _PERMISSIONS_DENYLIST_RE.match(p))
3754
3755    if ('android.permission.WRITE_EXTERNAL_STORAGE' in permissions
3756        and 'android.permission.READ_EXTERNAL_STORAGE' not in permissions):
3757      permissions.add('android.permission.READ_EXTERNAL_STORAGE')
3758
3759    script_raw = [
3760        'p={package}',
3761        'for q in {permissions}',
3762        'do pm grant "$p" "$q"',
3763        'echo "{sep}$q{sep}$?{sep}"',
3764        'done',
3765    ] + script_manage_ext_storage
3766
3767    script = ';'.join(script_raw).format(
3768        package=cmd_helper.SingleQuote(package),
3769        permissions=' '.join(
3770            cmd_helper.SingleQuote(p) for p in sorted(permissions)),
3771        sep=_SHELL_OUTPUT_SEPARATOR)
3772
3773    logger.info('Setting permissions for %s.', package)
3774    res = self.RunShellCommand(
3775        script,
3776        shell=True,
3777        raw_output=True,
3778        large_output=True,
3779        check_return=True)
3780    res = res.split(_SHELL_OUTPUT_SEPARATOR)
3781    failures = [
3782        (permission, output.strip())
3783        for permission, status, output in zip(res[1::3], res[2::3], res[0::3])
3784        if int(status)
3785    ]
3786
3787    if failures:
3788      logger.warning(
3789          'Failed to grant some permissions. Denylist may need to be updated?')
3790      for permission, output in failures:
3791        # Try to grab the relevant error message from the output.
3792        m = _PERMISSIONS_EXCEPTION_RE.search(output)
3793        if m:
3794          error_msg = m.group(0)
3795        elif len(output) > 200:
3796          error_msg = repr(output[:200]) + ' (truncated)'
3797        else:
3798          error_msg = repr(output)
3799        logger.warning('- %s: %s', permission, error_msg)
3800
3801  @decorators.WithTimeoutAndRetriesFromInstance()
3802  def IsScreenOn(self, timeout=None, retries=None):
3803    """Determines if screen is on.
3804
3805    Dumpsys input_method exposes screen on/off state. Below is an explination of
3806    the states.
3807
3808    Pre-L:
3809      On: mScreenOn=true
3810      Off: mScreenOn=false
3811    L+:
3812      On: mInteractive=true
3813      Off: mInteractive=false
3814
3815    Returns:
3816      True if screen is on, false if it is off.
3817
3818    Raises:
3819      device_errors.CommandFailedError: If screen state cannot be found.
3820    """
3821    if self.build_version_sdk < version_codes.LOLLIPOP:
3822      input_check = 'mScreenOn'
3823      check_value = 'mScreenOn=true'
3824    else:
3825      input_check = 'mInteractive'
3826      check_value = 'mInteractive=true'
3827    dumpsys_out = self._RunPipedShellCommand(
3828        'dumpsys input_method | grep %s' % input_check)
3829    if not dumpsys_out:
3830      raise device_errors.CommandFailedError('Unable to detect screen state',
3831                                             str(self))
3832    return check_value in dumpsys_out[0]
3833
3834  @decorators.WithTimeoutAndRetriesFromInstance()
3835  def SetScreen(self, on, timeout=None, retries=None):
3836    """Turns screen on and off.
3837
3838    Args:
3839      on: bool to decide state to switch to. True = on False = off.
3840    """
3841
3842    def screen_test():
3843      return self.IsScreenOn() == on
3844
3845    if screen_test():
3846      logger.info('Screen already in expected state.')
3847      return
3848    self.SendKeyEvent(keyevent.KEYCODE_POWER)
3849    timeout_retry.WaitFor(screen_test, wait_period=1)
3850
3851  @decorators.WithTimeoutAndRetriesFromInstance()
3852  def ChangeOwner(self, owner_group, paths, timeout=None, retries=None):
3853    """Changes file system ownership for permissions.
3854
3855    Args:
3856      owner_group: New owner and group to assign. Note that this should be a
3857        string in the form user[.group] where the group is option.
3858      paths: Paths to change ownership of.
3859
3860      Note that the -R recursive option is not supported by all Android
3861      versions.
3862    """
3863    if not paths:
3864      return
3865    self.RunShellCommand(['chown', owner_group] + paths, check_return=True)
3866
3867  @decorators.WithTimeoutAndRetriesFromInstance()
3868  def ChangeSecurityContext(self,
3869                            security_context,
3870                            paths,
3871                            timeout=None,
3872                            retries=None):
3873    """Changes the SELinux security context for files.
3874
3875    Args:
3876      security_context: The new security context as a string
3877      paths: Paths to change the security context of.
3878
3879      Note that the -R recursive option is not supported by all Android
3880      versions.
3881    """
3882    if not paths:
3883      return
3884    command = ['chcon', security_context] + paths
3885
3886    # Note, need to force su because chcon can fail with permission errors even
3887    # if the device is rooted.
3888    self.RunShellCommand(command, as_root=_FORCE_SU, check_return=True)
3889