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.""" | |
| 6 | |
| 7 import errno | |
| 8 import os | |
| 9 | |
| 10 from pylib import cmd_helper | |
| 11 | |
| 12 from pylib.utils import reraiser_thread | |
| 13 from pylib.utils import timeout_retry | |
| 14 | |
| 15 _DEFAULT_TIMEOUT = 30 | |
| 16 _DEFAULT_RETRIES = 2 | |
| 17 | |
| 18 | |
| 19 class BaseError(Exception): | |
| 20 """Base exception for all device and command errors.""" | |
| 21 pass | |
| 22 | |
| 23 | |
| 24 class CommandFailedError(BaseError): | |
| 25 """Exception for command failures.""" | |
| 26 | |
| 27 def __init__(self, cmd, msg, device=None): | |
| 28 super(CommandFailedError, self).__init__( | |
| 29 (('device %s: ' % device) if device else '') + | |
| 30 'adb command \'%s\' failed with message: \'%s\'' % (' '.join(cmd), msg)) | |
| 31 | |
| 32 | |
| 33 class CommandTimeoutError(BaseError): | |
| 34 """Exception for command timeouts.""" | |
| 35 pass | |
| 36 | |
| 37 | |
| 38 def _VerifyLocalFileExists(path): | |
| 39 """Verifies a local file exists. | |
| 40 | |
| 41 Args: | |
| 42 path: path to the local file. | |
| 43 | |
| 44 Raises: | |
| 45 IOError: if the file doesn't exist. | |
| 46 """ | |
| 47 if not os.path.exists(path): | |
| 48 raise IOError(errno.ENOENT, os.strerror(errno.ENOENT), path) | |
| 49 | |
| 50 | |
| 51 class AdbWrapper(object): | |
| 52 """A wrapper around a local Android Debug Bridge executable.""" | |
| 53 | |
| 54 def __init__(self, device_serial): | |
| 55 """Initializes the AdbWrapper. | |
| 56 | |
| 57 Args: | |
| 58 device_serial: The device serial number as a string. | |
| 59 """ | |
| 60 self._device_serial = str(device_serial) | |
| 61 | |
| 62 @classmethod | |
| 63 def _AdbCmd(cls, arg_list, timeout, retries, check_error=True): | |
| 64 """Runs an adb command with a timeout and retries. | |
| 65 | |
| 66 Args: | |
| 67 arg_list: a list of arguments to adb. | |
| 68 timeout: timeout in seconds. | |
| 69 retries: number of retries. | |
| 70 check_error: check that the command doesn't return an error message. This | |
|
frankf
2013/11/21 01:12:57
actually, you'll only get 'error:' if device is of
craigdh
2013/11/26 19:00:34
Install already checks for 'Success'
| |
| 71 does NOT check the return code of shell commands. | |
| 72 | |
| 73 Returns: | |
| 74 The output of the command. | |
| 75 """ | |
| 76 cmd = ['adb'] + list(arg_list) | |
|
frankf
2013/11/21 01:12:57
get rid of these conversions as discussed
craigdh
2013/11/26 19:00:34
Done.
| |
| 77 | |
| 78 # This method runs inside the timeout/retries. | |
| 79 def RunCmd(): | |
| 80 exit_code, output = cmd_helper.GetCmdStatusAndOutput(cmd) | |
| 81 if exit_code != 0: | |
| 82 raise CommandFailedError( | |
| 83 'Command \'%s\' failed with code %d' % (' '.join(cmd), exit_code)) | |
| 84 if check_error and output[:len('error:')] == 'error:': | |
| 85 raise CommandFailedError(arg_list, output) | |
| 86 return output | |
| 87 | |
| 88 try: | |
| 89 return timeout_retry.Run(RunCmd, timeout, retries) | |
| 90 except reraiser_thread.TimeoutError as e: | |
| 91 raise CommandTimeoutError(str(e)) | |
| 92 | |
| 93 def _DeviceAdbCmd(self, arg_list, timeout, retries, check_error=True): | |
| 94 """Runs an adb command on the device associated with this object. | |
| 95 | |
| 96 Args: | |
| 97 arg_list: a list of arguments to adb. | |
| 98 timeout: timeout in seconds. | |
| 99 retries: number of retries. | |
| 100 check_error: check that the command doesn't return an error message. This | |
| 101 does NOT check the return code of shell commands. | |
| 102 | |
| 103 Returns: | |
| 104 The output of the command. | |
| 105 """ | |
| 106 return self._AdbCmd( | |
| 107 ['-s', self._device_serial] + list(arg_list), timeout, retries, | |
| 108 check_error=check_error) | |
| 109 | |
| 110 def __eq__(self, other): | |
| 111 """Consider instances equal if they refer to the same device. | |
| 112 | |
| 113 Args: | |
| 114 other: the instance to compare equality with. | |
| 115 | |
| 116 Returns: | |
| 117 True if the instances are considered equal, false otherwise. | |
| 118 """ | |
| 119 return self._device_serial == str(other) | |
| 120 | |
| 121 def __str__(self): | |
| 122 """The string representation of an instance. | |
| 123 | |
| 124 Returns: | |
| 125 The device serial number as a string. | |
| 126 """ | |
| 127 return self._device_serial | |
| 128 | |
| 129 def __repr__(self): | |
| 130 return '%s(\'%s\')' % (self.__class__.__name__, str(self)) | |
| 131 | |
| 132 # TODO(craigdh): Determine the filter criteria that should be supported. | |
| 133 @classmethod | |
| 134 def GetDevices(cls, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): | |
| 135 """Get the list of active attached devices. | |
| 136 | |
| 137 Args: | |
| 138 timeout: timeout per try in seconds. | |
| 139 retries: number of retries to attempt. | |
| 140 | |
| 141 Yields: | |
| 142 AdbWrapper instances. | |
| 143 """ | |
| 144 output = cls._AdbCmd(['devices'], timeout, retries) | |
| 145 for line in output.split('\n'): | |
| 146 fields = line.split() | |
| 147 if len(fields) == 2 and fields[1] == 'device': | |
| 148 yield AdbWrapper(fields[0]) | |
| 149 | |
| 150 def GetDeviceSerial(self): | |
| 151 """Gets the device serial number associated with this object. | |
| 152 | |
| 153 Returns: | |
| 154 Device serial number as a string. | |
| 155 """ | |
| 156 return self._device_serial | |
| 157 | |
| 158 def Push(self, local, remote, timeout=_DEFAULT_TIMEOUT, | |
|
frankf
2013/11/21 01:12:57
please add test cases for any method where feasabl
craigdh
2013/11/26 19:00:34
Done. I avoided testing all cases such as file/dir
| |
| 159 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: timeout per try in seconds. | |
| 166 retries: 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, | |
| 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: timeout per try in seconds. | |
| 179 retries: number of retries to attempt. | |
| 180 """ | |
| 181 self._DeviceAdbCmd(['pull', local, remote], 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 | |
|
frankf
2013/11/21 01:12:57
(optional) if set, checks...
craigdh
2013/11/26 19:00:34
Done.
| |
| 191 this value. | |
| 192 timeout: timeout per try in seconds. | |
| 193 retries: 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. | |
| 224 timeout: timeout per try in seconds. | |
| 225 retries: 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: | |
| 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: timeout per try in seconds. | |
| 251 retries: number of retries to attempt. | |
| 252 """ | |
| 253 self._DeviceAdbCmd(['forward', local, remote], timeout, retries) | |
| 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: timeout per try in seconds. | |
| 260 retries: 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=_DEFAULT_TIMEOUT, | |
| 270 retries=_DEFAULT_RETRIES): | |
| 271 """Push a package file to the device and install it. | |
| 272 | |
| 273 Args: | |
| 274 apk_path: host path to the APK file. | |
| 275 forward_lock: optional, if set forward-locks the app. | |
| 276 reinstall: optional, if set reinstalls the app, keeping its data. | |
| 277 sd_card: optional, if set installs on the SD card. | |
| 278 timeout: timeout per try in seconds. | |
| 279 retries: number of retries to attempt. | |
| 280 """ | |
| 281 _VerifyLocalFileExists(apk_path) | |
| 282 cmd = ['install'] | |
| 283 if forward_lock: | |
| 284 cmd.append('-l') | |
| 285 if reinstall: | |
| 286 cmd.append('-r') | |
| 287 if sd_card: | |
| 288 cmd.append('-s') | |
| 289 cmd.append(apk_path) | |
| 290 output = self._DeviceAdbCmd(cmd, timeout, retries) | |
| 291 if 'Success' not in output: | |
| 292 raise CommandFailedError(cmd, output) | |
| 293 | |
| 294 def Uninstall(self, package, keep_data=False, timeout=_DEFAULT_TIMEOUT, | |
| 295 retries=_DEFAULT_RETRIES): | |
| 296 """Remove the app |package| from the device. | |
| 297 | |
| 298 Args: | |
| 299 package: the package to uninstall. | |
| 300 keep_data: optional, if set keep the data and cache directories. | |
| 301 timeout: timeout per try in seconds. | |
| 302 retries: number of retries to attempt. | |
| 303 """ | |
| 304 cmd = ['uninstall'] | |
| 305 if keep_data: | |
| 306 cmd.append('-k') | |
| 307 cmd.append(package) | |
| 308 return self._DeviceAdbCmd(cmd, timeout, retries) | |
| 309 | |
| 310 def Backup(self, path, packages=None, apk=False, shared=False, | |
| 311 nosystem=True, include_all=False, timeout=_DEFAULT_TIMEOUT, | |
| 312 retries=_DEFAULT_RETRIES): | |
| 313 """Write an archive of the device's data to |path|. | |
| 314 | |
| 315 Args: | |
| 316 path: local path to store the backup file. | |
| 317 packages: list of to packages to be backed up. | |
| 318 apk: optional, if set include the .apk files in the archive. | |
| 319 shared: optional, if set buckup the device's SD card. | |
| 320 nosystem: optional, if set exclude system applications. | |
| 321 include_all: optional, if set back up all installed applications and | |
| 322 |packages| is optional. | |
| 323 timeout: timeout per try in seconds. | |
| 324 retries: number of retries to attempt. | |
| 325 """ | |
| 326 cmd = ['backup', path] | |
| 327 if apk: | |
| 328 cmd.append('-apk') | |
| 329 if shared: | |
| 330 cmd.append('-shared') | |
| 331 if nosystem: | |
| 332 cmd.append('-nosystem') | |
| 333 if include_all: | |
| 334 cmd.append('-all') | |
| 335 if packages: | |
| 336 cmd.extend(packages) | |
| 337 assert bool(packages) ^ bool(include_all), ( | |
| 338 'Provide \'packages\' or set \'include_all\' but not both.') | |
| 339 ret = self._DeviceAdbCmd(cmd, timeout, retries) | |
| 340 _VerifyLocalFileExists(path) | |
| 341 return ret | |
| 342 | |
| 343 def Restore(self, path, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): | |
| 344 """Restore device contents from the backup archive. | |
| 345 | |
| 346 Args: | |
| 347 path: host path to the backup archive. | |
| 348 timeout: timeout per try in seconds. | |
| 349 retries: number of retries to attempt. | |
| 350 """ | |
| 351 _VerifyLocalFileExists(path) | |
| 352 self._DeviceAdbCmd(['restore'] + [path], timeout, retries) | |
| 353 | |
| 354 def WaitForDevice(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): | |
| 355 """Block until the device is online. | |
| 356 | |
| 357 Args: | |
| 358 timeout: timeout per try in seconds. | |
| 359 retries: number of retries to attempt. | |
| 360 """ | |
| 361 self._DeviceAdbCmd(['wait-for-device'], timeout, retries) | |
| 362 | |
| 363 def GetState(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): | |
| 364 """Get device state. | |
| 365 | |
| 366 Args: | |
| 367 timeout: timeout per try in seconds. | |
| 368 retries: number of retries to attempt. | |
| 369 | |
| 370 Returns: | |
| 371 One of 'offline', 'bootloader', or 'device'. | |
| 372 """ | |
| 373 return self._DeviceAdbCmd(['get-state'], timeout, retries) | |
| 374 | |
| 375 def GetDevPath(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): | |
| 376 """Gets the device path. | |
| 377 | |
| 378 Args: | |
| 379 timeout: timeout per try in seconds. | |
| 380 retries: number of retries to attempt. | |
| 381 | |
| 382 Returns: | |
| 383 The device path (e.g. usb:3-4) | |
| 384 """ | |
| 385 return self._DeviceAdbCmd(['get-devpath'], timeout, retries) | |
| 386 | |
| 387 def Remount(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): | |
| 388 """Remounts the /system partition on the device read-write.""" | |
| 389 self._DeviceAdbCmd(['remount'], timeout, retries) | |
| 390 | |
| 391 def Reboot(self, to_bootloader=False, timeout=_DEFAULT_TIMEOUT, | |
| 392 retries=_DEFAULT_RETRIES): | |
| 393 """Reboots the device. | |
| 394 | |
| 395 Args: | |
| 396 to_bootloader: optional, if set reboots to the bootloader. | |
| 397 timeout: timeout per try in seconds. | |
| 398 retries: number of retries to attempt. | |
| 399 """ | |
| 400 if to_bootloader: | |
| 401 cmd = ['reboot-bootloader'] | |
| 402 else: | |
| 403 cmd = ['reboot'] | |
| 404 self._DeviceAdbCmd(cmd, timeout, retries) | |
| 405 | |
| 406 def Root(self, timeout=_DEFAULT_TIMEOUT, retries=_DEFAULT_RETRIES): | |
| 407 """Restarts the adbd daemon with root permissions, if possible. | |
| 408 | |
| 409 Args: | |
| 410 timeout: timeout per try in seconds. | |
| 411 retries: number of retries to attempt. | |
| 412 """ | |
| 413 output = self._DeviceAdbCmd('root', timeout, retries) | |
| 414 if 'cannot' in output: | |
| 415 raise CommandFailedError('root', output) | |
| 416 | |
| OLD | NEW |