OLD | NEW |
| (Empty) |
1 # Copyright 2014 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 """Provides a variety of device interactions based on adb. | |
6 | |
7 Eventually, this will be based on adb_wrapper. | |
8 """ | |
9 # pylint: disable=unused-argument | |
10 | |
11 import collections | |
12 import contextlib | |
13 import itertools | |
14 import logging | |
15 import multiprocessing | |
16 import os | |
17 import posixpath | |
18 import re | |
19 import shutil | |
20 import sys | |
21 import tempfile | |
22 import time | |
23 import zipfile | |
24 | |
25 import pylib.android_commands | |
26 from pylib import cmd_helper | |
27 from pylib import constants | |
28 from pylib import device_signal | |
29 from pylib.constants import keyevent | |
30 from pylib.device import adb_wrapper | |
31 from pylib.device import decorators | |
32 from pylib.device import device_blacklist | |
33 from pylib.device import device_errors | |
34 from pylib.device import intent | |
35 from pylib.device import logcat_monitor | |
36 from pylib.device.commands import install_commands | |
37 from pylib.sdk import split_select | |
38 from pylib.utils import apk_helper | |
39 from pylib.utils import base_error | |
40 from pylib.utils import device_temp_file | |
41 from pylib.utils import host_utils | |
42 from pylib.utils import md5sum | |
43 from pylib.utils import parallelizer | |
44 from pylib.utils import timeout_retry | |
45 from pylib.utils import zip_utils | |
46 | |
47 _DEFAULT_TIMEOUT = 30 | |
48 _DEFAULT_RETRIES = 3 | |
49 | |
50 # A sentinel object for default values | |
51 # TODO(jbudorick,perezju): revisit how default values are handled by | |
52 # the timeout_retry decorators. | |
53 DEFAULT = object() | |
54 | |
55 _CONTROL_CHARGING_COMMANDS = [ | |
56 { | |
57 # Nexus 4 | |
58 'witness_file': '/sys/module/pm8921_charger/parameters/disabled', | |
59 'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled', | |
60 'disable_command': | |
61 'echo 1 > /sys/module/pm8921_charger/parameters/disabled', | |
62 }, | |
63 { | |
64 # Nexus 5 | |
65 # Setting the HIZ bit of the bq24192 causes the charger to actually ignore | |
66 # energy coming from USB. Setting the power_supply offline just updates the | |
67 # Android system to reflect that. | |
68 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT', | |
69 'enable_command': ( | |
70 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' | |
71 'echo 1 > /sys/class/power_supply/usb/online'), | |
72 'disable_command': ( | |
73 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' | |
74 'chmod 644 /sys/class/power_supply/usb/online && ' | |
75 'echo 0 > /sys/class/power_supply/usb/online'), | |
76 }, | |
77 ] | |
78 | |
79 | |
80 @decorators.WithExplicitTimeoutAndRetries( | |
81 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) | |
82 def GetAVDs(): | |
83 """Returns a list of Android Virtual Devices. | |
84 | |
85 Returns: | |
86 A list containing the configured AVDs. | |
87 """ | |
88 lines = cmd_helper.GetCmdOutput([ | |
89 os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'android'), | |
90 'list', 'avd']).splitlines() | |
91 avds = [] | |
92 for line in lines: | |
93 if 'Name:' not in line: | |
94 continue | |
95 key, value = (s.strip() for s in line.split(':', 1)) | |
96 if key == 'Name': | |
97 avds.append(value) | |
98 return avds | |
99 | |
100 | |
101 @decorators.WithExplicitTimeoutAndRetries( | |
102 _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) | |
103 def RestartServer(): | |
104 """Restarts the adb server. | |
105 | |
106 Raises: | |
107 CommandFailedError if we fail to kill or restart the server. | |
108 """ | |
109 def adb_killed(): | |
110 return not adb_wrapper.AdbWrapper.IsServerOnline() | |
111 | |
112 def adb_started(): | |
113 return adb_wrapper.AdbWrapper.IsServerOnline() | |
114 | |
115 adb_wrapper.AdbWrapper.KillServer() | |
116 if not timeout_retry.WaitFor(adb_killed, wait_period=1, max_tries=5): | |
117 # TODO(perezju): raise an exception after fixng http://crbug.com/442319 | |
118 logging.warning('Failed to kill adb server') | |
119 adb_wrapper.AdbWrapper.StartServer() | |
120 if not timeout_retry.WaitFor(adb_started, wait_period=1, max_tries=5): | |
121 raise device_errors.CommandFailedError('Failed to start adb server') | |
122 | |
123 | |
124 def _GetTimeStamp(): | |
125 """Return a basic ISO 8601 time stamp with the current local time.""" | |
126 return time.strftime('%Y%m%dT%H%M%S', time.localtime()) | |
127 | |
128 | |
129 def _JoinLines(lines): | |
130 # makes sure that the last line is also terminated, and is more memory | |
131 # efficient than first appending an end-line to each line and then joining | |
132 # all of them together. | |
133 return ''.join(s for line in lines for s in (line, '\n')) | |
134 | |
135 | |
136 class DeviceUtils(object): | |
137 | |
138 _MAX_ADB_COMMAND_LENGTH = 512 | |
139 _MAX_ADB_OUTPUT_LENGTH = 32768 | |
140 _LAUNCHER_FOCUSED_RE = re.compile( | |
141 '\s*mCurrentFocus.*(Launcher|launcher).*') | |
142 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') | |
143 | |
144 # Property in /data/local.prop that controls Java assertions. | |
145 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' | |
146 | |
147 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, | |
148 default_retries=_DEFAULT_RETRIES): | |
149 """DeviceUtils constructor. | |
150 | |
151 Args: | |
152 device: Either a device serial, an existing AdbWrapper instance, or an | |
153 an existing AndroidCommands instance. | |
154 default_timeout: An integer containing the default number of seconds to | |
155 wait for an operation to complete if no explicit value | |
156 is provided. | |
157 default_retries: An integer containing the default number or times an | |
158 operation should be retried on failure if no explicit | |
159 value is provided. | |
160 """ | |
161 self.adb = None | |
162 self.old_interface = None | |
163 if isinstance(device, basestring): | |
164 self.adb = adb_wrapper.AdbWrapper(device) | |
165 self.old_interface = pylib.android_commands.AndroidCommands(device) | |
166 elif isinstance(device, adb_wrapper.AdbWrapper): | |
167 self.adb = device | |
168 self.old_interface = pylib.android_commands.AndroidCommands(str(device)) | |
169 elif isinstance(device, pylib.android_commands.AndroidCommands): | |
170 self.adb = adb_wrapper.AdbWrapper(device.GetDevice()) | |
171 self.old_interface = device | |
172 else: | |
173 raise ValueError('Unsupported device value: %r' % device) | |
174 self._commands_installed = None | |
175 self._default_timeout = default_timeout | |
176 self._default_retries = default_retries | |
177 self._cache = {} | |
178 self._client_caches = {} | |
179 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) | |
180 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) | |
181 | |
182 def __eq__(self, other): | |
183 """Checks whether |other| refers to the same device as |self|. | |
184 | |
185 Args: | |
186 other: The object to compare to. This can be a basestring, an instance | |
187 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils. | |
188 Returns: | |
189 Whether |other| refers to the same device as |self|. | |
190 """ | |
191 return self.adb.GetDeviceSerial() == str(other) | |
192 | |
193 def __lt__(self, other): | |
194 """Compares two instances of DeviceUtils. | |
195 | |
196 This merely compares their serial numbers. | |
197 | |
198 Args: | |
199 other: The instance of DeviceUtils to compare to. | |
200 Returns: | |
201 Whether |self| is less than |other|. | |
202 """ | |
203 return self.adb.GetDeviceSerial() < other.adb.GetDeviceSerial() | |
204 | |
205 def __str__(self): | |
206 """Returns the device serial.""" | |
207 return self.adb.GetDeviceSerial() | |
208 | |
209 @decorators.WithTimeoutAndRetriesFromInstance() | |
210 def IsOnline(self, timeout=None, retries=None): | |
211 """Checks whether the device is online. | |
212 | |
213 Args: | |
214 timeout: timeout in seconds | |
215 retries: number of retries | |
216 | |
217 Returns: | |
218 True if the device is online, False otherwise. | |
219 | |
220 Raises: | |
221 CommandTimeoutError on timeout. | |
222 """ | |
223 try: | |
224 return self.adb.GetState() == 'device' | |
225 except base_error.BaseError as exc: | |
226 logging.info('Failed to get state: %s', exc) | |
227 return False | |
228 | |
229 @decorators.WithTimeoutAndRetriesFromInstance() | |
230 def HasRoot(self, timeout=None, retries=None): | |
231 """Checks whether or not adbd has root privileges. | |
232 | |
233 Args: | |
234 timeout: timeout in seconds | |
235 retries: number of retries | |
236 | |
237 Returns: | |
238 True if adbd has root privileges, False otherwise. | |
239 | |
240 Raises: | |
241 CommandTimeoutError on timeout. | |
242 DeviceUnreachableError on missing device. | |
243 """ | |
244 try: | |
245 self.RunShellCommand('ls /root', check_return=True) | |
246 return True | |
247 except device_errors.AdbCommandFailedError: | |
248 return False | |
249 | |
250 def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT): | |
251 """Checks whether 'su' is needed to access protected resources. | |
252 | |
253 Args: | |
254 timeout: timeout in seconds | |
255 retries: number of retries | |
256 | |
257 Returns: | |
258 True if 'su' is available on the device and is needed to to access | |
259 protected resources; False otherwise if either 'su' is not available | |
260 (e.g. because the device has a user build), or not needed (because adbd | |
261 already has root privileges). | |
262 | |
263 Raises: | |
264 CommandTimeoutError on timeout. | |
265 DeviceUnreachableError on missing device. | |
266 """ | |
267 if 'needs_su' not in self._cache: | |
268 try: | |
269 self.RunShellCommand( | |
270 'su -c ls /root && ! ls /root', check_return=True, | |
271 timeout=self._default_timeout if timeout is DEFAULT else timeout, | |
272 retries=self._default_retries if retries is DEFAULT else retries) | |
273 self._cache['needs_su'] = True | |
274 except device_errors.AdbCommandFailedError: | |
275 self._cache['needs_su'] = False | |
276 return self._cache['needs_su'] | |
277 | |
278 | |
279 @decorators.WithTimeoutAndRetriesFromInstance() | |
280 def EnableRoot(self, timeout=None, retries=None): | |
281 """Restarts adbd with root privileges. | |
282 | |
283 Args: | |
284 timeout: timeout in seconds | |
285 retries: number of retries | |
286 | |
287 Raises: | |
288 CommandFailedError if root could not be enabled. | |
289 CommandTimeoutError on timeout. | |
290 """ | |
291 if self.IsUserBuild(): | |
292 raise device_errors.CommandFailedError( | |
293 'Cannot enable root in user builds.', str(self)) | |
294 if 'needs_su' in self._cache: | |
295 del self._cache['needs_su'] | |
296 self.adb.Root() | |
297 self.WaitUntilFullyBooted() | |
298 | |
299 @decorators.WithTimeoutAndRetriesFromInstance() | |
300 def IsUserBuild(self, timeout=None, retries=None): | |
301 """Checks whether or not the device is running a user build. | |
302 | |
303 Args: | |
304 timeout: timeout in seconds | |
305 retries: number of retries | |
306 | |
307 Returns: | |
308 True if the device is running a user build, False otherwise (i.e. if | |
309 it's running a userdebug build). | |
310 | |
311 Raises: | |
312 CommandTimeoutError on timeout. | |
313 DeviceUnreachableError on missing device. | |
314 """ | |
315 return self.build_type == 'user' | |
316 | |
317 @decorators.WithTimeoutAndRetriesFromInstance() | |
318 def GetExternalStoragePath(self, timeout=None, retries=None): | |
319 """Get the device's path to its SD card. | |
320 | |
321 Args: | |
322 timeout: timeout in seconds | |
323 retries: number of retries | |
324 | |
325 Returns: | |
326 The device's path to its SD card. | |
327 | |
328 Raises: | |
329 CommandFailedError if the external storage path could not be determined. | |
330 CommandTimeoutError on timeout. | |
331 DeviceUnreachableError on missing device. | |
332 """ | |
333 if 'external_storage' in self._cache: | |
334 return self._cache['external_storage'] | |
335 | |
336 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', | |
337 single_line=True, | |
338 check_return=True) | |
339 if not value: | |
340 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', | |
341 str(self)) | |
342 self._cache['external_storage'] = value | |
343 return value | |
344 | |
345 @decorators.WithTimeoutAndRetriesFromInstance() | |
346 def GetApplicationPaths(self, package, timeout=None, retries=None): | |
347 """Get the paths of the installed apks on the device for the given package. | |
348 | |
349 Args: | |
350 package: Name of the package. | |
351 | |
352 Returns: | |
353 List of paths to the apks on the device for the given package. | |
354 """ | |
355 # 'pm path' is liable to incorrectly exit with a nonzero number starting | |
356 # in Lollipop. | |
357 # TODO(jbudorick): Check if this is fixed as new Android versions are | |
358 # released to put an upper bound on this. | |
359 should_check_return = (self.build_version_sdk < | |
360 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | |
361 output = self.RunShellCommand( | |
362 ['pm', 'path', package], check_return=should_check_return) | |
363 apks = [] | |
364 for line in output: | |
365 if not line.startswith('package:'): | |
366 raise device_errors.CommandFailedError( | |
367 'pm path returned: %r' % '\n'.join(output), str(self)) | |
368 apks.append(line[len('package:'):]) | |
369 return apks | |
370 | |
371 @decorators.WithTimeoutAndRetriesFromInstance() | |
372 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): | |
373 """Get the data directory on the device for the given package. | |
374 | |
375 Args: | |
376 package: Name of the package. | |
377 | |
378 Returns: | |
379 The package's data directory, or None if the package doesn't exist on the | |
380 device. | |
381 """ | |
382 try: | |
383 output = self._RunPipedShellCommand( | |
384 'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package)) | |
385 for line in output: | |
386 _, _, dataDir = line.partition('dataDir=') | |
387 if dataDir: | |
388 return dataDir | |
389 except device_errors.CommandFailedError: | |
390 logging.exception('Could not find data directory for %s', package) | |
391 return None | |
392 | |
393 @decorators.WithTimeoutAndRetriesFromInstance() | |
394 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): | |
395 """Wait for the device to fully boot. | |
396 | |
397 This means waiting for the device to boot, the package manager to be | |
398 available, and the SD card to be ready. It can optionally mean waiting | |
399 for wifi to come up, too. | |
400 | |
401 Args: | |
402 wifi: A boolean indicating if we should wait for wifi to come up or not. | |
403 timeout: timeout in seconds | |
404 retries: number of retries | |
405 | |
406 Raises: | |
407 CommandFailedError on failure. | |
408 CommandTimeoutError if one of the component waits times out. | |
409 DeviceUnreachableError if the device becomes unresponsive. | |
410 """ | |
411 def sd_card_ready(): | |
412 try: | |
413 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], | |
414 check_return=True) | |
415 return True | |
416 except device_errors.AdbCommandFailedError: | |
417 return False | |
418 | |
419 def pm_ready(): | |
420 try: | |
421 return self.GetApplicationPaths('android') | |
422 except device_errors.CommandFailedError: | |
423 return False | |
424 | |
425 def boot_completed(): | |
426 return self.GetProp('sys.boot_completed') == '1' | |
427 | |
428 def wifi_enabled(): | |
429 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], | |
430 check_return=False) | |
431 | |
432 self.adb.WaitForDevice() | |
433 timeout_retry.WaitFor(sd_card_ready) | |
434 timeout_retry.WaitFor(pm_ready) | |
435 timeout_retry.WaitFor(boot_completed) | |
436 if wifi: | |
437 timeout_retry.WaitFor(wifi_enabled) | |
438 | |
439 REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT | |
440 REBOOT_DEFAULT_RETRIES = _DEFAULT_RETRIES | |
441 | |
442 @decorators.WithTimeoutAndRetriesDefaults( | |
443 REBOOT_DEFAULT_TIMEOUT, | |
444 REBOOT_DEFAULT_RETRIES) | |
445 def Reboot(self, block=True, wifi=False, timeout=None, retries=None): | |
446 """Reboot the device. | |
447 | |
448 Args: | |
449 block: A boolean indicating if we should wait for the reboot to complete. | |
450 wifi: A boolean indicating if we should wait for wifi to be enabled after | |
451 the reboot. The option has no effect unless |block| is also True. | |
452 timeout: timeout in seconds | |
453 retries: number of retries | |
454 | |
455 Raises: | |
456 CommandTimeoutError on timeout. | |
457 DeviceUnreachableError on missing device. | |
458 """ | |
459 def device_offline(): | |
460 return not self.IsOnline() | |
461 | |
462 self.adb.Reboot() | |
463 self._ClearCache() | |
464 timeout_retry.WaitFor(device_offline, wait_period=1) | |
465 if block: | |
466 self.WaitUntilFullyBooted(wifi=wifi) | |
467 | |
468 INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT | |
469 INSTALL_DEFAULT_RETRIES = _DEFAULT_RETRIES | |
470 | |
471 @decorators.WithTimeoutAndRetriesDefaults( | |
472 INSTALL_DEFAULT_TIMEOUT, | |
473 INSTALL_DEFAULT_RETRIES) | |
474 def Install(self, apk_path, reinstall=False, timeout=None, retries=None): | |
475 """Install an APK. | |
476 | |
477 Noop if an identical APK is already installed. | |
478 | |
479 Args: | |
480 apk_path: A string containing the path to the APK to install. | |
481 reinstall: A boolean indicating if we should keep any existing app data. | |
482 timeout: timeout in seconds | |
483 retries: number of retries | |
484 | |
485 Raises: | |
486 CommandFailedError if the installation fails. | |
487 CommandTimeoutError if the installation times out. | |
488 DeviceUnreachableError on missing device. | |
489 """ | |
490 package_name = apk_helper.GetPackageName(apk_path) | |
491 device_paths = self.GetApplicationPaths(package_name) | |
492 if device_paths: | |
493 if len(device_paths) > 1: | |
494 logging.warning( | |
495 'Installing single APK (%s) when split APKs (%s) are currently ' | |
496 'installed.', apk_path, ' '.join(device_paths)) | |
497 (files_to_push, _) = self._GetChangedAndStaleFiles( | |
498 apk_path, device_paths[0]) | |
499 should_install = bool(files_to_push) | |
500 if should_install and not reinstall: | |
501 self.adb.Uninstall(package_name) | |
502 else: | |
503 should_install = True | |
504 if should_install: | |
505 self.adb.Install(apk_path, reinstall=reinstall) | |
506 | |
507 @decorators.WithTimeoutAndRetriesDefaults( | |
508 INSTALL_DEFAULT_TIMEOUT, | |
509 INSTALL_DEFAULT_RETRIES) | |
510 def InstallSplitApk(self, base_apk, split_apks, reinstall=False, | |
511 timeout=None, retries=None): | |
512 """Install a split APK. | |
513 | |
514 Noop if all of the APK splits are already installed. | |
515 | |
516 Args: | |
517 base_apk: A string of the path to the base APK. | |
518 split_apks: A list of strings of paths of all of the APK splits. | |
519 reinstall: A boolean indicating if we should keep any existing app data. | |
520 timeout: timeout in seconds | |
521 retries: number of retries | |
522 | |
523 Raises: | |
524 CommandFailedError if the installation fails. | |
525 CommandTimeoutError if the installation times out. | |
526 DeviceUnreachableError on missing device. | |
527 DeviceVersionError if device SDK is less than Android L. | |
528 """ | |
529 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | |
530 | |
531 all_apks = [base_apk] + split_select.SelectSplits( | |
532 self, base_apk, split_apks) | |
533 package_name = apk_helper.GetPackageName(base_apk) | |
534 device_apk_paths = self.GetApplicationPaths(package_name) | |
535 | |
536 if device_apk_paths: | |
537 partial_install_package = package_name | |
538 device_checksums = md5sum.CalculateDeviceMd5Sums(device_apk_paths, self) | |
539 host_checksums = md5sum.CalculateHostMd5Sums(all_apks) | |
540 apks_to_install = [k for (k, v) in host_checksums.iteritems() | |
541 if v not in device_checksums.values()] | |
542 if apks_to_install and not reinstall: | |
543 self.adb.Uninstall(package_name) | |
544 partial_install_package = None | |
545 apks_to_install = all_apks | |
546 else: | |
547 partial_install_package = None | |
548 apks_to_install = all_apks | |
549 if apks_to_install: | |
550 self.adb.InstallMultiple( | |
551 apks_to_install, partial=partial_install_package, reinstall=reinstall) | |
552 | |
553 def _CheckSdkLevel(self, required_sdk_level): | |
554 """Raises an exception if the device does not have the required SDK level. | |
555 """ | |
556 if self.build_version_sdk < required_sdk_level: | |
557 raise device_errors.DeviceVersionError( | |
558 ('Requires SDK level %s, device is SDK level %s' % | |
559 (required_sdk_level, self.build_version_sdk)), | |
560 device_serial=self.adb.GetDeviceSerial()) | |
561 | |
562 | |
563 @decorators.WithTimeoutAndRetriesFromInstance() | |
564 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, | |
565 as_root=False, single_line=False, large_output=False, | |
566 timeout=None, retries=None): | |
567 """Run an ADB shell command. | |
568 | |
569 The command to run |cmd| should be a sequence of program arguments or else | |
570 a single string. | |
571 | |
572 When |cmd| is a sequence, it is assumed to contain the name of the command | |
573 to run followed by its arguments. In this case, arguments are passed to the | |
574 command exactly as given, without any further processing by the shell. This | |
575 allows to easily pass arguments containing spaces or special characters | |
576 without having to worry about getting quoting right. Whenever possible, it | |
577 is recomended to pass |cmd| as a sequence. | |
578 | |
579 When |cmd| is given as a string, it will be interpreted and run by the | |
580 shell on the device. | |
581 | |
582 This behaviour is consistent with that of command runners in cmd_helper as | |
583 well as Python's own subprocess.Popen. | |
584 | |
585 TODO(perezju) Change the default of |check_return| to True when callers | |
586 have switched to the new behaviour. | |
587 | |
588 Args: | |
589 cmd: A string with the full command to run on the device, or a sequence | |
590 containing the command and its arguments. | |
591 check_return: A boolean indicating whether or not the return code should | |
592 be checked. | |
593 cwd: The device directory in which the command should be run. | |
594 env: The environment variables with which the command should be run. | |
595 as_root: A boolean indicating whether the shell command should be run | |
596 with root privileges. | |
597 single_line: A boolean indicating if only a single line of output is | |
598 expected. | |
599 large_output: Uses a work-around for large shell command output. Without | |
600 this large output will be truncated. | |
601 timeout: timeout in seconds | |
602 retries: number of retries | |
603 | |
604 Returns: | |
605 If single_line is False, the output of the command as a list of lines, | |
606 otherwise, a string with the unique line of output emmited by the command | |
607 (with the optional newline at the end stripped). | |
608 | |
609 Raises: | |
610 AdbCommandFailedError if check_return is True and the exit code of | |
611 the command run on the device is non-zero. | |
612 CommandFailedError if single_line is True but the output contains two or | |
613 more lines. | |
614 CommandTimeoutError on timeout. | |
615 DeviceUnreachableError on missing device. | |
616 """ | |
617 def env_quote(key, value): | |
618 if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): | |
619 raise KeyError('Invalid shell variable name %r' % key) | |
620 # using double quotes here to allow interpolation of shell variables | |
621 return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) | |
622 | |
623 def run(cmd): | |
624 return self.adb.Shell(cmd) | |
625 | |
626 def handle_check_return(cmd): | |
627 try: | |
628 return run(cmd) | |
629 except device_errors.AdbCommandFailedError as exc: | |
630 if check_return: | |
631 raise | |
632 else: | |
633 return exc.output | |
634 | |
635 def handle_large_command(cmd): | |
636 if len(cmd) < self._MAX_ADB_COMMAND_LENGTH: | |
637 return handle_check_return(cmd) | |
638 else: | |
639 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: | |
640 self._WriteFileWithPush(script.name, cmd) | |
641 logging.info('Large shell command will be run from file: %s ...', | |
642 cmd[:100]) | |
643 return handle_check_return('sh %s' % script.name_quoted) | |
644 | |
645 def handle_large_output(cmd, large_output_mode): | |
646 if large_output_mode: | |
647 with device_temp_file.DeviceTempFile(self.adb) as large_output_file: | |
648 cmd = '%s > %s' % (cmd, large_output_file.name) | |
649 logging.debug('Large output mode enabled. Will write output to ' | |
650 'device and read results from file.') | |
651 handle_large_command(cmd) | |
652 return self.ReadFile(large_output_file.name, force_pull=True) | |
653 else: | |
654 try: | |
655 return handle_large_command(cmd) | |
656 except device_errors.AdbCommandFailedError as exc: | |
657 if exc.status is None: | |
658 logging.exception('No output found for %s', cmd) | |
659 logging.warning('Attempting to run in large_output mode.') | |
660 logging.warning('Use RunShellCommand(..., large_output=True) for ' | |
661 'shell commands that expect a lot of output.') | |
662 return handle_large_output(cmd, True) | |
663 else: | |
664 raise | |
665 | |
666 if not isinstance(cmd, basestring): | |
667 cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) | |
668 if env: | |
669 env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) | |
670 cmd = '%s %s' % (env, cmd) | |
671 if cwd: | |
672 cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) | |
673 if as_root and self.NeedsSU(): | |
674 # "su -c sh -c" allows using shell features in |cmd| | |
675 cmd = 'su -c sh -c %s' % cmd_helper.SingleQuote(cmd) | |
676 | |
677 output = handle_large_output(cmd, large_output).splitlines() | |
678 | |
679 if single_line: | |
680 if not output: | |
681 return '' | |
682 elif len(output) == 1: | |
683 return output[0] | |
684 else: | |
685 msg = 'one line of output was expected, but got: %s' | |
686 raise device_errors.CommandFailedError(msg % output, str(self)) | |
687 else: | |
688 return output | |
689 | |
690 def _RunPipedShellCommand(self, script, **kwargs): | |
691 PIPESTATUS_LEADER = 'PIPESTATUS: ' | |
692 | |
693 script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER | |
694 kwargs['check_return'] = True | |
695 output = self.RunShellCommand(script, **kwargs) | |
696 pipestatus_line = output[-1] | |
697 | |
698 if not pipestatus_line.startswith(PIPESTATUS_LEADER): | |
699 logging.error('Pipe exit statuses of shell script missing.') | |
700 raise device_errors.AdbShellCommandFailedError( | |
701 script, output, status=None, | |
702 device_serial=self.adb.GetDeviceSerial()) | |
703 | |
704 output = output[:-1] | |
705 statuses = [ | |
706 int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split()] | |
707 if any(statuses): | |
708 raise device_errors.AdbShellCommandFailedError( | |
709 script, output, status=statuses, | |
710 device_serial=self.adb.GetDeviceSerial()) | |
711 return output | |
712 | |
713 @decorators.WithTimeoutAndRetriesFromInstance() | |
714 def KillAll(self, process_name, signum=device_signal.SIGKILL, as_root=False, | |
715 blocking=False, quiet=False, timeout=None, retries=None): | |
716 """Kill all processes with the given name on the device. | |
717 | |
718 Args: | |
719 process_name: A string containing the name of the process to kill. | |
720 signum: An integer containing the signal number to send to kill. Defaults | |
721 to SIGKILL (9). | |
722 as_root: A boolean indicating whether the kill should be executed with | |
723 root privileges. | |
724 blocking: A boolean indicating whether we should wait until all processes | |
725 with the given |process_name| are dead. | |
726 quiet: A boolean indicating whether to ignore the fact that no processes | |
727 to kill were found. | |
728 timeout: timeout in seconds | |
729 retries: number of retries | |
730 | |
731 Returns: | |
732 The number of processes attempted to kill. | |
733 | |
734 Raises: | |
735 CommandFailedError if no process was killed and |quiet| is False. | |
736 CommandTimeoutError on timeout. | |
737 DeviceUnreachableError on missing device. | |
738 """ | |
739 pids = self.GetPids(process_name) | |
740 if not pids: | |
741 if quiet: | |
742 return 0 | |
743 else: | |
744 raise device_errors.CommandFailedError( | |
745 'No process "%s"' % process_name, str(self)) | |
746 | |
747 cmd = ['kill', '-%d' % signum] + pids.values() | |
748 self.RunShellCommand(cmd, as_root=as_root, check_return=True) | |
749 | |
750 if blocking: | |
751 # TODO(perezu): use timeout_retry.WaitFor | |
752 wait_period = 0.1 | |
753 while self.GetPids(process_name): | |
754 time.sleep(wait_period) | |
755 | |
756 return len(pids) | |
757 | |
758 @decorators.WithTimeoutAndRetriesFromInstance() | |
759 def StartActivity(self, intent_obj, blocking=False, trace_file_name=None, | |
760 force_stop=False, timeout=None, retries=None): | |
761 """Start package's activity on the device. | |
762 | |
763 Args: | |
764 intent_obj: An Intent object to send. | |
765 blocking: A boolean indicating whether we should wait for the activity to | |
766 finish launching. | |
767 trace_file_name: If present, a string that both indicates that we want to | |
768 profile the activity and contains the path to which the | |
769 trace should be saved. | |
770 force_stop: A boolean indicating whether we should stop the activity | |
771 before starting it. | |
772 timeout: timeout in seconds | |
773 retries: number of retries | |
774 | |
775 Raises: | |
776 CommandFailedError if the activity could not be started. | |
777 CommandTimeoutError on timeout. | |
778 DeviceUnreachableError on missing device. | |
779 """ | |
780 cmd = ['am', 'start'] | |
781 if blocking: | |
782 cmd.append('-W') | |
783 if trace_file_name: | |
784 cmd.extend(['--start-profiler', trace_file_name]) | |
785 if force_stop: | |
786 cmd.append('-S') | |
787 cmd.extend(intent_obj.am_args) | |
788 for line in self.RunShellCommand(cmd, check_return=True): | |
789 if line.startswith('Error:'): | |
790 raise device_errors.CommandFailedError(line, str(self)) | |
791 | |
792 @decorators.WithTimeoutAndRetriesFromInstance() | |
793 def StartInstrumentation(self, component, finish=True, raw=False, | |
794 extras=None, timeout=None, retries=None): | |
795 if extras is None: | |
796 extras = {} | |
797 | |
798 cmd = ['am', 'instrument'] | |
799 if finish: | |
800 cmd.append('-w') | |
801 if raw: | |
802 cmd.append('-r') | |
803 for k, v in extras.iteritems(): | |
804 cmd.extend(['-e', str(k), str(v)]) | |
805 cmd.append(component) | |
806 return self.RunShellCommand(cmd, check_return=True, large_output=True) | |
807 | |
808 @decorators.WithTimeoutAndRetriesFromInstance() | |
809 def BroadcastIntent(self, intent_obj, timeout=None, retries=None): | |
810 """Send a broadcast intent. | |
811 | |
812 Args: | |
813 intent: An Intent to broadcast. | |
814 timeout: timeout in seconds | |
815 retries: number of retries | |
816 | |
817 Raises: | |
818 CommandTimeoutError on timeout. | |
819 DeviceUnreachableError on missing device. | |
820 """ | |
821 cmd = ['am', 'broadcast'] + intent_obj.am_args | |
822 self.RunShellCommand(cmd, check_return=True) | |
823 | |
824 @decorators.WithTimeoutAndRetriesFromInstance() | |
825 def GoHome(self, timeout=None, retries=None): | |
826 """Return to the home screen and obtain launcher focus. | |
827 | |
828 This command launches the home screen and attempts to obtain | |
829 launcher focus until the timeout is reached. | |
830 | |
831 Args: | |
832 timeout: timeout in seconds | |
833 retries: number of retries | |
834 | |
835 Raises: | |
836 CommandTimeoutError on timeout. | |
837 DeviceUnreachableError on missing device. | |
838 """ | |
839 def is_launcher_focused(): | |
840 output = self.RunShellCommand(['dumpsys', 'window', 'windows'], | |
841 check_return=True, large_output=True) | |
842 return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output) | |
843 | |
844 def dismiss_popups(): | |
845 # There is a dialog present; attempt to get rid of it. | |
846 # Not all dialogs can be dismissed with back. | |
847 self.SendKeyEvent(keyevent.KEYCODE_ENTER) | |
848 self.SendKeyEvent(keyevent.KEYCODE_BACK) | |
849 return is_launcher_focused() | |
850 | |
851 # If Home is already focused, return early to avoid unnecessary work. | |
852 if is_launcher_focused(): | |
853 return | |
854 | |
855 self.StartActivity( | |
856 intent.Intent(action='android.intent.action.MAIN', | |
857 category='android.intent.category.HOME'), | |
858 blocking=True) | |
859 | |
860 if not is_launcher_focused(): | |
861 timeout_retry.WaitFor(dismiss_popups, wait_period=1) | |
862 | |
863 @decorators.WithTimeoutAndRetriesFromInstance() | |
864 def ForceStop(self, package, timeout=None, retries=None): | |
865 """Close the application. | |
866 | |
867 Args: | |
868 package: A string containing the name of the package to stop. | |
869 timeout: timeout in seconds | |
870 retries: number of retries | |
871 | |
872 Raises: | |
873 CommandTimeoutError on timeout. | |
874 DeviceUnreachableError on missing device. | |
875 """ | |
876 self.RunShellCommand(['am', 'force-stop', package], check_return=True) | |
877 | |
878 @decorators.WithTimeoutAndRetriesFromInstance() | |
879 def ClearApplicationState(self, package, timeout=None, retries=None): | |
880 """Clear all state for the given package. | |
881 | |
882 Args: | |
883 package: A string containing the name of the package to stop. | |
884 timeout: timeout in seconds | |
885 retries: number of retries | |
886 | |
887 Raises: | |
888 CommandTimeoutError on timeout. | |
889 DeviceUnreachableError on missing device. | |
890 """ | |
891 # Check that the package exists before clearing it for android builds below | |
892 # JB MR2. Necessary because calling pm clear on a package that doesn't exist | |
893 # may never return. | |
894 if ((self.build_version_sdk >= | |
895 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) | |
896 or self.GetApplicationPaths(package)): | |
897 self.RunShellCommand(['pm', 'clear', package], check_return=True) | |
898 | |
899 @decorators.WithTimeoutAndRetriesFromInstance() | |
900 def SendKeyEvent(self, keycode, timeout=None, retries=None): | |
901 """Sends a keycode to the device. | |
902 | |
903 See the pylib.constants.keyevent module for suitable keycode values. | |
904 | |
905 Args: | |
906 keycode: A integer keycode to send to the device. | |
907 timeout: timeout in seconds | |
908 retries: number of retries | |
909 | |
910 Raises: | |
911 CommandTimeoutError on timeout. | |
912 DeviceUnreachableError on missing device. | |
913 """ | |
914 self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')], | |
915 check_return=True) | |
916 | |
917 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT | |
918 PUSH_CHANGED_FILES_DEFAULT_RETRIES = _DEFAULT_RETRIES | |
919 | |
920 @decorators.WithTimeoutAndRetriesDefaults( | |
921 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT, | |
922 PUSH_CHANGED_FILES_DEFAULT_RETRIES) | |
923 def PushChangedFiles(self, host_device_tuples, timeout=None, | |
924 retries=None, delete_device_stale=False): | |
925 """Push files to the device, skipping files that don't need updating. | |
926 | |
927 When a directory is pushed, it is traversed recursively on the host and | |
928 all files in it are pushed to the device as needed. | |
929 Additionally, if delete_device_stale option is True, | |
930 files that exist on the device but don't exist on the host are deleted. | |
931 | |
932 Args: | |
933 host_device_tuples: A list of (host_path, device_path) tuples, where | |
934 |host_path| is an absolute path of a file or directory on the host | |
935 that should be minimially pushed to the device, and |device_path| is | |
936 an absolute path of the destination on the device. | |
937 timeout: timeout in seconds | |
938 retries: number of retries | |
939 delete_device_stale: option to delete stale files on device | |
940 | |
941 Raises: | |
942 CommandFailedError on failure. | |
943 CommandTimeoutError on timeout. | |
944 DeviceUnreachableError on missing device. | |
945 """ | |
946 | |
947 all_changed_files = [] | |
948 all_stale_files = [] | |
949 for h, d in host_device_tuples: | |
950 if os.path.isdir(h): | |
951 self.RunShellCommand(['mkdir', '-p', d], check_return=True) | |
952 changed_files, stale_files = ( | |
953 self._GetChangedAndStaleFiles(h, d, delete_device_stale)) | |
954 all_changed_files += changed_files | |
955 all_stale_files += stale_files | |
956 | |
957 if delete_device_stale: | |
958 self.RunShellCommand(['rm', '-f'] + all_stale_files, | |
959 check_return=True) | |
960 | |
961 if not all_changed_files: | |
962 return | |
963 | |
964 self._PushFilesImpl(host_device_tuples, all_changed_files) | |
965 | |
966 def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False): | |
967 """Get files to push and delete | |
968 | |
969 Args: | |
970 host_path: an absolute path of a file or directory on the host | |
971 device_path: an absolute path of a file or directory on the device | |
972 track_stale: whether to bother looking for stale files (slower) | |
973 | |
974 Returns: | |
975 a two-element tuple | |
976 1st element: a list of (host_files_path, device_files_path) tuples to push | |
977 2nd element: a list of stale files under device_path, or [] when | |
978 track_stale == False | |
979 """ | |
980 real_host_path = os.path.realpath(host_path) | |
981 try: | |
982 real_device_path = self.RunShellCommand( | |
983 ['realpath', device_path], single_line=True, check_return=True) | |
984 except device_errors.CommandFailedError: | |
985 real_device_path = None | |
986 if not real_device_path: | |
987 return ([(host_path, device_path)], []) | |
988 | |
989 try: | |
990 host_checksums = md5sum.CalculateHostMd5Sums([real_host_path]) | |
991 interesting_device_paths = [real_device_path] | |
992 if not track_stale and os.path.isdir(real_host_path): | |
993 interesting_device_paths = [ | |
994 posixpath.join(real_device_path, os.path.relpath(p, real_host_path)) | |
995 for p in host_checksums.keys()] | |
996 device_checksums = md5sum.CalculateDeviceMd5Sums( | |
997 interesting_device_paths, self) | |
998 except EnvironmentError as e: | |
999 logging.warning('Error calculating md5: %s', e) | |
1000 return ([(host_path, device_path)], []) | |
1001 | |
1002 if os.path.isfile(host_path): | |
1003 host_checksum = host_checksums.get(real_host_path) | |
1004 device_checksum = device_checksums.get(real_device_path) | |
1005 if host_checksum != device_checksum: | |
1006 return ([(host_path, device_path)], []) | |
1007 else: | |
1008 return ([], []) | |
1009 else: | |
1010 to_push = [] | |
1011 for host_abs_path, host_checksum in host_checksums.iteritems(): | |
1012 device_abs_path = '%s/%s' % ( | |
1013 real_device_path, os.path.relpath(host_abs_path, real_host_path)) | |
1014 device_checksum = device_checksums.pop(device_abs_path, None) | |
1015 if device_checksum != host_checksum: | |
1016 to_push.append((host_abs_path, device_abs_path)) | |
1017 to_delete = device_checksums.keys() | |
1018 return (to_push, to_delete) | |
1019 | |
1020 def _PushFilesImpl(self, host_device_tuples, files): | |
1021 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) | |
1022 file_count = len(files) | |
1023 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) | |
1024 for h, _ in host_device_tuples) | |
1025 dir_file_count = 0 | |
1026 for h, _ in host_device_tuples: | |
1027 if os.path.isdir(h): | |
1028 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) | |
1029 else: | |
1030 dir_file_count += 1 | |
1031 | |
1032 push_duration = self._ApproximateDuration( | |
1033 file_count, file_count, size, False) | |
1034 dir_push_duration = self._ApproximateDuration( | |
1035 len(host_device_tuples), dir_file_count, dir_size, False) | |
1036 zip_duration = self._ApproximateDuration(1, 1, size, True) | |
1037 | |
1038 self._InstallCommands() | |
1039 | |
1040 if dir_push_duration < push_duration and ( | |
1041 dir_push_duration < zip_duration or not self._commands_installed): | |
1042 self._PushChangedFilesIndividually(host_device_tuples) | |
1043 elif push_duration < zip_duration or not self._commands_installed: | |
1044 self._PushChangedFilesIndividually(files) | |
1045 else: | |
1046 self._PushChangedFilesZipped(files) | |
1047 self.RunShellCommand( | |
1048 ['chmod', '-R', '777'] + [d for _, d in host_device_tuples], | |
1049 as_root=True, check_return=True) | |
1050 | |
1051 def _InstallCommands(self): | |
1052 if self._commands_installed is None: | |
1053 try: | |
1054 if not install_commands.Installed(self): | |
1055 install_commands.InstallCommands(self) | |
1056 self._commands_installed = True | |
1057 except Exception as e: | |
1058 logging.warning('unzip not available: %s' % str(e)) | |
1059 self._commands_installed = False | |
1060 | |
1061 @staticmethod | |
1062 def _ApproximateDuration(adb_calls, file_count, byte_count, is_zipping): | |
1063 # We approximate the time to push a set of files to a device as: | |
1064 # t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where | |
1065 # t: total time (sec) | |
1066 # c1: adb call time delay (sec) | |
1067 # a: number of times adb is called (unitless) | |
1068 # c2: push time delay (sec) | |
1069 # f: number of files pushed via adb (unitless) | |
1070 # c3: zip time delay (sec) | |
1071 # c4: zip rate (bytes/sec) | |
1072 # b: total number of bytes (bytes) | |
1073 # c5: transfer rate (bytes/sec) | |
1074 # c6: compression ratio (unitless) | |
1075 | |
1076 # All of these are approximations. | |
1077 ADB_CALL_PENALTY = 0.1 # seconds | |
1078 ADB_PUSH_PENALTY = 0.01 # seconds | |
1079 ZIP_PENALTY = 2.0 # seconds | |
1080 ZIP_RATE = 10000000.0 # bytes / second | |
1081 TRANSFER_RATE = 2000000.0 # bytes / second | |
1082 COMPRESSION_RATIO = 2.0 # unitless | |
1083 | |
1084 adb_call_time = ADB_CALL_PENALTY * adb_calls | |
1085 adb_push_setup_time = ADB_PUSH_PENALTY * file_count | |
1086 if is_zipping: | |
1087 zip_time = ZIP_PENALTY + byte_count / ZIP_RATE | |
1088 transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO) | |
1089 else: | |
1090 zip_time = 0 | |
1091 transfer_time = byte_count / TRANSFER_RATE | |
1092 return adb_call_time + adb_push_setup_time + zip_time + transfer_time | |
1093 | |
1094 def _PushChangedFilesIndividually(self, files): | |
1095 for h, d in files: | |
1096 self.adb.Push(h, d) | |
1097 | |
1098 def _PushChangedFilesZipped(self, files): | |
1099 if not files: | |
1100 return | |
1101 | |
1102 with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: | |
1103 zip_proc = multiprocessing.Process( | |
1104 target=DeviceUtils._CreateDeviceZip, | |
1105 args=(zip_file.name, files)) | |
1106 zip_proc.start() | |
1107 zip_proc.join() | |
1108 | |
1109 zip_on_device = '%s/tmp.zip' % self.GetExternalStoragePath() | |
1110 try: | |
1111 self.adb.Push(zip_file.name, zip_on_device) | |
1112 self.RunShellCommand( | |
1113 ['unzip', zip_on_device], | |
1114 as_root=True, | |
1115 env={'PATH': '%s:$PATH' % install_commands.BIN_DIR}, | |
1116 check_return=True) | |
1117 finally: | |
1118 if zip_proc.is_alive(): | |
1119 zip_proc.terminate() | |
1120 if self.IsOnline(): | |
1121 self.RunShellCommand(['rm', zip_on_device], check_return=True) | |
1122 | |
1123 @staticmethod | |
1124 def _CreateDeviceZip(zip_path, host_device_tuples): | |
1125 with zipfile.ZipFile(zip_path, 'w') as zip_file: | |
1126 for host_path, device_path in host_device_tuples: | |
1127 zip_utils.WriteToZipFile(zip_file, host_path, device_path) | |
1128 | |
1129 @decorators.WithTimeoutAndRetriesFromInstance() | |
1130 def FileExists(self, device_path, timeout=None, retries=None): | |
1131 """Checks whether the given file exists on the device. | |
1132 | |
1133 Args: | |
1134 device_path: A string containing the absolute path to the file on the | |
1135 device. | |
1136 timeout: timeout in seconds | |
1137 retries: number of retries | |
1138 | |
1139 Returns: | |
1140 True if the file exists on the device, False otherwise. | |
1141 | |
1142 Raises: | |
1143 CommandTimeoutError on timeout. | |
1144 DeviceUnreachableError on missing device. | |
1145 """ | |
1146 try: | |
1147 self.RunShellCommand(['test', '-e', device_path], check_return=True) | |
1148 return True | |
1149 except device_errors.AdbCommandFailedError: | |
1150 return False | |
1151 | |
1152 @decorators.WithTimeoutAndRetriesFromInstance() | |
1153 def PullFile(self, device_path, host_path, timeout=None, retries=None): | |
1154 """Pull a file from the device. | |
1155 | |
1156 Args: | |
1157 device_path: A string containing the absolute path of the file to pull | |
1158 from the device. | |
1159 host_path: A string containing the absolute path of the destination on | |
1160 the host. | |
1161 timeout: timeout in seconds | |
1162 retries: number of retries | |
1163 | |
1164 Raises: | |
1165 CommandFailedError on failure. | |
1166 CommandTimeoutError on timeout. | |
1167 """ | |
1168 # Create the base dir if it doesn't exist already | |
1169 dirname = os.path.dirname(host_path) | |
1170 if dirname and not os.path.exists(dirname): | |
1171 os.makedirs(dirname) | |
1172 self.adb.Pull(device_path, host_path) | |
1173 | |
1174 def _ReadFileWithPull(self, device_path): | |
1175 try: | |
1176 d = tempfile.mkdtemp() | |
1177 host_temp_path = os.path.join(d, 'tmp_ReadFileWithPull') | |
1178 self.adb.Pull(device_path, host_temp_path) | |
1179 with open(host_temp_path, 'r') as host_temp: | |
1180 return host_temp.read() | |
1181 finally: | |
1182 if os.path.exists(d): | |
1183 shutil.rmtree(d) | |
1184 | |
1185 _LS_RE = re.compile( | |
1186 r'(?P<perms>\S+) +(?P<owner>\S+) +(?P<group>\S+) +(?:(?P<size>\d+) +)?' | |
1187 + r'(?P<date>\S+) +(?P<time>\S+) +(?P<name>.+)$') | |
1188 | |
1189 @decorators.WithTimeoutAndRetriesFromInstance() | |
1190 def ReadFile(self, device_path, as_root=False, force_pull=False, | |
1191 timeout=None, retries=None): | |
1192 """Reads the contents of a file from the device. | |
1193 | |
1194 Args: | |
1195 device_path: A string containing the absolute path of the file to read | |
1196 from the device. | |
1197 as_root: A boolean indicating whether the read should be executed with | |
1198 root privileges. | |
1199 force_pull: A boolean indicating whether to force the operation to be | |
1200 performed by pulling a file from the device. The default is, when the | |
1201 contents are short, to retrieve the contents using cat instead. | |
1202 timeout: timeout in seconds | |
1203 retries: number of retries | |
1204 | |
1205 Returns: | |
1206 The contents of |device_path| as a string. Contents are intepreted using | |
1207 universal newlines, so the caller will see them encoded as '\n'. Also, | |
1208 all lines will be terminated. | |
1209 | |
1210 Raises: | |
1211 AdbCommandFailedError if the file can't be read. | |
1212 CommandTimeoutError on timeout. | |
1213 DeviceUnreachableError on missing device. | |
1214 """ | |
1215 def get_size(path): | |
1216 # TODO(jbudorick): Implement a generic version of Stat() that handles | |
1217 # as_root=True, then switch this implementation to use that. | |
1218 ls_out = self.RunShellCommand(['ls', '-l', device_path], as_root=as_root, | |
1219 check_return=True) | |
1220 for line in ls_out: | |
1221 m = self._LS_RE.match(line) | |
1222 if m and m.group('name') == posixpath.basename(device_path): | |
1223 return int(m.group('size')) | |
1224 logging.warning('Could not determine size of %s.', device_path) | |
1225 return None | |
1226 | |
1227 if (not force_pull | |
1228 and 0 < get_size(device_path) <= self._MAX_ADB_OUTPUT_LENGTH): | |
1229 return _JoinLines(self.RunShellCommand( | |
1230 ['cat', device_path], as_root=as_root, check_return=True)) | |
1231 elif as_root and self.NeedsSU(): | |
1232 with device_temp_file.DeviceTempFile(self.adb) as device_temp: | |
1233 self.RunShellCommand(['cp', device_path, device_temp.name], | |
1234 as_root=True, check_return=True) | |
1235 return self._ReadFileWithPull(device_temp.name) | |
1236 else: | |
1237 return self._ReadFileWithPull(device_path) | |
1238 | |
1239 def _WriteFileWithPush(self, device_path, contents): | |
1240 with tempfile.NamedTemporaryFile() as host_temp: | |
1241 host_temp.write(contents) | |
1242 host_temp.flush() | |
1243 self.adb.Push(host_temp.name, device_path) | |
1244 | |
1245 @decorators.WithTimeoutAndRetriesFromInstance() | |
1246 def WriteFile(self, device_path, contents, as_root=False, force_push=False, | |
1247 timeout=None, retries=None): | |
1248 """Writes |contents| to a file on the device. | |
1249 | |
1250 Args: | |
1251 device_path: A string containing the absolute path to the file to write | |
1252 on the device. | |
1253 contents: A string containing the data to write to the device. | |
1254 as_root: A boolean indicating whether the write should be executed with | |
1255 root privileges (if available). | |
1256 force_push: A boolean indicating whether to force the operation to be | |
1257 performed by pushing a file to the device. The default is, when the | |
1258 contents are short, to pass the contents using a shell script instead. | |
1259 timeout: timeout in seconds | |
1260 retries: number of retries | |
1261 | |
1262 Raises: | |
1263 CommandFailedError if the file could not be written on the device. | |
1264 CommandTimeoutError on timeout. | |
1265 DeviceUnreachableError on missing device. | |
1266 """ | |
1267 if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH: | |
1268 # If the contents are small, for efficieny we write the contents with | |
1269 # a shell command rather than pushing a file. | |
1270 cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents), | |
1271 cmd_helper.SingleQuote(device_path)) | |
1272 self.RunShellCommand(cmd, as_root=as_root, check_return=True) | |
1273 elif as_root and self.NeedsSU(): | |
1274 # Adb does not allow to "push with su", so we first push to a temp file | |
1275 # on a safe location, and then copy it to the desired location with su. | |
1276 with device_temp_file.DeviceTempFile(self.adb) as device_temp: | |
1277 self._WriteFileWithPush(device_temp.name, contents) | |
1278 # Here we need 'cp' rather than 'mv' because the temp and | |
1279 # destination files might be on different file systems (e.g. | |
1280 # on internal storage and an external sd card). | |
1281 self.RunShellCommand(['cp', device_temp.name, device_path], | |
1282 as_root=True, check_return=True) | |
1283 else: | |
1284 # If root is not needed, we can push directly to the desired location. | |
1285 self._WriteFileWithPush(device_path, contents) | |
1286 | |
1287 @decorators.WithTimeoutAndRetriesFromInstance() | |
1288 def Ls(self, device_path, timeout=None, retries=None): | |
1289 """Lists the contents of a directory on the device. | |
1290 | |
1291 Args: | |
1292 device_path: A string containing the path of the directory on the device | |
1293 to list. | |
1294 timeout: timeout in seconds | |
1295 retries: number of retries | |
1296 | |
1297 Returns: | |
1298 A list of pairs (filename, stat) for each file found in the directory, | |
1299 where the stat object has the properties: st_mode, st_size, and st_time. | |
1300 | |
1301 Raises: | |
1302 AdbCommandFailedError if |device_path| does not specify a valid and | |
1303 accessible directory in the device. | |
1304 CommandTimeoutError on timeout. | |
1305 DeviceUnreachableError on missing device. | |
1306 """ | |
1307 return self.adb.Ls(device_path) | |
1308 | |
1309 @decorators.WithTimeoutAndRetriesFromInstance() | |
1310 def Stat(self, device_path, timeout=None, retries=None): | |
1311 """Get the stat attributes of a file or directory on the device. | |
1312 | |
1313 Args: | |
1314 device_path: A string containing the path of from which to get attributes | |
1315 on the device. | |
1316 timeout: timeout in seconds | |
1317 retries: number of retries | |
1318 | |
1319 Returns: | |
1320 A stat object with the properties: st_mode, st_size, and st_time | |
1321 | |
1322 Raises: | |
1323 CommandFailedError if device_path cannot be found on the device. | |
1324 CommandTimeoutError on timeout. | |
1325 DeviceUnreachableError on missing device. | |
1326 """ | |
1327 dirname, target = device_path.rsplit('/', 1) | |
1328 for filename, stat in self.adb.Ls(dirname): | |
1329 if filename == target: | |
1330 return stat | |
1331 raise device_errors.CommandFailedError( | |
1332 'Cannot find file or directory: %r' % device_path, str(self)) | |
1333 | |
1334 @decorators.WithTimeoutAndRetriesFromInstance() | |
1335 def SetJavaAsserts(self, enabled, timeout=None, retries=None): | |
1336 """Enables or disables Java asserts. | |
1337 | |
1338 Args: | |
1339 enabled: A boolean indicating whether Java asserts should be enabled | |
1340 or disabled. | |
1341 timeout: timeout in seconds | |
1342 retries: number of retries | |
1343 | |
1344 Returns: | |
1345 True if the device-side property changed and a restart is required as a | |
1346 result, False otherwise. | |
1347 | |
1348 Raises: | |
1349 CommandTimeoutError on timeout. | |
1350 """ | |
1351 def find_property(lines, property_name): | |
1352 for index, line in enumerate(lines): | |
1353 if line.strip() == '': | |
1354 continue | |
1355 key, value = (s.strip() for s in line.split('=', 1)) | |
1356 if key == property_name: | |
1357 return index, value | |
1358 return None, '' | |
1359 | |
1360 new_value = 'all' if enabled else '' | |
1361 | |
1362 # First ensure the desired property is persisted. | |
1363 try: | |
1364 properties = self.ReadFile( | |
1365 constants.DEVICE_LOCAL_PROPERTIES_PATH).splitlines() | |
1366 except device_errors.CommandFailedError: | |
1367 properties = [] | |
1368 index, value = find_property(properties, self.JAVA_ASSERT_PROPERTY) | |
1369 if new_value != value: | |
1370 if new_value: | |
1371 new_line = '%s=%s' % (self.JAVA_ASSERT_PROPERTY, new_value) | |
1372 if index is None: | |
1373 properties.append(new_line) | |
1374 else: | |
1375 properties[index] = new_line | |
1376 else: | |
1377 assert index is not None # since new_value == '' and new_value != value | |
1378 properties.pop(index) | |
1379 self.WriteFile(constants.DEVICE_LOCAL_PROPERTIES_PATH, | |
1380 _JoinLines(properties)) | |
1381 | |
1382 # Next, check the current runtime value is what we need, and | |
1383 # if not, set it and report that a reboot is required. | |
1384 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) | |
1385 if new_value != value: | |
1386 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) | |
1387 return True | |
1388 else: | |
1389 return False | |
1390 | |
1391 @property | |
1392 def language(self): | |
1393 """Returns the language setting on the device.""" | |
1394 return self.GetProp('persist.sys.language', cache=False) | |
1395 | |
1396 @property | |
1397 def country(self): | |
1398 """Returns the country setting on the device.""" | |
1399 return self.GetProp('persist.sys.country', cache=False) | |
1400 | |
1401 @property | |
1402 def screen_density(self): | |
1403 """Returns the screen density of the device.""" | |
1404 DPI_TO_DENSITY = { | |
1405 120: 'ldpi', | |
1406 160: 'mdpi', | |
1407 240: 'hdpi', | |
1408 320: 'xhdpi', | |
1409 480: 'xxhdpi', | |
1410 640: 'xxxhdpi', | |
1411 } | |
1412 dpi = int(self.GetProp('ro.sf.lcd_density', cache=True)) | |
1413 return DPI_TO_DENSITY.get(dpi, 'tvdpi') | |
1414 | |
1415 @property | |
1416 def build_description(self): | |
1417 """Returns the build description of the system. | |
1418 | |
1419 For example: | |
1420 nakasi-user 4.4.4 KTU84P 1227136 release-keys | |
1421 """ | |
1422 return self.GetProp('ro.build.description', cache=True) | |
1423 | |
1424 @property | |
1425 def build_fingerprint(self): | |
1426 """Returns the build fingerprint of the system. | |
1427 | |
1428 For example: | |
1429 google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys | |
1430 """ | |
1431 return self.GetProp('ro.build.fingerprint', cache=True) | |
1432 | |
1433 @property | |
1434 def build_id(self): | |
1435 """Returns the build ID of the system (e.g. 'KTU84P').""" | |
1436 return self.GetProp('ro.build.id', cache=True) | |
1437 | |
1438 @property | |
1439 def build_product(self): | |
1440 """Returns the build product of the system (e.g. 'grouper').""" | |
1441 return self.GetProp('ro.build.product', cache=True) | |
1442 | |
1443 @property | |
1444 def build_type(self): | |
1445 """Returns the build type of the system (e.g. 'user').""" | |
1446 return self.GetProp('ro.build.type', cache=True) | |
1447 | |
1448 @property | |
1449 def build_version_sdk(self): | |
1450 """Returns the build version sdk of the system as a number (e.g. 19). | |
1451 | |
1452 For version code numbers see: | |
1453 http://developer.android.com/reference/android/os/Build.VERSION_CODES.html | |
1454 | |
1455 For named constants see: | |
1456 pylib.constants.ANDROID_SDK_VERSION_CODES | |
1457 | |
1458 Raises: | |
1459 CommandFailedError if the build version sdk is not a number. | |
1460 """ | |
1461 value = self.GetProp('ro.build.version.sdk', cache=True) | |
1462 try: | |
1463 return int(value) | |
1464 except ValueError: | |
1465 raise device_errors.CommandFailedError( | |
1466 'Invalid build version sdk: %r' % value) | |
1467 | |
1468 @property | |
1469 def product_cpu_abi(self): | |
1470 """Returns the product cpu abi of the device (e.g. 'armeabi-v7a').""" | |
1471 return self.GetProp('ro.product.cpu.abi', cache=True) | |
1472 | |
1473 @property | |
1474 def product_model(self): | |
1475 """Returns the name of the product model (e.g. 'Nexus 7').""" | |
1476 return self.GetProp('ro.product.model', cache=True) | |
1477 | |
1478 @property | |
1479 def product_name(self): | |
1480 """Returns the product name of the device (e.g. 'nakasi').""" | |
1481 return self.GetProp('ro.product.name', cache=True) | |
1482 | |
1483 def GetProp(self, property_name, cache=False, timeout=DEFAULT, | |
1484 retries=DEFAULT): | |
1485 """Gets a property from the device. | |
1486 | |
1487 Args: | |
1488 property_name: A string containing the name of the property to get from | |
1489 the device. | |
1490 cache: A boolean indicating whether to cache the value of this property. | |
1491 timeout: timeout in seconds | |
1492 retries: number of retries | |
1493 | |
1494 Returns: | |
1495 The value of the device's |property_name| property. | |
1496 | |
1497 Raises: | |
1498 CommandTimeoutError on timeout. | |
1499 """ | |
1500 assert isinstance(property_name, basestring), ( | |
1501 "property_name is not a string: %r" % property_name) | |
1502 | |
1503 cache_key = '_prop:' + property_name | |
1504 if cache and cache_key in self._cache: | |
1505 return self._cache[cache_key] | |
1506 else: | |
1507 # timeout and retries are handled down at run shell, because we don't | |
1508 # want to apply them in the other branch when reading from the cache | |
1509 value = self.RunShellCommand( | |
1510 ['getprop', property_name], single_line=True, check_return=True, | |
1511 timeout=self._default_timeout if timeout is DEFAULT else timeout, | |
1512 retries=self._default_retries if retries is DEFAULT else retries) | |
1513 if cache or cache_key in self._cache: | |
1514 self._cache[cache_key] = value | |
1515 return value | |
1516 | |
1517 @decorators.WithTimeoutAndRetriesFromInstance() | |
1518 def SetProp(self, property_name, value, check=False, timeout=None, | |
1519 retries=None): | |
1520 """Sets a property on the device. | |
1521 | |
1522 Args: | |
1523 property_name: A string containing the name of the property to set on | |
1524 the device. | |
1525 value: A string containing the value to set to the property on the | |
1526 device. | |
1527 check: A boolean indicating whether to check that the property was | |
1528 successfully set on the device. | |
1529 timeout: timeout in seconds | |
1530 retries: number of retries | |
1531 | |
1532 Raises: | |
1533 CommandFailedError if check is true and the property was not correctly | |
1534 set on the device (e.g. because it is not rooted). | |
1535 CommandTimeoutError on timeout. | |
1536 """ | |
1537 assert isinstance(property_name, basestring), ( | |
1538 "property_name is not a string: %r" % property_name) | |
1539 assert isinstance(value, basestring), "value is not a string: %r" % value | |
1540 | |
1541 self.RunShellCommand(['setprop', property_name, value], check_return=True) | |
1542 if property_name in self._cache: | |
1543 del self._cache[property_name] | |
1544 # TODO(perezju) remove the option and make the check mandatory, but using a | |
1545 # single shell script to both set- and getprop. | |
1546 if check and value != self.GetProp(property_name): | |
1547 raise device_errors.CommandFailedError( | |
1548 'Unable to set property %r on the device to %r' | |
1549 % (property_name, value), str(self)) | |
1550 | |
1551 @decorators.WithTimeoutAndRetriesFromInstance() | |
1552 def GetABI(self, timeout=None, retries=None): | |
1553 """Gets the device main ABI. | |
1554 | |
1555 Args: | |
1556 timeout: timeout in seconds | |
1557 retries: number of retries | |
1558 | |
1559 Returns: | |
1560 The device's main ABI name. | |
1561 | |
1562 Raises: | |
1563 CommandTimeoutError on timeout. | |
1564 """ | |
1565 return self.GetProp('ro.product.cpu.abi') | |
1566 | |
1567 @decorators.WithTimeoutAndRetriesFromInstance() | |
1568 def GetPids(self, process_name, timeout=None, retries=None): | |
1569 """Returns the PIDs of processes with the given name. | |
1570 | |
1571 Note that the |process_name| is often the package name. | |
1572 | |
1573 Args: | |
1574 process_name: A string containing the process name to get the PIDs for. | |
1575 timeout: timeout in seconds | |
1576 retries: number of retries | |
1577 | |
1578 Returns: | |
1579 A dict mapping process name to PID for each process that contained the | |
1580 provided |process_name|. | |
1581 | |
1582 Raises: | |
1583 CommandTimeoutError on timeout. | |
1584 DeviceUnreachableError on missing device. | |
1585 """ | |
1586 procs_pids = {} | |
1587 try: | |
1588 ps_output = self._RunPipedShellCommand( | |
1589 'ps | grep -F %s' % cmd_helper.SingleQuote(process_name)) | |
1590 except device_errors.AdbShellCommandFailedError as e: | |
1591 if e.status and isinstance(e.status, list) and not e.status[0]: | |
1592 # If ps succeeded but grep failed, there were no processes with the | |
1593 # given name. | |
1594 return procs_pids | |
1595 else: | |
1596 raise | |
1597 | |
1598 for line in ps_output: | |
1599 try: | |
1600 ps_data = line.split() | |
1601 if process_name in ps_data[-1]: | |
1602 procs_pids[ps_data[-1]] = ps_data[1] | |
1603 except IndexError: | |
1604 pass | |
1605 return procs_pids | |
1606 | |
1607 @decorators.WithTimeoutAndRetriesFromInstance() | |
1608 def TakeScreenshot(self, host_path=None, timeout=None, retries=None): | |
1609 """Takes a screenshot of the device. | |
1610 | |
1611 Args: | |
1612 host_path: A string containing the path on the host to save the | |
1613 screenshot to. If None, a file name in the current | |
1614 directory will be generated. | |
1615 timeout: timeout in seconds | |
1616 retries: number of retries | |
1617 | |
1618 Returns: | |
1619 The name of the file on the host to which the screenshot was saved. | |
1620 | |
1621 Raises: | |
1622 CommandFailedError on failure. | |
1623 CommandTimeoutError on timeout. | |
1624 DeviceUnreachableError on missing device. | |
1625 """ | |
1626 if not host_path: | |
1627 host_path = os.path.abspath('screenshot-%s.png' % _GetTimeStamp()) | |
1628 with device_temp_file.DeviceTempFile(self.adb, suffix='.png') as device_tmp: | |
1629 self.RunShellCommand(['/system/bin/screencap', '-p', device_tmp.name], | |
1630 check_return=True) | |
1631 self.PullFile(device_tmp.name, host_path) | |
1632 return host_path | |
1633 | |
1634 @decorators.WithTimeoutAndRetriesFromInstance() | |
1635 def GetMemoryUsageForPid(self, pid, timeout=None, retries=None): | |
1636 """Gets the memory usage for the given PID. | |
1637 | |
1638 Args: | |
1639 pid: PID of the process. | |
1640 timeout: timeout in seconds | |
1641 retries: number of retries | |
1642 | |
1643 Returns: | |
1644 A dict containing memory usage statistics for the PID. May include: | |
1645 Size, Rss, Pss, Shared_Clean, Shared_Dirty, Private_Clean, | |
1646 Private_Dirty, VmHWM | |
1647 | |
1648 Raises: | |
1649 CommandTimeoutError on timeout. | |
1650 """ | |
1651 result = collections.defaultdict(int) | |
1652 | |
1653 try: | |
1654 result.update(self._GetMemoryUsageForPidFromSmaps(pid)) | |
1655 except device_errors.CommandFailedError: | |
1656 logging.exception('Error getting memory usage from smaps') | |
1657 | |
1658 try: | |
1659 result.update(self._GetMemoryUsageForPidFromStatus(pid)) | |
1660 except device_errors.CommandFailedError: | |
1661 logging.exception('Error getting memory usage from status') | |
1662 | |
1663 return result | |
1664 | |
1665 def _GetMemoryUsageForPidFromSmaps(self, pid): | |
1666 SMAPS_COLUMNS = ( | |
1667 'Size', 'Rss', 'Pss', 'Shared_Clean', 'Shared_Dirty', 'Private_Clean', | |
1668 'Private_Dirty') | |
1669 | |
1670 showmap_out = self._RunPipedShellCommand( | |
1671 'showmap %d | grep TOTAL' % int(pid), as_root=True) | |
1672 | |
1673 split_totals = showmap_out[-1].split() | |
1674 if (not split_totals | |
1675 or len(split_totals) != 9 | |
1676 or split_totals[-1] != 'TOTAL'): | |
1677 raise device_errors.CommandFailedError( | |
1678 'Invalid output from showmap: %s' % '\n'.join(showmap_out)) | |
1679 | |
1680 return dict(itertools.izip(SMAPS_COLUMNS, (int(n) for n in split_totals))) | |
1681 | |
1682 def _GetMemoryUsageForPidFromStatus(self, pid): | |
1683 for line in self.ReadFile( | |
1684 '/proc/%s/status' % str(pid), as_root=True).splitlines(): | |
1685 if line.startswith('VmHWM:'): | |
1686 return {'VmHWM': int(line.split()[1])} | |
1687 else: | |
1688 raise device_errors.CommandFailedError( | |
1689 'Could not find memory peak value for pid %s', str(pid)) | |
1690 | |
1691 @decorators.WithTimeoutAndRetriesFromInstance() | |
1692 def GetLogcatMonitor(self, timeout=None, retries=None, *args, **kwargs): | |
1693 """Returns a new LogcatMonitor associated with this device. | |
1694 | |
1695 Parameters passed to this function are passed directly to | |
1696 |logcat_monitor.LogcatMonitor| and are documented there. | |
1697 | |
1698 Args: | |
1699 timeout: timeout in seconds | |
1700 retries: number of retries | |
1701 """ | |
1702 return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs) | |
1703 | |
1704 def GetClientCache(self, client_name): | |
1705 """Returns client cache.""" | |
1706 if client_name not in self._client_caches: | |
1707 self._client_caches[client_name] = {} | |
1708 return self._client_caches[client_name] | |
1709 | |
1710 def _ClearCache(self): | |
1711 """Clears all caches.""" | |
1712 for client in self._client_caches: | |
1713 self._client_caches[client].clear() | |
1714 self._cache.clear() | |
1715 | |
1716 @classmethod | |
1717 def parallel(cls, devices=None, async=False): | |
1718 """Creates a Parallelizer to operate over the provided list of devices. | |
1719 | |
1720 If |devices| is either |None| or an empty list, the Parallelizer will | |
1721 operate over all attached devices that have not been blacklisted. | |
1722 | |
1723 Args: | |
1724 devices: A list of either DeviceUtils instances or objects from | |
1725 from which DeviceUtils instances can be constructed. If None, | |
1726 all attached devices will be used. | |
1727 async: If true, returns a Parallelizer that runs operations | |
1728 asynchronously. | |
1729 | |
1730 Returns: | |
1731 A Parallelizer operating over |devices|. | |
1732 """ | |
1733 if not devices: | |
1734 devices = cls.HealthyDevices() | |
1735 if not devices: | |
1736 raise device_errors.NoDevicesError() | |
1737 | |
1738 devices = [d if isinstance(d, cls) else cls(d) for d in devices] | |
1739 if async: | |
1740 return parallelizer.Parallelizer(devices) | |
1741 else: | |
1742 return parallelizer.SyncParallelizer(devices) | |
1743 | |
1744 @classmethod | |
1745 def HealthyDevices(cls): | |
1746 blacklist = device_blacklist.ReadBlacklist() | |
1747 def blacklisted(adb): | |
1748 if adb.GetDeviceSerial() in blacklist: | |
1749 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) | |
1750 return True | |
1751 return False | |
1752 | |
1753 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() | |
1754 if not blacklisted(adb)] | |
OLD | NEW |