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