device_utils.py revision 7332cdb42368a904cbf7418de329868989e592da
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
5"""Provides a variety of device interactions based on adb.
6
7Eventually, this will be based on adb_wrapper.
8"""
9# pylint: disable=unused-argument
10
11import calendar
12import collections
13import itertools
14import json
15import logging
16import multiprocessing
17import os
18import posixpath
19import re
20import shutil
21import stat
22import tempfile
23import time
24import threading
25import uuid
26import zipfile
27
28from devil import base_error
29from devil import devil_env
30from devil.utils import cmd_helper
31from devil.android import apk_helper
32from devil.android import device_signal
33from devil.android import decorators
34from devil.android import device_errors
35from devil.android import device_temp_file
36from devil.android import install_commands
37from devil.android import logcat_monitor
38from devil.android import md5sum
39from devil.android.constants import chrome
40from devil.android.sdk import adb_wrapper
41from devil.android.sdk import gce_adb_wrapper
42from devil.android.sdk import intent
43from devil.android.sdk import keyevent
44from devil.android.sdk import split_select
45from devil.android.sdk import version_codes
46from devil.utils import host_utils
47from devil.utils import parallelizer
48from devil.utils import reraiser_thread
49from devil.utils import timeout_retry
50from devil.utils import zip_utils
51
52_DEFAULT_TIMEOUT = 30
53_DEFAULT_RETRIES = 3
54
55# A sentinel object for default values
56# TODO(jbudorick,perezju): revisit how default values are handled by
57# the timeout_retry decorators.
58DEFAULT = object()
59
60_RESTART_ADBD_SCRIPT = """
61  trap '' HUP
62  trap '' TERM
63  trap '' PIPE
64  function restart() {
65    stop adbd
66    start adbd
67  }
68  restart &
69"""
70
71# Not all permissions can be set.
72_PERMISSIONS_BLACKLIST = [
73    'android.permission.ACCESS_MOCK_LOCATION',
74    'android.permission.ACCESS_NETWORK_STATE',
75    'android.permission.ACCESS_WIFI_STATE',
76    'android.permission.AUTHENTICATE_ACCOUNTS',
77    'android.permission.BLUETOOTH',
78    'android.permission.BLUETOOTH_ADMIN',
79    'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION',
80    'android.permission.INTERNET',
81    'android.permission.MANAGE_ACCOUNTS',
82    'android.permission.MODIFY_AUDIO_SETTINGS',
83    'android.permission.NFC',
84    'android.permission.READ_SYNC_SETTINGS',
85    'android.permission.READ_SYNC_STATS',
86    'android.permission.RECEIVE_BOOT_COMPLETED',
87    'android.permission.RECORD_VIDEO',
88    'android.permission.RUN_INSTRUMENTATION',
89    'android.permission.USE_CREDENTIALS',
90    'android.permission.VIBRATE',
91    'android.permission.WAKE_LOCK',
92    'android.permission.WRITE_SYNC_SETTINGS',
93    'com.android.browser.permission.READ_HISTORY_BOOKMARKS',
94    'com.android.browser.permission.WRITE_HISTORY_BOOKMARKS',
95    'com.android.launcher.permission.INSTALL_SHORTCUT',
96    'com.chrome.permission.DEVICE_EXTRAS',
97    'com.google.android.c2dm.permission.RECEIVE',
98    'com.google.android.providers.gsf.permission.READ_GSERVICES',
99    'com.sec.enterprise.knox.MDM_CONTENT_PROVIDER',
100]
101for package_info in chrome.PACKAGE_INFO.itervalues():
102  _PERMISSIONS_BLACKLIST.extend([
103      '%s.permission.C2D_MESSAGE' % package_info.package,
104      '%s.permission.READ_WRITE_BOOKMARK_FOLDERS' % package_info.package,
105      '%s.TOS_ACKED' % package_info.package])
106
107_CURRENT_FOCUS_CRASH_RE = re.compile(
108    r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}')
109
110_GETPROP_RE = re.compile(r'\[(.*?)\]: \[(.*?)\]')
111_IPV4_ADDRESS_RE = re.compile(r'([0-9]{1,3}\.){3}[0-9]{1,3}\:[0-9]{4,5}')
112
113# Regex to parse the long (-l) output of 'ls' command, c.f.
114# https://github.com/landley/toybox/blob/master/toys/posix/ls.c#L446
115_LONG_LS_OUTPUT_RE = re.compile(
116    r'(?P<st_mode>[\w-]{10})\s+'                  # File permissions
117    r'(?:(?P<st_nlink>\d+)\s+)?'                  # Number of links (optional)
118    r'(?P<st_owner>\w+)\s+'                       # Name of owner
119    r'(?P<st_group>\w+)\s+'                       # Group of owner
120    r'(?:'                                        # Either ...
121      r'(?P<st_rdev_major>\d+),\s+'                 # Device major, and
122      r'(?P<st_rdev_minor>\d+)\s+'                  # Device minor
123    r'|'                                          # .. or
124      r'(?P<st_size>\d+)\s+'                        # Size in bytes
125    r')?'                                         # .. or nothing
126    r'(?P<st_mtime>\d{4}-\d\d-\d\d \d\d:\d\d)\s+' # Modification date/time
127    r'(?P<filename>.+?)'                          # File name
128    r'(?: -> (?P<symbolic_link_to>.+))?'          # Symbolic link (optional)
129    r'$'                                          # End of string
130)
131_LS_DATE_FORMAT = '%Y-%m-%d %H:%M'
132_FILE_MODE_RE = re.compile(r'[dbclps-](?:[r-][w-][xSs-]){2}[r-][w-][xTt-]$')
133_FILE_MODE_KIND = {
134    'd': stat.S_IFDIR, 'b': stat.S_IFBLK, 'c': stat.S_IFCHR,
135    'l': stat.S_IFLNK, 'p': stat.S_IFIFO, 's': stat.S_IFSOCK,
136    '-': stat.S_IFREG}
137_FILE_MODE_PERMS = [
138    stat.S_IRUSR, stat.S_IWUSR, stat.S_IXUSR,
139    stat.S_IRGRP, stat.S_IWGRP, stat.S_IXGRP,
140    stat.S_IROTH, stat.S_IWOTH, stat.S_IXOTH,
141]
142_FILE_MODE_SPECIAL = [
143    ('s', stat.S_ISUID),
144    ('s', stat.S_ISGID),
145    ('t', stat.S_ISVTX),
146]
147
148@decorators.WithExplicitTimeoutAndRetries(
149    _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
150def GetAVDs():
151  """Returns a list of Android Virtual Devices.
152
153  Returns:
154    A list containing the configured AVDs.
155  """
156  lines = cmd_helper.GetCmdOutput([
157      os.path.join(devil_env.config.LocalPath('android_sdk'),
158                   'tools', 'android'),
159      'list', 'avd']).splitlines()
160  avds = []
161  for line in lines:
162    if 'Name:' not in line:
163      continue
164    key, value = (s.strip() for s in line.split(':', 1))
165    if key == 'Name':
166      avds.append(value)
167  return avds
168
169
170@decorators.WithExplicitTimeoutAndRetries(
171    _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
172def RestartServer():
173  """Restarts the adb server.
174
175  Raises:
176    CommandFailedError if we fail to kill or restart the server.
177  """
178  def adb_killed():
179    return not adb_wrapper.AdbWrapper.IsServerOnline()
180
181  def adb_started():
182    return adb_wrapper.AdbWrapper.IsServerOnline()
183
184  adb_wrapper.AdbWrapper.KillServer()
185  if not timeout_retry.WaitFor(adb_killed, wait_period=1, max_tries=5):
186    # TODO(perezju): raise an exception after fixng http://crbug.com/442319
187    logging.warning('Failed to kill adb server')
188  adb_wrapper.AdbWrapper.StartServer()
189  if not timeout_retry.WaitFor(adb_started, wait_period=1, max_tries=5):
190    raise device_errors.CommandFailedError('Failed to start adb server')
191
192
193def _ParseModeString(mode_str):
194  """Parse a mode string, e.g. 'drwxrwxrwx', into a st_mode value.
195
196  Effectively the reverse of |mode_to_string| in, e.g.:
197  https://github.com/landley/toybox/blob/master/lib/lib.c#L896
198  """
199  if not _FILE_MODE_RE.match(mode_str):
200    raise ValueError('Unexpected file mode %r', mode_str)
201  mode = _FILE_MODE_KIND[mode_str[0]]
202  for c, flag in zip(mode_str[1:], _FILE_MODE_PERMS):
203    if c != '-' and c.islower():
204      mode |= flag
205  for c, (t, flag) in zip(mode_str[3::3], _FILE_MODE_SPECIAL):
206    if c.lower() == t:
207      mode |= flag
208  return mode
209
210
211def _GetTimeStamp():
212  """Return a basic ISO 8601 time stamp with the current local time."""
213  return time.strftime('%Y%m%dT%H%M%S', time.localtime())
214
215
216def _JoinLines(lines):
217  # makes sure that the last line is also terminated, and is more memory
218  # efficient than first appending an end-line to each line and then joining
219  # all of them together.
220  return ''.join(s for line in lines for s in (line, '\n'))
221
222
223def _IsGceInstance(serial):
224  return _IPV4_ADDRESS_RE.match(serial)
225
226
227def _CreateAdbWrapper(device):
228  if _IsGceInstance(str(device)):
229    return gce_adb_wrapper.GceAdbWrapper(str(device))
230  else:
231    if isinstance(device, adb_wrapper.AdbWrapper):
232      return device
233    else:
234      return adb_wrapper.AdbWrapper(device)
235
236
237def _FormatPartialOutputError(output):
238  lines = output.splitlines() if isinstance(output, basestring) else output
239  message = ['Partial output found:']
240  if len(lines) > 11:
241    message.extend('- %s' % line for line in lines[:5])
242    message.extend('<snip>')
243    message.extend('- %s' % line for line in lines[-5:])
244  else:
245    message.extend('- %s' % line for line in lines)
246  return '\n'.join(message)
247
248
249class DeviceUtils(object):
250
251  _MAX_ADB_COMMAND_LENGTH = 512
252  _MAX_ADB_OUTPUT_LENGTH = 32768
253  _LAUNCHER_FOCUSED_RE = re.compile(
254      r'\s*mCurrentFocus.*(Launcher|launcher).*')
255  _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
256
257  LOCAL_PROPERTIES_PATH = posixpath.join('/', 'data', 'local.prop')
258
259  # Property in /data/local.prop that controls Java assertions.
260  JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
261
262  def __init__(self, device, enable_device_files_cache=False,
263               default_timeout=_DEFAULT_TIMEOUT,
264               default_retries=_DEFAULT_RETRIES):
265    """DeviceUtils constructor.
266
267    Args:
268      device: Either a device serial, an existing AdbWrapper instance, or an
269        an existing AndroidCommands instance.
270      enable_device_files_cache: For PushChangedFiles(), cache checksums of
271        pushed files rather than recomputing them on a subsequent call.
272      default_timeout: An integer containing the default number of seconds to
273        wait for an operation to complete if no explicit value is provided.
274      default_retries: An integer containing the default number or times an
275        operation should be retried on failure if no explicit value is provided.
276    """
277    self.adb = None
278    if isinstance(device, basestring):
279      self.adb = _CreateAdbWrapper(device)
280    elif isinstance(device, adb_wrapper.AdbWrapper):
281      self.adb = device
282    else:
283      raise ValueError('Unsupported device value: %r' % device)
284    self._commands_installed = None
285    self._default_timeout = default_timeout
286    self._default_retries = default_retries
287    self._enable_device_files_cache = enable_device_files_cache
288    self._cache = {}
289    self._client_caches = {}
290    self._cache_lock = threading.RLock()
291    assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR)
292    assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR)
293
294    self._ClearCache()
295
296  def __eq__(self, other):
297    """Checks whether |other| refers to the same device as |self|.
298
299    Args:
300      other: The object to compare to. This can be a basestring, an instance
301        of adb_wrapper.AdbWrapper, or an instance of DeviceUtils.
302    Returns:
303      Whether |other| refers to the same device as |self|.
304    """
305    return self.adb.GetDeviceSerial() == str(other)
306
307  def __lt__(self, other):
308    """Compares two instances of DeviceUtils.
309
310    This merely compares their serial numbers.
311
312    Args:
313      other: The instance of DeviceUtils to compare to.
314    Returns:
315      Whether |self| is less than |other|.
316    """
317    return self.adb.GetDeviceSerial() < other.adb.GetDeviceSerial()
318
319  def __str__(self):
320    """Returns the device serial."""
321    return self.adb.GetDeviceSerial()
322
323  @decorators.WithTimeoutAndRetriesFromInstance()
324  def IsOnline(self, timeout=None, retries=None):
325    """Checks whether the device is online.
326
327    Args:
328      timeout: timeout in seconds
329      retries: number of retries
330
331    Returns:
332      True if the device is online, False otherwise.
333
334    Raises:
335      CommandTimeoutError on timeout.
336    """
337    try:
338      return self.adb.GetState() == 'device'
339    except base_error.BaseError as exc:
340      logging.info('Failed to get state: %s', exc)
341      return False
342
343  @decorators.WithTimeoutAndRetriesFromInstance()
344  def HasRoot(self, timeout=None, retries=None):
345    """Checks whether or not adbd has root privileges.
346
347    Args:
348      timeout: timeout in seconds
349      retries: number of retries
350
351    Returns:
352      True if adbd has root privileges, False otherwise.
353
354    Raises:
355      CommandTimeoutError on timeout.
356      DeviceUnreachableError on missing device.
357    """
358    try:
359      self.RunShellCommand(['ls', '/root'], check_return=True)
360      return True
361    except device_errors.AdbCommandFailedError:
362      return False
363
364  def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT):
365    """Checks whether 'su' is needed to access protected resources.
366
367    Args:
368      timeout: timeout in seconds
369      retries: number of retries
370
371    Returns:
372      True if 'su' is available on the device and is needed to to access
373        protected resources; False otherwise if either 'su' is not available
374        (e.g. because the device has a user build), or not needed (because adbd
375        already has root privileges).
376
377    Raises:
378      CommandTimeoutError on timeout.
379      DeviceUnreachableError on missing device.
380    """
381    if 'needs_su' not in self._cache:
382      try:
383        self.RunShellCommand(
384            '%s && ! ls /root' % self._Su('ls /root'), check_return=True,
385            timeout=self._default_timeout if timeout is DEFAULT else timeout,
386            retries=self._default_retries if retries is DEFAULT else retries)
387        self._cache['needs_su'] = True
388      except device_errors.AdbCommandFailedError:
389        self._cache['needs_su'] = False
390    return self._cache['needs_su']
391
392  def _Su(self, command):
393    if self.build_version_sdk >= version_codes.MARSHMALLOW:
394      return 'su 0 %s' % command
395    return 'su -c %s' % command
396
397  @decorators.WithTimeoutAndRetriesFromInstance()
398  def EnableRoot(self, timeout=None, retries=None):
399    """Restarts adbd with root privileges.
400
401    Args:
402      timeout: timeout in seconds
403      retries: number of retries
404
405    Raises:
406      CommandFailedError if root could not be enabled.
407      CommandTimeoutError on timeout.
408    """
409    if self.IsUserBuild():
410      raise device_errors.CommandFailedError(
411          'Cannot enable root in user builds.', str(self))
412    if 'needs_su' in self._cache:
413      del self._cache['needs_su']
414    self.adb.Root()
415    self.WaitUntilFullyBooted()
416
417  @decorators.WithTimeoutAndRetriesFromInstance()
418  def IsUserBuild(self, timeout=None, retries=None):
419    """Checks whether or not the device is running a user build.
420
421    Args:
422      timeout: timeout in seconds
423      retries: number of retries
424
425    Returns:
426      True if the device is running a user build, False otherwise (i.e. if
427        it's running a userdebug build).
428
429    Raises:
430      CommandTimeoutError on timeout.
431      DeviceUnreachableError on missing device.
432    """
433    return self.build_type == 'user'
434
435  @decorators.WithTimeoutAndRetriesFromInstance()
436  def GetExternalStoragePath(self, timeout=None, retries=None):
437    """Get the device's path to its SD card.
438
439    Args:
440      timeout: timeout in seconds
441      retries: number of retries
442
443    Returns:
444      The device's path to its SD card.
445
446    Raises:
447      CommandFailedError if the external storage path could not be determined.
448      CommandTimeoutError on timeout.
449      DeviceUnreachableError on missing device.
450    """
451    self._EnsureCacheInitialized()
452    if not self._cache['external_storage']:
453      raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set',
454                                             str(self))
455    return self._cache['external_storage']
456
457  @decorators.WithTimeoutAndRetriesFromInstance()
458  def GetApplicationPaths(self, package, timeout=None, retries=None):
459    """Get the paths of the installed apks on the device for the given package.
460
461    Args:
462      package: Name of the package.
463
464    Returns:
465      List of paths to the apks on the device for the given package.
466    """
467    return self._GetApplicationPathsInternal(package)
468
469  def _GetApplicationPathsInternal(self, package, skip_cache=False):
470    cached_result = self._cache['package_apk_paths'].get(package)
471    if cached_result is not None and not skip_cache:
472      if package in self._cache['package_apk_paths_to_verify']:
473        self._cache['package_apk_paths_to_verify'].remove(package)
474        # Don't verify an app that is not thought to be installed. We are
475        # concerned only with apps we think are installed having been
476        # uninstalled manually.
477        if cached_result and not self.PathExists(cached_result):
478          cached_result = None
479          self._cache['package_apk_checksums'].pop(package, 0)
480      if cached_result is not None:
481        return list(cached_result)
482    # 'pm path' is liable to incorrectly exit with a nonzero number starting
483    # in Lollipop.
484    # TODO(jbudorick): Check if this is fixed as new Android versions are
485    # released to put an upper bound on this.
486    should_check_return = (self.build_version_sdk < version_codes.LOLLIPOP)
487    output = self.RunShellCommand(
488        ['pm', 'path', package], check_return=should_check_return)
489    apks = []
490    for line in output:
491      if not line.startswith('package:'):
492        # KitKat on x86 may show following warnings that is safe to ignore.
493        if (line.startswith('WARNING: linker: libdvm.so has text relocations.')
494            and version_codes.KITKAT <= self.build_version_sdk
495            and self.build_version_sdk <= version_codes.KITKAT_WATCH
496            and self.product_cpu_abi == 'x86'):
497          continue
498        raise device_errors.CommandFailedError(
499            'pm path returned: %r' % '\n'.join(output), str(self))
500      apks.append(line[len('package:'):])
501    self._cache['package_apk_paths'][package] = list(apks)
502    return apks
503
504  @decorators.WithTimeoutAndRetriesFromInstance()
505  def GetApplicationVersion(self, package, timeout=None, retries=None):
506    """Get the version name of a package installed on the device.
507
508    Args:
509      package: Name of the package.
510
511    Returns:
512      A string with the version name or None if the package is not found
513      on the device.
514    """
515    output = self.RunShellCommand(
516        ['dumpsys', 'package', package], check_return=True)
517    if not output:
518      return None
519    for line in output:
520      line = line.strip()
521      if line.startswith('versionName='):
522        return line[len('versionName='):]
523    raise device_errors.CommandFailedError(
524        'Version name for %s not found on dumpsys output' % package, str(self))
525
526  @decorators.WithTimeoutAndRetriesFromInstance()
527  def GetApplicationDataDirectory(self, package, timeout=None, retries=None):
528    """Get the data directory on the device for the given package.
529
530    Args:
531      package: Name of the package.
532
533    Returns:
534      The package's data directory, or None if the package doesn't exist on the
535      device.
536    """
537    try:
538      output = self._RunPipedShellCommand(
539          'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package))
540      for line in output:
541        _, _, dataDir = line.partition('dataDir=')
542        if dataDir:
543          return dataDir
544    except device_errors.CommandFailedError:
545      logging.exception('Could not find data directory for %s', package)
546    return None
547
548  @decorators.WithTimeoutAndRetriesFromInstance()
549  def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None):
550    """Wait for the device to fully boot.
551
552    This means waiting for the device to boot, the package manager to be
553    available, and the SD card to be ready. It can optionally mean waiting
554    for wifi to come up, too.
555
556    Args:
557      wifi: A boolean indicating if we should wait for wifi to come up or not.
558      timeout: timeout in seconds
559      retries: number of retries
560
561    Raises:
562      CommandFailedError on failure.
563      CommandTimeoutError if one of the component waits times out.
564      DeviceUnreachableError if the device becomes unresponsive.
565    """
566    def sd_card_ready():
567      try:
568        self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()],
569                             check_return=True)
570        return True
571      except device_errors.AdbCommandFailedError:
572        return False
573
574    def pm_ready():
575      try:
576        return self._GetApplicationPathsInternal('android', skip_cache=True)
577      except device_errors.CommandFailedError:
578        return False
579
580    def boot_completed():
581      try:
582        return self.GetProp('sys.boot_completed', cache=False) == '1'
583      except device_errors.CommandFailedError:
584        return False
585
586    def wifi_enabled():
587      return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'],
588                                                        check_return=False)
589
590    self.adb.WaitForDevice()
591    timeout_retry.WaitFor(sd_card_ready)
592    timeout_retry.WaitFor(pm_ready)
593    timeout_retry.WaitFor(boot_completed)
594    if wifi:
595      timeout_retry.WaitFor(wifi_enabled)
596
597  REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
598
599  @decorators.WithTimeoutAndRetriesFromInstance(
600      min_default_timeout=REBOOT_DEFAULT_TIMEOUT)
601  def Reboot(self, block=True, wifi=False, timeout=None, retries=None):
602    """Reboot the device.
603
604    Args:
605      block: A boolean indicating if we should wait for the reboot to complete.
606      wifi: A boolean indicating if we should wait for wifi to be enabled after
607        the reboot. The option has no effect unless |block| is also True.
608      timeout: timeout in seconds
609      retries: number of retries
610
611    Raises:
612      CommandTimeoutError on timeout.
613      DeviceUnreachableError on missing device.
614    """
615    def device_offline():
616      return not self.IsOnline()
617
618    self.adb.Reboot()
619    self._ClearCache()
620    timeout_retry.WaitFor(device_offline, wait_period=1)
621    if block:
622      self.WaitUntilFullyBooted(wifi=wifi)
623
624  INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT
625
626  @decorators.WithTimeoutAndRetriesFromInstance(
627      min_default_timeout=INSTALL_DEFAULT_TIMEOUT)
628  def Install(self, apk, allow_downgrade=False, reinstall=False,
629              permissions=None, timeout=None, retries=None):
630    """Install an APK.
631
632    Noop if an identical APK is already installed.
633
634    Args:
635      apk: An ApkHelper instance or string containing the path to the APK.
636      allow_downgrade: A boolean indicating if we should allow downgrades.
637      reinstall: A boolean indicating if we should keep any existing app data.
638      permissions: Set of permissions to set. If not set, finds permissions with
639          apk helper. To set no permissions, pass [].
640      timeout: timeout in seconds
641      retries: number of retries
642
643    Raises:
644      CommandFailedError if the installation fails.
645      CommandTimeoutError if the installation times out.
646      DeviceUnreachableError on missing device.
647    """
648    self._InstallInternal(apk, None, allow_downgrade=allow_downgrade,
649                          reinstall=reinstall, permissions=permissions)
650
651  @decorators.WithTimeoutAndRetriesFromInstance(
652      min_default_timeout=INSTALL_DEFAULT_TIMEOUT)
653  def InstallSplitApk(self, base_apk, split_apks, allow_downgrade=False,
654                      reinstall=False, allow_cached_props=False,
655                      permissions=None, timeout=None, retries=None):
656    """Install a split APK.
657
658    Noop if all of the APK splits are already installed.
659
660    Args:
661      base_apk: An ApkHelper instance or string containing the path to the base
662          APK.
663      split_apks: A list of strings of paths of all of the APK splits.
664      allow_downgrade: A boolean indicating if we should allow downgrades.
665      reinstall: A boolean indicating if we should keep any existing app data.
666      allow_cached_props: Whether to use cached values for device properties.
667      permissions: Set of permissions to set. If not set, finds permissions with
668          apk helper. To set no permissions, pass [].
669      timeout: timeout in seconds
670      retries: number of retries
671
672    Raises:
673      CommandFailedError if the installation fails.
674      CommandTimeoutError if the installation times out.
675      DeviceUnreachableError on missing device.
676      DeviceVersionError if device SDK is less than Android L.
677    """
678    self._InstallInternal(base_apk, split_apks, reinstall=reinstall,
679                          allow_cached_props=allow_cached_props,
680                          permissions=permissions,
681                          allow_downgrade=allow_downgrade)
682
683  def _InstallInternal(self, base_apk, split_apks, allow_downgrade=False,
684                       reinstall=False, allow_cached_props=False,
685                       permissions=None):
686    if split_apks:
687      self._CheckSdkLevel(version_codes.LOLLIPOP)
688
689    base_apk = apk_helper.ToHelper(base_apk)
690
691    all_apks = [base_apk.path]
692    if split_apks:
693      all_apks += split_select.SelectSplits(
694        self, base_apk.path, split_apks, allow_cached_props=allow_cached_props)
695      if len(all_apks) == 1:
696        logging.warning('split-select did not select any from %s', split_apks)
697
698    package_name = base_apk.GetPackageName()
699    device_apk_paths = self._GetApplicationPathsInternal(package_name)
700
701    apks_to_install = None
702    host_checksums = None
703    if not device_apk_paths:
704      apks_to_install = all_apks
705    elif len(device_apk_paths) > 1 and not split_apks:
706      logging.warning(
707          'Installing non-split APK when split APK was previously installed')
708      apks_to_install = all_apks
709    elif len(device_apk_paths) == 1 and split_apks:
710      logging.warning(
711          'Installing split APK when non-split APK was previously installed')
712      apks_to_install = all_apks
713    else:
714      try:
715        apks_to_install, host_checksums = (
716            self._ComputeStaleApks(package_name, all_apks))
717      except EnvironmentError as e:
718        logging.warning('Error calculating md5: %s', e)
719        apks_to_install, host_checksums = all_apks, None
720      if apks_to_install and not reinstall:
721        self.Uninstall(package_name)
722        apks_to_install = all_apks
723
724    if apks_to_install:
725      # Assume that we won't know the resulting device state.
726      self._cache['package_apk_paths'].pop(package_name, 0)
727      self._cache['package_apk_checksums'].pop(package_name, 0)
728      if split_apks:
729        partial = package_name if len(apks_to_install) < len(all_apks) else None
730        self.adb.InstallMultiple(
731            apks_to_install, partial=partial, reinstall=reinstall,
732            allow_downgrade=allow_downgrade)
733      else:
734        self.adb.Install(
735            base_apk.path, reinstall=reinstall, allow_downgrade=allow_downgrade)
736      if (permissions is None
737          and self.build_version_sdk >= version_codes.MARSHMALLOW):
738        permissions = base_apk.GetPermissions()
739      self.GrantPermissions(package_name, permissions)
740      # Upon success, we know the device checksums, but not their paths.
741      if host_checksums is not None:
742        self._cache['package_apk_checksums'][package_name] = host_checksums
743    else:
744      # Running adb install terminates running instances of the app, so to be
745      # consistent, we explicitly terminate it when skipping the install.
746      self.ForceStop(package_name)
747
748  @decorators.WithTimeoutAndRetriesFromInstance()
749  def Uninstall(self, package_name, keep_data=False, timeout=None,
750                retries=None):
751    """Remove the app |package_name| from the device.
752
753    This is a no-op if the app is not already installed.
754
755    Args:
756      package_name: The package to uninstall.
757      keep_data: (optional) Whether to keep the data and cache directories.
758      timeout: Timeout in seconds.
759      retries: Number of retries.
760
761    Raises:
762      CommandFailedError if the uninstallation fails.
763      CommandTimeoutError if the uninstallation times out.
764      DeviceUnreachableError on missing device.
765    """
766    installed = self._GetApplicationPathsInternal(package_name)
767    if not installed:
768      return
769    try:
770      self.adb.Uninstall(package_name, keep_data)
771      self._cache['package_apk_paths'][package_name] = []
772      self._cache['package_apk_checksums'][package_name] = set()
773    except:
774      # Clear cache since we can't be sure of the state.
775      self._cache['package_apk_paths'].pop(package_name, 0)
776      self._cache['package_apk_checksums'].pop(package_name, 0)
777      raise
778
779  def _CheckSdkLevel(self, required_sdk_level):
780    """Raises an exception if the device does not have the required SDK level.
781    """
782    if self.build_version_sdk < required_sdk_level:
783      raise device_errors.DeviceVersionError(
784          ('Requires SDK level %s, device is SDK level %s' %
785           (required_sdk_level, self.build_version_sdk)),
786           device_serial=self.adb.GetDeviceSerial())
787
788  @decorators.WithTimeoutAndRetriesFromInstance()
789  def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None,
790                      as_root=False, single_line=False, large_output=False,
791                      raw_output=False, timeout=None, retries=None):
792    """Run an ADB shell command.
793
794    The command to run |cmd| should be a sequence of program arguments or else
795    a single string.
796
797    When |cmd| is a sequence, it is assumed to contain the name of the command
798    to run followed by its arguments. In this case, arguments are passed to the
799    command exactly as given, without any further processing by the shell. This
800    allows to easily pass arguments containing spaces or special characters
801    without having to worry about getting quoting right. Whenever possible, it
802    is recomended to pass |cmd| as a sequence.
803
804    When |cmd| is given as a string, it will be interpreted and run by the
805    shell on the device.
806
807    This behaviour is consistent with that of command runners in cmd_helper as
808    well as Python's own subprocess.Popen.
809
810    TODO(perezju) Change the default of |check_return| to True when callers
811      have switched to the new behaviour.
812
813    Args:
814      cmd: A string with the full command to run on the device, or a sequence
815        containing the command and its arguments.
816      check_return: A boolean indicating whether or not the return code should
817        be checked.
818      cwd: The device directory in which the command should be run.
819      env: The environment variables with which the command should be run.
820      as_root: A boolean indicating whether the shell command should be run
821        with root privileges.
822      single_line: A boolean indicating if only a single line of output is
823        expected.
824      large_output: Uses a work-around for large shell command output. Without
825        this large output will be truncated.
826      raw_output: Whether to only return the raw output
827          (no splitting into lines).
828      timeout: timeout in seconds
829      retries: number of retries
830
831    Returns:
832      If single_line is False, the output of the command as a list of lines,
833      otherwise, a string with the unique line of output emmited by the command
834      (with the optional newline at the end stripped).
835
836    Raises:
837      AdbCommandFailedError if check_return is True and the exit code of
838        the command run on the device is non-zero.
839      CommandFailedError if single_line is True but the output contains two or
840        more lines.
841      CommandTimeoutError on timeout.
842      DeviceUnreachableError on missing device.
843    """
844    def env_quote(key, value):
845      if not DeviceUtils._VALID_SHELL_VARIABLE.match(key):
846        raise KeyError('Invalid shell variable name %r' % key)
847      # using double quotes here to allow interpolation of shell variables
848      return '%s=%s' % (key, cmd_helper.DoubleQuote(value))
849
850    def run(cmd):
851      return self.adb.Shell(cmd)
852
853    def handle_check_return(cmd):
854      try:
855        return run(cmd)
856      except device_errors.AdbCommandFailedError as exc:
857        if check_return:
858          raise
859        else:
860          return exc.output
861
862    def handle_large_command(cmd):
863      if len(cmd) < self._MAX_ADB_COMMAND_LENGTH:
864        return handle_check_return(cmd)
865      else:
866        with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
867          self._WriteFileWithPush(script.name, cmd)
868          logging.info('Large shell command will be run from file: %s ...',
869                       cmd[:self._MAX_ADB_COMMAND_LENGTH])
870          return handle_check_return('sh %s' % script.name_quoted)
871
872    def handle_large_output(cmd, large_output_mode):
873      if large_output_mode:
874        with device_temp_file.DeviceTempFile(self.adb) as large_output_file:
875          cmd = '( %s )>%s' % (cmd, large_output_file.name)
876          logging.debug('Large output mode enabled. Will write output to '
877                        'device and read results from file.')
878          handle_large_command(cmd)
879          return self.ReadFile(large_output_file.name, force_pull=True)
880      else:
881        try:
882          return handle_large_command(cmd)
883        except device_errors.AdbCommandFailedError as exc:
884          if exc.status is None:
885            logging.error(_FormatPartialOutputError(exc.output))
886            logging.warning('Attempting to run in large_output mode.')
887            logging.warning('Use RunShellCommand(..., large_output=True) for '
888                            'shell commands that expect a lot of output.')
889            return handle_large_output(cmd, True)
890          else:
891            raise
892
893    if not isinstance(cmd, basestring):
894      cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd)
895    if env:
896      env = ' '.join(env_quote(k, v) for k, v in env.iteritems())
897      cmd = '%s %s' % (env, cmd)
898    if cwd:
899      cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd)
900    if as_root and self.NeedsSU():
901      # "su -c sh -c" allows using shell features in |cmd|
902      cmd = self._Su('sh -c %s' % cmd_helper.SingleQuote(cmd))
903
904    output = handle_large_output(cmd, large_output)
905
906    if raw_output:
907      return output
908
909    output = output.splitlines()
910    if single_line:
911      if not output:
912        return ''
913      elif len(output) == 1:
914        return output[0]
915      else:
916        msg = 'one line of output was expected, but got: %s'
917        raise device_errors.CommandFailedError(msg % output, str(self))
918    else:
919      return output
920
921  def _RunPipedShellCommand(self, script, **kwargs):
922    PIPESTATUS_LEADER = 'PIPESTATUS: '
923
924    script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER
925    kwargs['check_return'] = True
926    output = self.RunShellCommand(script, **kwargs)
927    pipestatus_line = output[-1]
928
929    if not pipestatus_line.startswith(PIPESTATUS_LEADER):
930      logging.error('Pipe exit statuses of shell script missing.')
931      raise device_errors.AdbShellCommandFailedError(
932          script, output, status=None,
933          device_serial=self.adb.GetDeviceSerial())
934
935    output = output[:-1]
936    statuses = [
937        int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split()]
938    if any(statuses):
939      raise device_errors.AdbShellCommandFailedError(
940          script, output, status=statuses,
941          device_serial=self.adb.GetDeviceSerial())
942    return output
943
944  @decorators.WithTimeoutAndRetriesFromInstance()
945  def KillAll(self, process_name, exact=False, signum=device_signal.SIGKILL,
946              as_root=False, blocking=False, quiet=False,
947              timeout=None, retries=None):
948    """Kill all processes with the given name on the device.
949
950    Args:
951      process_name: A string containing the name of the process to kill.
952      exact: A boolean indicating whether to kill all processes matching
953             the string |process_name| exactly, or all of those which contain
954             |process_name| as a substring. Defaults to False.
955      signum: An integer containing the signal number to send to kill. Defaults
956              to SIGKILL (9).
957      as_root: A boolean indicating whether the kill should be executed with
958               root privileges.
959      blocking: A boolean indicating whether we should wait until all processes
960                with the given |process_name| are dead.
961      quiet: A boolean indicating whether to ignore the fact that no processes
962             to kill were found.
963      timeout: timeout in seconds
964      retries: number of retries
965
966    Returns:
967      The number of processes attempted to kill.
968
969    Raises:
970      CommandFailedError if no process was killed and |quiet| is False.
971      CommandTimeoutError on timeout.
972      DeviceUnreachableError on missing device.
973    """
974    procs_pids = self.GetPids(process_name)
975    if exact:
976      procs_pids = {process_name: procs_pids.get(process_name, [])}
977    pids = set(itertools.chain(*procs_pids.values()))
978    if not pids:
979      if quiet:
980        return 0
981      else:
982        raise device_errors.CommandFailedError(
983            'No process "%s"' % process_name, str(self))
984
985    logging.info(
986        'KillAll(%r, ...) attempting to kill the following:', process_name)
987    for name, ids in procs_pids.iteritems():
988      for i in ids:
989        logging.info('  %05s %s', str(i), name)
990
991    cmd = ['kill', '-%d' % signum] + sorted(pids)
992    self.RunShellCommand(cmd, as_root=as_root, check_return=True)
993
994    def all_pids_killed():
995      procs_pids_remain = self.GetPids(process_name)
996      return not pids.intersection(itertools.chain(*procs_pids_remain.values()))
997
998    if blocking:
999      timeout_retry.WaitFor(all_pids_killed, wait_period=0.1)
1000
1001    return len(pids)
1002
1003  @decorators.WithTimeoutAndRetriesFromInstance()
1004  def StartActivity(self, intent_obj, blocking=False, trace_file_name=None,
1005                    force_stop=False, timeout=None, retries=None):
1006    """Start package's activity on the device.
1007
1008    Args:
1009      intent_obj: An Intent object to send.
1010      blocking: A boolean indicating whether we should wait for the activity to
1011                finish launching.
1012      trace_file_name: If present, a string that both indicates that we want to
1013                       profile the activity and contains the path to which the
1014                       trace should be saved.
1015      force_stop: A boolean indicating whether we should stop the activity
1016                  before starting it.
1017      timeout: timeout in seconds
1018      retries: number of retries
1019
1020    Raises:
1021      CommandFailedError if the activity could not be started.
1022      CommandTimeoutError on timeout.
1023      DeviceUnreachableError on missing device.
1024    """
1025    cmd = ['am', 'start']
1026    if blocking:
1027      cmd.append('-W')
1028    if trace_file_name:
1029      cmd.extend(['--start-profiler', trace_file_name])
1030    if force_stop:
1031      cmd.append('-S')
1032    cmd.extend(intent_obj.am_args)
1033    for line in self.RunShellCommand(cmd, check_return=True):
1034      if line.startswith('Error:'):
1035        raise device_errors.CommandFailedError(line, str(self))
1036
1037  @decorators.WithTimeoutAndRetriesFromInstance()
1038  def StartInstrumentation(self, component, finish=True, raw=False,
1039                           extras=None, timeout=None, retries=None):
1040    if extras is None:
1041      extras = {}
1042
1043    cmd = ['am', 'instrument']
1044    if finish:
1045      cmd.append('-w')
1046    if raw:
1047      cmd.append('-r')
1048    for k, v in extras.iteritems():
1049      cmd.extend(['-e', str(k), str(v)])
1050    cmd.append(component)
1051
1052    # Store the package name in a shell variable to help the command stay under
1053    # the _MAX_ADB_COMMAND_LENGTH limit.
1054    package = component.split('/')[0]
1055    shell_snippet = 'p=%s;%s' % (package,
1056                                 cmd_helper.ShrinkToSnippet(cmd, 'p', package))
1057    return self.RunShellCommand(shell_snippet, check_return=True,
1058                                large_output=True)
1059
1060  @decorators.WithTimeoutAndRetriesFromInstance()
1061  def BroadcastIntent(self, intent_obj, timeout=None, retries=None):
1062    """Send a broadcast intent.
1063
1064    Args:
1065      intent: An Intent to broadcast.
1066      timeout: timeout in seconds
1067      retries: number of retries
1068
1069    Raises:
1070      CommandTimeoutError on timeout.
1071      DeviceUnreachableError on missing device.
1072    """
1073    cmd = ['am', 'broadcast'] + intent_obj.am_args
1074    self.RunShellCommand(cmd, check_return=True)
1075
1076  @decorators.WithTimeoutAndRetriesFromInstance()
1077  def GoHome(self, timeout=None, retries=None):
1078    """Return to the home screen and obtain launcher focus.
1079
1080    This command launches the home screen and attempts to obtain
1081    launcher focus until the timeout is reached.
1082
1083    Args:
1084      timeout: timeout in seconds
1085      retries: number of retries
1086
1087    Raises:
1088      CommandTimeoutError on timeout.
1089      DeviceUnreachableError on missing device.
1090    """
1091    def is_launcher_focused():
1092      output = self.RunShellCommand(['dumpsys', 'window', 'windows'],
1093                                    check_return=True, large_output=True)
1094      return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output)
1095
1096    def dismiss_popups():
1097      # There is a dialog present; attempt to get rid of it.
1098      # Not all dialogs can be dismissed with back.
1099      self.SendKeyEvent(keyevent.KEYCODE_ENTER)
1100      self.SendKeyEvent(keyevent.KEYCODE_BACK)
1101      return is_launcher_focused()
1102
1103    # If Home is already focused, return early to avoid unnecessary work.
1104    if is_launcher_focused():
1105      return
1106
1107    self.StartActivity(
1108        intent.Intent(action='android.intent.action.MAIN',
1109                      category='android.intent.category.HOME'),
1110        blocking=True)
1111
1112    if not is_launcher_focused():
1113      timeout_retry.WaitFor(dismiss_popups, wait_period=1)
1114
1115  @decorators.WithTimeoutAndRetriesFromInstance()
1116  def ForceStop(self, package, timeout=None, retries=None):
1117    """Close the application.
1118
1119    Args:
1120      package: A string containing the name of the package to stop.
1121      timeout: timeout in seconds
1122      retries: number of retries
1123
1124    Raises:
1125      CommandTimeoutError on timeout.
1126      DeviceUnreachableError on missing device.
1127    """
1128    cmd = 'p=%s;if [[ "$(ps)" = *$p* ]]; then am force-stop $p; fi'
1129    self.RunShellCommand(cmd % package, check_return=True)
1130
1131  @decorators.WithTimeoutAndRetriesFromInstance()
1132  def ClearApplicationState(
1133      self, package, permissions=None, timeout=None, retries=None):
1134    """Clear all state for the given package.
1135
1136    Args:
1137      package: A string containing the name of the package to stop.
1138      permissions: List of permissions to set after clearing data.
1139      timeout: timeout in seconds
1140      retries: number of retries
1141
1142    Raises:
1143      CommandTimeoutError on timeout.
1144      DeviceUnreachableError on missing device.
1145    """
1146    # Check that the package exists before clearing it for android builds below
1147    # JB MR2. Necessary because calling pm clear on a package that doesn't exist
1148    # may never return.
1149    if ((self.build_version_sdk >= version_codes.JELLY_BEAN_MR2)
1150        or self._GetApplicationPathsInternal(package)):
1151      self.RunShellCommand(['pm', 'clear', package], check_return=True)
1152      self.GrantPermissions(package, permissions)
1153
1154  @decorators.WithTimeoutAndRetriesFromInstance()
1155  def SendKeyEvent(self, keycode, timeout=None, retries=None):
1156    """Sends a keycode to the device.
1157
1158    See the devil.android.sdk.keyevent module for suitable keycode values.
1159
1160    Args:
1161      keycode: A integer keycode to send to the device.
1162      timeout: timeout in seconds
1163      retries: number of retries
1164
1165    Raises:
1166      CommandTimeoutError on timeout.
1167      DeviceUnreachableError on missing device.
1168    """
1169    self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')],
1170                         check_return=True)
1171
1172  PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT
1173
1174  @decorators.WithTimeoutAndRetriesFromInstance(
1175      min_default_timeout=PUSH_CHANGED_FILES_DEFAULT_TIMEOUT)
1176  def PushChangedFiles(self, host_device_tuples, timeout=None,
1177                       retries=None, delete_device_stale=False):
1178    """Push files to the device, skipping files that don't need updating.
1179
1180    When a directory is pushed, it is traversed recursively on the host and
1181    all files in it are pushed to the device as needed.
1182    Additionally, if delete_device_stale option is True,
1183    files that exist on the device but don't exist on the host are deleted.
1184
1185    Args:
1186      host_device_tuples: A list of (host_path, device_path) tuples, where
1187        |host_path| is an absolute path of a file or directory on the host
1188        that should be minimially pushed to the device, and |device_path| is
1189        an absolute path of the destination on the device.
1190      timeout: timeout in seconds
1191      retries: number of retries
1192      delete_device_stale: option to delete stale files on device
1193
1194    Raises:
1195      CommandFailedError on failure.
1196      CommandTimeoutError on timeout.
1197      DeviceUnreachableError on missing device.
1198    """
1199
1200    all_changed_files = []
1201    all_stale_files = []
1202    missing_dirs = []
1203    cache_commit_funcs = []
1204    for h, d in host_device_tuples:
1205      assert os.path.isabs(h) and posixpath.isabs(d)
1206      changed_files, up_to_date_files, stale_files, cache_commit_func = (
1207          self._GetChangedAndStaleFiles(h, d, delete_device_stale))
1208      all_changed_files += changed_files
1209      all_stale_files += stale_files
1210      cache_commit_funcs.append(cache_commit_func)
1211      if changed_files and not up_to_date_files and not stale_files:
1212        if os.path.isdir(h):
1213          missing_dirs.append(d)
1214        else:
1215          missing_dirs.append(posixpath.dirname(d))
1216
1217    if delete_device_stale and all_stale_files:
1218      self.RunShellCommand(['rm', '-f'] + all_stale_files, check_return=True)
1219
1220    if all_changed_files:
1221      if missing_dirs:
1222        self.RunShellCommand(['mkdir', '-p'] + missing_dirs, check_return=True)
1223      self._PushFilesImpl(host_device_tuples, all_changed_files)
1224    for func in cache_commit_funcs:
1225      func()
1226
1227  def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False):
1228    """Get files to push and delete
1229
1230    Args:
1231      host_path: an absolute path of a file or directory on the host
1232      device_path: an absolute path of a file or directory on the device
1233      track_stale: whether to bother looking for stale files (slower)
1234
1235    Returns:
1236      a four-element tuple
1237      1st element: a list of (host_files_path, device_files_path) tuples to push
1238      2nd element: a list of host_files_path that are up-to-date
1239      3rd element: a list of stale files under device_path, or [] when
1240        track_stale == False
1241      4th element: a cache commit function.
1242    """
1243    try:
1244      # Length calculations below assume no trailing /.
1245      host_path = host_path.rstrip('/')
1246      device_path = device_path.rstrip('/')
1247
1248      specific_device_paths = [device_path]
1249      ignore_other_files = not track_stale and os.path.isdir(host_path)
1250      if ignore_other_files:
1251        specific_device_paths = []
1252        for root, _, filenames in os.walk(host_path):
1253          relative_dir = root[len(host_path) + 1:]
1254          specific_device_paths.extend(
1255              posixpath.join(device_path, relative_dir, f) for f in filenames)
1256
1257      def calculate_host_checksums():
1258        return md5sum.CalculateHostMd5Sums([host_path])
1259
1260      def calculate_device_checksums():
1261        if self._enable_device_files_cache:
1262          cache_entry = self._cache['device_path_checksums'].get(device_path)
1263          if cache_entry and cache_entry[0] == ignore_other_files:
1264            return dict(cache_entry[1])
1265
1266        sums = md5sum.CalculateDeviceMd5Sums(specific_device_paths, self)
1267
1268        cache_entry = [ignore_other_files, sums]
1269        self._cache['device_path_checksums'][device_path] = cache_entry
1270        return dict(sums)
1271
1272      host_checksums, device_checksums = reraiser_thread.RunAsync((
1273          calculate_host_checksums,
1274          calculate_device_checksums))
1275    except EnvironmentError as e:
1276      logging.warning('Error calculating md5: %s', e)
1277      return ([(host_path, device_path)], [], [], lambda: 0)
1278
1279    to_push = []
1280    up_to_date = []
1281    to_delete = []
1282    if os.path.isfile(host_path):
1283      host_checksum = host_checksums.get(host_path)
1284      device_checksum = device_checksums.get(device_path)
1285      if host_checksum == device_checksum:
1286        up_to_date.append(host_path)
1287      else:
1288        to_push.append((host_path, device_path))
1289    else:
1290      for host_abs_path, host_checksum in host_checksums.iteritems():
1291        device_abs_path = posixpath.join(
1292            device_path, os.path.relpath(host_abs_path, host_path))
1293        device_checksum = device_checksums.pop(device_abs_path, None)
1294        if device_checksum == host_checksum:
1295          up_to_date.append(host_abs_path)
1296        else:
1297          to_push.append((host_abs_path, device_abs_path))
1298      to_delete = device_checksums.keys()
1299
1300    def cache_commit_func():
1301      new_sums = {posixpath.join(device_path, path[len(host_path) + 1:]): val
1302                  for path, val in host_checksums.iteritems()}
1303      cache_entry = [ignore_other_files, new_sums]
1304      self._cache['device_path_checksums'][device_path] = cache_entry
1305
1306    return (to_push, up_to_date, to_delete, cache_commit_func)
1307
1308  def _ComputeDeviceChecksumsForApks(self, package_name):
1309    ret = self._cache['package_apk_checksums'].get(package_name)
1310    if ret is None:
1311      device_paths = self._GetApplicationPathsInternal(package_name)
1312      file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self)
1313      ret = set(file_to_checksums.values())
1314      self._cache['package_apk_checksums'][package_name] = ret
1315    return ret
1316
1317  def _ComputeStaleApks(self, package_name, host_apk_paths):
1318    def calculate_host_checksums():
1319      return md5sum.CalculateHostMd5Sums(host_apk_paths)
1320
1321    def calculate_device_checksums():
1322      return self._ComputeDeviceChecksumsForApks(package_name)
1323
1324    host_checksums, device_checksums = reraiser_thread.RunAsync((
1325        calculate_host_checksums, calculate_device_checksums))
1326    stale_apks = [k for (k, v) in host_checksums.iteritems()
1327                  if v not in device_checksums]
1328    return stale_apks, set(host_checksums.values())
1329
1330  def _PushFilesImpl(self, host_device_tuples, files):
1331    if not files:
1332      return
1333
1334    size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files)
1335    file_count = len(files)
1336    dir_size = sum(host_utils.GetRecursiveDiskUsage(h)
1337                   for h, _ in host_device_tuples)
1338    dir_file_count = 0
1339    for h, _ in host_device_tuples:
1340      if os.path.isdir(h):
1341        dir_file_count += sum(len(f) for _r, _d, f in os.walk(h))
1342      else:
1343        dir_file_count += 1
1344
1345    push_duration = self._ApproximateDuration(
1346        file_count, file_count, size, False)
1347    dir_push_duration = self._ApproximateDuration(
1348        len(host_device_tuples), dir_file_count, dir_size, False)
1349    zip_duration = self._ApproximateDuration(1, 1, size, True)
1350
1351    if (dir_push_duration < push_duration and dir_push_duration < zip_duration
1352        # TODO(jbudorick): Resume directory pushing once clients have switched
1353        # to 1.0.36-compatible syntax.
1354        and False):
1355      self._PushChangedFilesIndividually(host_device_tuples)
1356    elif push_duration < zip_duration:
1357      self._PushChangedFilesIndividually(files)
1358    elif self._commands_installed is False:
1359      # Already tried and failed to install unzip command.
1360      self._PushChangedFilesIndividually(files)
1361    elif not self._PushChangedFilesZipped(
1362        files, [d for _, d in host_device_tuples]):
1363      self._PushChangedFilesIndividually(files)
1364
1365  def _MaybeInstallCommands(self):
1366    if self._commands_installed is None:
1367      try:
1368        if not install_commands.Installed(self):
1369          install_commands.InstallCommands(self)
1370        self._commands_installed = True
1371      except device_errors.CommandFailedError as e:
1372        logging.warning('unzip not available: %s', str(e))
1373        self._commands_installed = False
1374    return self._commands_installed
1375
1376  @staticmethod
1377  def _ApproximateDuration(adb_calls, file_count, byte_count, is_zipping):
1378    # We approximate the time to push a set of files to a device as:
1379    #   t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where
1380    #     t: total time (sec)
1381    #     c1: adb call time delay (sec)
1382    #     a: number of times adb is called (unitless)
1383    #     c2: push time delay (sec)
1384    #     f: number of files pushed via adb (unitless)
1385    #     c3: zip time delay (sec)
1386    #     c4: zip rate (bytes/sec)
1387    #     b: total number of bytes (bytes)
1388    #     c5: transfer rate (bytes/sec)
1389    #     c6: compression ratio (unitless)
1390
1391    # All of these are approximations.
1392    ADB_CALL_PENALTY = 0.1  # seconds
1393    ADB_PUSH_PENALTY = 0.01  # seconds
1394    ZIP_PENALTY = 2.0  # seconds
1395    ZIP_RATE = 10000000.0  # bytes / second
1396    TRANSFER_RATE = 2000000.0  # bytes / second
1397    COMPRESSION_RATIO = 2.0  # unitless
1398
1399    adb_call_time = ADB_CALL_PENALTY * adb_calls
1400    adb_push_setup_time = ADB_PUSH_PENALTY * file_count
1401    if is_zipping:
1402      zip_time = ZIP_PENALTY + byte_count / ZIP_RATE
1403      transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO)
1404    else:
1405      zip_time = 0
1406      transfer_time = byte_count / TRANSFER_RATE
1407    return adb_call_time + adb_push_setup_time + zip_time + transfer_time
1408
1409  def _PushChangedFilesIndividually(self, files):
1410    for h, d in files:
1411      self.adb.Push(h, d)
1412
1413  def _PushChangedFilesZipped(self, files, dirs):
1414    with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file:
1415      zip_proc = multiprocessing.Process(
1416          target=DeviceUtils._CreateDeviceZip,
1417          args=(zip_file.name, files))
1418      zip_proc.start()
1419      try:
1420        # While it's zipping, ensure the unzip command exists on the device.
1421        if not self._MaybeInstallCommands():
1422          zip_proc.terminate()
1423          return False
1424
1425        # Warm up NeedsSU cache while we're still zipping.
1426        self.NeedsSU()
1427        with device_temp_file.DeviceTempFile(
1428            self.adb, suffix='.zip') as device_temp:
1429          zip_proc.join()
1430          self.adb.Push(zip_file.name, device_temp.name)
1431          quoted_dirs = ' '.join(cmd_helper.SingleQuote(d) for d in dirs)
1432          self.RunShellCommand(
1433              'unzip %s&&chmod -R 777 %s' % (device_temp.name, quoted_dirs),
1434              as_root=True,
1435              env={'PATH': '%s:$PATH' % install_commands.BIN_DIR},
1436              check_return=True)
1437      finally:
1438        if zip_proc.is_alive():
1439          zip_proc.terminate()
1440    return True
1441
1442  @staticmethod
1443  def _CreateDeviceZip(zip_path, host_device_tuples):
1444    with zipfile.ZipFile(zip_path, 'w') as zip_file:
1445      for host_path, device_path in host_device_tuples:
1446        zip_utils.WriteToZipFile(zip_file, host_path, device_path)
1447
1448  # TODO(nednguyen): remove this and migrate the callsite to PathExists().
1449  @decorators.WithTimeoutAndRetriesFromInstance()
1450  def FileExists(self, device_path, timeout=None, retries=None):
1451    """Checks whether the given file exists on the device.
1452
1453    Arguments are the same as PathExists.
1454    """
1455    return self.PathExists(device_path, timeout=timeout, retries=retries)
1456
1457  @decorators.WithTimeoutAndRetriesFromInstance()
1458  def PathExists(self, device_paths, as_root=False, timeout=None, retries=None):
1459    """Checks whether the given path(s) exists on the device.
1460
1461    Args:
1462      device_path: A string containing the absolute path to the file on the
1463                   device, or an iterable of paths to check.
1464      as_root: Whether root permissions should be use to check for the existence
1465               of the given path(s).
1466      timeout: timeout in seconds
1467      retries: number of retries
1468
1469    Returns:
1470      True if the all given paths exist on the device, False otherwise.
1471
1472    Raises:
1473      CommandTimeoutError on timeout.
1474      DeviceUnreachableError on missing device.
1475    """
1476    paths = device_paths
1477    if isinstance(paths, basestring):
1478      paths = (paths,)
1479    condition = ' -a '.join('-e %s' % cmd_helper.SingleQuote(p) for p in paths)
1480    cmd = 'test %s' % condition
1481    try:
1482      self.RunShellCommand(cmd, as_root=as_root, check_return=True,
1483                           timeout=timeout, retries=retries)
1484      return True
1485    except device_errors.CommandFailedError:
1486      return False
1487
1488  @decorators.WithTimeoutAndRetriesFromInstance()
1489  def PullFile(self, device_path, host_path, timeout=None, retries=None):
1490    """Pull a file from the device.
1491
1492    Args:
1493      device_path: A string containing the absolute path of the file to pull
1494                   from the device.
1495      host_path: A string containing the absolute path of the destination on
1496                 the host.
1497      timeout: timeout in seconds
1498      retries: number of retries
1499
1500    Raises:
1501      CommandFailedError on failure.
1502      CommandTimeoutError on timeout.
1503    """
1504    # Create the base dir if it doesn't exist already
1505    dirname = os.path.dirname(host_path)
1506    if dirname and not os.path.exists(dirname):
1507      os.makedirs(dirname)
1508    self.adb.Pull(device_path, host_path)
1509
1510  def _ReadFileWithPull(self, device_path):
1511    try:
1512      d = tempfile.mkdtemp()
1513      host_temp_path = os.path.join(d, 'tmp_ReadFileWithPull')
1514      self.adb.Pull(device_path, host_temp_path)
1515      with open(host_temp_path, 'r') as host_temp:
1516        return host_temp.read()
1517    finally:
1518      if os.path.exists(d):
1519        shutil.rmtree(d)
1520
1521  @decorators.WithTimeoutAndRetriesFromInstance()
1522  def ReadFile(self, device_path, as_root=False, force_pull=False,
1523               timeout=None, retries=None):
1524    """Reads the contents of a file from the device.
1525
1526    Args:
1527      device_path: A string containing the absolute path of the file to read
1528                   from the device.
1529      as_root: A boolean indicating whether the read should be executed with
1530               root privileges.
1531      force_pull: A boolean indicating whether to force the operation to be
1532          performed by pulling a file from the device. The default is, when the
1533          contents are short, to retrieve the contents using cat instead.
1534      timeout: timeout in seconds
1535      retries: number of retries
1536
1537    Returns:
1538      The contents of |device_path| as a string. Contents are intepreted using
1539      universal newlines, so the caller will see them encoded as '\n'. Also,
1540      all lines will be terminated.
1541
1542    Raises:
1543      AdbCommandFailedError if the file can't be read.
1544      CommandTimeoutError on timeout.
1545      DeviceUnreachableError on missing device.
1546    """
1547    def get_size(path):
1548      return self.FileSize(path, as_root=as_root)
1549
1550    if (not force_pull
1551        and 0 < get_size(device_path) <= self._MAX_ADB_OUTPUT_LENGTH):
1552      return _JoinLines(self.RunShellCommand(
1553          ['cat', device_path], as_root=as_root, check_return=True))
1554    elif as_root and self.NeedsSU():
1555      with device_temp_file.DeviceTempFile(self.adb) as device_temp:
1556        cmd = 'SRC=%s DEST=%s;cp "$SRC" "$DEST" && chmod 666 "$DEST"' % (
1557            cmd_helper.SingleQuote(device_path),
1558            cmd_helper.SingleQuote(device_temp.name))
1559        self.RunShellCommand(cmd, as_root=True, check_return=True)
1560        return self._ReadFileWithPull(device_temp.name)
1561    else:
1562      return self._ReadFileWithPull(device_path)
1563
1564  def _WriteFileWithPush(self, device_path, contents):
1565    with tempfile.NamedTemporaryFile() as host_temp:
1566      host_temp.write(contents)
1567      host_temp.flush()
1568      self.adb.Push(host_temp.name, device_path)
1569
1570  @decorators.WithTimeoutAndRetriesFromInstance()
1571  def WriteFile(self, device_path, contents, as_root=False, force_push=False,
1572                timeout=None, retries=None):
1573    """Writes |contents| to a file on the device.
1574
1575    Args:
1576      device_path: A string containing the absolute path to the file to write
1577          on the device.
1578      contents: A string containing the data to write to the device.
1579      as_root: A boolean indicating whether the write should be executed with
1580          root privileges (if available).
1581      force_push: A boolean indicating whether to force the operation to be
1582          performed by pushing a file to the device. The default is, when the
1583          contents are short, to pass the contents using a shell script instead.
1584      timeout: timeout in seconds
1585      retries: number of retries
1586
1587    Raises:
1588      CommandFailedError if the file could not be written on the device.
1589      CommandTimeoutError on timeout.
1590      DeviceUnreachableError on missing device.
1591    """
1592    if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH:
1593      # If the contents are small, for efficieny we write the contents with
1594      # a shell command rather than pushing a file.
1595      cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents),
1596                                 cmd_helper.SingleQuote(device_path))
1597      self.RunShellCommand(cmd, as_root=as_root, check_return=True)
1598    elif as_root and self.NeedsSU():
1599      # Adb does not allow to "push with su", so we first push to a temp file
1600      # on a safe location, and then copy it to the desired location with su.
1601      with device_temp_file.DeviceTempFile(self.adb) as device_temp:
1602        self._WriteFileWithPush(device_temp.name, contents)
1603        # Here we need 'cp' rather than 'mv' because the temp and
1604        # destination files might be on different file systems (e.g.
1605        # on internal storage and an external sd card).
1606        self.RunShellCommand(['cp', device_temp.name, device_path],
1607                             as_root=True, check_return=True)
1608    else:
1609      # If root is not needed, we can push directly to the desired location.
1610      self._WriteFileWithPush(device_path, contents)
1611
1612  def _ParseLongLsOutput(self, device_path, as_root=False, **kwargs):
1613    """Run and scrape the output of 'ls -a -l' on a device directory."""
1614    device_path = posixpath.join(device_path, '')  # Force trailing '/'.
1615    output = self.RunShellCommand(
1616        ['ls', '-a', '-l', device_path], as_root=as_root,
1617        check_return=True, env={'TZ': 'utc'}, **kwargs)
1618    if output and output[0].startswith('total '):
1619      output.pop(0) # pylint: disable=maybe-no-member
1620
1621    entries = []
1622    for line in output:
1623      m = _LONG_LS_OUTPUT_RE.match(line)
1624      if m:
1625        if m.group('filename') not in ['.', '..']:
1626          entries.append(m.groupdict())
1627      else:
1628        logging.info('Skipping: %s', line)
1629
1630    return entries
1631
1632  def ListDirectory(self, device_path, as_root=False, **kwargs):
1633    """List all files on a device directory.
1634
1635    Mirroring os.listdir (and most client expectations) the resulting list
1636    does not include the special entries '.' and '..' even if they are present
1637    in the directory.
1638
1639    Args:
1640      device_path: A string containing the path of the directory on the device
1641                   to list.
1642      as_root: A boolean indicating whether the to use root privileges to list
1643               the directory contents.
1644      timeout: timeout in seconds
1645      retries: number of retries
1646
1647    Returns:
1648      A list of filenames for all entries contained in the directory.
1649
1650    Raises:
1651      AdbCommandFailedError if |device_path| does not specify a valid and
1652          accessible directory in the device.
1653      CommandTimeoutError on timeout.
1654      DeviceUnreachableError on missing device.
1655    """
1656    entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs)
1657    return [d['filename'] for d in entries]
1658
1659  def StatDirectory(self, device_path, as_root=False, **kwargs):
1660    """List file and stat info for all entries on a device directory.
1661
1662    Implementation notes: this is currently implemented by parsing the output
1663    of 'ls -a -l' on the device. Whether possible and convenient, we attempt to
1664    make parsing strict and return values mirroring those of the standard |os|
1665    and |stat| Python modules.
1666
1667    Mirroring os.listdir (and most client expectations) the resulting list
1668    does not include the special entries '.' and '..' even if they are present
1669    in the directory.
1670
1671    Args:
1672      device_path: A string containing the path of the directory on the device
1673                   to list.
1674      as_root: A boolean indicating whether the to use root privileges to list
1675               the directory contents.
1676      timeout: timeout in seconds
1677      retries: number of retries
1678
1679    Returns:
1680      A list of dictionaries, each containing the following keys:
1681        filename: A string with the file name.
1682        st_mode: File permissions, use the stat module to interpret these.
1683        st_nlink: Number of hard links (may be missing).
1684        st_owner: A string with the user name of the owner.
1685        st_group: A string with the group name of the owner.
1686        st_rdev_pair: Device type as (major, minior) (only if inode device).
1687        st_size: Size of file, in bytes (may be missing for non-regular files).
1688        st_mtime: Time of most recent modification, in seconds since epoch
1689          (although resolution is in minutes).
1690        symbolic_link_to: If entry is a symbolic link, path where it points to;
1691          missing otherwise.
1692
1693    Raises:
1694      AdbCommandFailedError if |device_path| does not specify a valid and
1695          accessible directory in the device.
1696      CommandTimeoutError on timeout.
1697      DeviceUnreachableError on missing device.
1698    """
1699    entries = self._ParseLongLsOutput(device_path, as_root=as_root, **kwargs)
1700    for d in entries:
1701      for key, value in d.items():
1702        if value is None:
1703          del d[key]  # Remove missing fields.
1704      d['st_mode'] = _ParseModeString(d['st_mode'])
1705      d['st_mtime'] = calendar.timegm(
1706          time.strptime(d['st_mtime'], _LS_DATE_FORMAT))
1707      for key in ['st_nlink', 'st_size', 'st_rdev_major', 'st_rdev_minor']:
1708        if key in d:
1709          d[key] = int(d[key])
1710      if 'st_rdev_major' in d and 'st_rdev_minor' in d:
1711        d['st_rdev_pair'] = (d.pop('st_rdev_major'), d.pop('st_rdev_minor'))
1712    return entries
1713
1714  def StatPath(self, device_path, as_root=False, **kwargs):
1715    """Get the stat attributes of a file or directory on the device.
1716
1717    Args:
1718      device_path: A string containing the path of a file or directory from
1719                   which to get attributes.
1720      as_root: A boolean indicating whether the to use root privileges to
1721               access the file information.
1722      timeout: timeout in seconds
1723      retries: number of retries
1724
1725    Returns:
1726      A dictionary with the stat info collected; see StatDirectory for details.
1727
1728    Raises:
1729      CommandFailedError if device_path cannot be found on the device.
1730      CommandTimeoutError on timeout.
1731      DeviceUnreachableError on missing device.
1732    """
1733    dirname, filename = posixpath.split(posixpath.normpath(device_path))
1734    for entry in self.StatDirectory(dirname, as_root=as_root, **kwargs):
1735      if entry['filename'] == filename:
1736        return entry
1737    raise device_errors.CommandFailedError(
1738        'Cannot find file or directory: %r' % device_path, str(self))
1739
1740  def FileSize(self, device_path, as_root=False, **kwargs):
1741    """Get the size of a file on the device.
1742
1743    Note: This is implemented by parsing the output of the 'ls' command on
1744    the device. On some Android versions, when passing a directory or special
1745    file, the size is *not* reported and this function will throw an exception.
1746
1747    Args:
1748      device_path: A string containing the path of a file on the device.
1749      as_root: A boolean indicating whether the to use root privileges to
1750               access the file information.
1751      timeout: timeout in seconds
1752      retries: number of retries
1753
1754    Returns:
1755      The size of the file in bytes.
1756
1757    Raises:
1758      CommandFailedError if device_path cannot be found on the device, or
1759        its size cannot be determited for some reason.
1760      CommandTimeoutError on timeout.
1761      DeviceUnreachableError on missing device.
1762    """
1763    entry = self.StatPath(device_path, as_root=as_root, **kwargs)
1764    try:
1765      return entry['st_size']
1766    except KeyError:
1767      raise device_errors.CommandFailedError(
1768          'Could not determine the size of: %s' % device_path, str(self))
1769
1770  @decorators.WithTimeoutAndRetriesFromInstance()
1771  def SetJavaAsserts(self, enabled, timeout=None, retries=None):
1772    """Enables or disables Java asserts.
1773
1774    Args:
1775      enabled: A boolean indicating whether Java asserts should be enabled
1776               or disabled.
1777      timeout: timeout in seconds
1778      retries: number of retries
1779
1780    Returns:
1781      True if the device-side property changed and a restart is required as a
1782      result, False otherwise.
1783
1784    Raises:
1785      CommandTimeoutError on timeout.
1786    """
1787    def find_property(lines, property_name):
1788      for index, line in enumerate(lines):
1789        if line.strip() == '':
1790          continue
1791        key_value = tuple(s.strip() for s in line.split('=', 1))
1792        if len(key_value) != 2:
1793          continue
1794        key, value = key_value
1795        if key == property_name:
1796          return index, value
1797      return None, ''
1798
1799    new_value = 'all' if enabled else ''
1800
1801    # First ensure the desired property is persisted.
1802    try:
1803      properties = self.ReadFile(self.LOCAL_PROPERTIES_PATH).splitlines()
1804    except device_errors.CommandFailedError:
1805      properties = []
1806    index, value = find_property(properties, self.JAVA_ASSERT_PROPERTY)
1807    if new_value != value:
1808      if new_value:
1809        new_line = '%s=%s' % (self.JAVA_ASSERT_PROPERTY, new_value)
1810        if index is None:
1811          properties.append(new_line)
1812        else:
1813          properties[index] = new_line
1814      else:
1815        assert index is not None  # since new_value == '' and new_value != value
1816        properties.pop(index)
1817      self.WriteFile(self.LOCAL_PROPERTIES_PATH, _JoinLines(properties))
1818
1819    # Next, check the current runtime value is what we need, and
1820    # if not, set it and report that a reboot is required.
1821    value = self.GetProp(self.JAVA_ASSERT_PROPERTY)
1822    if new_value != value:
1823      self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value)
1824      return True
1825    else:
1826      return False
1827
1828  def GetLanguage(self, cache=False):
1829    """Returns the language setting on the device.
1830    Args:
1831      cache: Whether to use cached properties when available.
1832    """
1833    return self.GetProp('persist.sys.language', cache=cache)
1834
1835  def GetCountry(self, cache=False):
1836    """Returns the country setting on the device.
1837
1838    Args:
1839      cache: Whether to use cached properties when available.
1840    """
1841    return self.GetProp('persist.sys.country', cache=cache)
1842
1843  @property
1844  def screen_density(self):
1845    """Returns the screen density of the device."""
1846    DPI_TO_DENSITY = {
1847      120: 'ldpi',
1848      160: 'mdpi',
1849      240: 'hdpi',
1850      320: 'xhdpi',
1851      480: 'xxhdpi',
1852      640: 'xxxhdpi',
1853    }
1854    return DPI_TO_DENSITY.get(self.pixel_density, 'tvdpi')
1855
1856  @property
1857  def pixel_density(self):
1858    return int(self.GetProp('ro.sf.lcd_density', cache=True))
1859
1860  @property
1861  def build_description(self):
1862    """Returns the build description of the system.
1863
1864    For example:
1865      nakasi-user 4.4.4 KTU84P 1227136 release-keys
1866    """
1867    return self.GetProp('ro.build.description', cache=True)
1868
1869  @property
1870  def build_fingerprint(self):
1871    """Returns the build fingerprint of the system.
1872
1873    For example:
1874      google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys
1875    """
1876    return self.GetProp('ro.build.fingerprint', cache=True)
1877
1878  @property
1879  def build_id(self):
1880    """Returns the build ID of the system (e.g. 'KTU84P')."""
1881    return self.GetProp('ro.build.id', cache=True)
1882
1883  @property
1884  def build_product(self):
1885    """Returns the build product of the system (e.g. 'grouper')."""
1886    return self.GetProp('ro.build.product', cache=True)
1887
1888  @property
1889  def build_type(self):
1890    """Returns the build type of the system (e.g. 'user')."""
1891    return self.GetProp('ro.build.type', cache=True)
1892
1893  @property
1894  def build_version_sdk(self):
1895    """Returns the build version sdk of the system as a number (e.g. 19).
1896
1897    For version code numbers see:
1898    http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
1899
1900    For named constants see devil.android.sdk.version_codes
1901
1902    Raises:
1903      CommandFailedError if the build version sdk is not a number.
1904    """
1905    value = self.GetProp('ro.build.version.sdk', cache=True)
1906    try:
1907      return int(value)
1908    except ValueError:
1909      raise device_errors.CommandFailedError(
1910          'Invalid build version sdk: %r' % value)
1911
1912  @property
1913  def product_cpu_abi(self):
1914    """Returns the product cpu abi of the device (e.g. 'armeabi-v7a')."""
1915    return self.GetProp('ro.product.cpu.abi', cache=True)
1916
1917  @property
1918  def product_model(self):
1919    """Returns the name of the product model (e.g. 'Nexus 7')."""
1920    return self.GetProp('ro.product.model', cache=True)
1921
1922  @property
1923  def product_name(self):
1924    """Returns the product name of the device (e.g. 'nakasi')."""
1925    return self.GetProp('ro.product.name', cache=True)
1926
1927  @property
1928  def product_board(self):
1929    """Returns the product board name of the device (e.g. 'shamu')."""
1930    return self.GetProp('ro.product.board', cache=True)
1931
1932  def _EnsureCacheInitialized(self):
1933    """Populates cache token, runs getprop and fetches $EXTERNAL_STORAGE."""
1934    if self._cache['token']:
1935      return
1936    with self._cache_lock:
1937      if self._cache['token']:
1938        return
1939      # Change the token every time to ensure that it will match only the
1940      # previously dumped cache.
1941      token = str(uuid.uuid1())
1942      cmd = (
1943          'c=/data/local/tmp/cache_token;'
1944          'echo $EXTERNAL_STORAGE;'
1945          'cat $c 2>/dev/null||echo;'
1946          'echo "%s">$c &&' % token +
1947          'getprop'
1948      )
1949      output = self.RunShellCommand(cmd, check_return=True, large_output=True)
1950      # Error-checking for this existing is done in GetExternalStoragePath().
1951      self._cache['external_storage'] = output[0]
1952      self._cache['prev_token'] = output[1]
1953      output = output[2:]
1954
1955      prop_cache = self._cache['getprop']
1956      prop_cache.clear()
1957      for key, value in _GETPROP_RE.findall(''.join(output)):
1958        prop_cache[key] = value
1959      self._cache['token'] = token
1960
1961  @decorators.WithTimeoutAndRetriesFromInstance()
1962  def GetProp(self, property_name, cache=False, timeout=None, retries=None):
1963    """Gets a property from the device.
1964
1965    Args:
1966      property_name: A string containing the name of the property to get from
1967                     the device.
1968      cache: Whether to use cached properties when available.
1969      timeout: timeout in seconds
1970      retries: number of retries
1971
1972    Returns:
1973      The value of the device's |property_name| property.
1974
1975    Raises:
1976      CommandTimeoutError on timeout.
1977    """
1978    assert isinstance(property_name, basestring), (
1979        "property_name is not a string: %r" % property_name)
1980
1981    if cache:
1982      # It takes ~120ms to query a single property, and ~130ms to query all
1983      # properties. So, when caching we always query all properties.
1984      self._EnsureCacheInitialized()
1985    else:
1986      # timeout and retries are handled down at run shell, because we don't
1987      # want to apply them in the other branch when reading from the cache
1988      value = self.RunShellCommand(
1989          ['getprop', property_name], single_line=True, check_return=True,
1990          timeout=timeout, retries=retries)
1991      self._cache['getprop'][property_name] = value
1992    # Non-existent properties are treated as empty strings by getprop.
1993    return self._cache['getprop'].get(property_name, '')
1994
1995  @decorators.WithTimeoutAndRetriesFromInstance()
1996  def SetProp(self, property_name, value, check=False, timeout=None,
1997              retries=None):
1998    """Sets a property on the device.
1999
2000    Args:
2001      property_name: A string containing the name of the property to set on
2002                     the device.
2003      value: A string containing the value to set to the property on the
2004             device.
2005      check: A boolean indicating whether to check that the property was
2006             successfully set on the device.
2007      timeout: timeout in seconds
2008      retries: number of retries
2009
2010    Raises:
2011      CommandFailedError if check is true and the property was not correctly
2012        set on the device (e.g. because it is not rooted).
2013      CommandTimeoutError on timeout.
2014    """
2015    assert isinstance(property_name, basestring), (
2016        "property_name is not a string: %r" % property_name)
2017    assert isinstance(value, basestring), "value is not a string: %r" % value
2018
2019    self.RunShellCommand(['setprop', property_name, value], check_return=True)
2020    prop_cache = self._cache['getprop']
2021    if property_name in prop_cache:
2022      del prop_cache[property_name]
2023    # TODO(perezju) remove the option and make the check mandatory, but using a
2024    # single shell script to both set- and getprop.
2025    if check and value != self.GetProp(property_name, cache=False):
2026      raise device_errors.CommandFailedError(
2027          'Unable to set property %r on the device to %r'
2028          % (property_name, value), str(self))
2029
2030  @decorators.WithTimeoutAndRetriesFromInstance()
2031  def GetABI(self, timeout=None, retries=None):
2032    """Gets the device main ABI.
2033
2034    Args:
2035      timeout: timeout in seconds
2036      retries: number of retries
2037
2038    Returns:
2039      The device's main ABI name.
2040
2041    Raises:
2042      CommandTimeoutError on timeout.
2043    """
2044    return self.GetProp('ro.product.cpu.abi', cache=True)
2045
2046  @decorators.WithTimeoutAndRetriesFromInstance()
2047  def GetPids(self, process_name, timeout=None, retries=None):
2048    """Returns the PIDs of processes with the given name.
2049
2050    Note that the |process_name| is often the package name.
2051
2052    Args:
2053      process_name: A string containing the process name to get the PIDs for.
2054      timeout: timeout in seconds
2055      retries: number of retries
2056
2057    Returns:
2058      A dict mapping process name to a list of PIDs for each process that
2059      contained the provided |process_name|.
2060
2061    Raises:
2062      CommandTimeoutError on timeout.
2063      DeviceUnreachableError on missing device.
2064    """
2065    procs_pids = collections.defaultdict(list)
2066    try:
2067      ps_output = self._RunPipedShellCommand(
2068          'ps | grep -F %s' % cmd_helper.SingleQuote(process_name))
2069    except device_errors.AdbShellCommandFailedError as e:
2070      if e.status and isinstance(e.status, list) and not e.status[0]:
2071        # If ps succeeded but grep failed, there were no processes with the
2072        # given name.
2073        return procs_pids
2074      else:
2075        raise
2076
2077    for line in ps_output:
2078      try:
2079        ps_data = line.split()
2080        if process_name in ps_data[-1]:
2081          pid, process = ps_data[1], ps_data[-1]
2082          procs_pids[process].append(pid)
2083      except IndexError:
2084        pass
2085    return procs_pids
2086
2087  @decorators.WithTimeoutAndRetriesFromInstance()
2088  def TakeScreenshot(self, host_path=None, timeout=None, retries=None):
2089    """Takes a screenshot of the device.
2090
2091    Args:
2092      host_path: A string containing the path on the host to save the
2093                 screenshot to. If None, a file name in the current
2094                 directory will be generated.
2095      timeout: timeout in seconds
2096      retries: number of retries
2097
2098    Returns:
2099      The name of the file on the host to which the screenshot was saved.
2100
2101    Raises:
2102      CommandFailedError on failure.
2103      CommandTimeoutError on timeout.
2104      DeviceUnreachableError on missing device.
2105    """
2106    if not host_path:
2107      host_path = os.path.abspath('screenshot-%s-%s.png' % (
2108          self.adb.GetDeviceSerial(), _GetTimeStamp()))
2109    with device_temp_file.DeviceTempFile(self.adb, suffix='.png') as device_tmp:
2110      self.RunShellCommand(['/system/bin/screencap', '-p', device_tmp.name],
2111                           check_return=True)
2112      self.PullFile(device_tmp.name, host_path)
2113    return host_path
2114
2115  @decorators.WithTimeoutAndRetriesFromInstance()
2116  def GetMemoryUsageForPid(self, pid, timeout=None, retries=None):
2117    """Gets the memory usage for the given PID.
2118
2119    Args:
2120      pid: PID of the process.
2121      timeout: timeout in seconds
2122      retries: number of retries
2123
2124    Returns:
2125      A dict containing memory usage statistics for the PID. May include:
2126        Size, Rss, Pss, Shared_Clean, Shared_Dirty, Private_Clean,
2127        Private_Dirty, VmHWM
2128
2129    Raises:
2130      CommandTimeoutError on timeout.
2131    """
2132    result = collections.defaultdict(int)
2133
2134    try:
2135      result.update(self._GetMemoryUsageForPidFromSmaps(pid))
2136    except device_errors.CommandFailedError:
2137      logging.exception('Error getting memory usage from smaps')
2138
2139    try:
2140      result.update(self._GetMemoryUsageForPidFromStatus(pid))
2141    except device_errors.CommandFailedError:
2142      logging.exception('Error getting memory usage from status')
2143
2144    return result
2145
2146  @decorators.WithTimeoutAndRetriesFromInstance()
2147  def DismissCrashDialogIfNeeded(self, timeout=None, retries=None):
2148    """Dismiss the error/ANR dialog if present.
2149
2150    Returns: Name of the crashed package if a dialog is focused,
2151             None otherwise.
2152    """
2153    def _FindFocusedWindow():
2154      match = None
2155      # TODO(jbudorick): Try to grep the output on the device instead of using
2156      # large_output if/when DeviceUtils exposes a public interface for piped
2157      # shell command handling.
2158      for line in self.RunShellCommand(['dumpsys', 'window', 'windows'],
2159                                       check_return=True, large_output=True):
2160        match = re.match(_CURRENT_FOCUS_CRASH_RE, line)
2161        if match:
2162          break
2163      return match
2164
2165    match = _FindFocusedWindow()
2166    if not match:
2167      return None
2168    package = match.group(2)
2169    logging.warning('Trying to dismiss %s dialog for %s', *match.groups())
2170    self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT)
2171    self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT)
2172    self.SendKeyEvent(keyevent.KEYCODE_ENTER)
2173    match = _FindFocusedWindow()
2174    if match:
2175      logging.error('Still showing a %s dialog for %s', *match.groups())
2176    return package
2177
2178  def _GetMemoryUsageForPidFromSmaps(self, pid):
2179    SMAPS_COLUMNS = (
2180        'Size', 'Rss', 'Pss', 'Shared_Clean', 'Shared_Dirty', 'Private_Clean',
2181        'Private_Dirty')
2182
2183    showmap_out = self._RunPipedShellCommand(
2184        'showmap %d | grep TOTAL' % int(pid), as_root=True)
2185
2186    split_totals = showmap_out[-1].split()
2187    if (not split_totals
2188        or len(split_totals) != 9
2189        or split_totals[-1] != 'TOTAL'):
2190      raise device_errors.CommandFailedError(
2191          'Invalid output from showmap: %s' % '\n'.join(showmap_out))
2192
2193    return dict(itertools.izip(SMAPS_COLUMNS, (int(n) for n in split_totals)))
2194
2195  def _GetMemoryUsageForPidFromStatus(self, pid):
2196    for line in self.ReadFile(
2197        '/proc/%s/status' % str(pid), as_root=True).splitlines():
2198      if line.startswith('VmHWM:'):
2199        return {'VmHWM': int(line.split()[1])}
2200    raise device_errors.CommandFailedError(
2201        'Could not find memory peak value for pid %s', str(pid))
2202
2203  def GetLogcatMonitor(self, *args, **kwargs):
2204    """Returns a new LogcatMonitor associated with this device.
2205
2206    Parameters passed to this function are passed directly to
2207    |logcat_monitor.LogcatMonitor| and are documented there.
2208    """
2209    return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs)
2210
2211  def GetClientCache(self, client_name):
2212    """Returns client cache."""
2213    if client_name not in self._client_caches:
2214      self._client_caches[client_name] = {}
2215    return self._client_caches[client_name]
2216
2217  def _ClearCache(self):
2218    """Clears all caches."""
2219    for client in self._client_caches:
2220      self._client_caches[client].clear()
2221    self._cache = {
2222        # Map of packageId -> list of on-device .apk paths
2223        'package_apk_paths': {},
2224        # Set of packageId that were loaded from LoadCacheData and not yet
2225        # verified.
2226        'package_apk_paths_to_verify': set(),
2227        # Map of packageId -> set of on-device .apk checksums
2228        'package_apk_checksums': {},
2229        # Map of property_name -> value
2230        'getprop': {},
2231        # Map of device_path -> [ignore_other_files, map of path->checksum]
2232        'device_path_checksums': {},
2233        # Location of sdcard ($EXTERNAL_STORAGE).
2234        'external_storage': None,
2235        # Token used to detect when LoadCacheData is stale.
2236        'token': None,
2237        'prev_token': None,
2238    }
2239
2240  @decorators.WithTimeoutAndRetriesFromInstance()
2241  def LoadCacheData(self, data, timeout=None, retries=None):
2242    """Initializes the cache from data created using DumpCacheData.
2243
2244    The cache is used only if its token matches the one found on the device.
2245    This prevents a stale cache from being used (which can happen when sharing
2246    devices).
2247
2248    Args:
2249      data: A previously serialized cache (string).
2250      timeout: timeout in seconds
2251      retries: number of retries
2252
2253    Returns:
2254      Whether the cache was loaded.
2255    """
2256    obj = json.loads(data)
2257    self._EnsureCacheInitialized()
2258    given_token = obj.get('token')
2259    if not given_token or self._cache['prev_token'] != given_token:
2260      logging.warning('Stale cache detected. Not using it.')
2261      return False
2262
2263    self._cache['package_apk_paths'] = obj.get('package_apk_paths', {})
2264    # When using a cache across script invokations, verify that apps have
2265    # not been uninstalled.
2266    self._cache['package_apk_paths_to_verify'] = set(
2267        self._cache['package_apk_paths'].iterkeys())
2268
2269    package_apk_checksums = obj.get('package_apk_checksums', {})
2270    for k, v in package_apk_checksums.iteritems():
2271      package_apk_checksums[k] = set(v)
2272    self._cache['package_apk_checksums'] = package_apk_checksums
2273    device_path_checksums = obj.get('device_path_checksums', {})
2274    self._cache['device_path_checksums'] = device_path_checksums
2275    return True
2276
2277  @decorators.WithTimeoutAndRetriesFromInstance()
2278  def DumpCacheData(self, timeout=None, retries=None):
2279    """Dumps the current cache state to a string.
2280
2281    Args:
2282      timeout: timeout in seconds
2283      retries: number of retries
2284
2285    Returns:
2286      A serialized cache as a string.
2287    """
2288    self._EnsureCacheInitialized()
2289    obj = {}
2290    obj['token'] = self._cache['token']
2291    obj['package_apk_paths'] = self._cache['package_apk_paths']
2292    obj['package_apk_checksums'] = self._cache['package_apk_checksums']
2293    # JSON can't handle sets.
2294    for k, v in obj['package_apk_checksums'].iteritems():
2295      obj['package_apk_checksums'][k] = list(v)
2296    obj['device_path_checksums'] = self._cache['device_path_checksums']
2297    return json.dumps(obj, separators=(',', ':'))
2298
2299  @classmethod
2300  def parallel(cls, devices, async=False):
2301    """Creates a Parallelizer to operate over the provided list of devices.
2302
2303    Args:
2304      devices: A list of either DeviceUtils instances or objects from
2305               from which DeviceUtils instances can be constructed. If None,
2306               all attached devices will be used.
2307      async: If true, returns a Parallelizer that runs operations
2308             asynchronously.
2309
2310    Returns:
2311      A Parallelizer operating over |devices|.
2312    """
2313    devices = [d if isinstance(d, cls) else cls(d) for d in devices]
2314    if async:
2315      return parallelizer.Parallelizer(devices)
2316    else:
2317      return parallelizer.SyncParallelizer(devices)
2318
2319  @classmethod
2320  def HealthyDevices(cls, blacklist=None, device_arg='default', **kwargs):
2321    """Returns a list of DeviceUtils instances.
2322
2323    Returns a list of DeviceUtils instances that are attached, not blacklisted,
2324    and optionally filtered by --device flags or ANDROID_SERIAL environment
2325    variable.
2326
2327    Args:
2328      blacklist: A DeviceBlacklist instance (optional). Device serials in this
2329          blacklist will never be returned, but a warning will be logged if they
2330          otherwise would have been.
2331      device_arg: The value of the --device flag. This can be:
2332          'default' -> Same as [], but returns an empty list rather than raise a
2333              NoDevicesError.
2334          [] -> Returns all devices, unless $ANDROID_SERIAL is set.
2335          None -> Use $ANDROID_SERIAL if set, otherwise looks for a single
2336              attached device. Raises an exception if multiple devices are
2337              attached.
2338          'serial' -> Returns an instance for the given serial, if not
2339              blacklisted.
2340          ['A', 'B', ...] -> Returns instances for the subset that is not
2341              blacklisted.
2342      A device serial, or a list of device serials (optional).
2343
2344    Returns:
2345      A list of one or more DeviceUtils instances.
2346
2347    Raises:
2348      NoDevicesError: Raised when no non-blacklisted devices exist and
2349          device_arg is passed.
2350      MultipleDevicesError: Raise when multiple devices exist, but |device_arg|
2351          is None.
2352    """
2353    allow_no_devices = False
2354    if device_arg == 'default':
2355      allow_no_devices = True
2356      device_arg = ()
2357
2358    select_multiple = True
2359    if not (isinstance(device_arg, tuple) or isinstance(device_arg, list)):
2360      select_multiple = False
2361      if device_arg:
2362        device_arg = (device_arg,)
2363
2364    blacklisted_devices = blacklist.Read() if blacklist else []
2365
2366    # adb looks for ANDROID_SERIAL, so support it as well.
2367    android_serial = os.environ.get('ANDROID_SERIAL')
2368    if not device_arg and android_serial:
2369      device_arg = (android_serial,)
2370
2371    def blacklisted(serial):
2372      if serial in blacklisted_devices:
2373        logging.warning('Device %s is blacklisted.', serial)
2374        return True
2375      return False
2376
2377    if device_arg:
2378      devices = [cls(x, **kwargs) for x in device_arg if not blacklisted(x)]
2379    else:
2380      devices = []
2381      for adb in adb_wrapper.AdbWrapper.Devices():
2382        if not blacklisted(adb.GetDeviceSerial()):
2383          devices.append(cls(_CreateAdbWrapper(adb), **kwargs))
2384
2385    if len(devices) == 0 and not allow_no_devices:
2386      raise device_errors.NoDevicesError()
2387    if len(devices) > 1 and not select_multiple:
2388      raise device_errors.MultipleDevicesError(devices)
2389    return sorted(devices)
2390
2391  @decorators.WithTimeoutAndRetriesFromInstance()
2392  def RestartAdbd(self, timeout=None, retries=None):
2393    logging.info('Restarting adbd on device.')
2394    with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script:
2395      self.WriteFile(script.name, _RESTART_ADBD_SCRIPT)
2396      self.RunShellCommand(['source', script.name], as_root=True)
2397      self.adb.WaitForDevice()
2398
2399  @decorators.WithTimeoutAndRetriesFromInstance()
2400  def GrantPermissions(self, package, permissions, timeout=None, retries=None):
2401    # Permissions only need to be set on M and above because of the changes to
2402    # the permission model.
2403    if not permissions or self.build_version_sdk < version_codes.MARSHMALLOW:
2404      return
2405    logging.info('Setting permissions for %s.', package)
2406    permissions = [p for p in permissions if p not in _PERMISSIONS_BLACKLIST]
2407    if ('android.permission.WRITE_EXTERNAL_STORAGE' in permissions
2408        and 'android.permission.READ_EXTERNAL_STORAGE' not in permissions):
2409      permissions.append('android.permission.READ_EXTERNAL_STORAGE')
2410    cmd = '&&'.join('pm grant %s %s' % (package, p) for p in permissions)
2411    if cmd:
2412      output = self.RunShellCommand(cmd, check_return=True)
2413      if output:
2414        logging.warning('Possible problem when granting permissions. Blacklist '
2415                        'may need to be updated.')
2416        for line in output:
2417          logging.warning('  %s', line)
2418
2419  @decorators.WithTimeoutAndRetriesFromInstance()
2420  def IsScreenOn(self, timeout=None, retries=None):
2421    """Determines if screen is on.
2422
2423    Dumpsys input_method exposes screen on/off state. Below is an explination of
2424    the states.
2425
2426    Pre-L:
2427      On: mScreenOn=true
2428      Off: mScreenOn=false
2429    L+:
2430      On: mInteractive=true
2431      Off: mInteractive=false
2432
2433    Returns:
2434      True if screen is on, false if it is off.
2435
2436    Raises:
2437      device_errors.CommandFailedError: If screen state cannot be found.
2438    """
2439    if self.build_version_sdk < version_codes.LOLLIPOP:
2440      input_check = 'mScreenOn'
2441      check_value = 'mScreenOn=true'
2442    else:
2443      input_check = 'mInteractive'
2444      check_value = 'mInteractive=true'
2445    dumpsys_out = self._RunPipedShellCommand(
2446        'dumpsys input_method | grep %s' % input_check)
2447    if not dumpsys_out:
2448      raise device_errors.CommandFailedError(
2449          'Unable to detect screen state', str(self))
2450    return check_value in dumpsys_out[0]
2451
2452  @decorators.WithTimeoutAndRetriesFromInstance()
2453  def SetScreen(self, on, timeout=None, retries=None):
2454    """Turns screen on and off.
2455
2456    Args:
2457      on: bool to decide state to switch to. True = on False = off.
2458    """
2459    def screen_test():
2460      return self.IsScreenOn() == on
2461
2462    if screen_test():
2463      logging.info('Screen already in expected state.')
2464      return
2465    self.RunShellCommand('input keyevent 26')
2466    timeout_retry.WaitFor(screen_test, wait_period=1)
2467