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