Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(733)

Unified Diff: build/android/pylib/device/device_utils.py

Issue 1222313015: Manual partial update from from https://crrev.com/337502 (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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 c8b471831e62aec343c705afb12bd3095bd8ea24..3a6563e8442e7783751d3f52f6a2cdd19661afe5 100644
--- a/build/android/pylib/device/device_utils.py
+++ b/build/android/pylib/device/device_utils.py
@@ -26,6 +26,7 @@ import pylib.android_commands
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
@@ -33,6 +34,7 @@ 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
@@ -135,6 +137,8 @@ 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.
@@ -339,14 +343,14 @@ class DeviceUtils(object):
return value
@decorators.WithTimeoutAndRetriesFromInstance()
- def GetApplicationPath(self, package, timeout=None, retries=None):
- """Get the path of the installed apk on the device for the given package.
+ 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:
- Path to the apk on the device if it exists, None otherwise.
+ List of paths to the apks on the device for the given package.
"""
# 'pm path' is liable to incorrectly exit with a nonzero number starting
# in Lollipop.
@@ -354,14 +358,37 @@ class DeviceUtils(object):
# 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], single_line=True,
- check_return=should_check_return)
- if not output:
- return None
- if not output.startswith('package:'):
- raise device_errors.CommandFailedError('pm path returned: %r' % output,
- str(self))
- return output[len('package:'):]
+ 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:'):])
+ return apks
+
+ @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):
@@ -391,7 +418,7 @@ class DeviceUtils(object):
def pm_ready():
try:
- return self.GetApplicationPath('android')
+ return self.GetApplicationPaths('android')
except device_errors.CommandFailedError:
return False
@@ -461,9 +488,15 @@ class DeviceUtils(object):
DeviceUnreachableError on missing device.
"""
package_name = apk_helper.GetPackageName(apk_path)
- device_path = self.GetApplicationPath(package_name)
- if device_path is not None:
- should_install = bool(self._GetChangedFilesImpl(apk_path, device_path))
+ device_paths = self.GetApplicationPaths(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))
+ (files_to_push, _) = self._GetChangedAndStaleFiles(
+ apk_path, device_paths[0])
+ should_install = bool(files_to_push)
if should_install and not reinstall:
self.adb.Uninstall(package_name)
else:
@@ -471,6 +504,62 @@ class DeviceUtils(object):
if should_install:
self.adb.Install(apk_path, reinstall=reinstall)
+ @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.GetApplicationPaths(package_name)
+
+ if device_apk_paths:
+ partial_install_package = package_name
+ device_checksums = md5sum.CalculateDeviceMd5Sums(device_apk_paths, self)
+ host_checksums = md5sum.CalculateHostMd5Sums(all_apks)
+ apks_to_install = [k for (k, v) in host_checksums.iteritems()
+ if v not in device_checksums.values()]
+ if apks_to_install and not reinstall:
+ self.adb.Uninstall(package_name)
+ partial_install_package = None
+ apks_to_install = all_apks
+ else:
+ partial_install_package = None
+ apks_to_install = all_apks
+ if apks_to_install:
+ self.adb.InstallMultiple(
+ apks_to_install, partial=partial_install_package, reinstall=reinstall)
+
+ 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,
@@ -557,8 +646,8 @@ class DeviceUtils(object):
if large_output_mode:
with device_temp_file.DeviceTempFile(self.adb) as large_output_file:
cmd = '%s > %s' % (cmd, large_output_file.name)
- logging.info('Large output mode enabled. Will write output to device '
- 'and read results from file.')
+ 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:
@@ -714,7 +803,7 @@ class DeviceUtils(object):
for k, v in extras.iteritems():
cmd.extend(['-e', str(k), str(v)])
cmd.append(component)
- return self.RunShellCommand(cmd, check_return=True)
+ return self.RunShellCommand(cmd, check_return=True, large_output=True)
@decorators.WithTimeoutAndRetriesFromInstance()
def BroadcastIntent(self, intent_obj, timeout=None, retries=None):
@@ -734,7 +823,10 @@ class DeviceUtils(object):
@decorators.WithTimeoutAndRetriesFromInstance()
def GoHome(self, timeout=None, retries=None):
- """Return to the home screen.
+ """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
@@ -744,11 +836,30 @@ class DeviceUtils(object):
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.
@@ -782,7 +893,7 @@ class DeviceUtils(object):
# may never return.
if ((self.build_version_sdk >=
constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2)
- or self.GetApplicationPath(package)):
+ or self.GetApplicationPaths(package)):
self.RunShellCommand(['pm', 'clear', package], check_return=True)
@decorators.WithTimeoutAndRetriesFromInstance()
@@ -810,9 +921,14 @@ class DeviceUtils(object):
PUSH_CHANGED_FILES_DEFAULT_TIMEOUT,
PUSH_CHANGED_FILES_DEFAULT_RETRIES)
def PushChangedFiles(self, host_device_tuples, timeout=None,
- retries=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
@@ -820,6 +936,7 @@ class DeviceUtils(object):
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.
@@ -827,15 +944,72 @@ class DeviceUtils(object):
DeviceUnreachableError on missing device.
"""
- files = []
+ all_changed_files = []
+ all_stale_files = []
for h, d in host_device_tuples:
if os.path.isdir(h):
self.RunShellCommand(['mkdir', '-p', d], check_return=True)
- files += self._GetChangedFilesImpl(h, d)
+ (changed_files, stale_files) = self._GetChangedAndStaleFiles(h, d)
+ all_changed_files += changed_files
+ all_stale_files += stale_files
- if not files:
+ if delete_device_stale:
+ self.RunShellCommand(['rm', '-f'] + all_stale_files,
+ check_return=True)
+
+ if not all_changed_files:
return
+ self._PushFilesImpl(host_device_tuples, all_changed_files)
+
+ def _GetChangedAndStaleFiles(self, host_path, device_path):
+ """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
+
+ Returns:
+ a two-element tuple
+ 1st element: a list of (host_files_path, device_files_path) tuples to push
+ 2nd element: a list of stale files under device_path
+ """
+ real_host_path = os.path.realpath(host_path)
+ try:
+ real_device_path = self.RunShellCommand(
+ ['realpath', device_path], single_line=True, check_return=True)
+ except device_errors.CommandFailedError:
+ real_device_path = None
+ if not real_device_path:
+ return ([(host_path, device_path)], [])
+
+ try:
+ host_checksums = md5sum.CalculateHostMd5Sums([real_host_path])
+ device_checksums = md5sum.CalculateDeviceMd5Sums(
+ [real_device_path], self)
+ except EnvironmentError as e:
+ logging.warning('Error calculating md5: %s', e)
+ return ([(host_path, device_path)], [])
+
+ if os.path.isfile(host_path):
+ host_checksum = host_checksums.get(real_host_path)
+ device_checksum = device_checksums.get(real_device_path)
+ if host_checksum != device_checksum:
+ return ([(host_path, device_path)], [])
+ else:
+ return ([], [])
+ else:
+ to_push = []
+ for host_abs_path, host_checksum in host_checksums.iteritems():
+ device_abs_path = '%s/%s' % (
+ real_device_path, os.path.relpath(host_abs_path, real_host_path))
+ device_checksum = device_checksums.pop(device_abs_path, None)
+ if device_checksum != host_checksum:
+ to_push.append((host_abs_path, device_abs_path))
+ to_delete = device_checksums.keys()
+ return (to_push, to_delete)
+
+ 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)
@@ -866,44 +1040,6 @@ class DeviceUtils(object):
['chmod', '-R', '777'] + [d for _, d in host_device_tuples],
as_root=True, check_return=True)
- def _GetChangedFilesImpl(self, host_path, device_path):
- real_host_path = os.path.realpath(host_path)
- try:
- real_device_path = self.RunShellCommand(
- ['realpath', device_path], single_line=True, check_return=True)
- except device_errors.CommandFailedError:
- real_device_path = None
- if not real_device_path:
- return [(host_path, device_path)]
-
- try:
- host_checksums = md5sum.CalculateHostMd5Sums([real_host_path])
- device_paths_to_md5 = (
- real_device_path if os.path.isfile(real_host_path)
- else ('%s/%s' % (real_device_path, os.path.relpath(p, real_host_path))
- for p in host_checksums.iterkeys()))
- device_checksums = md5sum.CalculateDeviceMd5Sums(
- device_paths_to_md5, self)
- except EnvironmentError as e:
- logging.warning('Error calculating md5: %s', e)
- return [(host_path, device_path)]
-
- if os.path.isfile(host_path):
- host_checksum = host_checksums.get(real_host_path)
- device_checksum = device_checksums.get(real_device_path)
- if host_checksum != device_checksum:
- return [(host_path, device_path)]
- else:
- return []
- else:
- to_push = []
- for host_abs_path, host_checksum in host_checksums.iteritems():
- device_abs_path = '%s/%s' % (
- real_device_path, os.path.relpath(host_abs_path, real_host_path))
- if (device_checksums.get(device_abs_path) != host_checksum):
- to_push.append((host_abs_path, device_abs_path))
- return to_push
-
def _InstallCommands(self):
if self._commands_installed is None:
try:
@@ -1244,6 +1380,29 @@ class DeviceUtils(object):
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):
@@ -1585,4 +1744,3 @@ class DeviceUtils(object):
return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices()
if not blacklisted(adb)]
-
« no previous file with comments | « build/android/pylib/device/battery_utils_test.py ('k') | build/android/pylib/device/device_utils_device_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698