Index: build/android/pylib/device/device_utils.py |
diff --git a/build/android/pylib/device/device_utils.py b/build/android/pylib/device/device_utils.py |
index 4d2fff0a88ede41f80fcf34868231d8302029210..b8e8de24d065aa164750d5185cb2fe97c8eb49bc 100644 |
--- a/build/android/pylib/device/device_utils.py |
+++ b/build/android/pylib/device/device_utils.py |
@@ -1,1925 +1,8 @@ |
-# Copyright 2014 The Chromium Authors. All rights reserved. |
+# Copyright 2015 The Chromium Authors. All rights reserved. |
# Use of this source code is governed by a BSD-style license that can be |
# found in the LICENSE file. |
-"""Provides a variety of device interactions based on adb. |
+# pylint: disable=unused-wildcard-import |
+# pylint: disable=wildcard-import |
-Eventually, this will be based on adb_wrapper. |
-""" |
-# pylint: disable=unused-argument |
- |
-import collections |
-import contextlib |
-import itertools |
-import logging |
-import multiprocessing |
-import os |
-import posixpath |
-import re |
-import shutil |
-import sys |
-import tempfile |
-import threading |
-import time |
-import zipfile |
- |
-from pylib import cmd_helper |
-from pylib import constants |
-from pylib import device_signal |
-from pylib.constants import keyevent |
-from pylib.device import adb_wrapper |
-from pylib.device import decorators |
-from pylib.device import device_blacklist |
-from pylib.device import device_errors |
-from pylib.device import intent |
-from pylib.device import logcat_monitor |
-from pylib.device.commands import install_commands |
-from pylib.sdk import split_select |
-from pylib.utils import apk_helper |
-from pylib.utils import base_error |
-from pylib.utils import device_temp_file |
-from pylib.utils import host_utils |
-from pylib.utils import md5sum |
-from pylib.utils import parallelizer |
-from pylib.utils import timeout_retry |
-from pylib.utils import zip_utils |
- |
-_DEFAULT_TIMEOUT = 30 |
-_DEFAULT_RETRIES = 3 |
- |
-# A sentinel object for default values |
-# TODO(jbudorick,perezju): revisit how default values are handled by |
-# the timeout_retry decorators. |
-DEFAULT = object() |
- |
-_CONTROL_CHARGING_COMMANDS = [ |
- { |
- # Nexus 4 |
- 'witness_file': '/sys/module/pm8921_charger/parameters/disabled', |
- 'enable_command': 'echo 0 > /sys/module/pm8921_charger/parameters/disabled', |
- 'disable_command': |
- 'echo 1 > /sys/module/pm8921_charger/parameters/disabled', |
- }, |
- { |
- # Nexus 5 |
- # Setting the HIZ bit of the bq24192 causes the charger to actually ignore |
- # energy coming from USB. Setting the power_supply offline just updates the |
- # Android system to reflect that. |
- 'witness_file': '/sys/kernel/debug/bq24192/INPUT_SRC_CONT', |
- 'enable_command': ( |
- 'echo 0x4A > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' |
- 'echo 1 > /sys/class/power_supply/usb/online'), |
- 'disable_command': ( |
- 'echo 0xCA > /sys/kernel/debug/bq24192/INPUT_SRC_CONT && ' |
- 'chmod 644 /sys/class/power_supply/usb/online && ' |
- 'echo 0 > /sys/class/power_supply/usb/online'), |
- }, |
-] |
- |
-_RESTART_ADBD_SCRIPT = """ |
- trap '' HUP |
- trap '' TERM |
- trap '' PIPE |
- function restart() { |
- stop adbd |
- start adbd |
- } |
- restart & |
-""" |
- |
-_CURRENT_FOCUS_CRASH_RE = re.compile( |
- r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') |
- |
- |
-@decorators.WithExplicitTimeoutAndRetries( |
- _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) |
-def GetAVDs(): |
- """Returns a list of Android Virtual Devices. |
- |
- Returns: |
- A list containing the configured AVDs. |
- """ |
- lines = cmd_helper.GetCmdOutput([ |
- os.path.join(constants.ANDROID_SDK_ROOT, 'tools', 'android'), |
- 'list', 'avd']).splitlines() |
- avds = [] |
- for line in lines: |
- if 'Name:' not in line: |
- continue |
- key, value = (s.strip() for s in line.split(':', 1)) |
- if key == 'Name': |
- avds.append(value) |
- return avds |
- |
- |
-@decorators.WithExplicitTimeoutAndRetries( |
- _DEFAULT_TIMEOUT, _DEFAULT_RETRIES) |
-def RestartServer(): |
- """Restarts the adb server. |
- |
- Raises: |
- CommandFailedError if we fail to kill or restart the server. |
- """ |
- def adb_killed(): |
- return not adb_wrapper.AdbWrapper.IsServerOnline() |
- |
- def adb_started(): |
- return adb_wrapper.AdbWrapper.IsServerOnline() |
- |
- adb_wrapper.AdbWrapper.KillServer() |
- if not timeout_retry.WaitFor(adb_killed, wait_period=1, max_tries=5): |
- # TODO(perezju): raise an exception after fixng http://crbug.com/442319 |
- logging.warning('Failed to kill adb server') |
- adb_wrapper.AdbWrapper.StartServer() |
- if not timeout_retry.WaitFor(adb_started, wait_period=1, max_tries=5): |
- raise device_errors.CommandFailedError('Failed to start adb server') |
- |
- |
-def _GetTimeStamp(): |
- """Return a basic ISO 8601 time stamp with the current local time.""" |
- return time.strftime('%Y%m%dT%H%M%S', time.localtime()) |
- |
- |
-def _JoinLines(lines): |
- # makes sure that the last line is also terminated, and is more memory |
- # efficient than first appending an end-line to each line and then joining |
- # all of them together. |
- return ''.join(s for line in lines for s in (line, '\n')) |
- |
- |
-class DeviceUtils(object): |
- |
- _MAX_ADB_COMMAND_LENGTH = 512 |
- _MAX_ADB_OUTPUT_LENGTH = 32768 |
- _LAUNCHER_FOCUSED_RE = re.compile( |
- '\s*mCurrentFocus.*(Launcher|launcher).*') |
- _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') |
- |
- # Property in /data/local.prop that controls Java assertions. |
- JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' |
- |
- def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, |
- default_retries=_DEFAULT_RETRIES): |
- """DeviceUtils constructor. |
- |
- Args: |
- device: Either a device serial, an existing AdbWrapper instance, or an |
- an existing AndroidCommands instance. |
- default_timeout: An integer containing the default number of seconds to |
- wait for an operation to complete if no explicit value |
- is provided. |
- default_retries: An integer containing the default number or times an |
- operation should be retried on failure if no explicit |
- value is provided. |
- """ |
- self.adb = None |
- if isinstance(device, basestring): |
- self.adb = adb_wrapper.AdbWrapper(device) |
- elif isinstance(device, adb_wrapper.AdbWrapper): |
- self.adb = device |
- else: |
- raise ValueError('Unsupported device value: %r' % device) |
- self._commands_installed = None |
- self._default_timeout = default_timeout |
- self._default_retries = default_retries |
- self._cache = {} |
- self._client_caches = {} |
- assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) |
- assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) |
- |
- self._ClearCache() |
- |
- def __eq__(self, other): |
- """Checks whether |other| refers to the same device as |self|. |
- |
- Args: |
- other: The object to compare to. This can be a basestring, an instance |
- of adb_wrapper.AdbWrapper, or an instance of DeviceUtils. |
- Returns: |
- Whether |other| refers to the same device as |self|. |
- """ |
- return self.adb.GetDeviceSerial() == str(other) |
- |
- def __lt__(self, other): |
- """Compares two instances of DeviceUtils. |
- |
- This merely compares their serial numbers. |
- |
- Args: |
- other: The instance of DeviceUtils to compare to. |
- Returns: |
- Whether |self| is less than |other|. |
- """ |
- return self.adb.GetDeviceSerial() < other.adb.GetDeviceSerial() |
- |
- def __str__(self): |
- """Returns the device serial.""" |
- return self.adb.GetDeviceSerial() |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def IsOnline(self, timeout=None, retries=None): |
- """Checks whether the device is online. |
- |
- Args: |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- True if the device is online, False otherwise. |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- """ |
- try: |
- return self.adb.GetState() == 'device' |
- except base_error.BaseError as exc: |
- logging.info('Failed to get state: %s', exc) |
- return False |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def HasRoot(self, timeout=None, retries=None): |
- """Checks whether or not adbd has root privileges. |
- |
- Args: |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- True if adbd has root privileges, False otherwise. |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- try: |
- self.RunShellCommand('ls /root', check_return=True) |
- return True |
- except device_errors.AdbCommandFailedError: |
- return False |
- |
- def NeedsSU(self, timeout=DEFAULT, retries=DEFAULT): |
- """Checks whether 'su' is needed to access protected resources. |
- |
- Args: |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- True if 'su' is available on the device and is needed to to access |
- protected resources; False otherwise if either 'su' is not available |
- (e.g. because the device has a user build), or not needed (because adbd |
- already has root privileges). |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- if 'needs_su' not in self._cache: |
- try: |
- self.RunShellCommand( |
- '%s && ! ls /root' % self._Su('ls /root'), check_return=True, |
- timeout=self._default_timeout if timeout is DEFAULT else timeout, |
- retries=self._default_retries if retries is DEFAULT else retries) |
- self._cache['needs_su'] = True |
- except device_errors.AdbCommandFailedError: |
- self._cache['needs_su'] = False |
- return self._cache['needs_su'] |
- |
- def _Su(self, command): |
- if (self.build_version_sdk |
- >= constants.ANDROID_SDK_VERSION_CODES.MARSHMALLOW): |
- return 'su 0 %s' % command |
- return 'su -c %s' % command |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def EnableRoot(self, timeout=None, retries=None): |
- """Restarts adbd with root privileges. |
- |
- Args: |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandFailedError if root could not be enabled. |
- CommandTimeoutError on timeout. |
- """ |
- if self.IsUserBuild(): |
- raise device_errors.CommandFailedError( |
- 'Cannot enable root in user builds.', str(self)) |
- if 'needs_su' in self._cache: |
- del self._cache['needs_su'] |
- self.adb.Root() |
- self.WaitUntilFullyBooted() |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def IsUserBuild(self, timeout=None, retries=None): |
- """Checks whether or not the device is running a user build. |
- |
- Args: |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- True if the device is running a user build, False otherwise (i.e. if |
- it's running a userdebug build). |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- return self.build_type == 'user' |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GetExternalStoragePath(self, timeout=None, retries=None): |
- """Get the device's path to its SD card. |
- |
- Args: |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- The device's path to its SD card. |
- |
- Raises: |
- CommandFailedError if the external storage path could not be determined. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- if 'external_storage' in self._cache: |
- return self._cache['external_storage'] |
- |
- value = self.RunShellCommand('echo $EXTERNAL_STORAGE', |
- single_line=True, |
- check_return=True) |
- if not value: |
- raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', |
- str(self)) |
- self._cache['external_storage'] = value |
- return value |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GetApplicationPaths(self, package, timeout=None, retries=None): |
- """Get the paths of the installed apks on the device for the given package. |
- |
- Args: |
- package: Name of the package. |
- |
- Returns: |
- List of paths to the apks on the device for the given package. |
- """ |
- return self._GetApplicationPathsInternal(package) |
- |
- def _GetApplicationPathsInternal(self, package, skip_cache=False): |
- cached_result = self._cache['package_apk_paths'].get(package) |
- if cached_result is not None and not skip_cache: |
- return list(cached_result) |
- # 'pm path' is liable to incorrectly exit with a nonzero number starting |
- # in Lollipop. |
- # TODO(jbudorick): Check if this is fixed as new Android versions are |
- # released to put an upper bound on this. |
- should_check_return = (self.build_version_sdk < |
- constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
- output = self.RunShellCommand( |
- ['pm', 'path', package], check_return=should_check_return) |
- apks = [] |
- for line in output: |
- if not line.startswith('package:'): |
- raise device_errors.CommandFailedError( |
- 'pm path returned: %r' % '\n'.join(output), str(self)) |
- apks.append(line[len('package:'):]) |
- self._cache['package_apk_paths'][package] = list(apks) |
- return apks |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GetApplicationVersion(self, package, timeout=None, retries=None): |
- """Get the version name of a package installed on the device. |
- |
- Args: |
- package: Name of the package. |
- |
- Returns: |
- A string with the version name or None if the package is not found |
- on the device. |
- """ |
- output = self.RunShellCommand( |
- ['dumpsys', 'package', package], check_return=True) |
- if not output: |
- return None |
- for line in output: |
- line = line.strip() |
- if line.startswith('versionName='): |
- return line[len('versionName='):] |
- raise device_errors.CommandFailedError( |
- 'Version name for %s not found on dumpsys output' % package, str(self)) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GetApplicationDataDirectory(self, package, timeout=None, retries=None): |
- """Get the data directory on the device for the given package. |
- |
- Args: |
- package: Name of the package. |
- |
- Returns: |
- The package's data directory, or None if the package doesn't exist on the |
- device. |
- """ |
- try: |
- output = self._RunPipedShellCommand( |
- 'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package)) |
- for line in output: |
- _, _, dataDir = line.partition('dataDir=') |
- if dataDir: |
- return dataDir |
- except device_errors.CommandFailedError: |
- logging.exception('Could not find data directory for %s', package) |
- return None |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): |
- """Wait for the device to fully boot. |
- |
- This means waiting for the device to boot, the package manager to be |
- available, and the SD card to be ready. It can optionally mean waiting |
- for wifi to come up, too. |
- |
- Args: |
- wifi: A boolean indicating if we should wait for wifi to come up or not. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandFailedError on failure. |
- CommandTimeoutError if one of the component waits times out. |
- DeviceUnreachableError if the device becomes unresponsive. |
- """ |
- def sd_card_ready(): |
- try: |
- self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], |
- check_return=True) |
- return True |
- except device_errors.AdbCommandFailedError: |
- return False |
- |
- def pm_ready(): |
- try: |
- return self._GetApplicationPathsInternal('android', skip_cache=True) |
- except device_errors.CommandFailedError: |
- return False |
- |
- def boot_completed(): |
- return self.GetProp('sys.boot_completed') == '1' |
- |
- def wifi_enabled(): |
- return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], |
- check_return=False) |
- |
- self.adb.WaitForDevice() |
- timeout_retry.WaitFor(sd_card_ready) |
- timeout_retry.WaitFor(pm_ready) |
- timeout_retry.WaitFor(boot_completed) |
- if wifi: |
- timeout_retry.WaitFor(wifi_enabled) |
- |
- REBOOT_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT |
- REBOOT_DEFAULT_RETRIES = _DEFAULT_RETRIES |
- |
- @decorators.WithTimeoutAndRetriesDefaults( |
- REBOOT_DEFAULT_TIMEOUT, |
- REBOOT_DEFAULT_RETRIES) |
- def Reboot(self, block=True, wifi=False, timeout=None, retries=None): |
- """Reboot the device. |
- |
- Args: |
- block: A boolean indicating if we should wait for the reboot to complete. |
- wifi: A boolean indicating if we should wait for wifi to be enabled after |
- the reboot. The option has no effect unless |block| is also True. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- def device_offline(): |
- return not self.IsOnline() |
- |
- self.adb.Reboot() |
- self._ClearCache() |
- timeout_retry.WaitFor(device_offline, wait_period=1) |
- if block: |
- self.WaitUntilFullyBooted(wifi=wifi) |
- |
- INSTALL_DEFAULT_TIMEOUT = 4 * _DEFAULT_TIMEOUT |
- INSTALL_DEFAULT_RETRIES = _DEFAULT_RETRIES |
- |
- @decorators.WithTimeoutAndRetriesDefaults( |
- INSTALL_DEFAULT_TIMEOUT, |
- INSTALL_DEFAULT_RETRIES) |
- def Install(self, apk_path, reinstall=False, timeout=None, retries=None): |
- """Install an APK. |
- |
- Noop if an identical APK is already installed. |
- |
- Args: |
- apk_path: A string containing the path to the APK to install. |
- reinstall: A boolean indicating if we should keep any existing app data. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandFailedError if the installation fails. |
- CommandTimeoutError if the installation times out. |
- DeviceUnreachableError on missing device. |
- """ |
- package_name = apk_helper.GetPackageName(apk_path) |
- device_paths = self._GetApplicationPathsInternal(package_name) |
- if device_paths: |
- if len(device_paths) > 1: |
- logging.warning( |
- 'Installing single APK (%s) when split APKs (%s) are currently ' |
- 'installed.', apk_path, ' '.join(device_paths)) |
- apks_to_install, host_checksums = ( |
- self._ComputeStaleApks(package_name, [apk_path])) |
- should_install = bool(apks_to_install) |
- if should_install and not reinstall: |
- self.Uninstall(package_name) |
- else: |
- should_install = True |
- host_checksums = None |
- |
- if should_install: |
- # We won't know the resulting device apk names. |
- self._cache['package_apk_paths'].pop(package_name, 0) |
- self.adb.Install(apk_path, reinstall=reinstall) |
- self._cache['package_apk_checksums'][package_name] = host_checksums |
- |
- @decorators.WithTimeoutAndRetriesDefaults( |
- INSTALL_DEFAULT_TIMEOUT, |
- INSTALL_DEFAULT_RETRIES) |
- def InstallSplitApk(self, base_apk, split_apks, reinstall=False, |
- timeout=None, retries=None): |
- """Install a split APK. |
- |
- Noop if all of the APK splits are already installed. |
- |
- Args: |
- base_apk: A string of the path to the base APK. |
- split_apks: A list of strings of paths of all of the APK splits. |
- reinstall: A boolean indicating if we should keep any existing app data. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandFailedError if the installation fails. |
- CommandTimeoutError if the installation times out. |
- DeviceUnreachableError on missing device. |
- DeviceVersionError if device SDK is less than Android L. |
- """ |
- self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
- |
- all_apks = [base_apk] + split_select.SelectSplits( |
- self, base_apk, split_apks) |
- package_name = apk_helper.GetPackageName(base_apk) |
- device_apk_paths = self._GetApplicationPathsInternal(package_name) |
- |
- if device_apk_paths: |
- partial_install_package = package_name |
- apks_to_install, host_checksums = ( |
- self._ComputeStaleApks(package_name, all_apks)) |
- if apks_to_install and not reinstall: |
- self.Uninstall(package_name) |
- partial_install_package = None |
- apks_to_install = all_apks |
- else: |
- partial_install_package = None |
- apks_to_install = all_apks |
- host_checksums = None |
- |
- if apks_to_install: |
- # We won't know the resulting device apk names. |
- self._cache['package_apk_paths'].pop(package_name, 0) |
- self.adb.InstallMultiple( |
- apks_to_install, partial=partial_install_package, |
- reinstall=reinstall) |
- self._cache['package_apk_checksums'][package_name] = host_checksums |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def Uninstall(self, package_name, keep_data=False, timeout=None, |
- retries=None): |
- """Remove the app |package_name| from the device. |
- |
- Args: |
- package_name: The package to uninstall. |
- keep_data: (optional) Whether to keep the data and cache directories. |
- timeout: Timeout in seconds. |
- retries: Number of retries. |
- |
- Raises: |
- CommandFailedError if the uninstallation fails. |
- CommandTimeoutError if the uninstallation times out. |
- DeviceUnreachableError on missing device. |
- """ |
- try: |
- self.adb.Uninstall(package_name, keep_data) |
- self._cache['package_apk_paths'][package_name] = [] |
- self._cache['package_apk_checksums'][package_name] = set() |
- except: |
- # Clear cache since we can't be sure of the state. |
- self._cache['package_apk_paths'].pop(package_name, 0) |
- self._cache['package_apk_checksums'].pop(package_name, 0) |
- raise |
- |
- def _CheckSdkLevel(self, required_sdk_level): |
- """Raises an exception if the device does not have the required SDK level. |
- """ |
- if self.build_version_sdk < required_sdk_level: |
- raise device_errors.DeviceVersionError( |
- ('Requires SDK level %s, device is SDK level %s' % |
- (required_sdk_level, self.build_version_sdk)), |
- device_serial=self.adb.GetDeviceSerial()) |
- |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, |
- as_root=False, single_line=False, large_output=False, |
- timeout=None, retries=None): |
- """Run an ADB shell command. |
- |
- The command to run |cmd| should be a sequence of program arguments or else |
- a single string. |
- |
- When |cmd| is a sequence, it is assumed to contain the name of the command |
- to run followed by its arguments. In this case, arguments are passed to the |
- command exactly as given, without any further processing by the shell. This |
- allows to easily pass arguments containing spaces or special characters |
- without having to worry about getting quoting right. Whenever possible, it |
- is recomended to pass |cmd| as a sequence. |
- |
- When |cmd| is given as a string, it will be interpreted and run by the |
- shell on the device. |
- |
- This behaviour is consistent with that of command runners in cmd_helper as |
- well as Python's own subprocess.Popen. |
- |
- TODO(perezju) Change the default of |check_return| to True when callers |
- have switched to the new behaviour. |
- |
- Args: |
- cmd: A string with the full command to run on the device, or a sequence |
- containing the command and its arguments. |
- check_return: A boolean indicating whether or not the return code should |
- be checked. |
- cwd: The device directory in which the command should be run. |
- env: The environment variables with which the command should be run. |
- as_root: A boolean indicating whether the shell command should be run |
- with root privileges. |
- single_line: A boolean indicating if only a single line of output is |
- expected. |
- large_output: Uses a work-around for large shell command output. Without |
- this large output will be truncated. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- If single_line is False, the output of the command as a list of lines, |
- otherwise, a string with the unique line of output emmited by the command |
- (with the optional newline at the end stripped). |
- |
- Raises: |
- AdbCommandFailedError if check_return is True and the exit code of |
- the command run on the device is non-zero. |
- CommandFailedError if single_line is True but the output contains two or |
- more lines. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- def env_quote(key, value): |
- if not DeviceUtils._VALID_SHELL_VARIABLE.match(key): |
- raise KeyError('Invalid shell variable name %r' % key) |
- # using double quotes here to allow interpolation of shell variables |
- return '%s=%s' % (key, cmd_helper.DoubleQuote(value)) |
- |
- def run(cmd): |
- return self.adb.Shell(cmd) |
- |
- def handle_check_return(cmd): |
- try: |
- return run(cmd) |
- except device_errors.AdbCommandFailedError as exc: |
- if check_return: |
- raise |
- else: |
- return exc.output |
- |
- def handle_large_command(cmd): |
- if len(cmd) < self._MAX_ADB_COMMAND_LENGTH: |
- return handle_check_return(cmd) |
- else: |
- with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: |
- self._WriteFileWithPush(script.name, cmd) |
- logging.info('Large shell command will be run from file: %s ...', |
- cmd[:100]) |
- return handle_check_return('sh %s' % script.name_quoted) |
- |
- def handle_large_output(cmd, large_output_mode): |
- if large_output_mode: |
- with device_temp_file.DeviceTempFile(self.adb) as large_output_file: |
- cmd = '%s > %s' % (cmd, large_output_file.name) |
- logging.debug('Large output mode enabled. Will write output to ' |
- 'device and read results from file.') |
- handle_large_command(cmd) |
- return self.ReadFile(large_output_file.name, force_pull=True) |
- else: |
- try: |
- return handle_large_command(cmd) |
- except device_errors.AdbCommandFailedError as exc: |
- if exc.status is None: |
- logging.exception('No output found for %s', cmd) |
- logging.warning('Attempting to run in large_output mode.') |
- logging.warning('Use RunShellCommand(..., large_output=True) for ' |
- 'shell commands that expect a lot of output.') |
- return handle_large_output(cmd, True) |
- else: |
- raise |
- |
- if not isinstance(cmd, basestring): |
- cmd = ' '.join(cmd_helper.SingleQuote(s) for s in cmd) |
- if env: |
- env = ' '.join(env_quote(k, v) for k, v in env.iteritems()) |
- cmd = '%s %s' % (env, cmd) |
- if cwd: |
- cmd = 'cd %s && %s' % (cmd_helper.SingleQuote(cwd), cmd) |
- if as_root and self.NeedsSU(): |
- # "su -c sh -c" allows using shell features in |cmd| |
- cmd = self._Su('sh -c %s' % cmd_helper.SingleQuote(cmd)) |
- |
- output = handle_large_output(cmd, large_output).splitlines() |
- |
- if single_line: |
- if not output: |
- return '' |
- elif len(output) == 1: |
- return output[0] |
- else: |
- msg = 'one line of output was expected, but got: %s' |
- raise device_errors.CommandFailedError(msg % output, str(self)) |
- else: |
- return output |
- |
- def _RunPipedShellCommand(self, script, **kwargs): |
- PIPESTATUS_LEADER = 'PIPESTATUS: ' |
- |
- script += '; echo "%s${PIPESTATUS[@]}"' % PIPESTATUS_LEADER |
- kwargs['check_return'] = True |
- output = self.RunShellCommand(script, **kwargs) |
- pipestatus_line = output[-1] |
- |
- if not pipestatus_line.startswith(PIPESTATUS_LEADER): |
- logging.error('Pipe exit statuses of shell script missing.') |
- raise device_errors.AdbShellCommandFailedError( |
- script, output, status=None, |
- device_serial=self.adb.GetDeviceSerial()) |
- |
- output = output[:-1] |
- statuses = [ |
- int(s) for s in pipestatus_line[len(PIPESTATUS_LEADER):].split()] |
- if any(statuses): |
- raise device_errors.AdbShellCommandFailedError( |
- script, output, status=statuses, |
- device_serial=self.adb.GetDeviceSerial()) |
- return output |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def KillAll(self, process_name, exact=False, signum=device_signal.SIGKILL, |
- as_root=False, blocking=False, quiet=False, |
- timeout=None, retries=None): |
- """Kill all processes with the given name on the device. |
- |
- Args: |
- process_name: A string containing the name of the process to kill. |
- exact: A boolean indicating whether to kill all processes matching |
- the string |process_name| exactly, or all of those which contain |
- |process_name| as a substring. Defaults to False. |
- signum: An integer containing the signal number to send to kill. Defaults |
- to SIGKILL (9). |
- as_root: A boolean indicating whether the kill should be executed with |
- root privileges. |
- blocking: A boolean indicating whether we should wait until all processes |
- with the given |process_name| are dead. |
- quiet: A boolean indicating whether to ignore the fact that no processes |
- to kill were found. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- The number of processes attempted to kill. |
- |
- Raises: |
- CommandFailedError if no process was killed and |quiet| is False. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- procs_pids = self.GetPids(process_name) |
- if exact: |
- procs_pids = {process_name: procs_pids.get(process_name, [])} |
- pids = set(itertools.chain(*procs_pids.values())) |
- if not pids: |
- if quiet: |
- return 0 |
- else: |
- raise device_errors.CommandFailedError( |
- 'No process "%s"' % process_name, str(self)) |
- |
- logging.info( |
- 'KillAll(%r, ...) attempting to kill the following:', process_name) |
- for name, ids in procs_pids.iteritems(): |
- for i in ids: |
- logging.info(' %05s %s', str(i), name) |
- |
- cmd = ['kill', '-%d' % signum] + sorted(pids) |
- self.RunShellCommand(cmd, as_root=as_root, check_return=True) |
- |
- def all_pids_killed(): |
- procs_pids_remain = self.GetPids(process_name) |
- return not pids.intersection(itertools.chain(*procs_pids_remain.values())) |
- |
- if blocking: |
- timeout_retry.WaitFor(all_pids_killed, wait_period=0.1) |
- |
- return len(pids) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def StartActivity(self, intent_obj, blocking=False, trace_file_name=None, |
- force_stop=False, timeout=None, retries=None): |
- """Start package's activity on the device. |
- |
- Args: |
- intent_obj: An Intent object to send. |
- blocking: A boolean indicating whether we should wait for the activity to |
- finish launching. |
- trace_file_name: If present, a string that both indicates that we want to |
- profile the activity and contains the path to which the |
- trace should be saved. |
- force_stop: A boolean indicating whether we should stop the activity |
- before starting it. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandFailedError if the activity could not be started. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- cmd = ['am', 'start'] |
- if blocking: |
- cmd.append('-W') |
- if trace_file_name: |
- cmd.extend(['--start-profiler', trace_file_name]) |
- if force_stop: |
- cmd.append('-S') |
- cmd.extend(intent_obj.am_args) |
- for line in self.RunShellCommand(cmd, check_return=True): |
- if line.startswith('Error:'): |
- raise device_errors.CommandFailedError(line, str(self)) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def StartInstrumentation(self, component, finish=True, raw=False, |
- extras=None, timeout=None, retries=None): |
- if extras is None: |
- extras = {} |
- |
- cmd = ['am', 'instrument'] |
- if finish: |
- cmd.append('-w') |
- if raw: |
- cmd.append('-r') |
- for k, v in extras.iteritems(): |
- cmd.extend(['-e', str(k), str(v)]) |
- cmd.append(component) |
- return self.RunShellCommand(cmd, check_return=True, large_output=True) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def BroadcastIntent(self, intent_obj, timeout=None, retries=None): |
- """Send a broadcast intent. |
- |
- Args: |
- intent: An Intent to broadcast. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- cmd = ['am', 'broadcast'] + intent_obj.am_args |
- self.RunShellCommand(cmd, check_return=True) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GoHome(self, timeout=None, retries=None): |
- """Return to the home screen and obtain launcher focus. |
- |
- This command launches the home screen and attempts to obtain |
- launcher focus until the timeout is reached. |
- |
- Args: |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- def is_launcher_focused(): |
- output = self.RunShellCommand(['dumpsys', 'window', 'windows'], |
- check_return=True, large_output=True) |
- return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output) |
- |
- def dismiss_popups(): |
- # There is a dialog present; attempt to get rid of it. |
- # Not all dialogs can be dismissed with back. |
- self.SendKeyEvent(keyevent.KEYCODE_ENTER) |
- self.SendKeyEvent(keyevent.KEYCODE_BACK) |
- return is_launcher_focused() |
- |
- # If Home is already focused, return early to avoid unnecessary work. |
- if is_launcher_focused(): |
- return |
- |
- self.StartActivity( |
- intent.Intent(action='android.intent.action.MAIN', |
- category='android.intent.category.HOME'), |
- blocking=True) |
- |
- if not is_launcher_focused(): |
- timeout_retry.WaitFor(dismiss_popups, wait_period=1) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def ForceStop(self, package, timeout=None, retries=None): |
- """Close the application. |
- |
- Args: |
- package: A string containing the name of the package to stop. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- self.RunShellCommand(['am', 'force-stop', package], check_return=True) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def ClearApplicationState(self, package, timeout=None, retries=None): |
- """Clear all state for the given package. |
- |
- Args: |
- package: A string containing the name of the package to stop. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- # Check that the package exists before clearing it for android builds below |
- # JB MR2. Necessary because calling pm clear on a package that doesn't exist |
- # may never return. |
- if ((self.build_version_sdk >= |
- constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) |
- or self._GetApplicationPathsInternal(package)): |
- self.RunShellCommand(['pm', 'clear', package], check_return=True) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def SendKeyEvent(self, keycode, timeout=None, retries=None): |
- """Sends a keycode to the device. |
- |
- See the pylib.constants.keyevent module for suitable keycode values. |
- |
- Args: |
- keycode: A integer keycode to send to the device. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')], |
- check_return=True) |
- |
- PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT |
- PUSH_CHANGED_FILES_DEFAULT_RETRIES = _DEFAULT_RETRIES |
- |
- @decorators.WithTimeoutAndRetriesDefaults( |
- PUSH_CHANGED_FILES_DEFAULT_TIMEOUT, |
- PUSH_CHANGED_FILES_DEFAULT_RETRIES) |
- def PushChangedFiles(self, host_device_tuples, timeout=None, |
- retries=None, delete_device_stale=False): |
- """Push files to the device, skipping files that don't need updating. |
- |
- When a directory is pushed, it is traversed recursively on the host and |
- all files in it are pushed to the device as needed. |
- Additionally, if delete_device_stale option is True, |
- files that exist on the device but don't exist on the host are deleted. |
- |
- Args: |
- host_device_tuples: A list of (host_path, device_path) tuples, where |
- |host_path| is an absolute path of a file or directory on the host |
- that should be minimially pushed to the device, and |device_path| is |
- an absolute path of the destination on the device. |
- timeout: timeout in seconds |
- retries: number of retries |
- delete_device_stale: option to delete stale files on device |
- |
- Raises: |
- CommandFailedError on failure. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- |
- all_changed_files = [] |
- all_stale_files = [] |
- missing_dirs = [] |
- for h, d in host_device_tuples: |
- changed_files, up_to_date_files, stale_files = ( |
- self._GetChangedAndStaleFiles(h, d, delete_device_stale)) |
- all_changed_files += changed_files |
- all_stale_files += stale_files |
- if (os.path.isdir(h) and changed_files and not up_to_date_files |
- and not stale_files): |
- missing_dirs.append(d) |
- |
- if delete_device_stale and all_stale_files: |
- self.RunShellCommand(['rm', '-f'] + all_stale_files, |
- check_return=True) |
- |
- if all_changed_files: |
- if missing_dirs: |
- self.RunShellCommand(['mkdir', '-p'] + missing_dirs, check_return=True) |
- self._PushFilesImpl(host_device_tuples, all_changed_files) |
- |
- def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False): |
- """Get files to push and delete |
- |
- Args: |
- host_path: an absolute path of a file or directory on the host |
- device_path: an absolute path of a file or directory on the device |
- track_stale: whether to bother looking for stale files (slower) |
- |
- Returns: |
- a three-element tuple |
- 1st element: a list of (host_files_path, device_files_path) tuples to push |
- 2nd element: a list of host_files_path that are up-to-date |
- 3rd element: a list of stale files under device_path, or [] when |
- track_stale == False |
- """ |
- try: |
- host_checksums = md5sum.CalculateHostMd5Sums([host_path]) |
- interesting_device_paths = [device_path] |
- if not track_stale and os.path.isdir(host_path): |
- interesting_device_paths = [ |
- posixpath.join(device_path, os.path.relpath(p, host_path)) |
- for p in host_checksums.keys()] |
- device_checksums = md5sum.CalculateDeviceMd5Sums( |
- interesting_device_paths, self) |
- except EnvironmentError as e: |
- logging.warning('Error calculating md5: %s', e) |
- return ([(host_path, device_path)], [], []) |
- |
- to_push = [] |
- up_to_date = [] |
- to_delete = [] |
- if os.path.isfile(host_path): |
- host_checksum = host_checksums.get(host_path) |
- device_checksum = device_checksums.get(device_path) |
- if host_checksum == device_checksum: |
- up_to_date.append(host_path) |
- else: |
- to_push.append((host_path, device_path)) |
- else: |
- for host_abs_path, host_checksum in host_checksums.iteritems(): |
- device_abs_path = posixpath.join( |
- device_path, os.path.relpath(host_abs_path, host_path)) |
- device_checksum = device_checksums.pop(device_abs_path, None) |
- if device_checksum == host_checksum: |
- up_to_date.append(host_abs_path) |
- else: |
- to_push.append((host_abs_path, device_abs_path)) |
- to_delete = device_checksums.keys() |
- return (to_push, up_to_date, to_delete) |
- |
- def _ComputeDeviceChecksumsForApks(self, package_name): |
- ret = self._cache['package_apk_checksums'].get(package_name) |
- if ret is None: |
- device_paths = self._GetApplicationPathsInternal(package_name) |
- file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self) |
- ret = set(file_to_checksums.values()) |
- self._cache['package_apk_checksums'][package_name] = ret |
- return ret |
- |
- def _ComputeStaleApks(self, package_name, host_apk_paths): |
- host_checksums_holder = [None] |
- def compute_host_checksums(): |
- host_checksums_holder[0] = md5sum.CalculateHostMd5Sums(host_apk_paths) |
- |
- host_thread = threading.Thread(target=compute_host_checksums) |
- host_thread.start() |
- device_checksums = self._ComputeDeviceChecksumsForApks(package_name) |
- host_thread.join() |
- host_checksums = host_checksums_holder[0] |
- stale_apks = [k for (k, v) in host_checksums.iteritems() |
- if v not in device_checksums] |
- return stale_apks, set(host_checksums.values()) |
- |
- def _PushFilesImpl(self, host_device_tuples, files): |
- size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) |
- file_count = len(files) |
- dir_size = sum(host_utils.GetRecursiveDiskUsage(h) |
- for h, _ in host_device_tuples) |
- dir_file_count = 0 |
- for h, _ in host_device_tuples: |
- if os.path.isdir(h): |
- dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) |
- else: |
- dir_file_count += 1 |
- |
- push_duration = self._ApproximateDuration( |
- file_count, file_count, size, False) |
- dir_push_duration = self._ApproximateDuration( |
- len(host_device_tuples), dir_file_count, dir_size, False) |
- zip_duration = self._ApproximateDuration(1, 1, size, True) |
- |
- self._InstallCommands() |
- |
- if dir_push_duration < push_duration and ( |
- dir_push_duration < zip_duration or not self._commands_installed): |
- self._PushChangedFilesIndividually(host_device_tuples) |
- elif push_duration < zip_duration or not self._commands_installed: |
- self._PushChangedFilesIndividually(files) |
- else: |
- self._PushChangedFilesZipped(files) |
- self.RunShellCommand( |
- ['chmod', '-R', '777'] + [d for _, d in host_device_tuples], |
- as_root=True, check_return=True) |
- |
- def _InstallCommands(self): |
- if self._commands_installed is None: |
- try: |
- if not install_commands.Installed(self): |
- install_commands.InstallCommands(self) |
- self._commands_installed = True |
- except Exception as e: |
- logging.warning('unzip not available: %s' % str(e)) |
- self._commands_installed = False |
- |
- @staticmethod |
- def _ApproximateDuration(adb_calls, file_count, byte_count, is_zipping): |
- # We approximate the time to push a set of files to a device as: |
- # t = c1 * a + c2 * f + c3 + b / c4 + b / (c5 * c6), where |
- # t: total time (sec) |
- # c1: adb call time delay (sec) |
- # a: number of times adb is called (unitless) |
- # c2: push time delay (sec) |
- # f: number of files pushed via adb (unitless) |
- # c3: zip time delay (sec) |
- # c4: zip rate (bytes/sec) |
- # b: total number of bytes (bytes) |
- # c5: transfer rate (bytes/sec) |
- # c6: compression ratio (unitless) |
- |
- # All of these are approximations. |
- ADB_CALL_PENALTY = 0.1 # seconds |
- ADB_PUSH_PENALTY = 0.01 # seconds |
- ZIP_PENALTY = 2.0 # seconds |
- ZIP_RATE = 10000000.0 # bytes / second |
- TRANSFER_RATE = 2000000.0 # bytes / second |
- COMPRESSION_RATIO = 2.0 # unitless |
- |
- adb_call_time = ADB_CALL_PENALTY * adb_calls |
- adb_push_setup_time = ADB_PUSH_PENALTY * file_count |
- if is_zipping: |
- zip_time = ZIP_PENALTY + byte_count / ZIP_RATE |
- transfer_time = byte_count / (TRANSFER_RATE * COMPRESSION_RATIO) |
- else: |
- zip_time = 0 |
- transfer_time = byte_count / TRANSFER_RATE |
- return adb_call_time + adb_push_setup_time + zip_time + transfer_time |
- |
- def _PushChangedFilesIndividually(self, files): |
- for h, d in files: |
- self.adb.Push(h, d) |
- |
- def _PushChangedFilesZipped(self, files): |
- if not files: |
- return |
- |
- with tempfile.NamedTemporaryFile(suffix='.zip') as zip_file: |
- zip_proc = multiprocessing.Process( |
- target=DeviceUtils._CreateDeviceZip, |
- args=(zip_file.name, files)) |
- zip_proc.start() |
- zip_proc.join() |
- |
- zip_on_device = '%s/tmp.zip' % self.GetExternalStoragePath() |
- try: |
- self.adb.Push(zip_file.name, zip_on_device) |
- self.RunShellCommand( |
- ['unzip', zip_on_device], |
- as_root=True, |
- env={'PATH': '%s:$PATH' % install_commands.BIN_DIR}, |
- check_return=True) |
- finally: |
- if zip_proc.is_alive(): |
- zip_proc.terminate() |
- if self.IsOnline(): |
- self.RunShellCommand(['rm', zip_on_device], check_return=True) |
- |
- @staticmethod |
- def _CreateDeviceZip(zip_path, host_device_tuples): |
- with zipfile.ZipFile(zip_path, 'w') as zip_file: |
- for host_path, device_path in host_device_tuples: |
- zip_utils.WriteToZipFile(zip_file, host_path, device_path) |
- |
- # TODO(nednguyen): remove this and migrate the callsite to PathExists(). |
- def FileExists(self, device_path, timeout=None, retries=None): |
- """Checks whether the given file exists on the device. |
- |
- Arguments are the same as PathExists. |
- """ |
- return self.PathExists(device_path, timeout=timeout, retries=retries) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def PathExists(self, device_path, timeout=None, retries=None): |
- """Checks whether the given path exists on the device. |
- |
- Args: |
- device_path: A string containing the absolute path to the file on the |
- device. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- True if the file exists on the device, False otherwise. |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- try: |
- self.RunShellCommand(['test', '-e', device_path], check_return=True) |
- return True |
- except device_errors.AdbCommandFailedError: |
- return False |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def PullFile(self, device_path, host_path, timeout=None, retries=None): |
- """Pull a file from the device. |
- |
- Args: |
- device_path: A string containing the absolute path of the file to pull |
- from the device. |
- host_path: A string containing the absolute path of the destination on |
- the host. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandFailedError on failure. |
- CommandTimeoutError on timeout. |
- """ |
- # Create the base dir if it doesn't exist already |
- dirname = os.path.dirname(host_path) |
- if dirname and not os.path.exists(dirname): |
- os.makedirs(dirname) |
- self.adb.Pull(device_path, host_path) |
- |
- def _ReadFileWithPull(self, device_path): |
- try: |
- d = tempfile.mkdtemp() |
- host_temp_path = os.path.join(d, 'tmp_ReadFileWithPull') |
- self.adb.Pull(device_path, host_temp_path) |
- with open(host_temp_path, 'r') as host_temp: |
- return host_temp.read() |
- finally: |
- if os.path.exists(d): |
- shutil.rmtree(d) |
- |
- _LS_RE = re.compile( |
- r'(?P<perms>\S+) +(?P<owner>\S+) +(?P<group>\S+) +(?:(?P<size>\d+) +)?' |
- + r'(?P<date>\S+) +(?P<time>\S+) +(?P<name>.+)$') |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def ReadFile(self, device_path, as_root=False, force_pull=False, |
- timeout=None, retries=None): |
- """Reads the contents of a file from the device. |
- |
- Args: |
- device_path: A string containing the absolute path of the file to read |
- from the device. |
- as_root: A boolean indicating whether the read should be executed with |
- root privileges. |
- force_pull: A boolean indicating whether to force the operation to be |
- performed by pulling a file from the device. The default is, when the |
- contents are short, to retrieve the contents using cat instead. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- The contents of |device_path| as a string. Contents are intepreted using |
- universal newlines, so the caller will see them encoded as '\n'. Also, |
- all lines will be terminated. |
- |
- Raises: |
- AdbCommandFailedError if the file can't be read. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- def get_size(path): |
- # TODO(jbudorick): Implement a generic version of Stat() that handles |
- # as_root=True, then switch this implementation to use that. |
- ls_out = self.RunShellCommand(['ls', '-l', device_path], as_root=as_root, |
- check_return=True) |
- for line in ls_out: |
- m = self._LS_RE.match(line) |
- if m and m.group('name') == posixpath.basename(device_path): |
- return int(m.group('size')) |
- logging.warning('Could not determine size of %s.', device_path) |
- return None |
- |
- if (not force_pull |
- and 0 < get_size(device_path) <= self._MAX_ADB_OUTPUT_LENGTH): |
- return _JoinLines(self.RunShellCommand( |
- ['cat', device_path], as_root=as_root, check_return=True)) |
- elif as_root and self.NeedsSU(): |
- with device_temp_file.DeviceTempFile(self.adb) as device_temp: |
- self.RunShellCommand(['cp', device_path, device_temp.name], |
- as_root=True, check_return=True) |
- return self._ReadFileWithPull(device_temp.name) |
- else: |
- return self._ReadFileWithPull(device_path) |
- |
- def _WriteFileWithPush(self, device_path, contents): |
- with tempfile.NamedTemporaryFile() as host_temp: |
- host_temp.write(contents) |
- host_temp.flush() |
- self.adb.Push(host_temp.name, device_path) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def WriteFile(self, device_path, contents, as_root=False, force_push=False, |
- timeout=None, retries=None): |
- """Writes |contents| to a file on the device. |
- |
- Args: |
- device_path: A string containing the absolute path to the file to write |
- on the device. |
- contents: A string containing the data to write to the device. |
- as_root: A boolean indicating whether the write should be executed with |
- root privileges (if available). |
- force_push: A boolean indicating whether to force the operation to be |
- performed by pushing a file to the device. The default is, when the |
- contents are short, to pass the contents using a shell script instead. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandFailedError if the file could not be written on the device. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- if not force_push and len(contents) < self._MAX_ADB_COMMAND_LENGTH: |
- # If the contents are small, for efficieny we write the contents with |
- # a shell command rather than pushing a file. |
- cmd = 'echo -n %s > %s' % (cmd_helper.SingleQuote(contents), |
- cmd_helper.SingleQuote(device_path)) |
- self.RunShellCommand(cmd, as_root=as_root, check_return=True) |
- elif as_root and self.NeedsSU(): |
- # Adb does not allow to "push with su", so we first push to a temp file |
- # on a safe location, and then copy it to the desired location with su. |
- with device_temp_file.DeviceTempFile(self.adb) as device_temp: |
- self._WriteFileWithPush(device_temp.name, contents) |
- # Here we need 'cp' rather than 'mv' because the temp and |
- # destination files might be on different file systems (e.g. |
- # on internal storage and an external sd card). |
- self.RunShellCommand(['cp', device_temp.name, device_path], |
- as_root=True, check_return=True) |
- else: |
- # If root is not needed, we can push directly to the desired location. |
- self._WriteFileWithPush(device_path, contents) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def Ls(self, device_path, timeout=None, retries=None): |
- """Lists the contents of a directory on the device. |
- |
- Args: |
- device_path: A string containing the path of the directory on the device |
- to list. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- A list of pairs (filename, stat) for each file found in the directory, |
- where the stat object has the properties: st_mode, st_size, and st_time. |
- |
- Raises: |
- AdbCommandFailedError if |device_path| does not specify a valid and |
- accessible directory in the device. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- return self.adb.Ls(device_path) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def Stat(self, device_path, timeout=None, retries=None): |
- """Get the stat attributes of a file or directory on the device. |
- |
- Args: |
- device_path: A string containing the path of from which to get attributes |
- on the device. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- A stat object with the properties: st_mode, st_size, and st_time |
- |
- Raises: |
- CommandFailedError if device_path cannot be found on the device. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- dirname, target = device_path.rsplit('/', 1) |
- for filename, stat in self.adb.Ls(dirname): |
- if filename == target: |
- return stat |
- raise device_errors.CommandFailedError( |
- 'Cannot find file or directory: %r' % device_path, str(self)) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def SetJavaAsserts(self, enabled, timeout=None, retries=None): |
- """Enables or disables Java asserts. |
- |
- Args: |
- enabled: A boolean indicating whether Java asserts should be enabled |
- or disabled. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- True if the device-side property changed and a restart is required as a |
- result, False otherwise. |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- """ |
- def find_property(lines, property_name): |
- for index, line in enumerate(lines): |
- if line.strip() == '': |
- continue |
- key, value = (s.strip() for s in line.split('=', 1)) |
- if key == property_name: |
- return index, value |
- return None, '' |
- |
- new_value = 'all' if enabled else '' |
- |
- # First ensure the desired property is persisted. |
- try: |
- properties = self.ReadFile( |
- constants.DEVICE_LOCAL_PROPERTIES_PATH).splitlines() |
- except device_errors.CommandFailedError: |
- properties = [] |
- index, value = find_property(properties, self.JAVA_ASSERT_PROPERTY) |
- if new_value != value: |
- if new_value: |
- new_line = '%s=%s' % (self.JAVA_ASSERT_PROPERTY, new_value) |
- if index is None: |
- properties.append(new_line) |
- else: |
- properties[index] = new_line |
- else: |
- assert index is not None # since new_value == '' and new_value != value |
- properties.pop(index) |
- self.WriteFile(constants.DEVICE_LOCAL_PROPERTIES_PATH, |
- _JoinLines(properties)) |
- |
- # Next, check the current runtime value is what we need, and |
- # if not, set it and report that a reboot is required. |
- value = self.GetProp(self.JAVA_ASSERT_PROPERTY) |
- if new_value != value: |
- self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) |
- return True |
- else: |
- return False |
- |
- @property |
- def language(self): |
- """Returns the language setting on the device.""" |
- return self.GetProp('persist.sys.language', cache=False) |
- |
- @property |
- def country(self): |
- """Returns the country setting on the device.""" |
- return self.GetProp('persist.sys.country', cache=False) |
- |
- @property |
- def screen_density(self): |
- """Returns the screen density of the device.""" |
- DPI_TO_DENSITY = { |
- 120: 'ldpi', |
- 160: 'mdpi', |
- 240: 'hdpi', |
- 320: 'xhdpi', |
- 480: 'xxhdpi', |
- 640: 'xxxhdpi', |
- } |
- dpi = int(self.GetProp('ro.sf.lcd_density', cache=True)) |
- return DPI_TO_DENSITY.get(dpi, 'tvdpi') |
- |
- @property |
- def build_description(self): |
- """Returns the build description of the system. |
- |
- For example: |
- nakasi-user 4.4.4 KTU84P 1227136 release-keys |
- """ |
- return self.GetProp('ro.build.description', cache=True) |
- |
- @property |
- def build_fingerprint(self): |
- """Returns the build fingerprint of the system. |
- |
- For example: |
- google/nakasi/grouper:4.4.4/KTU84P/1227136:user/release-keys |
- """ |
- return self.GetProp('ro.build.fingerprint', cache=True) |
- |
- @property |
- def build_id(self): |
- """Returns the build ID of the system (e.g. 'KTU84P').""" |
- return self.GetProp('ro.build.id', cache=True) |
- |
- @property |
- def build_product(self): |
- """Returns the build product of the system (e.g. 'grouper').""" |
- return self.GetProp('ro.build.product', cache=True) |
- |
- @property |
- def build_type(self): |
- """Returns the build type of the system (e.g. 'user').""" |
- return self.GetProp('ro.build.type', cache=True) |
- |
- @property |
- def build_version_sdk(self): |
- """Returns the build version sdk of the system as a number (e.g. 19). |
- |
- For version code numbers see: |
- http://developer.android.com/reference/android/os/Build.VERSION_CODES.html |
- |
- For named constants see: |
- pylib.constants.ANDROID_SDK_VERSION_CODES |
- |
- Raises: |
- CommandFailedError if the build version sdk is not a number. |
- """ |
- value = self.GetProp('ro.build.version.sdk', cache=True) |
- try: |
- return int(value) |
- except ValueError: |
- raise device_errors.CommandFailedError( |
- 'Invalid build version sdk: %r' % value) |
- |
- @property |
- def product_cpu_abi(self): |
- """Returns the product cpu abi of the device (e.g. 'armeabi-v7a').""" |
- return self.GetProp('ro.product.cpu.abi', cache=True) |
- |
- @property |
- def product_model(self): |
- """Returns the name of the product model (e.g. 'Nexus 7').""" |
- return self.GetProp('ro.product.model', cache=True) |
- |
- @property |
- def product_name(self): |
- """Returns the product name of the device (e.g. 'nakasi').""" |
- return self.GetProp('ro.product.name', cache=True) |
- |
- def GetProp(self, property_name, cache=False, timeout=DEFAULT, |
- retries=DEFAULT): |
- """Gets a property from the device. |
- |
- Args: |
- property_name: A string containing the name of the property to get from |
- the device. |
- cache: A boolean indicating whether to cache the value of this property. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- The value of the device's |property_name| property. |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- """ |
- assert isinstance(property_name, basestring), ( |
- "property_name is not a string: %r" % property_name) |
- |
- cache_key = '_prop:' + property_name |
- if cache and cache_key in self._cache: |
- return self._cache[cache_key] |
- else: |
- # timeout and retries are handled down at run shell, because we don't |
- # want to apply them in the other branch when reading from the cache |
- value = self.RunShellCommand( |
- ['getprop', property_name], single_line=True, check_return=True, |
- timeout=self._default_timeout if timeout is DEFAULT else timeout, |
- retries=self._default_retries if retries is DEFAULT else retries) |
- if cache or cache_key in self._cache: |
- self._cache[cache_key] = value |
- return value |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def SetProp(self, property_name, value, check=False, timeout=None, |
- retries=None): |
- """Sets a property on the device. |
- |
- Args: |
- property_name: A string containing the name of the property to set on |
- the device. |
- value: A string containing the value to set to the property on the |
- device. |
- check: A boolean indicating whether to check that the property was |
- successfully set on the device. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Raises: |
- CommandFailedError if check is true and the property was not correctly |
- set on the device (e.g. because it is not rooted). |
- CommandTimeoutError on timeout. |
- """ |
- assert isinstance(property_name, basestring), ( |
- "property_name is not a string: %r" % property_name) |
- assert isinstance(value, basestring), "value is not a string: %r" % value |
- |
- self.RunShellCommand(['setprop', property_name, value], check_return=True) |
- cache_key = '_prop:' + property_name |
- if cache_key in self._cache: |
- del self._cache[cache_key] |
- # TODO(perezju) remove the option and make the check mandatory, but using a |
- # single shell script to both set- and getprop. |
- if check and value != self.GetProp(property_name): |
- raise device_errors.CommandFailedError( |
- 'Unable to set property %r on the device to %r' |
- % (property_name, value), str(self)) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GetABI(self, timeout=None, retries=None): |
- """Gets the device main ABI. |
- |
- Args: |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- The device's main ABI name. |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- """ |
- return self.GetProp('ro.product.cpu.abi') |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GetPids(self, process_name, timeout=None, retries=None): |
- """Returns the PIDs of processes with the given name. |
- |
- Note that the |process_name| is often the package name. |
- |
- Args: |
- process_name: A string containing the process name to get the PIDs for. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- A dict mapping process name to a list of PIDs for each process that |
- contained the provided |process_name|. |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- procs_pids = collections.defaultdict(list) |
- try: |
- ps_output = self._RunPipedShellCommand( |
- 'ps | grep -F %s' % cmd_helper.SingleQuote(process_name)) |
- except device_errors.AdbShellCommandFailedError as e: |
- if e.status and isinstance(e.status, list) and not e.status[0]: |
- # If ps succeeded but grep failed, there were no processes with the |
- # given name. |
- return procs_pids |
- else: |
- raise |
- |
- for line in ps_output: |
- try: |
- ps_data = line.split() |
- if process_name in ps_data[-1]: |
- pid, process = ps_data[1], ps_data[-1] |
- procs_pids[process].append(pid) |
- except IndexError: |
- pass |
- return procs_pids |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def TakeScreenshot(self, host_path=None, timeout=None, retries=None): |
- """Takes a screenshot of the device. |
- |
- Args: |
- host_path: A string containing the path on the host to save the |
- screenshot to. If None, a file name in the current |
- directory will be generated. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- The name of the file on the host to which the screenshot was saved. |
- |
- Raises: |
- CommandFailedError on failure. |
- CommandTimeoutError on timeout. |
- DeviceUnreachableError on missing device. |
- """ |
- if not host_path: |
- host_path = os.path.abspath('screenshot-%s.png' % _GetTimeStamp()) |
- with device_temp_file.DeviceTempFile(self.adb, suffix='.png') as device_tmp: |
- self.RunShellCommand(['/system/bin/screencap', '-p', device_tmp.name], |
- check_return=True) |
- self.PullFile(device_tmp.name, host_path) |
- return host_path |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GetMemoryUsageForPid(self, pid, timeout=None, retries=None): |
- """Gets the memory usage for the given PID. |
- |
- Args: |
- pid: PID of the process. |
- timeout: timeout in seconds |
- retries: number of retries |
- |
- Returns: |
- A dict containing memory usage statistics for the PID. May include: |
- Size, Rss, Pss, Shared_Clean, Shared_Dirty, Private_Clean, |
- Private_Dirty, VmHWM |
- |
- Raises: |
- CommandTimeoutError on timeout. |
- """ |
- result = collections.defaultdict(int) |
- |
- try: |
- result.update(self._GetMemoryUsageForPidFromSmaps(pid)) |
- except device_errors.CommandFailedError: |
- logging.exception('Error getting memory usage from smaps') |
- |
- try: |
- result.update(self._GetMemoryUsageForPidFromStatus(pid)) |
- except device_errors.CommandFailedError: |
- logging.exception('Error getting memory usage from status') |
- |
- return result |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def DismissCrashDialogIfNeeded(self, timeout=None, retries=None): |
- """Dismiss the error/ANR dialog if present. |
- |
- Returns: Name of the crashed package if a dialog is focused, |
- None otherwise. |
- """ |
- def _FindFocusedWindow(): |
- match = None |
- # TODO(jbudorick): Try to grep the output on the device instead of using |
- # large_output if/when DeviceUtils exposes a public interface for piped |
- # shell command handling. |
- for line in self.RunShellCommand(['dumpsys', 'window', 'windows'], |
- check_return=True, large_output=True): |
- match = re.match(_CURRENT_FOCUS_CRASH_RE, line) |
- if match: |
- break |
- return match |
- |
- match = _FindFocusedWindow() |
- if not match: |
- return None |
- package = match.group(2) |
- logging.warning('Trying to dismiss %s dialog for %s' % match.groups()) |
- self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT) |
- self.SendKeyEvent(keyevent.KEYCODE_DPAD_RIGHT) |
- self.SendKeyEvent(keyevent.KEYCODE_ENTER) |
- match = _FindFocusedWindow() |
- if match: |
- logging.error('Still showing a %s dialog for %s' % match.groups()) |
- return package |
- |
- def _GetMemoryUsageForPidFromSmaps(self, pid): |
- SMAPS_COLUMNS = ( |
- 'Size', 'Rss', 'Pss', 'Shared_Clean', 'Shared_Dirty', 'Private_Clean', |
- 'Private_Dirty') |
- |
- showmap_out = self._RunPipedShellCommand( |
- 'showmap %d | grep TOTAL' % int(pid), as_root=True) |
- |
- split_totals = showmap_out[-1].split() |
- if (not split_totals |
- or len(split_totals) != 9 |
- or split_totals[-1] != 'TOTAL'): |
- raise device_errors.CommandFailedError( |
- 'Invalid output from showmap: %s' % '\n'.join(showmap_out)) |
- |
- return dict(itertools.izip(SMAPS_COLUMNS, (int(n) for n in split_totals))) |
- |
- def _GetMemoryUsageForPidFromStatus(self, pid): |
- for line in self.ReadFile( |
- '/proc/%s/status' % str(pid), as_root=True).splitlines(): |
- if line.startswith('VmHWM:'): |
- return {'VmHWM': int(line.split()[1])} |
- else: |
- raise device_errors.CommandFailedError( |
- 'Could not find memory peak value for pid %s', str(pid)) |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def GetLogcatMonitor(self, timeout=None, retries=None, *args, **kwargs): |
- """Returns a new LogcatMonitor associated with this device. |
- |
- Parameters passed to this function are passed directly to |
- |logcat_monitor.LogcatMonitor| and are documented there. |
- |
- Args: |
- timeout: timeout in seconds |
- retries: number of retries |
- """ |
- return logcat_monitor.LogcatMonitor(self.adb, *args, **kwargs) |
- |
- def GetClientCache(self, client_name): |
- """Returns client cache.""" |
- if client_name not in self._client_caches: |
- self._client_caches[client_name] = {} |
- return self._client_caches[client_name] |
- |
- def _ClearCache(self): |
- """Clears all caches.""" |
- for client in self._client_caches: |
- self._client_caches[client].clear() |
- self._cache = { |
- # Map of packageId -> list of on-device .apk paths |
- 'package_apk_paths': {}, |
- # Map of packageId -> set of on-device .apk checksums |
- 'package_apk_checksums': {}, |
- } |
- |
- @classmethod |
- def parallel(cls, devices, async=False): |
- """Creates a Parallelizer to operate over the provided list of devices. |
- |
- If |devices| is either |None| or an empty list, the Parallelizer will |
- operate over all attached devices that have not been blacklisted. |
- |
- Args: |
- devices: A list of either DeviceUtils instances or objects from |
- from which DeviceUtils instances can be constructed. If None, |
- all attached devices will be used. |
- async: If true, returns a Parallelizer that runs operations |
- asynchronously. |
- |
- Returns: |
- A Parallelizer operating over |devices|. |
- """ |
- if not devices: |
- raise device_errors.NoDevicesError() |
- |
- devices = [d if isinstance(d, cls) else cls(d) for d in devices] |
- if async: |
- return parallelizer.Parallelizer(devices) |
- else: |
- return parallelizer.SyncParallelizer(devices) |
- |
- @classmethod |
- def HealthyDevices(cls, blacklist=None, **kwargs): |
- if not blacklist: |
- # TODO(jbudorick): Remove once clients pass in the blacklist. |
- blacklist = device_blacklist.Blacklist(device_blacklist.BLACKLIST_JSON) |
- |
- blacklisted_devices = blacklist.Read() |
- def blacklisted(adb): |
- if adb.GetDeviceSerial() in blacklisted_devices: |
- logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) |
- return True |
- return False |
- |
- return [cls(adb, **kwargs) for adb in adb_wrapper.AdbWrapper.Devices() |
- if not blacklisted(adb)] |
- |
- @decorators.WithTimeoutAndRetriesFromInstance() |
- def RestartAdbd(self, timeout=None, retries=None): |
- logging.info('Restarting adbd on device.') |
- with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: |
- self.WriteFile(script.name, _RESTART_ADBD_SCRIPT) |
- self.RunShellCommand(['source', script.name], as_root=True) |
- self.adb.WaitForDevice() |
+from devil.android.device_utils import * |