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 |