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.
5 """Provides a variety of device interactions based on adb.
7 Eventually, this will be based on adb_wrapper.
9 # pylint: disable=unused-argument
15 import multiprocessing
25 import pylib
.android_commands
26 from pylib
import cmd_helper
27 from pylib
import constants
28 from pylib
.device
import adb_wrapper
29 from pylib
.device
import decorators
30 from pylib
.device
import device_errors
31 from pylib
.device
import intent
32 from pylib
.device
import logcat_monitor
33 from pylib
.device
.commands
import install_commands
34 from pylib
.utils
import apk_helper
35 from pylib
.utils
import base_error
36 from pylib
.utils
import device_temp_file
37 from pylib
.utils
import host_utils
38 from pylib
.utils
import md5sum
39 from pylib
.utils
import parallelizer
40 from pylib
.utils
import timeout_retry
41 from pylib
.utils
import zip_utils
46 # A sentinel object for default values
47 # TODO(jbudorick,perezju): revisit how default values are handled by
48 # the timeout_retry decorators.
51 _CONTROL_CHARGING_COMMANDS
= [
54 'witness_file': '/sys/module/pm8921_charger/parameters/disabled',
55 'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled',
57 'echo 1 > /sys/module/pm8921_charger/parameters/disabled',
61 # Setting the HIZ bit of the bq24192 causes the charger to actually ignore
62 # energy coming from USB. Setting the power_supply offline just updates the
63 # Android system to reflect that.
64 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT',
66 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
67 'echo 1 > /sys/class/power_supply/usb/online'),
69 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && '
70 'chmod 644 /sys/class/power_supply/usb/online && '
71 'echo 0 > /sys/class/power_supply/usb/online'),
75 @decorators.WithExplicitTimeoutAndRetries(
76 _DEFAULT_TIMEOUT
, _DEFAULT_RETRIES
)
78 """Returns a list of Android Virtual Devices.
81 A list containing the configured AVDs.
83 lines
= cmd_helper
.GetCmdOutput([
84 os
.path
.join(constants
.ANDROID_SDK_ROOT
, 'tools', 'android'),
85 'list', 'avd']).splitlines()
88 if 'Name:' not in line
:
90 key
, value
= (s
.strip() for s
in line
.split(':', 1))
96 @decorators.WithExplicitTimeoutAndRetries(
97 _DEFAULT_TIMEOUT
, _DEFAULT_RETRIES
)
99 """Restarts the adb server.
102 CommandFailedError if we fail to kill or restart the server.
105 return not adb_wrapper
.AdbWrapper
.IsServerOnline()
108 return adb_wrapper
.AdbWrapper
.IsServerOnline()
110 adb_wrapper
.AdbWrapper
.KillServer()
111 if not timeout_retry
.WaitFor(adb_killed
, wait_period
=1, max_tries
=5):
112 # TODO(perezju): raise an exception after fixng http://crbug.com/442319
113 logging
.warning('Failed to kill adb server')
114 adb_wrapper
.AdbWrapper
.StartServer()
115 if not timeout_retry
.WaitFor(adb_started
, wait_period
=1, max_tries
=5):
116 raise device_errors
.CommandFailedError('Failed to start adb server')
120 """Return a basic ISO 8601 time stamp with the current local time."""
121 return time
.strftime('%Y%m%dT%H%M%S', time
.localtime())
124 def _JoinLines(lines
):
125 # makes sure that the last line is also terminated, and is more memory
126 # efficient than first appending an end-line to each line and then joining
127 # all of them together.
128 return ''.join(s
for line
in lines
for s
in (line
, '\n'))
131 class DeviceUtils(object):
133 _MAX_ADB_COMMAND_LENGTH
= 512
134 _MAX_ADB_OUTPUT_LENGTH
= 32768
135 _VALID_SHELL_VARIABLE
= re
.compile('^[a-zA-Z_][a-zA-Z0-9_]*$')
137 # Property in /data/local.prop that controls Java assertions.
138 JAVA_ASSERT_PROPERTY
= 'dalvik.vm.enableassertions'
140 def __init__(self
, device
, default_timeout
=_DEFAULT_TIMEOUT
,
141 default_retries
=_DEFAULT_RETRIES
):
142 """DeviceUtils constructor.
145 device: Either a device serial, an existing AdbWrapper instance, or an
146 an existing AndroidCommands instance.
147 default_timeout: An integer containing the default number of seconds to
148 wait for an operation to complete if no explicit value
150 default_retries: An integer containing the default number or times an
151 operation should be retried on failure if no explicit
155 self
.old_interface
= None
156 if isinstance(device
, basestring
):
157 self
.adb
= adb_wrapper
.AdbWrapper(device
)
158 self
.old_interface
= pylib
.android_commands
.AndroidCommands(device
)
159 elif isinstance(device
, adb_wrapper
.AdbWrapper
):
161 self
.old_interface
= pylib
.android_commands
.AndroidCommands(str(device
))
162 elif isinstance(device
, pylib
.android_commands
.AndroidCommands
):
163 self
.adb
= adb_wrapper
.AdbWrapper(device
.GetDevice())
164 self
.old_interface
= device
166 raise ValueError('Unsupported device value: %r' % device
)
167 self
._commands
_installed
= None
168 self
._default
_timeout
= default_timeout
169 self
._default
_retries
= default_retries
171 self
._client
_caches
= {}
172 assert hasattr(self
, decorators
.DEFAULT_TIMEOUT_ATTR
)
173 assert hasattr(self
, decorators
.DEFAULT_RETRIES_ATTR
)
176 """Returns the device serial."""
177 return self
.adb
.GetDeviceSerial()
179 @decorators.WithTimeoutAndRetriesFromInstance()
180 def IsOnline(self
, timeout
=None, retries
=None):
181 """Checks whether the device is online.
184 timeout: timeout in seconds
185 retries: number of retries
188 True if the device is online, False otherwise.
191 CommandTimeoutError on timeout.
194 return self
.adb
.GetState() == 'device'
195 except base_error
.BaseError
as exc
:
196 logging
.info('Failed to get state: %s', exc
)
199 @decorators.WithTimeoutAndRetriesFromInstance()
200 def HasRoot(self
, timeout
=None, retries
=None):
201 """Checks whether or not adbd has root privileges.
204 timeout: timeout in seconds
205 retries: number of retries
208 True if adbd has root privileges, False otherwise.
211 CommandTimeoutError on timeout.
212 DeviceUnreachableError on missing device.
215 self
.RunShellCommand('ls /root', check_return
=True)
217 except device_errors
.AdbCommandFailedError
:
220 def NeedsSU(self
, timeout
=DEFAULT
, retries
=DEFAULT
):
221 """Checks whether 'su' is needed to access protected resources.
224 timeout: timeout in seconds
225 retries: number of retries
228 True if 'su' is available on the device and is needed to to access
229 protected resources; False otherwise if either 'su' is not available
230 (e.g. because the device has a user build), or not needed (because adbd
231 already has root privileges).
234 CommandTimeoutError on timeout.
235 DeviceUnreachableError on missing device.
237 if 'needs_su' not in self
._cache
:
239 self
.RunShellCommand(
240 'su -c ls /root && ! ls /root', check_return
=True,
241 timeout
=self
._default
_timeout
if timeout
is DEFAULT
else timeout
,
242 retries
=self
._default
_retries
if retries
is DEFAULT
else retries
)
243 self
._cache
['needs_su'] = True
244 except device_errors
.AdbCommandFailedError
:
245 self
._cache
['needs_su'] = False
246 return self
._cache
['needs_su']
249 @decorators.WithTimeoutAndRetriesFromInstance()
250 def EnableRoot(self
, timeout
=None, retries
=None):
251 """Restarts adbd with root privileges.
254 timeout: timeout in seconds
255 retries: number of retries
258 CommandFailedError if root could not be enabled.
259 CommandTimeoutError on timeout.
261 if self
.IsUserBuild():
262 raise device_errors
.CommandFailedError(
263 'Cannot enable root in user builds.', str(self
))
264 if 'needs_su' in self
._cache
:
265 del self
._cache
['needs_su']
267 self
.adb
.WaitForDevice()
269 @decorators.WithTimeoutAndRetriesFromInstance()
270 def IsUserBuild(self
, timeout
=None, retries
=None):
271 """Checks whether or not the device is running a user build.
274 timeout: timeout in seconds
275 retries: number of retries
278 True if the device is running a user build, False otherwise (i.e. if
279 it's running a userdebug build).
282 CommandTimeoutError on timeout.
283 DeviceUnreachableError on missing device.
285 return self
.build_type
== 'user'
287 @decorators.WithTimeoutAndRetriesFromInstance()
288 def GetExternalStoragePath(self
, timeout
=None, retries
=None):
289 """Get the device's path to its SD card.
292 timeout: timeout in seconds
293 retries: number of retries
296 The device's path to its SD card.
299 CommandFailedError if the external storage path could not be determined.
300 CommandTimeoutError on timeout.
301 DeviceUnreachableError on missing device.
303 if 'external_storage' in self
._cache
:
304 return self
._cache
['external_storage']
306 value
= self
.RunShellCommand('echo $EXTERNAL_STORAGE',
310 raise device_errors
.CommandFailedError('$EXTERNAL_STORAGE is not set',
312 self
._cache
['external_storage'] = value
315 @decorators.WithTimeoutAndRetriesFromInstance()
316 def GetApplicationPath(self
, package
, timeout
=None, retries
=None):
317 """Get the path of the installed apk on the device for the given package.
320 package: Name of the package.
323 Path to the apk on the device if it exists, None otherwise.
325 # 'pm path' is liable to incorrectly exit with a nonzero number starting
327 # TODO(jbudorick): Check if this is fixed as new Android versions are
328 # released to put an upper bound on this.
329 should_check_return
= (self
.build_version_sdk
<
330 constants
.ANDROID_SDK_VERSION_CODES
.LOLLIPOP
)
331 output
= self
.RunShellCommand(['pm', 'path', package
], single_line
=True,
332 check_return
=should_check_return
)
335 if not output
.startswith('package:'):
336 raise device_errors
.CommandFailedError('pm path returned: %r' % output
,
338 return output
[len('package:'):]
340 @decorators.WithTimeoutAndRetriesFromInstance()
341 def WaitUntilFullyBooted(self
, wifi
=False, timeout
=None, retries
=None):
342 """Wait for the device to fully boot.
344 This means waiting for the device to boot, the package manager to be
345 available, and the SD card to be ready. It can optionally mean waiting
346 for wifi to come up, too.
349 wifi: A boolean indicating if we should wait for wifi to come up or not.
350 timeout: timeout in seconds
351 retries: number of retries
354 CommandFailedError on failure.
355 CommandTimeoutError if one of the component waits times out.
356 DeviceUnreachableError if the device becomes unresponsive.
360 self
.RunShellCommand(['test', '-d', self
.GetExternalStoragePath()],
363 except device_errors
.AdbCommandFailedError
:
368 return self
.GetApplicationPath('android')
369 except device_errors
.CommandFailedError
:
372 def boot_completed():
373 return self
.GetProp('sys.boot_completed') == '1'
376 return 'Wi-Fi is enabled' in self
.RunShellCommand(['dumpsys', 'wifi'],
379 self
.adb
.WaitForDevice()
380 timeout_retry
.WaitFor(sd_card_ready
)
381 timeout_retry
.WaitFor(pm_ready
)
382 timeout_retry
.WaitFor(boot_completed
)
384 timeout_retry
.WaitFor(wifi_enabled
)
386 REBOOT_DEFAULT_TIMEOUT
= 10 * _DEFAULT_TIMEOUT
387 REBOOT_DEFAULT_RETRIES
= _DEFAULT_RETRIES
389 @decorators.WithTimeoutAndRetriesDefaults(
390 REBOOT_DEFAULT_TIMEOUT
,
391 REBOOT_DEFAULT_RETRIES
)
392 def Reboot(self
, block
=True, wifi
=False, timeout
=None, retries
=None):
393 """Reboot the device.
396 block: A boolean indicating if we should wait for the reboot to complete.
397 wifi: A boolean indicating if we should wait for wifi to be enabled after
398 the reboot. The option has no effect unless |block| is also True.
399 timeout: timeout in seconds
400 retries: number of retries
403 CommandTimeoutError on timeout.
404 DeviceUnreachableError on missing device.
406 def device_offline():
407 return not self
.IsOnline()
411 timeout_retry
.WaitFor(device_offline
, wait_period
=1)
413 self
.WaitUntilFullyBooted(wifi
=wifi
)
415 INSTALL_DEFAULT_TIMEOUT
= 4 * _DEFAULT_TIMEOUT
416 INSTALL_DEFAULT_RETRIES
= _DEFAULT_RETRIES
418 @decorators.WithTimeoutAndRetriesDefaults(
419 INSTALL_DEFAULT_TIMEOUT
,
420 INSTALL_DEFAULT_RETRIES
)
421 def Install(self
, apk_path
, reinstall
=False, timeout
=None, retries
=None):
424 Noop if an identical APK is already installed.
427 apk_path: A string containing the path to the APK to install.
428 reinstall: A boolean indicating if we should keep any existing app data.
429 timeout: timeout in seconds
430 retries: number of retries
433 CommandFailedError if the installation fails.
434 CommandTimeoutError if the installation times out.
435 DeviceUnreachableError on missing device.
437 package_name
= apk_helper
.GetPackageName(apk_path
)
438 device_path
= self
.GetApplicationPath(package_name
)
439 if device_path
is not None:
440 should_install
= bool(self
._GetChangedFilesImpl
(apk_path
, device_path
))
441 if should_install
and not reinstall
:
442 self
.adb
.Uninstall(package_name
)
444 should_install
= True
446 self
.adb
.Install(apk_path
, reinstall
=reinstall
)
448 @decorators.WithTimeoutAndRetriesFromInstance()
449 def RunShellCommand(self
, cmd
, check_return
=False, cwd
=None, env
=None,
450 as_root
=False, single_line
=False, timeout
=None,
452 """Run an ADB shell command.
454 The command to run |cmd| should be a sequence of program arguments or else
457 When |cmd| is a sequence, it is assumed to contain the name of the command
458 to run followed by its arguments. In this case, arguments are passed to the
459 command exactly as given, without any further processing by the shell. This
460 allows to easily pass arguments containing spaces or special characters
461 without having to worry about getting quoting right. Whenever possible, it
462 is recomended to pass |cmd| as a sequence.
464 When |cmd| is given as a string, it will be interpreted and run by the
467 This behaviour is consistent with that of command runners in cmd_helper as
468 well as Python's own subprocess.Popen.
470 TODO(perezju) Change the default of |check_return| to True when callers
471 have switched to the new behaviour.
474 cmd: A string with the full command to run on the device, or a sequence
475 containing the command and its arguments.
476 check_return: A boolean indicating whether or not the return code should
478 cwd: The device directory in which the command should be run.
479 env: The environment variables with which the command should be run.
480 as_root: A boolean indicating whether the shell command should be run
481 with root privileges.
482 single_line: A boolean indicating if only a single line of output is
484 timeout: timeout in seconds
485 retries: number of retries
488 If single_line is False, the output of the command as a list of lines,
489 otherwise, a string with the unique line of output emmited by the command
490 (with the optional newline at the end stripped).
493 AdbCommandFailedError if check_return is True and the exit code of
494 the command run on the device is non-zero.
495 CommandFailedError if single_line is True but the output contains two or
497 CommandTimeoutError on timeout.
498 DeviceUnreachableError on missing device.
500 def env_quote(key
, value
):
501 if not DeviceUtils
._VALID
_SHELL
_VARIABLE
.match(key
):
502 raise KeyError('Invalid shell variable name %r' % key
)
503 # using double quotes here to allow interpolation of shell variables
504 return '%s=%s' % (key
, cmd_helper
.DoubleQuote(value
))
508 return self
.adb
.Shell(cmd
)
509 except device_errors
.AdbCommandFailedError
as exc
:
515 if not isinstance(cmd
, basestring
):
516 cmd
= ' '.join(cmd_helper
.SingleQuote(s
) for s
in cmd
)
518 env
= ' '.join(env_quote(k
, v
) for k
, v
in env
.iteritems())
519 cmd
= '%s %s' % (env
, cmd
)
521 cmd
= 'cd %s && %s' % (cmd_helper
.SingleQuote(cwd
), cmd
)
522 if as_root
and self
.NeedsSU():
523 # "su -c sh -c" allows using shell features in |cmd|
524 cmd
= 'su -c sh -c %s' % cmd_helper
.SingleQuote(cmd
)
526 timeout
= self
._default
_timeout
528 if len(cmd
) < self
._MAX
_ADB
_COMMAND
_LENGTH
:
531 with device_temp_file
.DeviceTempFile(self
.adb
, suffix
='.sh') as script
:
532 self
._WriteFileWithPush
(script
.name
, cmd
)
533 logging
.info('Large shell command will be run from file: %s ...',
535 output
= do_run('sh %s' % script
.name_quoted
)
537 output
= output
.splitlines()
541 elif len(output
) == 1:
544 msg
= 'one line of output was expected, but got: %s'
545 raise device_errors
.CommandFailedError(msg
% output
, str(self
))
549 @decorators.WithTimeoutAndRetriesFromInstance()
550 def KillAll(self
, process_name
, signum
=9, as_root
=False, blocking
=False,
551 timeout
=None, retries
=None):
552 """Kill all processes with the given name on the device.
555 process_name: A string containing the name of the process to kill.
556 signum: An integer containing the signal number to send to kill. Defaults
558 as_root: A boolean indicating whether the kill should be executed with
560 blocking: A boolean indicating whether we should wait until all processes
561 with the given |process_name| are dead.
562 timeout: timeout in seconds
563 retries: number of retries
566 CommandFailedError if no process was killed.
567 CommandTimeoutError on timeout.
568 DeviceUnreachableError on missing device.
570 pids
= self
._GetPidsImpl
(process_name
)
572 raise device_errors
.CommandFailedError(
573 'No process "%s"' % process_name
, str(self
))
575 cmd
= ['kill', '-%d' % signum
] + pids
.values()
576 self
.RunShellCommand(cmd
, as_root
=as_root
, check_return
=True)
580 while self
._GetPidsImpl
(process_name
):
581 time
.sleep(wait_period
)
585 @decorators.WithTimeoutAndRetriesFromInstance()
586 def StartActivity(self
, intent_obj
, blocking
=False, trace_file_name
=None,
587 force_stop
=False, timeout
=None, retries
=None):
588 """Start package's activity on the device.
591 intent_obj: An Intent object to send.
592 blocking: A boolean indicating whether we should wait for the activity to
594 trace_file_name: If present, a string that both indicates that we want to
595 profile the activity and contains the path to which the
596 trace should be saved.
597 force_stop: A boolean indicating whether we should stop the activity
599 timeout: timeout in seconds
600 retries: number of retries
603 CommandFailedError if the activity could not be started.
604 CommandTimeoutError on timeout.
605 DeviceUnreachableError on missing device.
607 cmd
= ['am', 'start']
611 cmd
.extend(['--start-profiler', trace_file_name
])
614 cmd
.extend(intent_obj
.am_args
)
615 for line
in self
.RunShellCommand(cmd
, check_return
=True):
616 if line
.startswith('Error:'):
617 raise device_errors
.CommandFailedError(line
, str(self
))
619 @decorators.WithTimeoutAndRetriesFromInstance()
620 def StartInstrumentation(self
, component
, finish
=True, raw
=False,
621 extras
=None, timeout
=None, retries
=None):
625 cmd
= ['am', 'instrument']
630 for k
, v
in extras
.iteritems():
631 cmd
.extend(['-e', str(k
), str(v
)])
632 cmd
.append(component
)
633 return self
.RunShellCommand(cmd
, check_return
=True)
635 @decorators.WithTimeoutAndRetriesFromInstance()
636 def BroadcastIntent(self
, intent_obj
, timeout
=None, retries
=None):
637 """Send a broadcast intent.
640 intent: An Intent to broadcast.
641 timeout: timeout in seconds
642 retries: number of retries
645 CommandTimeoutError on timeout.
646 DeviceUnreachableError on missing device.
648 cmd
= ['am', 'broadcast'] + intent_obj
.am_args
649 self
.RunShellCommand(cmd
, check_return
=True)
651 @decorators.WithTimeoutAndRetriesFromInstance()
652 def GoHome(self
, timeout
=None, retries
=None):
653 """Return to the home screen.
656 timeout: timeout in seconds
657 retries: number of retries
660 CommandTimeoutError on timeout.
661 DeviceUnreachableError on missing device.
664 intent
.Intent(action
='android.intent.action.MAIN',
665 category
='android.intent.category.HOME'),
668 @decorators.WithTimeoutAndRetriesFromInstance()
669 def ForceStop(self
, package
, timeout
=None, retries
=None):
670 """Close the application.
673 package: A string containing the name of the package to stop.
674 timeout: timeout in seconds
675 retries: number of retries
678 CommandTimeoutError on timeout.
679 DeviceUnreachableError on missing device.
681 self
.RunShellCommand(['am', 'force-stop', package
], check_return
=True)
683 @decorators.WithTimeoutAndRetriesFromInstance()
684 def ClearApplicationState(self
, package
, timeout
=None, retries
=None):
685 """Clear all state for the given package.
688 package: A string containing the name of the package to stop.
689 timeout: timeout in seconds
690 retries: number of retries
693 CommandTimeoutError on timeout.
694 DeviceUnreachableError on missing device.
696 # Check that the package exists before clearing it for android builds below
697 # JB MR2. Necessary because calling pm clear on a package that doesn't exist
699 if ((self
.build_version_sdk
>=
700 constants
.ANDROID_SDK_VERSION_CODES
.JELLY_BEAN_MR2
)
701 or self
.GetApplicationPath(package
)):
702 self
.RunShellCommand(['pm', 'clear', package
], check_return
=True)
704 @decorators.WithTimeoutAndRetriesFromInstance()
705 def SendKeyEvent(self
, keycode
, timeout
=None, retries
=None):
706 """Sends a keycode to the device.
708 See: http://developer.android.com/reference/android/view/KeyEvent.html
711 keycode: A integer keycode to send to the device.
712 timeout: timeout in seconds
713 retries: number of retries
716 CommandTimeoutError on timeout.
717 DeviceUnreachableError on missing device.
719 self
.RunShellCommand(['input', 'keyevent', format(keycode
, 'd')],
722 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT
= 10 * _DEFAULT_TIMEOUT
723 PUSH_CHANGED_FILES_DEFAULT_RETRIES
= _DEFAULT_RETRIES
725 @decorators.WithTimeoutAndRetriesDefaults(
726 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT
,
727 PUSH_CHANGED_FILES_DEFAULT_RETRIES
)
728 def PushChangedFiles(self
, host_device_tuples
, timeout
=None,
730 """Push files to the device, skipping files that don't need updating.
733 host_device_tuples: A list of (host_path, device_path) tuples, where
734 |host_path| is an absolute path of a file or directory on the host
735 that should be minimially pushed to the device, and |device_path| is
736 an absolute path of the destination on the device.
737 timeout: timeout in seconds
738 retries: number of retries
741 CommandFailedError on failure.
742 CommandTimeoutError on timeout.
743 DeviceUnreachableError on missing device.
747 for h
, d
in host_device_tuples
:
749 self
.RunShellCommand(['mkdir', '-p', d
], check_return
=True)
750 files
+= self
._GetChangedFilesImpl
(h
, d
)
755 size
= sum(host_utils
.GetRecursiveDiskUsage(h
) for h
, _
in files
)
756 file_count
= len(files
)
757 dir_size
= sum(host_utils
.GetRecursiveDiskUsage(h
)
758 for h
, _
in host_device_tuples
)
760 for h
, _
in host_device_tuples
:
762 dir_file_count
+= sum(len(f
) for _r
, _d
, f
in os
.walk(h
))
766 push_duration
= self
._ApproximateDuration
(
767 file_count
, file_count
, size
, False)
768 dir_push_duration
= self
._ApproximateDuration
(
769 len(host_device_tuples
), dir_file_count
, dir_size
, False)
770 zip_duration
= self
._ApproximateDuration
(1, 1, size
, True)
772 self
._InstallCommands
()
774 if dir_push_duration
< push_duration
and (
775 dir_push_duration
< zip_duration
or not self
._commands
_installed
):
776 self
._PushChangedFilesIndividually
(host_device_tuples
)
777 elif push_duration
< zip_duration
or not self
._commands
_installed
:
778 self
._PushChangedFilesIndividually
(files
)
780 self
._PushChangedFilesZipped
(files
)
781 self
.RunShellCommand(
782 ['chmod', '-R', '777'] + [d
for _
, d
in host_device_tuples
],
783 as_root
=True, check_return
=True)
785 def _GetChangedFilesImpl(self
, host_path
, device_path
):
786 real_host_path
= os
.path
.realpath(host_path
)
788 real_device_path
= self
.RunShellCommand(
789 ['realpath', device_path
], single_line
=True, check_return
=True)
790 except device_errors
.CommandFailedError
:
791 real_device_path
= None
792 if not real_device_path
:
793 return [(host_path
, device_path
)]
795 host_hash_tuples
= md5sum
.CalculateHostMd5Sums([real_host_path
])
796 device_paths_to_md5
= (
797 real_device_path
if os
.path
.isfile(real_host_path
)
798 else ('%s/%s' % (real_device_path
, os
.path
.relpath(p
, real_host_path
))
799 for _
, p
in host_hash_tuples
))
800 device_hash_tuples
= md5sum
.CalculateDeviceMd5Sums(
801 device_paths_to_md5
, self
)
803 if os
.path
.isfile(host_path
):
804 if (not device_hash_tuples
805 or device_hash_tuples
[0].hash != host_hash_tuples
[0].hash):
806 return [(host_path
, device_path
)]
810 device_tuple_dict
= dict((d
.path
, d
.hash) for d
in device_hash_tuples
)
812 for host_hash
, host_abs_path
in (
813 (h
.hash, h
.path
) for h
in host_hash_tuples
):
814 device_abs_path
= '%s/%s' % (
815 real_device_path
, os
.path
.relpath(host_abs_path
, real_host_path
))
816 if (device_abs_path
not in device_tuple_dict
817 or device_tuple_dict
[device_abs_path
] != host_hash
):
818 to_push
.append((host_abs_path
, device_abs_path
))
821 def _InstallCommands(self
):
822 if self
._commands
_installed
is None:
824 if not install_commands
.Installed(self
):
825 install_commands
.InstallCommands(self
)
826 self
._commands
_installed
= True
827 except Exception as e
:
828 logging
.warning('unzip not available: %s' % str(e
))
829 self
._commands
_installed
= False
832 def _ApproximateDuration(adb_calls
, file_count
, byte_count
, is_zipping
):
833 # We approximate the time to push a set of files to a device as:
834 # t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where
835 # t: total time (sec)
836 # c1: adb call time delay (sec)
837 # a: number of times adb is called (unitless)
838 # c2: push time delay (sec)
839 # f: number of files pushed via adb (unitless)
840 # c3: zip time delay (sec)
841 # c4: zip rate (bytes/sec)
842 # b: total number of bytes (bytes)
843 # c5: transfer rate (bytes/sec)
844 # c6: compression ratio (unitless)
846 # All of these are approximations.
847 ADB_CALL_PENALTY
= 0.1 # seconds
848 ADB_PUSH_PENALTY
= 0.01 # seconds
849 ZIP_PENALTY
= 2.0 # seconds
850 ZIP_RATE
= 10000000.0 # bytes / second
851 TRANSFER_RATE
= 2000000.0 # bytes / second
852 COMPRESSION_RATIO
= 2.0 # unitless
854 adb_call_time
= ADB_CALL_PENALTY
* adb_calls
855 adb_push_setup_time
= ADB_PUSH_PENALTY
* file_count
857 zip_time
= ZIP_PENALTY
+ byte_count
/ ZIP_RATE
858 transfer_time
= byte_count
/ (TRANSFER_RATE
* COMPRESSION_RATIO
)
861 transfer_time
= byte_count
/ TRANSFER_RATE
862 return adb_call_time
+ adb_push_setup_time
+ zip_time
+ transfer_time
864 def _PushChangedFilesIndividually(self
, files
):
868 def _PushChangedFilesZipped(self
, files
):
872 with tempfile
.NamedTemporaryFile(suffix
='.zip') as zip_file
:
873 zip_proc
= multiprocessing
.Process(
874 target
=DeviceUtils
._CreateDeviceZip
,
875 args
=(zip_file
.name
, files
))
879 zip_on_device
= '%s/tmp.zip' % self
.GetExternalStoragePath()
881 self
.adb
.Push(zip_file
.name
, zip_on_device
)
882 self
.RunShellCommand(
883 ['unzip', zip_on_device
],
885 env
={'PATH': '%s:$PATH' % install_commands
.BIN_DIR
},
888 if zip_proc
.is_alive():
891 self
.RunShellCommand(['rm', zip_on_device
], check_return
=True)
894 def _CreateDeviceZip(zip_path
, host_device_tuples
):
895 with zipfile
.ZipFile(zip_path
, 'w') as zip_file
:
896 for host_path
, device_path
in host_device_tuples
:
897 zip_utils
.WriteToZipFile(zip_file
, host_path
, device_path
)
899 @decorators.WithTimeoutAndRetriesFromInstance()
900 def FileExists(self
, device_path
, timeout
=None, retries
=None):
901 """Checks whether the given file exists on the device.
904 device_path: A string containing the absolute path to the file on the
906 timeout: timeout in seconds
907 retries: number of retries
910 True if the file exists on the device, False otherwise.
913 CommandTimeoutError on timeout.
914 DeviceUnreachableError on missing device.
917 self
.RunShellCommand(['test', '-e', device_path
], check_return
=True)
919 except device_errors
.AdbCommandFailedError
:
922 @decorators.WithTimeoutAndRetriesFromInstance()
923 def PullFile(self
, device_path
, host_path
, timeout
=None, retries
=None):
924 """Pull a file from the device.
927 device_path: A string containing the absolute path of the file to pull
929 host_path: A string containing the absolute path of the destination on
931 timeout: timeout in seconds
932 retries: number of retries
935 CommandFailedError on failure.
936 CommandTimeoutError on timeout.
938 # Create the base dir if it doesn't exist already
939 dirname
= os
.path
.dirname(host_path
)
940 if dirname
and not os
.path
.exists(dirname
):
942 self
.adb
.Pull(device_path
, host_path
)
944 def _ReadFileWithPull(self
, device_path
):
946 d
= tempfile
.mkdtemp()
947 host_temp_path
= os
.path
.join(d
, 'tmp_ReadFileWithPull')
948 self
.adb
.Pull(device_path
, host_temp_path
)
949 with
open(host_temp_path
, 'r') as host_temp
:
950 return host_temp
.read()
952 if os
.path
.exists(d
):
956 r
'(?P<perms>\S+) +(?P<owner>\S+) +(?P<group>\S+) +(?:(?P<size>\d+) +)?'
957 + r
'(?P<date>\S+) +(?P<time>\S+) +(?P<name>.+)$')
959 @decorators.WithTimeoutAndRetriesFromInstance()
960 def ReadFile(self
, device_path
, as_root
=False,
961 timeout
=None, retries
=None):
962 """Reads the contents of a file from the device.
965 device_path: A string containing the absolute path of the file to read
967 as_root: A boolean indicating whether the read should be executed with
969 timeout: timeout in seconds
970 retries: number of retries
973 The contents of |device_path| as a string. Contents are intepreted using
974 universal newlines, so the caller will see them encoded as '\n'. Also,
975 all lines will be terminated.
978 AdbCommandFailedError if the file can't be read.
979 CommandTimeoutError on timeout.
980 DeviceUnreachableError on missing device.
982 # TODO(jbudorick): Implement a generic version of Stat() that handles
983 # as_root=True, then switch this implementation to use that.
985 ls_out
= self
.RunShellCommand(['ls', '-l', device_path
], as_root
=as_root
,
988 m
= self
._LS
_RE
.match(line
)
989 if m
and m
.group('name') == posixpath
.basename(device_path
):
990 size
= int(m
.group('size'))
993 logging
.warning('Could not determine size of %s.', device_path
)
995 if size
is None or size
<= self
._MAX
_ADB
_OUTPUT
_LENGTH
:
996 return _JoinLines(self
.RunShellCommand(
997 ['cat', device_path
], as_root
=as_root
, check_return
=True))
998 elif as_root
and self
.NeedsSU():
999 with device_temp_file
.DeviceTempFile(self
.adb
) as device_temp
:
1000 self
.RunShellCommand(['cp', device_path
, device_temp
.name
],
1001 as_root
=True, check_return
=True)
1002 return self
._ReadFileWithPull
(device_temp
.name
)
1004 return self
._ReadFileWithPull
(device_path
)
1006 def _WriteFileWithPush(self
, device_path
, contents
):
1007 with tempfile
.NamedTemporaryFile() as host_temp
:
1008 host_temp
.write(contents
)
1010 self
.adb
.Push(host_temp
.name
, device_path
)
1012 @decorators.WithTimeoutAndRetriesFromInstance()
1013 def WriteFile(self
, device_path
, contents
, as_root
=False, force_push
=False,
1014 timeout
=None, retries
=None):
1015 """Writes |contents| to a file on the device.
1018 device_path: A string containing the absolute path to the file to write
1020 contents: A string containing the data to write to the device.
1021 as_root: A boolean indicating whether the write should be executed with
1022 root privileges (if available).
1023 force_push: A boolean indicating whether to force the operation to be
1024 performed by pushing a file to the device. The default is, when the
1025 contents are short, to pass the contents using a shell script instead.
1026 timeout: timeout in seconds
1027 retries: number of retries
1030 CommandFailedError if the file could not be written on the device.
1031 CommandTimeoutError on timeout.
1032 DeviceUnreachableError on missing device.
1034 if not force_push
and len(contents
) < self
._MAX
_ADB
_COMMAND
_LENGTH
:
1035 # If the contents are small, for efficieny we write the contents with
1036 # a shell command rather than pushing a file.
1037 cmd
= 'echo -n %s > %s' % (cmd_helper
.SingleQuote(contents
),
1038 cmd_helper
.SingleQuote(device_path
))
1039 self
.RunShellCommand(cmd
, as_root
=as_root
, check_return
=True)
1040 elif as_root
and self
.NeedsSU():
1041 # Adb does not allow to "push with su", so we first push to a temp file
1042 # on a safe location, and then copy it to the desired location with su.
1043 with device_temp_file
.DeviceTempFile(self
.adb
) as device_temp
:
1044 self
._WriteFileWithPush
(device_temp
.name
, contents
)
1045 # Here we need 'cp' rather than 'mv' because the temp and
1046 # destination files might be on different file systems (e.g.
1047 # on internal storage and an external sd card).
1048 self
.RunShellCommand(['cp', device_temp
.name
, device_path
],
1049 as_root
=True, check_return
=True)
1051 # If root is not needed, we can push directly to the desired location.
1052 self
._WriteFileWithPush
(device_path
, contents
)
1054 @decorators.WithTimeoutAndRetriesFromInstance()
1055 def Ls(self
, device_path
, timeout
=None, retries
=None):
1056 """Lists the contents of a directory on the device.
1059 device_path: A string containing the path of the directory on the device
1061 timeout: timeout in seconds
1062 retries: number of retries
1065 A list of pairs (filename, stat) for each file found in the directory,
1066 where the stat object has the properties: st_mode, st_size, and st_time.
1069 AdbCommandFailedError if |device_path| does not specify a valid and
1070 accessible directory in the device.
1071 CommandTimeoutError on timeout.
1072 DeviceUnreachableError on missing device.
1074 return self
.adb
.Ls(device_path
)
1076 @decorators.WithTimeoutAndRetriesFromInstance()
1077 def Stat(self
, device_path
, timeout
=None, retries
=None):
1078 """Get the stat attributes of a file or directory on the device.
1081 device_path: A string containing the path of from which to get attributes
1083 timeout: timeout in seconds
1084 retries: number of retries
1087 A stat object with the properties: st_mode, st_size, and st_time
1090 CommandFailedError if device_path cannot be found on the device.
1091 CommandTimeoutError on timeout.
1092 DeviceUnreachableError on missing device.
1094 dirname
, target
= device_path
.rsplit('/', 1)
1095 for filename
, stat
in self
.adb
.Ls(dirname
):
1096 if filename
== target
:
1098 raise device_errors
.CommandFailedError(
1099 'Cannot find file or directory: %r' % device_path
, str(self
))
1101 @decorators.WithTimeoutAndRetriesFromInstance()
1102 def SetJavaAsserts(self
, enabled
, timeout
=None, retries
=None):
1103 """Enables or disables Java asserts.
1106 enabled: A boolean indicating whether Java asserts should be enabled
1108 timeout: timeout in seconds
1109 retries: number of retries
1112 True if the device-side property changed and a restart is required as a
1113 result, False otherwise.
1116 CommandTimeoutError on timeout.
1118 def find_property(lines
, property_name
):
1119 for index
, line
in enumerate(lines
):
1120 if line
.strip() == '':
1122 key
, value
= (s
.strip() for s
in line
.split('=', 1))
1123 if key
== property_name
:
1127 new_value
= 'all' if enabled
else ''
1129 # First ensure the desired property is persisted.
1131 properties
= self
.ReadFile(
1132 constants
.DEVICE_LOCAL_PROPERTIES_PATH
).splitlines()
1133 except device_errors
.CommandFailedError
:
1135 index
, value
= find_property(properties
, self
.JAVA_ASSERT_PROPERTY
)
1136 if new_value
!= value
:
1138 new_line
= '%s=%s' % (self
.JAVA_ASSERT_PROPERTY
, new_value
)
1140 properties
.append(new_line
)
1142 properties
[index
] = new_line
1144 assert index
is not None # since new_value == '' and new_value != value
1145 properties
.pop(index
)
1146 self
.WriteFile(constants
.DEVICE_LOCAL_PROPERTIES_PATH
,
1147 _JoinLines(properties
))
1149 # Next, check the current runtime value is what we need, and
1150 # if not, set it and report that a reboot is required.
1151 value
= self
.GetProp(self
.JAVA_ASSERT_PROPERTY
)
1152 if new_value
!= value
:
1153 self
.SetProp(self
.JAVA_ASSERT_PROPERTY
, new_value
)
1160 def build_description(self
):
1161 """Returns the build description of the system.
1164 nakasi-user 4.4.4 KTU84P 1227136 release-keys
1166 return self
.GetProp('ro.build.description', cache
=True)
1169 def build_fingerprint(self
):
1170 """Returns the build fingerprint of the system.
1173 google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys
1175 return self
.GetProp('ro.build.fingerprint', cache
=True)
1179 """Returns the build ID of the system (e.g. 'KTU84P')."""
1180 return self
.GetProp('ro.build.id', cache
=True)
1183 def build_product(self
):
1184 """Returns the build product of the system (e.g. 'grouper')."""
1185 return self
.GetProp('ro.build.product', cache
=True)
1188 def build_type(self
):
1189 """Returns the build type of the system (e.g. 'user')."""
1190 return self
.GetProp('ro.build.type', cache
=True)
1193 def build_version_sdk(self
):
1194 """Returns the build version sdk of the system as a number (e.g. 19).
1196 For version code numbers see:
1197 http://developer.android.com/reference/android/os/Build.VERSION_CODES.html
1199 For named constants see:
1200 pylib.constants.ANDROID_SDK_VERSION_CODES
1203 CommandFailedError if the build version sdk is not a number.
1205 value
= self
.GetProp('ro.build.version.sdk', cache
=True)
1209 raise device_errors
.CommandFailedError(
1210 'Invalid build version sdk: %r' % value
)
1213 def product_cpu_abi(self
):
1214 """Returns the product cpu abi of the device (e.g. 'armeabi-v7a')."""
1215 return self
.GetProp('ro.product.cpu.abi', cache
=True)
1218 def product_model(self
):
1219 """Returns the name of the product model (e.g. 'Nexus 7')."""
1220 return self
.GetProp('ro.product.model', cache
=True)
1223 def product_name(self
):
1224 """Returns the product name of the device (e.g. 'nakasi')."""
1225 return self
.GetProp('ro.product.name', cache
=True)
1227 def GetProp(self
, property_name
, cache
=False, timeout
=DEFAULT
,
1229 """Gets a property from the device.
1232 property_name: A string containing the name of the property to get from
1234 cache: A boolean indicating whether to cache the value of this property.
1235 timeout: timeout in seconds
1236 retries: number of retries
1239 The value of the device's |property_name| property.
1242 CommandTimeoutError on timeout.
1244 assert isinstance(property_name
, basestring
), (
1245 "property_name is not a string: %r" % property_name
)
1247 cache_key
= '_prop:' + property_name
1248 if cache
and cache_key
in self
._cache
:
1249 return self
._cache
[cache_key
]
1251 # timeout and retries are handled down at run shell, because we don't
1252 # want to apply them in the other branch when reading from the cache
1253 value
= self
.RunShellCommand(
1254 ['getprop', property_name
], single_line
=True, check_return
=True,
1255 timeout
=self
._default
_timeout
if timeout
is DEFAULT
else timeout
,
1256 retries
=self
._default
_retries
if retries
is DEFAULT
else retries
)
1257 if cache
or cache_key
in self
._cache
:
1258 self
._cache
[cache_key
] = value
1261 @decorators.WithTimeoutAndRetriesFromInstance()
1262 def SetProp(self
, property_name
, value
, check
=False, timeout
=None,
1264 """Sets a property on the device.
1267 property_name: A string containing the name of the property to set on
1269 value: A string containing the value to set to the property on the
1271 check: A boolean indicating whether to check that the property was
1272 successfully set on the device.
1273 timeout: timeout in seconds
1274 retries: number of retries
1277 CommandFailedError if check is true and the property was not correctly
1278 set on the device (e.g. because it is not rooted).
1279 CommandTimeoutError on timeout.
1281 assert isinstance(property_name
, basestring
), (
1282 "property_name is not a string: %r" % property_name
)
1283 assert isinstance(value
, basestring
), "value is not a string: %r" % value
1285 self
.RunShellCommand(['setprop', property_name
, value
], check_return
=True)
1286 if property_name
in self
._cache
:
1287 del self
._cache
[property_name
]
1288 # TODO(perezju) remove the option and make the check mandatory, but using a
1289 # single shell script to both set- and getprop.
1290 if check
and value
!= self
.GetProp(property_name
):
1291 raise device_errors
.CommandFailedError(
1292 'Unable to set property %r on the device to %r'
1293 % (property_name
, value
), str(self
))
1295 @decorators.WithTimeoutAndRetriesFromInstance()
1296 def GetABI(self
, timeout
=None, retries
=None):
1297 """Gets the device main ABI.
1300 timeout: timeout in seconds
1301 retries: number of retries
1304 The device's main ABI name.
1307 CommandTimeoutError on timeout.
1309 return self
.GetProp('ro.product.cpu.abi')
1311 @decorators.WithTimeoutAndRetriesFromInstance()
1312 def GetPids(self
, process_name
, timeout
=None, retries
=None):
1313 """Returns the PIDs of processes with the given name.
1315 Note that the |process_name| is often the package name.
1318 process_name: A string containing the process name to get the PIDs for.
1319 timeout: timeout in seconds
1320 retries: number of retries
1323 A dict mapping process name to PID for each process that contained the
1324 provided |process_name|.
1327 CommandTimeoutError on timeout.
1328 DeviceUnreachableError on missing device.
1330 return self
._GetPidsImpl
(process_name
)
1332 def _GetPidsImpl(self
, process_name
):
1334 for line
in self
.RunShellCommand('ps', check_return
=True):
1336 ps_data
= line
.split()
1337 if process_name
in ps_data
[-1]:
1338 procs_pids
[ps_data
[-1]] = ps_data
[1]
1343 @decorators.WithTimeoutAndRetriesFromInstance()
1344 def TakeScreenshot(self
, host_path
=None, timeout
=None, retries
=None):
1345 """Takes a screenshot of the device.
1348 host_path: A string containing the path on the host to save the
1349 screenshot to. If None, a file name in the current
1350 directory will be generated.
1351 timeout: timeout in seconds
1352 retries: number of retries
1355 The name of the file on the host to which the screenshot was saved.
1358 CommandFailedError on failure.
1359 CommandTimeoutError on timeout.
1360 DeviceUnreachableError on missing device.
1363 host_path
= os
.path
.abspath('screenshot-%s.png' % _GetTimeStamp())
1364 with device_temp_file
.DeviceTempFile(self
.adb
, suffix
='.png') as device_tmp
:
1365 self
.RunShellCommand(['/system/bin/screencap', '-p', device_tmp
.name
],
1367 self
.PullFile(device_tmp
.name
, host_path
)
1370 @decorators.WithTimeoutAndRetriesFromInstance()
1371 def GetMemoryUsageForPid(self
, pid
, timeout
=None, retries
=None):
1372 """Gets the memory usage for the given PID.
1375 pid: PID of the process.
1376 timeout: timeout in seconds
1377 retries: number of retries
1380 A dict containing memory usage statistics for the PID. May include:
1381 Size, Rss, Pss, Shared_Clean, Shared_Dirty, Private_Clean,
1382 Private_Dirty, VmHWM
1385 CommandTimeoutError on timeout.
1387 result
= collections
.defaultdict(int)
1390 result
.update(self
._GetMemoryUsageForPidFromSmaps
(pid
))
1391 except device_errors
.CommandFailedError
:
1392 logging
.exception('Error getting memory usage from smaps')
1395 result
.update(self
._GetMemoryUsageForPidFromStatus
(pid
))
1396 except device_errors
.CommandFailedError
:
1397 logging
.exception('Error getting memory usage from status')
1401 def _GetMemoryUsageForPidFromSmaps(self
, pid
):
1403 'Size', 'Rss', 'Pss', 'Shared_Clean', 'Shared_Dirty', 'Private_Clean',
1406 showmap_out
= self
.RunShellCommand(
1407 ['showmap', str(pid
)], as_root
=True, check_return
=True)
1409 raise device_errors
.CommandFailedError('No output from showmap')
1411 split_totals
= showmap_out
[-1].split()
1412 if (not split_totals
1413 or len(split_totals
) != 9
1414 or split_totals
[-1] != 'TOTAL'):
1415 raise device_errors
.CommandFailedError(
1416 'Invalid output from showmap: %s' % '\n'.join(showmap_out
))
1418 return dict(itertools
.izip(SMAPS_COLUMNS
, (int(n
) for n
in split_totals
)))
1420 def _GetMemoryUsageForPidFromStatus(self
, pid
):
1421 for line
in self
.ReadFile(
1422 '/proc/%s/status' % str(pid
), as_root
=True).splitlines():
1423 if line
.startswith('VmHWM:'):
1424 return {'VmHWM': int(line
.split()[1])}
1426 raise device_errors
.CommandFailedError(
1427 'Could not find memory peak value for pid %s', str(pid
))
1429 @decorators.WithTimeoutAndRetriesFromInstance()
1430 def GetLogcatMonitor(self
, timeout
=None, retries
=None, *args
, **kwargs
):
1431 """Returns a new LogcatMonitor associated with this device.
1433 Parameters passed to this function are passed directly to
1434 |logcat_monitor.LogcatMonitor| and are documented there.
1437 timeout: timeout in seconds
1438 retries: number of retries
1440 return logcat_monitor
.LogcatMonitor(self
.adb
, *args
, **kwargs
)
1442 @decorators.WithTimeoutAndRetriesFromInstance()
1443 def GetDevicePieWrapper(self
, timeout
=None, retries
=None):
1444 """Gets the absolute path to the run_pie wrapper on the device.
1446 We have to build our device executables to be PIE, but they need to be able
1447 to run on versions of android that don't support PIE (i.e. ICS and below).
1448 To do so, we push a wrapper to the device that lets older android versions
1449 run PIE executables. This method pushes that wrapper to the device if
1450 necessary and returns the path to it.
1452 This is exposed publicly to allow clients to write scripts using run_pie
1453 (e.g. md5sum.CalculateDeviceMd5Sum).
1456 timeout: timeout in seconds
1457 retries: number of retries
1460 The path to the PIE wrapper on the device, or an empty string if the
1461 device does not require the wrapper.
1463 if 'run_pie' not in self
._cache
:
1465 if (self
.build_version_sdk
<
1466 constants
.ANDROID_SDK_VERSION_CODES
.JELLY_BEAN
):
1467 host_pie_path
= os
.path
.join(constants
.GetOutDirectory(), 'run_pie')
1468 if not os
.path
.exists(host_pie_path
):
1469 raise device_errors
.CommandFailedError('Please build run_pie')
1470 pie
= '%s/run_pie' % constants
.TEST_EXECUTABLE_DIR
1471 self
.adb
.Push(host_pie_path
, pie
)
1473 self
._cache
['run_pie'] = pie
1475 return self
._cache
['run_pie']
1478 def parallel(cls
, devices
=None, async=False):
1479 """Creates a Parallelizer to operate over the provided list of devices.
1481 If |devices| is either |None| or an empty list, the Parallelizer will
1482 operate over all attached devices.
1485 devices: A list of either DeviceUtils instances or objects from
1486 from which DeviceUtils instances can be constructed. If None,
1487 all attached devices will be used.
1488 async: If true, returns a Parallelizer that runs operations
1492 A Parallelizer operating over |devices|.
1495 devices
= adb_wrapper
.AdbWrapper
.GetDevices()
1497 raise device_errors
.NoDevicesError()
1498 devices
= [d
if isinstance(d
, cls
) else cls(d
) for d
in devices
]
1500 return parallelizer
.Parallelizer(devices
)
1502 return parallelizer
.SyncParallelizer(devices
)
1504 def GetClientCache(self
, client_name
):
1505 """Returns client cache."""
1506 if client_name
not in self
._client
_caches
:
1507 self
._client
_caches
[client_name
] = {}
1508 return self
._client
_caches
[client_name
]
1510 def _ClearCache(self
):
1511 """Clears all caches."""
1512 for client
in self
._client
_caches
:
1513 self
._client
_caches
[client
].clear()