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=W0613
10
11import time
12
13import pylib.android_commands
14from pylib.device import adb_wrapper
15from pylib.device import decorators
16from pylib.device import device_errors
17from pylib.utils import apk_helper
18from pylib.utils import parallelizer
19
20_DEFAULT_TIMEOUT = 30
21_DEFAULT_RETRIES = 3
22
23
24@decorators.WithExplicitTimeoutAndRetries(
25    _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
26def GetAVDs():
27  """Returns a list of Android Virtual Devices.
28
29  Returns:
30    A list containing the configured AVDs.
31  """
32  return pylib.android_commands.GetAVDs()
33
34
35@decorators.WithExplicitTimeoutAndRetries(
36    _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
37def RestartServer():
38  """Restarts the adb server.
39
40  Raises:
41    CommandFailedError if we fail to kill or restart the server.
42  """
43  pylib.android_commands.AndroidCommands().RestartAdbServer()
44
45
46class DeviceUtils(object):
47
48  def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT,
49               default_retries=_DEFAULT_RETRIES):
50    """DeviceUtils constructor.
51
52    Args:
53      device: Either a device serial, an existing AdbWrapper instance, an
54              an existing AndroidCommands instance, or nothing.
55      default_timeout: An integer containing the default number of seconds to
56                       wait for an operation to complete if no explicit value
57                       is provided.
58      default_retries: An integer containing the default number or times an
59                       operation should be retried on failure if no explicit
60                       value is provided.
61    """
62    self.old_interface = None
63    if isinstance(device, basestring):
64      self.old_interface = pylib.android_commands.AndroidCommands(device)
65    elif isinstance(device, adb_wrapper.AdbWrapper):
66      self.old_interface = pylib.android_commands.AndroidCommands(str(device))
67    elif isinstance(device, pylib.android_commands.AndroidCommands):
68      self.old_interface = device
69    elif not device:
70      self.old_interface = pylib.android_commands.AndroidCommands()
71    else:
72      raise ValueError('Unsupported type passed for argument "device"')
73    self._default_timeout = default_timeout
74    self._default_retries = default_retries
75    assert(hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR))
76    assert(hasattr(self, decorators.DEFAULT_RETRIES_ATTR))
77
78  @decorators.WithTimeoutAndRetriesFromInstance()
79  def IsOnline(self, timeout=None, retries=None):
80    """Checks whether the device is online.
81
82    Args:
83      timeout: An integer containing the number of seconds to wait for the
84               operation to complete.
85      retries: An integer containing the number of times the operation should
86               be retried if it fails.
87    Returns:
88      True if the device is online, False otherwise.
89    """
90    return self.old_interface.IsOnline()
91
92  @decorators.WithTimeoutAndRetriesFromInstance()
93  def HasRoot(self, timeout=None, retries=None):
94    """Checks whether or not adbd has root privileges.
95
96    Args:
97      timeout: Same as for |IsOnline|.
98      retries: Same as for |IsOnline|.
99    Returns:
100      True if adbd has root privileges, False otherwise.
101    """
102    return self._HasRootImpl()
103
104  def _HasRootImpl(self):
105    """ Implementation of HasRoot.
106
107    This is split from HasRoot to allow other DeviceUtils methods to call
108    HasRoot without spawning a new timeout thread.
109
110    Returns:
111      Same as for |HasRoot|.
112    """
113    return self.old_interface.IsRootEnabled()
114
115  @decorators.WithTimeoutAndRetriesFromInstance()
116  def EnableRoot(self, timeout=None, retries=None):
117    """Restarts adbd with root privileges.
118
119    Args:
120      timeout: Same as for |IsOnline|.
121      retries: Same as for |IsOnline|.
122    Raises:
123      CommandFailedError if root could not be enabled.
124    """
125    if not self.old_interface.EnableAdbRoot():
126      raise device_errors.CommandFailedError(
127          ['adb', 'root'], 'Could not enable root.')
128
129  @decorators.WithTimeoutAndRetriesFromInstance()
130  def GetExternalStoragePath(self, timeout=None, retries=None):
131    """Get the device's path to its SD card.
132
133    Args:
134      timeout: Same as for |IsOnline|.
135      retries: Same as for |IsOnline|.
136    Returns:
137      The device's path to its SD card.
138    """
139    try:
140      return self.old_interface.GetExternalStorage()
141    except AssertionError as e:
142      raise device_errors.CommandFailedError(
143          ['adb', 'shell', 'echo', '$EXTERNAL_STORAGE'], str(e))
144
145  @decorators.WithTimeoutAndRetriesFromInstance()
146  def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None):
147    """Wait for the device to fully boot.
148
149    This means waiting for the device to boot, the package manager to be
150    available, and the SD card to be ready. It can optionally mean waiting
151    for wifi to come up, too.
152
153    Args:
154      wifi: A boolean indicating if we should wait for wifi to come up or not.
155      timeout: Same as for |IsOnline|.
156      retries: Same as for |IsOnline|.
157    Raises:
158      CommandTimeoutError if one of the component waits times out.
159      DeviceUnreachableError if the device becomes unresponsive.
160    """
161    self._WaitUntilFullyBootedImpl(wifi=wifi, timeout=timeout)
162
163  def _WaitUntilFullyBootedImpl(self, wifi=False, timeout=None):
164    """ Implementation of WaitUntilFullyBooted.
165
166    This is split from WaitUntilFullyBooted to allow other DeviceUtils methods
167    to call WaitUntilFullyBooted without spawning a new timeout thread.
168
169    TODO(jbudorick) Remove the timeout parameter once this is no longer
170    implemented via AndroidCommands.
171
172    Args:
173      wifi: Same as for |WaitUntilFullyBooted|.
174      timeout: Same as for |IsOnline|.
175    Raises:
176      Same as for |WaitUntilFullyBooted|.
177    """
178    if timeout is None:
179      timeout = self._default_timeout
180    self.old_interface.WaitForSystemBootCompleted(timeout)
181    self.old_interface.WaitForDevicePm()
182    self.old_interface.WaitForSdCardReady(timeout)
183    if wifi:
184      while not 'Wi-Fi is enabled' in (
185          self._RunShellCommandImpl('dumpsys wifi')):
186        time.sleep(0.1)
187
188  @decorators.WithTimeoutAndRetriesDefaults(
189      10 * _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
190  def Reboot(self, block=True, timeout=None, retries=None):
191    """Reboot the device.
192
193    Args:
194      block: A boolean indicating if we should wait for the reboot to complete.
195      timeout: Same as for |IsOnline|.
196      retries: Same as for |IsOnline|.
197    """
198    self.old_interface.Reboot()
199    if block:
200      self._WaitUntilFullyBootedImpl(timeout=timeout)
201
202  @decorators.WithTimeoutAndRetriesDefaults(
203      4 * _DEFAULT_TIMEOUT, _DEFAULT_RETRIES)
204  def Install(self, apk_path, reinstall=False, timeout=None, retries=None):
205    """Install an APK.
206
207    Noop if an identical APK is already installed.
208
209    Args:
210      apk_path: A string containing the path to the APK to install.
211      reinstall: A boolean indicating if we should keep any existing app data.
212      timeout: Same as for |IsOnline|.
213      retries: Same as for |IsOnline|.
214    Raises:
215      CommandFailedError if the installation fails.
216      CommandTimeoutError if the installation times out.
217    """
218    package_name = apk_helper.GetPackageName(apk_path)
219    device_path = self.old_interface.GetApplicationPath(package_name)
220    if device_path is not None:
221      files_changed = self.old_interface.GetFilesChanged(
222          apk_path, device_path, ignore_filenames=True)
223      if len(files_changed) > 0:
224        should_install = True
225        if not reinstall:
226          out = self.old_interface.Uninstall(package_name)
227          for line in out.splitlines():
228            if 'Failure' in line:
229              raise device_errors.CommandFailedError(
230                  ['adb', 'uninstall', package_name], line.strip())
231      else:
232        should_install = False
233    else:
234      should_install = True
235    if should_install:
236      try:
237        out = self.old_interface.Install(apk_path, reinstall=reinstall)
238        for line in out.splitlines():
239          if 'Failure' in line:
240            raise device_errors.CommandFailedError(
241                ['adb', 'install', apk_path], line.strip())
242      except AssertionError as e:
243        raise device_errors.CommandFailedError(
244            ['adb', 'install', apk_path], str(e))
245
246  @decorators.WithTimeoutAndRetriesFromInstance()
247  def RunShellCommand(self, cmd, check_return=False, root=False, timeout=None,
248                      retries=None):
249    """Run an ADB shell command.
250
251    TODO(jbudorick) Switch the default value of check_return to True after
252    AndroidCommands is gone.
253
254    Args:
255      cmd: A list containing the command to run on the device and any arguments.
256      check_return: A boolean indicating whether or not the return code should
257                    be checked.
258      timeout: Same as for |IsOnline|.
259      retries: Same as for |IsOnline|.
260    Raises:
261      CommandFailedError if check_return is True and the return code is nozero.
262    Returns:
263      The output of the command.
264    """
265    return self._RunShellCommandImpl(cmd, check_return=check_return, root=root,
266                                     timeout=timeout)
267
268  def _RunShellCommandImpl(self, cmd, check_return=False, root=False,
269                           timeout=None):
270    """Implementation of RunShellCommand.
271
272    This is split from RunShellCommand to allow other DeviceUtils methods to
273    call RunShellCommand without spawning a new timeout thread.
274
275    TODO(jbudorick) Remove the timeout parameter once this is no longer
276    implemented via AndroidCommands.
277
278    Args:
279      cmd: Same as for |RunShellCommand|.
280      check_return: Same as for |RunShellCommand|.
281      timeout: Same as for |IsOnline|.
282    Raises:
283      Same as for |RunShellCommand|.
284    Returns:
285      Same as for |RunShellCommand|.
286    """
287    if isinstance(cmd, list):
288      cmd = ' '.join(cmd)
289    if root and not self.HasRoot():
290      cmd = 'su -c %s' % cmd
291    if check_return:
292      code, output = self.old_interface.GetShellCommandStatusAndOutput(
293          cmd, timeout_time=timeout)
294      if int(code) != 0:
295        raise device_errors.CommandFailedError(
296            cmd, 'Nonzero exit code (%d)' % code)
297    else:
298      output = self.old_interface.RunShellCommand(cmd, timeout_time=timeout)
299    return output
300
301  def __str__(self):
302    """Returns the device serial."""
303    return self.old_interface.GetDevice()
304
305  @staticmethod
306  def parallel(devices=None, async=False):
307    """Creates a Parallelizer to operate over the provided list of devices.
308
309    If |devices| is either |None| or an empty list, the Parallelizer will
310    operate over all attached devices.
311
312    Args:
313      devices: A list of either DeviceUtils instances or objects from
314               from which DeviceUtils instances can be constructed. If None,
315               all attached devices will be used.
316      async: If true, returns a Parallelizer that runs operations
317             asynchronously.
318    Returns:
319      A Parallelizer operating over |devices|.
320    """
321    if not devices or len(devices) == 0:
322      devices = pylib.android_commands.GetAttachedDevices()
323    parallelizer_type = (parallelizer.Parallelizer if async
324                         else parallelizer.SyncParallelizer)
325    return parallelizer_type([
326        d if isinstance(d, DeviceUtils) else DeviceUtils(d)
327        for d in devices])
328
329