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