Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 """Provides a variety of device interactions based on adb. | 5 """Provides a variety of device interactions based on adb. |
| 6 | 6 |
| 7 Eventually, this will be based on adb_wrapper. | 7 Eventually, this will be based on adb_wrapper. |
| 8 """ | 8 """ |
| 9 # pylint: disable=unused-argument | 9 # pylint: disable=unused-argument |
| 10 | 10 |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 169 elif isinstance(device, pylib.android_commands.AndroidCommands): | 169 elif isinstance(device, pylib.android_commands.AndroidCommands): |
| 170 self.adb = adb_wrapper.AdbWrapper(device.GetDevice()) | 170 self.adb = adb_wrapper.AdbWrapper(device.GetDevice()) |
| 171 self.old_interface = device | 171 self.old_interface = device |
| 172 else: | 172 else: |
| 173 raise ValueError('Unsupported device value: %r' % device) | 173 raise ValueError('Unsupported device value: %r' % device) |
| 174 self._commands_installed = None | 174 self._commands_installed = None |
| 175 self._default_timeout = default_timeout | 175 self._default_timeout = default_timeout |
| 176 self._default_retries = default_retries | 176 self._default_retries = default_retries |
| 177 self._cache = {} | 177 self._cache = {} |
| 178 self._client_caches = {} | 178 self._client_caches = {} |
| 179 self._package_to_device_apk_checksums_cache = {} | |
|
jbudorick
2015/07/16 16:31:33
This should use self._cache, which we already have
agrieve
2015/07/16 19:01:20
I think these caches are better split out since th
| |
| 180 self._package_to_device_apk_paths_cache = {} | |
| 179 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) | 181 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) |
| 180 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) | 182 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) |
| 181 | 183 |
| 182 def __eq__(self, other): | 184 def __eq__(self, other): |
| 183 """Checks whether |other| refers to the same device as |self|. | 185 """Checks whether |other| refers to the same device as |self|. |
| 184 | 186 |
| 185 Args: | 187 Args: |
| 186 other: The object to compare to. This can be a basestring, an instance | 188 other: The object to compare to. This can be a basestring, an instance |
| 187 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils. | 189 of adb_wrapper.AdbWrapper, or an instance of DeviceUtils. |
| 188 Returns: | 190 Returns: |
| (...skipping 147 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 336 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', | 338 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', |
| 337 single_line=True, | 339 single_line=True, |
| 338 check_return=True) | 340 check_return=True) |
| 339 if not value: | 341 if not value: |
| 340 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', | 342 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', |
| 341 str(self)) | 343 str(self)) |
| 342 self._cache['external_storage'] = value | 344 self._cache['external_storage'] = value |
| 343 return value | 345 return value |
| 344 | 346 |
| 345 @decorators.WithTimeoutAndRetriesFromInstance() | 347 @decorators.WithTimeoutAndRetriesFromInstance() |
| 346 def GetApplicationPaths(self, package, timeout=None, retries=None): | 348 def GetApplicationPaths(self, package, timeout=None, retries=None, |
| 349 skip_cache=False): | |
|
jbudorick
2015/07/16 16:31:33
Do we envision external clients using this, or doe
agrieve
2015/07/16 19:01:21
Made it an internal helper.
| |
| 347 """Get the paths of the installed apks on the device for the given package. | 350 """Get the paths of the installed apks on the device for the given package. |
| 348 | 351 |
| 349 Args: | 352 Args: |
| 350 package: Name of the package. | 353 package: Name of the package. |
| 351 | 354 |
| 352 Returns: | 355 Returns: |
| 353 List of paths to the apks on the device for the given package. | 356 List of paths to the apks on the device for the given package. |
| 354 """ | 357 """ |
| 358 cached_result = self._package_to_device_apk_paths_cache.get(package) | |
| 359 if cached_result is not None and not skip_cache: | |
| 360 return list(cached_result) | |
| 355 # 'pm path' is liable to incorrectly exit with a nonzero number starting | 361 # 'pm path' is liable to incorrectly exit with a nonzero number starting |
| 356 # in Lollipop. | 362 # in Lollipop. |
| 357 # TODO(jbudorick): Check if this is fixed as new Android versions are | 363 # TODO(jbudorick): Check if this is fixed as new Android versions are |
| 358 # released to put an upper bound on this. | 364 # released to put an upper bound on this. |
| 359 should_check_return = (self.build_version_sdk < | 365 should_check_return = (self.build_version_sdk < |
| 360 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | 366 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
| 361 output = self.RunShellCommand( | 367 output = self.RunShellCommand( |
| 362 ['pm', 'path', package], check_return=should_check_return) | 368 ['pm', 'path', package], check_return=should_check_return) |
| 363 apks = [] | 369 apks = [] |
| 364 for line in output: | 370 for line in output: |
| 365 if not line.startswith('package:'): | 371 if not line.startswith('package:'): |
| 366 raise device_errors.CommandFailedError( | 372 raise device_errors.CommandFailedError( |
| 367 'pm path returned: %r' % '\n'.join(output), str(self)) | 373 'pm path returned: %r' % '\n'.join(output), str(self)) |
| 368 apks.append(line[len('package:'):]) | 374 apks.append(line[len('package:'):]) |
| 375 self._package_to_device_apk_paths_cache[package] = list(apks) | |
| 369 return apks | 376 return apks |
| 370 | 377 |
| 371 @decorators.WithTimeoutAndRetriesFromInstance() | 378 @decorators.WithTimeoutAndRetriesFromInstance() |
| 372 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): | 379 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): |
| 373 """Get the data directory on the device for the given package. | 380 """Get the data directory on the device for the given package. |
| 374 | 381 |
| 375 Args: | 382 Args: |
| 376 package: Name of the package. | 383 package: Name of the package. |
| 377 | 384 |
| 378 Returns: | 385 Returns: |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 411 def sd_card_ready(): | 418 def sd_card_ready(): |
| 412 try: | 419 try: |
| 413 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], | 420 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], |
| 414 check_return=True) | 421 check_return=True) |
| 415 return True | 422 return True |
| 416 except device_errors.AdbCommandFailedError: | 423 except device_errors.AdbCommandFailedError: |
| 417 return False | 424 return False |
| 418 | 425 |
| 419 def pm_ready(): | 426 def pm_ready(): |
| 420 try: | 427 try: |
| 421 return self.GetApplicationPaths('android') | 428 return self.GetApplicationPaths('android', skip_cache=True) |
| 422 except device_errors.CommandFailedError: | 429 except device_errors.CommandFailedError: |
| 423 return False | 430 return False |
| 424 | 431 |
| 425 def boot_completed(): | 432 def boot_completed(): |
| 426 return self.GetProp('sys.boot_completed') == '1' | 433 return self.GetProp('sys.boot_completed') == '1' |
| 427 | 434 |
| 428 def wifi_enabled(): | 435 def wifi_enabled(): |
| 429 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], | 436 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], |
| 430 check_return=False) | 437 check_return=False) |
| 431 | 438 |
| (...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 487 CommandTimeoutError if the installation times out. | 494 CommandTimeoutError if the installation times out. |
| 488 DeviceUnreachableError on missing device. | 495 DeviceUnreachableError on missing device. |
| 489 """ | 496 """ |
| 490 package_name = apk_helper.GetPackageName(apk_path) | 497 package_name = apk_helper.GetPackageName(apk_path) |
| 491 device_paths = self.GetApplicationPaths(package_name) | 498 device_paths = self.GetApplicationPaths(package_name) |
| 492 if device_paths: | 499 if device_paths: |
| 493 if len(device_paths) > 1: | 500 if len(device_paths) > 1: |
| 494 logging.warning( | 501 logging.warning( |
| 495 'Installing single APK (%s) when split APKs (%s) are currently ' | 502 'Installing single APK (%s) when split APKs (%s) are currently ' |
| 496 'installed.', apk_path, ' '.join(device_paths)) | 503 'installed.', apk_path, ' '.join(device_paths)) |
| 497 (files_to_push, _) = self._GetChangedAndStaleFiles( | 504 apks_to_install, host_checksums = ( |
| 498 apk_path, device_paths[0]) | 505 self._ComputeStaleApks(package_name, [apk_path])) |
| 499 should_install = bool(files_to_push) | 506 should_install = bool(apks_to_install) |
| 500 if should_install and not reinstall: | 507 if should_install and not reinstall: |
| 501 self.adb.Uninstall(package_name) | 508 self._Uninstall(package_name) |
| 502 else: | 509 else: |
| 503 should_install = True | 510 should_install = True |
| 511 host_checksums = None | |
| 504 if should_install: | 512 if should_install: |
| 505 self.adb.Install(apk_path, reinstall=reinstall) | 513 # We won't know the resulting device apk names. |
| 514 self._package_to_device_apk_paths_cache.pop(package_name, 0) | |
| 515 try: | |
| 516 self.adb.Install(apk_path, reinstall=reinstall) | |
| 517 self._package_to_device_apk_checksums_cache[package_name] = ( | |
| 518 host_checksums) | |
| 519 except: | |
| 520 self._package_to_device_apk_paths_cache.pop(package_name, 0) | |
| 521 raise | |
| 506 | 522 |
| 507 @decorators.WithTimeoutAndRetriesDefaults( | 523 @decorators.WithTimeoutAndRetriesDefaults( |
| 508 INSTALL_DEFAULT_TIMEOUT, | 524 INSTALL_DEFAULT_TIMEOUT, |
| 509 INSTALL_DEFAULT_RETRIES) | 525 INSTALL_DEFAULT_RETRIES) |
| 510 def InstallSplitApk(self, base_apk, split_apks, reinstall=False, | 526 def InstallSplitApk(self, base_apk, split_apks, reinstall=False, |
| 511 timeout=None, retries=None): | 527 timeout=None, retries=None): |
| 512 """Install a split APK. | 528 """Install a split APK. |
| 513 | 529 |
| 514 Noop if all of the APK splits are already installed. | 530 Noop if all of the APK splits are already installed. |
| 515 | 531 |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 528 """ | 544 """ |
| 529 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | 545 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
| 530 | 546 |
| 531 all_apks = [base_apk] + split_select.SelectSplits( | 547 all_apks = [base_apk] + split_select.SelectSplits( |
| 532 self, base_apk, split_apks) | 548 self, base_apk, split_apks) |
| 533 package_name = apk_helper.GetPackageName(base_apk) | 549 package_name = apk_helper.GetPackageName(base_apk) |
| 534 device_apk_paths = self.GetApplicationPaths(package_name) | 550 device_apk_paths = self.GetApplicationPaths(package_name) |
| 535 | 551 |
| 536 if device_apk_paths: | 552 if device_apk_paths: |
| 537 partial_install_package = package_name | 553 partial_install_package = package_name |
| 538 device_checksums = md5sum.CalculateDeviceMd5Sums(device_apk_paths, self) | 554 apks_to_install, host_checksums = ( |
| 539 host_checksums = md5sum.CalculateHostMd5Sums(all_apks) | 555 self._ComputeStaleApks(package_name, all_apks)) |
| 540 apks_to_install = [k for (k, v) in host_checksums.iteritems() | |
| 541 if v not in device_checksums.values()] | |
| 542 if apks_to_install and not reinstall: | 556 if apks_to_install and not reinstall: |
| 543 self.adb.Uninstall(package_name) | 557 self._Uninstall(package_name) |
| 544 partial_install_package = None | 558 partial_install_package = None |
| 545 apks_to_install = all_apks | 559 apks_to_install = all_apks |
| 546 else: | 560 else: |
| 547 partial_install_package = None | 561 partial_install_package = None |
| 548 apks_to_install = all_apks | 562 apks_to_install = all_apks |
| 563 host_checksums = None | |
| 549 if apks_to_install: | 564 if apks_to_install: |
| 550 self.adb.InstallMultiple( | 565 # We won't know the resulting device apk names. |
| 551 apks_to_install, partial=partial_install_package, reinstall=reinstall) | 566 self._package_to_device_apk_paths_cache.pop(package_name, 0) |
| 567 try: | |
| 568 self.adb.InstallMultiple( | |
| 569 apks_to_install, partial=partial_install_package, | |
| 570 reinstall=reinstall) | |
| 571 self._package_to_device_apk_checksums_cache[package_name] = ( | |
| 572 host_checksums) | |
| 573 except: | |
| 574 self._package_to_device_apk_paths_cache.pop(package_name, 0) | |
| 575 raise | |
| 576 | |
| 577 def _Uninstall(self, package_name): | |
|
jbudorick
2015/07/16 16:31:32
This should _not_ be named "Uninstall". As-is, it'
agrieve
2015/07/16 19:01:21
Deleted in favour of hooking self.adb.Uninstall
| |
| 578 try: | |
| 579 self.adb.Uninstall(package_name) | |
| 580 except: | |
| 581 # Clear cache since we can't be sure of the state. | |
| 582 self._package_to_device_apk_paths_cache.pop(package_name, 0) | |
| 583 self._package_to_device_apk_checksums_cache.pop(package_name, 0) | |
| 584 raise | |
| 585 self._package_to_device_apk_paths_cache[package_name] = [] | |
| 586 self._package_to_device_apk_checksums_cache[package_name] = set() | |
| 552 | 587 |
| 553 def _CheckSdkLevel(self, required_sdk_level): | 588 def _CheckSdkLevel(self, required_sdk_level): |
| 554 """Raises an exception if the device does not have the required SDK level. | 589 """Raises an exception if the device does not have the required SDK level. |
| 555 """ | 590 """ |
| 556 if self.build_version_sdk < required_sdk_level: | 591 if self.build_version_sdk < required_sdk_level: |
| 557 raise device_errors.DeviceVersionError( | 592 raise device_errors.DeviceVersionError( |
| 558 ('Requires SDK level %s, device is SDK level %s' % | 593 ('Requires SDK level %s, device is SDK level %s' % |
| 559 (required_sdk_level, self.build_version_sdk)), | 594 (required_sdk_level, self.build_version_sdk)), |
| 560 device_serial=self.adb.GetDeviceSerial()) | 595 device_serial=self.adb.GetDeviceSerial()) |
| 561 | 596 |
| (...skipping 448 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1010 to_push = [] | 1045 to_push = [] |
| 1011 for host_abs_path, host_checksum in host_checksums.iteritems(): | 1046 for host_abs_path, host_checksum in host_checksums.iteritems(): |
| 1012 device_abs_path = '%s/%s' % ( | 1047 device_abs_path = '%s/%s' % ( |
| 1013 real_device_path, os.path.relpath(host_abs_path, real_host_path)) | 1048 real_device_path, os.path.relpath(host_abs_path, real_host_path)) |
| 1014 device_checksum = device_checksums.pop(device_abs_path, None) | 1049 device_checksum = device_checksums.pop(device_abs_path, None) |
| 1015 if device_checksum != host_checksum: | 1050 if device_checksum != host_checksum: |
| 1016 to_push.append((host_abs_path, device_abs_path)) | 1051 to_push.append((host_abs_path, device_abs_path)) |
| 1017 to_delete = device_checksums.keys() | 1052 to_delete = device_checksums.keys() |
| 1018 return (to_push, to_delete) | 1053 return (to_push, to_delete) |
| 1019 | 1054 |
| 1055 def _ComputeDeviceChecksumsForApks(self, package_name): | |
| 1056 ret = self._package_to_device_apk_checksums_cache.get(package_name) | |
| 1057 if ret is None: | |
| 1058 device_paths = self.GetApplicationPaths(package_name) | |
| 1059 file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self) | |
| 1060 ret = set(file_to_checksums.values()) | |
| 1061 self._package_to_device_apk_checksums_cache[package_name] = ret | |
| 1062 return ret | |
| 1063 | |
| 1064 def _ComputeStaleApks(self, package_name, host_apk_paths): | |
| 1065 host_checksums = md5sum.CalculateHostMd5Sums(host_apk_paths) | |
| 1066 device_checksums = self._ComputeDeviceChecksumsForApks(package_name) | |
| 1067 stale_apks = [k for (k, v) in host_checksums.iteritems() | |
| 1068 if v not in device_checksums] | |
| 1069 return stale_apks, set(host_checksums.values()) | |
| 1070 | |
| 1020 def _PushFilesImpl(self, host_device_tuples, files): | 1071 def _PushFilesImpl(self, host_device_tuples, files): |
| 1021 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) | 1072 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) |
| 1022 file_count = len(files) | 1073 file_count = len(files) |
| 1023 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) | 1074 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) |
| 1024 for h, _ in host_device_tuples) | 1075 for h, _ in host_device_tuples) |
| 1025 dir_file_count = 0 | 1076 dir_file_count = 0 |
| 1026 for h, _ in host_device_tuples: | 1077 for h, _ in host_device_tuples: |
| 1027 if os.path.isdir(h): | 1078 if os.path.isdir(h): |
| 1028 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) | 1079 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) |
| 1029 else: | 1080 else: |
| (...skipping 675 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1705 """Returns client cache.""" | 1756 """Returns client cache.""" |
| 1706 if client_name not in self._client_caches: | 1757 if client_name not in self._client_caches: |
| 1707 self._client_caches[client_name] = {} | 1758 self._client_caches[client_name] = {} |
| 1708 return self._client_caches[client_name] | 1759 return self._client_caches[client_name] |
| 1709 | 1760 |
| 1710 def _ClearCache(self): | 1761 def _ClearCache(self): |
| 1711 """Clears all caches.""" | 1762 """Clears all caches.""" |
| 1712 for client in self._client_caches: | 1763 for client in self._client_caches: |
| 1713 self._client_caches[client].clear() | 1764 self._client_caches[client].clear() |
| 1714 self._cache.clear() | 1765 self._cache.clear() |
| 1766 self._package_to_device_apk_paths_cache.clear() | |
| 1767 self._package_to_device_apk_checksums_cache.clear() | |
| 1715 | 1768 |
| 1716 @classmethod | 1769 @classmethod |
| 1717 def parallel(cls, devices=None, async=False): | 1770 def parallel(cls, devices=None, async=False): |
| 1718 """Creates a Parallelizer to operate over the provided list of devices. | 1771 """Creates a Parallelizer to operate over the provided list of devices. |
| 1719 | 1772 |
| 1720 If |devices| is either |None| or an empty list, the Parallelizer will | 1773 If |devices| is either |None| or an empty list, the Parallelizer will |
| 1721 operate over all attached devices that have not been blacklisted. | 1774 operate over all attached devices that have not been blacklisted. |
| 1722 | 1775 |
| 1723 Args: | 1776 Args: |
| 1724 devices: A list of either DeviceUtils instances or objects from | 1777 devices: A list of either DeviceUtils instances or objects from |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 1745 def HealthyDevices(cls): | 1798 def HealthyDevices(cls): |
| 1746 blacklist = device_blacklist.ReadBlacklist() | 1799 blacklist = device_blacklist.ReadBlacklist() |
| 1747 def blacklisted(adb): | 1800 def blacklisted(adb): |
| 1748 if adb.GetDeviceSerial() in blacklist: | 1801 if adb.GetDeviceSerial() in blacklist: |
| 1749 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) | 1802 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) |
| 1750 return True | 1803 return True |
| 1751 return False | 1804 return False |
| 1752 | 1805 |
| 1753 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() | 1806 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() |
| 1754 if not blacklisted(adb)] | 1807 if not blacklisted(adb)] |
| OLD | NEW |