| 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 348620abb3fc4cb38ac572286eacf042bf8a58f2..8064ce88cb28eccde7773258d5f56f29e21fc78f 100644
|
| --- a/build/android/pylib/device/device_utils.py
|
| +++ b/build/android/pylib/device/device_utils.py
|
| @@ -193,6 +193,8 @@ class DeviceUtils(object):
|
| 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|.
|
|
|
| @@ -366,6 +368,12 @@ class DeviceUtils(object):
|
| 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
|
| @@ -380,6 +388,7 @@ class DeviceUtils(object):
|
| 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()
|
| @@ -432,7 +441,7 @@ class DeviceUtils(object):
|
|
|
| def pm_ready():
|
| try:
|
| - return self.GetApplicationPaths('android')
|
| + return self._GetApplicationPathsInternal('android', skip_cache=True)
|
| except device_errors.CommandFailedError:
|
| return False
|
|
|
| @@ -502,21 +511,26 @@ class DeviceUtils(object):
|
| DeviceUnreachableError on missing device.
|
| """
|
| package_name = apk_helper.GetPackageName(apk_path)
|
| - device_paths = self.GetApplicationPaths(package_name)
|
| + 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))
|
| - (files_to_push, _) = self._GetChangedAndStaleFiles(
|
| - apk_path, device_paths[0])
|
| - should_install = bool(files_to_push)
|
| + apks_to_install, host_checksums = (
|
| + self._ComputeStaleApks(package_name, [apk_path]))
|
| + should_install = bool(apks_to_install)
|
| if should_install and not reinstall:
|
| - self.adb.Uninstall(package_name)
|
| + 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,
|
| @@ -545,24 +559,54 @@ class DeviceUtils(object):
|
| 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)
|
| + device_apk_paths = self._GetApplicationPathsInternal(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()]
|
| + apks_to_install, host_checksums = (
|
| + self._ComputeStaleApks(package_name, all_apks))
|
| if apks_to_install and not reinstall:
|
| - self.adb.Uninstall(package_name)
|
| + 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)
|
| + 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.
|
| @@ -914,7 +958,7 @@ class DeviceUtils(object):
|
| # may never return.
|
| if ((self.build_version_sdk >=
|
| constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2)
|
| - or self.GetApplicationPaths(package)):
|
| + or self._GetApplicationPathsInternal(package)):
|
| self.RunShellCommand(['pm', 'clear', package], check_return=True)
|
|
|
| @decorators.WithTimeoutAndRetriesFromInstance()
|
| @@ -1038,6 +1082,22 @@ class DeviceUtils(object):
|
| to_delete = device_checksums.keys()
|
| return (to_push, 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 = md5sum.CalculateHostMd5Sums(host_apk_paths)
|
| + device_checksums = self._ComputeDeviceChecksumsForApks(package_name)
|
| + 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)
|
| @@ -1773,7 +1833,12 @@ class DeviceUtils(object):
|
| """Clears all caches."""
|
| for client in self._client_caches:
|
| self._client_caches[client].clear()
|
| - self._cache.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=None, async=False):
|
|
|