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