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