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