Chromium Code Reviews| Index: build/android/pylib/device/adb_wrapper.py |
| diff --git a/build/android/pylib/device/adb_wrapper.py b/build/android/pylib/device/adb_wrapper.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..47c339487b309f1c8851b3a53c2559af742956b3 |
| --- /dev/null |
| +++ b/build/android/pylib/device/adb_wrapper.py |
| @@ -0,0 +1,369 @@ |
| +# Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +"""Module wrapping Android's Adb tool.""" |
|
frankf
2013/11/20 19:28:39
"adb"
craigdh
2013/11/21 00:35:48
Done.
|
| + |
| +from pylib import cmd_helper |
| + |
| +from pylib.utils import reraiser_thread |
| +from pylib.utils import timeout_retry |
| + |
| +_DEFAULT_TIMEOUT = 30 |
|
frankf
2013/11/20 19:28:39
add comments for each global constant
|
| +_DEFAULT_RETRIES = 2 |
| + |
| + |
| +class BaseError(Exception): |
| + pass |
| + |
| + |
| +class CommandFailedError(BaseError): |
| + """Exception for command failures.""" |
| + def __init__(self, cmd, msg, device=None): |
| + super(CommandFailedError, self).__init__( |
| + (('%s: ' % device) if device else '') + |
| + 'adb command \'%s\' failed with message: %s' % (cmd, msg)) |
|
frankf
2013/11/20 19:28:39
add quotation marks around msg
craigdh
2013/11/21 00:35:48
Done.
|
| + |
| + |
| +class CommandTimeoutError(BaseError): |
|
frankf
2013/11/20 19:28:39
docstring, run pylint
craigdh
2013/11/21 00:35:48
Done.
|
| + pass |
| + |
| + |
| +class AdbWrapper(object): |
| + """A wrapper around a local Android Debug Bridge executable.""" |
| + |
| + def __init__(self, device_serial): |
| + """Initializes the AdbWrapper. |
| + |
| + Args: |
| + device_serial: The device serial number as a string. |
| + """ |
| + self._device_serial = str(device_serial) |
|
frankf
2013/11/20 19:28:39
hmm, why wouldn't this be a str?
craigdh
2013/11/21 00:35:48
To be pythonic and support ducktyping with anythin
|
| + |
| + @classmethod |
| + def _AdbCmd(cls, arg_list, timeout, retries, check_error=True): |
| + """Runs an adb command with a timeout and retries. |
| + |
| + Args: |
| + arg_list: a list of arguments to adb. |
| + timeout: timeout in seconds. |
| + retries: number of retries. |
| + check_error: check that the command doesn't return an error message. |
|
frankf
2013/11/20 19:28:39
Expand this comment to state that this doesn't wor
craigdh
2013/11/21 00:35:48
Clarified.
|
| + |
| + Returns: |
| + The output of the command. |
| + """ |
| + cmd = ['adb'] + list(arg_list) |
|
frankf
2013/11/20 19:28:39
same here, do you mean to assert it's a list? If s
craigdh
2013/11/21 00:35:48
Not, list concatenation only works for lists but I
|
| + |
| + # This method runs inside the timeout/retries. |
| + def RunCmd(): |
| + exit_code, output = cmd_helper.GetCmdStatusAndOutput(cmd) |
| + if exit_code != 0: |
| + raise CommandFailedError( |
| + 'Command \'%s\' failed with code %d' % (' '.join(cmd), exit_code)) |
| + if check_error and output[:len('error:')] == 'error:': |
| + raise CommandFailedError(arg_list, output) |
| + return output |
| + |
| + try: |
| + return timeout_retry.Run(RunCmd, timeout, retries) |
| + except reraiser_thread.TimeoutError as e: |
| + raise CommandTimeoutError(str(e)) |
| + |
| + def _DeviceAdbCmd(self, arg_list, timeout, retries, check_error=True): |
| + """Runs an adb command on the device associated with this object. |
| + |
| + Args: |
| + arg_list: a list of arguments to adb. |
| + timeout: timeout in seconds. |
| + retries: number of retries. |
| + check_error: check that the command doesn't return an error message. |
| + |
| + Returns: |
| + The output of the command. |
| + """ |
| + return self._AdbCmd( |
| + ['-s', self._device_serial] + list(arg_list), timeout, retries, |
| + check_error=check_error) |
| + |
| + def __eq__(self, other): |
| + """Consider instances equal if they refer to the same device. |
| + |
| + Args: |
| + other: the instance to compare equality with. |
| + |
| + Returns: |
| + True if the instances are considered equal, false otherwise. |
| + """ |
| + return self._device_serial == str(other) |
| + |
| + def __str__(self): |
| + """The string representation of an instance. |
| + |
| + Returns: |
| + The device serial number as a string. |
| + """ |
| + return self._device_serial |
| + |
| + def __repr__(self): |
| + return '%s(\'%s\')' % (self.__class__.__name__, str(self)) |
| + |
| + # TODO(craigdh): Determine the filter criteria that should be supported. |
| + @classmethod |
| + def GetDevices(cls, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
|
frankf
2013/11/20 19:28:39
Take a look at current adb_commands. You want to g
craigdh
2013/11/21 00:35:48
Yes, that falls under the TODO.
|
| + """Get the list of active attached devices. |
| + |
| + Args: |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + |
| + Yields: |
| + AdbWrapper instances. |
| + """ |
| + output = cls._AdbCmd(['devices'], timeout, retries) |
| + for line in output.split('\n'): |
| + fields = line.split() |
| + if len(fields) == 2 and fields[1] == 'device': |
| + yield AdbWrapper(fields[0]) |
| + |
| + def GetDeviceSerial(self): |
| + """Gets the device serial number associated with this object. |
| + |
| + Returns: |
| + Device serial number as a string. |
| + """ |
| + return self._device_serial |
| + |
| + def Push(self, local, remote, timeout=_DEFAULT_TIMEOUT, |
| + retries=_DEFAULT_RETRIES): |
| + """Pushes a file from the host to the device. |
| + |
| + Args: |
| + local: path on the host filesystem. |
| + remote: path on the device filesystem. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + self._DeviceAdbCmd(['push', local, remote], timeout, retries) |
|
frankf
2013/11/20 19:28:39
YOu need check preconditions for these operations
craigdh
2013/11/21 00:35:48
Done.
craigdh
2013/11/21 00:35:48
Done.
|
| + |
| + def Pull(self, remote, local, timeout=_DEFAULT_TIMEOUT, |
| + retries=_DEFAULT_RETRIES): |
| + """Pulls a file from the device to the host. |
| + |
| + Args: |
| + remote: path on the device filesystem. |
| + local: path on the host filesystem. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + self._DeviceAdbCmd(['pull', local, remote], timeout, retries) |
| + |
| + def Shell(self, command, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
|
frankf
2013/11/20 19:28:39
how about returning the rc in the shell?
craigdh
2013/11/21 00:35:48
Done.
|
| + """Runs a shell command on the device. |
| + |
| + Args: |
| + command: the shell command to run. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + |
| + Returns: |
| + The output of the shell command as a string. |
| + """ |
| + return self._DeviceAdbCmd( |
| + ['shell', command], timeout, retries, check_error=False) |
| + |
| + def Logcat(self, filter_spec=None, timeout=_DEFAULT_TIMEOUT, |
| + retries=_DEFAULT_RETRIES): |
| + """Get the logcat output. |
| + |
| + Args: |
| + filter_spec: optional spec to filter the logcat. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + |
| + Returns: |
| + logcat output as a string. |
| + """ |
| + cmd = ['logcat'] |
| + if filter_spec is not None: |
| + cmd.append(filter_spec) |
| + return self._DeviceAdbCmd(cmd, timeout, retries, check_error=False) |
| + |
| + def Forward(self, local, remote, timeout=_DEFAULT_TIMEOUT, |
| + retries=_DEFAULT_RETRIES): |
| + """Forward socket connections from the local socket to the remote socket. |
| + |
| + Sockets are specified by one of: |
| + tcp:<port> |
| + localabstract:<unix domain socket name> |
| + localreserved:<unix domain socket name> |
| + localfilesystem:<unix domain socket name> |
| + dev:<character device name> |
| + jdwp:<process pid> (remote only) |
| + |
| + Args: |
| + local: the host socket. |
| + remote: the device socket. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + self._DeviceAdbCmd(['forward', local, remote], timeout, retries) |
| + |
| + def JDWP(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
| + """List of PIDs of processes hosting a JDWP transport. |
| + |
| + Args: |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + |
| + Returns: |
| + A list of PIDs as strings. |
| + """ |
| + return [a.strip() for a in |
| + self._DeviceAdbCmd(['jdwp'], timeout, retries).split('\n')] |
| + |
| + def Install(self, apk_path, forward_lock=False, reinstall=False, |
| + sd_card=False, timeout=_DEFAULT_TIMEOUT, |
| + retries=_DEFAULT_RETRIES): |
| + """Push a package file to the device and install it. |
| + |
| + Args: |
| + apk_path: host path to the APK file. |
| + forward_lock: optional, if set forward-locks the app. |
| + reinstall: optional, if set reinstalls the app, keeping its data. |
| + sd_card: optional, if set installs on the SD card. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + cmd = ['install'] |
| + if forward_lock: |
| + cmd.append('-l') |
| + if reinstall: |
| + cmd.append('-r') |
| + if sd_card: |
| + cmd.append('-s') |
| + cmd.append(apk_path) |
| + output = self._DeviceAdbCmd(cmd, timeout, retries) |
| + if 'Success' not in output: |
| + raise CommandFailedError(cmd, output) |
| + |
| + def Uninstall(self, package, keep_data=False, timeout=_DEFAULT_TIMEOUT, |
| + retries=_DEFAULT_RETRIES): |
| + """Remove the app |package| from the device. |
| + |
| + Args: |
| + package: the package to uninstall. |
| + keep_data: optional, if set keep the data and cache directories. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + cmd = ['uninstall'] |
| + if keep_data: |
| + cmd.append('-k') |
| + cmd.append(package) |
| + return self._DeviceAdbCmd(cmd, timeout, retries) |
| + |
| + def Backup(self, path, packages=None, apk=False, shared=False, |
| + nosystem=True, include_all=False, timeout=_DEFAULT_TIMEOUT, |
| + retries=_DEFAULT_RETRIES): |
| + """Write an archive of the device's data to |path|. |
| + |
| + Args: |
| + path: local path to store the backup file. |
| + packages: list of to packages to be backed up. |
| + apk: optional, if set include the .apk files in the archive. |
| + shared: optional, if set buckup the device's SD card. |
| + nosystem: optional, if set exclude system applications. |
| + include_all: optional, if set back up all installed applications and |
| + |packages| is optional. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + cmd = ['backup', path] |
| + if apk: |
| + cmd.append('-apk') |
| + if shared: |
| + cmd.append('-shared') |
| + if nosystem: |
| + cmd.append('-nosystem') |
| + if include_all: |
| + cmd.append('-all') |
| + if packages: |
| + cmd.extend(packages) |
| + assert bool(packages) ^ bool(include_all), ( |
| + 'Provide \'packages\' or set \'include_all\' but not both.') |
| + return self._DeviceAdbCmd(cmd, timeout, retries) |
| + |
| + def Restore(self, path, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
| + """Restore device contents from the backup archive. |
| + |
| + Args: |
| + path: host path to the backup archive. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + self._DeviceAdbCmd(['restore'] + [path], timeout, retries) |
| + |
| + def WaitForDevice(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
| + """Block until the device is online. |
| + |
| + Args: |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + self._DeviceAdbCmd(['wait-for-device'], timeout, retries) |
| + |
| + def GetState(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
| + """Get device state. |
| + |
| + Args: |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + |
| + Returns: |
| + One of 'offline', 'bootloader', or 'device'. |
| + """ |
| + return self._DeviceAdbCmd(['get-state'], timeout, retries) |
| + |
| + def GetDevPath(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
| + """Gets the device path. |
| + |
| + Args: |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + |
| + Returns: |
| + The device path (e.g. usb:3-4) |
| + """ |
| + return self._DeviceAdbCmd(['get-devpath'], timeout, retries) |
| + |
| + def Remount(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
| + """Remounts the /system partition on the device read-write.""" |
| + self._DeviceAdbCmd(['remount'], timeout, retries) |
| + |
| + def Reboot(self, to_bootloader=False, timeout=_DEFAULT_TIMEOUT, |
| + retries=_DEFAULT_RETRIES): |
| + """Reboots the device. |
| + |
| + Args: |
| + to_bootloader: optional, if set reboots to the bootloader. |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + if to_bootloader: |
| + cmd = ['reboot-bootloader'] |
| + else: |
| + cmd = ['reboot'] |
| + self._DeviceAdbCmd(cmd, timeout, retries) |
| + |
| + def Root(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): |
| + """Restarts the adbd daemon with root permissions, if possible. |
| + |
| + Args: |
| + timeout: timeout per try in seconds. |
| + retries: number of retries to attempt. |
| + """ |
| + output = self._DeviceAdbCmd('root', timeout, retries) |
| + if 'cannot' in output: |
| + raise CommandFailedError('root', output) |
| + |