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 |