• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 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"""This module wraps Android's adb tool.
6
7This is a thin wrapper around the adb interface. Any additional complexity
8should be delegated to a higher level (ex. DeviceUtils).
9"""
10
11import errno
12import logging
13import os
14
15from pylib import cmd_helper
16
17from pylib.utils import reraiser_thread
18from pylib.utils import timeout_retry
19
20_DEFAULT_TIMEOUT = 30
21_DEFAULT_RETRIES = 2
22
23
24class BaseError(Exception):
25  """Base exception for all device and command errors."""
26  pass
27
28
29class CommandFailedError(BaseError):
30  """Exception for command failures."""
31
32  def __init__(self, cmd, msg, device=None):
33    super(CommandFailedError, self).__init__(
34        (('device %s: ' % device) if device else '') +
35        'adb command \'%s\' failed with message: \'%s\'' % (' '.join(cmd), msg))
36
37
38class CommandTimeoutError(BaseError):
39  """Exception for command timeouts."""
40  pass
41
42
43def _VerifyLocalFileExists(path):
44  """Verifies a local file exists.
45
46  Args:
47    path: Path to the local file.
48
49  Raises:
50    IOError: If the file doesn't exist.
51  """
52  if not os.path.exists(path):
53    raise IOError(errno.ENOENT, os.strerror(errno.ENOENT), path)
54
55
56class AdbWrapper(object):
57  """A wrapper around a local Android Debug Bridge executable."""
58
59  def __init__(self, device_serial):
60    """Initializes the AdbWrapper.
61
62    Args:
63      device_serial: The device serial number as a string.
64    """
65    self._device_serial = str(device_serial)
66
67  @classmethod
68  def _AdbCmd(cls, arg_list, timeout, retries, check_error=True):
69    """Runs an adb command with a timeout and retries.
70
71    Args:
72      arg_list: A list of arguments to adb.
73      timeout: Timeout in seconds.
74      retries: Number of retries.
75      check_error: Check that the command doesn't return an error message. This
76        does NOT check the return code of shell commands.
77
78    Returns:
79      The output of the command.
80    """
81    cmd = ['adb'] + arg_list
82
83    # This method runs inside the timeout/retries.
84    def RunCmd():
85      exit_code, output = cmd_helper.GetCmdStatusAndOutput(cmd)
86      if exit_code != 0:
87        raise CommandFailedError(
88            cmd, 'returned non-zero exit code %s, output: %s' %
89            (exit_code, output))
90      # This catches some errors, including when the device drops offline;
91      # unfortunately adb is very inconsistent with error reporting so many
92      # command failures present differently.
93      if check_error and output[:len('error:')] == 'error:':
94        raise CommandFailedError(arg_list, output)
95      return output
96
97    try:
98      return timeout_retry.Run(RunCmd, timeout, retries)
99    except reraiser_thread.TimeoutError as e:
100      raise CommandTimeoutError(str(e))
101
102  def _DeviceAdbCmd(self, arg_list, timeout, retries, check_error=True):
103    """Runs an adb command on the device associated with this object.
104
105    Args:
106      arg_list: A list of arguments to adb.
107      timeout: Timeout in seconds.
108      retries: Number of retries.
109      check_error: Check that the command doesn't return an error message. This
110        does NOT check the return code of shell commands.
111
112    Returns:
113      The output of the command.
114    """
115    return self._AdbCmd(
116        ['-s', self._device_serial] + arg_list, timeout, retries,
117        check_error=check_error)
118
119  def __eq__(self, other):
120    """Consider instances equal if they refer to the same device.
121
122    Args:
123      other: The instance to compare equality with.
124
125    Returns:
126      True if the instances are considered equal, false otherwise.
127    """
128    return self._device_serial == str(other)
129
130  def __str__(self):
131    """The string representation of an instance.
132
133    Returns:
134      The device serial number as a string.
135    """
136    return self._device_serial
137
138  def __repr__(self):
139    return '%s(\'%s\')' % (self.__class__.__name__, self)
140
141  # TODO(craigdh): Determine the filter criteria that should be supported.
142  @classmethod
143  def GetDevices(cls, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
144    """Get the list of active attached devices.
145
146    Args:
147      timeout: (optional) Timeout per try in seconds.
148      retries: (optional) Number of retries to attempt.
149
150    Yields:
151      AdbWrapper instances.
152    """
153    output = cls._AdbCmd(['devices'], timeout, retries)
154    lines = [line.split() for line in output.split('\n')]
155    return [AdbWrapper(line[0]) for line in lines
156            if len(line) == 2 and line[1] == 'device']
157
158  def GetDeviceSerial(self):
159    """Gets the device serial number associated with this object.
160
161    Returns:
162      Device serial number as a string.
163    """
164    return self._device_serial
165
166  def Push(self, local, remote, timeout=60*5, retries=_DEFAULT_RETRIES):
167    """Pushes a file from the host to the device.
168
169    Args:
170      local: Path on the host filesystem.
171      remote: Path on the device filesystem.
172      timeout: (optional) Timeout per try in seconds.
173      retries: (optional) Number of retries to attempt.
174    """
175    _VerifyLocalFileExists(local)
176    self._DeviceAdbCmd(['push', local, remote], timeout, retries)
177
178  def Pull(self, remote, local, timeout=60*5, retries=_DEFAULT_RETRIES):
179    """Pulls a file from the device to the host.
180
181    Args:
182      remote: Path on the device filesystem.
183      local: Path on the host filesystem.
184      timeout: (optional) Timeout per try in seconds.
185      retries: (optional) Number of retries to attempt.
186    """
187    self._DeviceAdbCmd(['pull', remote, local], timeout, retries)
188    _VerifyLocalFileExists(local)
189
190  def Shell(self, command, expect_rc=None, timeout=_DEFAULT_TIMEOUT,
191            retries=_DEFAULT_RETRIES):
192    """Runs a shell command on the device.
193
194    Args:
195      command: The shell command to run.
196      expect_rc: (optional) If set checks that the command's return code matches
197        this value.
198      timeout: (optional) Timeout per try in seconds.
199      retries: (optional) Number of retries to attempt.
200
201    Returns:
202      The output of the shell command as a string.
203
204    Raises:
205      CommandFailedError: If the return code doesn't match |expect_rc|.
206    """
207    if expect_rc is None:
208      actual_command = command
209    else:
210      actual_command = '%s; echo $?;' % command
211    output = self._DeviceAdbCmd(
212        ['shell', actual_command], timeout, retries, check_error=False)
213    if expect_rc is not None:
214      output_end = output.rstrip().rfind('\n') + 1
215      rc = output[output_end:].strip()
216      output = output[:output_end]
217      if int(rc) != expect_rc:
218        raise CommandFailedError(
219            ['shell', command],
220            'shell command exited with code: %s' % rc,
221            self._device_serial)
222    return output
223
224  def Logcat(self, filter_spec=None, timeout=_DEFAULT_TIMEOUT,
225             retries=_DEFAULT_RETRIES):
226    """Get the logcat output.
227
228    Args:
229      filter_spec: (optional) Spec to filter the logcat.
230      timeout: (optional) Timeout per try in seconds.
231      retries: (optional) Number of retries to attempt.
232
233    Returns:
234      logcat output as a string.
235    """
236    cmd = ['logcat']
237    if filter_spec is not None:
238      cmd.append(filter_spec)
239    return self._DeviceAdbCmd(cmd, timeout, retries, check_error=False)
240
241  def Forward(self, local, remote, timeout=_DEFAULT_TIMEOUT,
242              retries=_DEFAULT_RETRIES):
243    """Forward socket connections from the local socket to the remote socket.
244
245    Sockets are specified by one of:
246      tcp:<port>
247      localabstract:<unix domain socket name>
248      localreserved:<unix domain socket name>
249      localfilesystem:<unix domain socket name>
250      dev:<character device name>
251      jdwp:<process pid> (remote only)
252
253    Args:
254      local: The host socket.
255      remote: The device socket.
256      timeout: (optional) Timeout per try in seconds.
257      retries: (optional) Number of retries to attempt.
258    """
259    self._DeviceAdbCmd(['forward', str(local), str(remote)], timeout, retries)
260
261  def JDWP(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
262    """List of PIDs of processes hosting a JDWP transport.
263
264    Args:
265      timeout: (optional) Timeout per try in seconds.
266      retries: (optional) Number of retries to attempt.
267
268    Returns:
269      A list of PIDs as strings.
270    """
271    return [a.strip() for a in
272            self._DeviceAdbCmd(['jdwp'], timeout, retries).split('\n')]
273
274  def Install(self, apk_path, forward_lock=False, reinstall=False,
275              sd_card=False, timeout=60*2, retries=_DEFAULT_RETRIES):
276    """Install an apk on the device.
277
278    Args:
279      apk_path: Host path to the APK file.
280      forward_lock: (optional) If set forward-locks the app.
281      reinstall: (optional) If set reinstalls the app, keeping its data.
282      sd_card: (optional) If set installs on the SD card.
283      timeout: (optional) Timeout per try in seconds.
284      retries: (optional) Number of retries to attempt.
285    """
286    _VerifyLocalFileExists(apk_path)
287    cmd = ['install']
288    if forward_lock:
289      cmd.append('-l')
290    if reinstall:
291      cmd.append('-r')
292    if sd_card:
293      cmd.append('-s')
294    cmd.append(apk_path)
295    output = self._DeviceAdbCmd(cmd, timeout, retries)
296    if 'Success' not in output:
297      raise CommandFailedError(cmd, output)
298
299  def Uninstall(self, package, keep_data=False, timeout=_DEFAULT_TIMEOUT,
300                retries=_DEFAULT_RETRIES):
301    """Remove the app |package| from the device.
302
303    Args:
304      package: The package to uninstall.
305      keep_data: (optional) If set keep the data and cache directories.
306      timeout: (optional) Timeout per try in seconds.
307      retries: (optional) Number of retries to attempt.
308    """
309    cmd = ['uninstall']
310    if keep_data:
311      cmd.append('-k')
312    cmd.append(package)
313    output = self._DeviceAdbCmd(cmd, timeout, retries)
314    if 'Failure' in output:
315      raise CommandFailedError(cmd, output)
316
317  def Backup(self, path, packages=None, apk=False, shared=False,
318             nosystem=True, include_all=False, timeout=_DEFAULT_TIMEOUT,
319             retries=_DEFAULT_RETRIES):
320    """Write an archive of the device's data to |path|.
321
322    Args:
323      path: Local path to store the backup file.
324      packages: List of to packages to be backed up.
325      apk: (optional) If set include the .apk files in the archive.
326      shared: (optional) If set buckup the device's SD card.
327      nosystem: (optional) If set exclude system applications.
328      include_all: (optional) If set back up all installed applications and
329        |packages| is optional.
330      timeout: (optional) Timeout per try in seconds.
331      retries: (optional) Number of retries to attempt.
332    """
333    cmd = ['backup', path]
334    if apk:
335      cmd.append('-apk')
336    if shared:
337      cmd.append('-shared')
338    if nosystem:
339      cmd.append('-nosystem')
340    if include_all:
341      cmd.append('-all')
342    if packages:
343      cmd.extend(packages)
344    assert bool(packages) ^ bool(include_all), (
345        'Provide \'packages\' or set \'include_all\' but not both.')
346    ret = self._DeviceAdbCmd(cmd, timeout, retries)
347    _VerifyLocalFileExists(path)
348    return ret
349
350  def Restore(self, path, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
351    """Restore device contents from the backup archive.
352
353    Args:
354      path: Host path to the backup archive.
355      timeout: (optional) Timeout per try in seconds.
356      retries: (optional) Number of retries to attempt.
357    """
358    _VerifyLocalFileExists(path)
359    self._DeviceAdbCmd(['restore'] + [path], timeout, retries)
360
361  def WaitForDevice(self, timeout=60*5, retries=_DEFAULT_RETRIES):
362    """Block until the device is online.
363
364    Args:
365      timeout: (optional) Timeout per try in seconds.
366      retries: (optional) Number of retries to attempt.
367    """
368    self._DeviceAdbCmd(['wait-for-device'], timeout, retries)
369
370  def GetState(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
371    """Get device state.
372
373    Args:
374      timeout: (optional) Timeout per try in seconds.
375      retries: (optional) Number of retries to attempt.
376
377    Returns:
378      One of 'offline', 'bootloader', or 'device'.
379    """
380    return self._DeviceAdbCmd(['get-state'], timeout, retries).strip()
381
382  def GetDevPath(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
383    """Gets the device path.
384
385    Args:
386      timeout: (optional) Timeout per try in seconds.
387      retries: (optional) Number of retries to attempt.
388
389    Returns:
390      The device path (e.g. usb:3-4)
391    """
392    return self._DeviceAdbCmd(['get-devpath'], timeout, retries)
393
394  def Remount(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
395    """Remounts the /system partition on the device read-write."""
396    self._DeviceAdbCmd(['remount'], timeout, retries)
397
398  def Reboot(self, to_bootloader=False, timeout=60*5,
399             retries=_DEFAULT_RETRIES):
400    """Reboots the device.
401
402    Args:
403      to_bootloader: (optional) If set reboots to the bootloader.
404      timeout: (optional) Timeout per try in seconds.
405      retries: (optional) Number of retries to attempt.
406    """
407    if to_bootloader:
408      cmd = ['reboot-bootloader']
409    else:
410      cmd = ['reboot']
411    self._DeviceAdbCmd(cmd, timeout, retries)
412
413  def Root(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES):
414    """Restarts the adbd daemon with root permissions, if possible.
415
416    Args:
417      timeout: (optional) Timeout per try in seconds.
418      retries: (optional) Number of retries to attempt.
419    """
420    output = self._DeviceAdbCmd(['root'], timeout, retries)
421    if 'cannot' in output:
422      raise CommandFailedError(['root'], output)
423
424