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 |