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 14 matching lines...) Expand all Loading... | |
| 25 import pylib.android_commands | 25 import pylib.android_commands |
| 26 from pylib import cmd_helper | 26 from pylib import cmd_helper |
| 27 from pylib import constants | 27 from pylib import constants |
| 28 from pylib import device_signal | 28 from pylib import device_signal |
| 29 from pylib.device import adb_wrapper | 29 from pylib.device import adb_wrapper |
| 30 from pylib.device import decorators | 30 from pylib.device import decorators |
| 31 from pylib.device import device_blacklist | 31 from pylib.device import device_blacklist |
| 32 from pylib.device import device_errors | 32 from pylib.device import device_errors |
| 33 from pylib.device import intent | 33 from pylib.device import intent |
| 34 from pylib.device import logcat_monitor | 34 from pylib.device import logcat_monitor |
| 35 from pylib.device import split_select_wrapper | |
| 35 from pylib.device.commands import install_commands | 36 from pylib.device.commands import install_commands |
| 36 from pylib.utils import apk_helper | 37 from pylib.utils import apk_helper |
| 37 from pylib.utils import base_error | 38 from pylib.utils import base_error |
| 38 from pylib.utils import device_temp_file | 39 from pylib.utils import device_temp_file |
| 39 from pylib.utils import host_utils | 40 from pylib.utils import host_utils |
| 40 from pylib.utils import md5sum | 41 from pylib.utils import md5sum |
| 41 from pylib.utils import parallelizer | 42 from pylib.utils import parallelizer |
| 42 from pylib.utils import timeout_retry | 43 from pylib.utils import timeout_retry |
| 43 from pylib.utils import zip_utils | 44 from pylib.utils import zip_utils |
| 44 | 45 |
| (...skipping 287 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 332 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', | 333 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', |
| 333 single_line=True, | 334 single_line=True, |
| 334 check_return=True) | 335 check_return=True) |
| 335 if not value: | 336 if not value: |
| 336 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', | 337 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', |
| 337 str(self)) | 338 str(self)) |
| 338 self._cache['external_storage'] = value | 339 self._cache['external_storage'] = value |
| 339 return value | 340 return value |
| 340 | 341 |
| 341 @decorators.WithTimeoutAndRetriesFromInstance() | 342 @decorators.WithTimeoutAndRetriesFromInstance() |
| 342 def GetApplicationPath(self, package, timeout=None, retries=None): | 343 def GetApplicationPaths(self, package, timeout=None, retries=None): |
| 343 """Get the path of the installed apk on the device for the given package. | 344 """Get the paths of the installed apks on the device for the given package. |
| 344 | 345 |
| 345 Args: | 346 Args: |
| 346 package: Name of the package. | 347 package: Name of the package. |
| 347 | 348 |
| 348 Returns: | 349 Returns: |
| 349 Path to the apk on the device if it exists, None otherwise. | 350 List of paths to the apks on the device if they exists, None otherwise. |
| 350 """ | 351 """ |
| 351 # 'pm path' is liable to incorrectly exit with a nonzero number starting | 352 # 'pm path' is liable to incorrectly exit with a nonzero number starting |
| 352 # in Lollipop. | 353 # in Lollipop. |
| 353 # TODO(jbudorick): Check if this is fixed as new Android versions are | 354 # TODO(jbudorick): Check if this is fixed as new Android versions are |
| 354 # released to put an upper bound on this. | 355 # released to put an upper bound on this. |
| 355 should_check_return = (self.build_version_sdk < | 356 should_check_return = (self.build_version_sdk < |
| 356 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | 357 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
| 357 output = self.RunShellCommand(['pm', 'path', package], single_line=True, | 358 output = self.RunShellCommand( |
| 358 check_return=should_check_return) | 359 ['pm', 'path', package], check_return=should_check_return) |
| 359 if not output: | 360 if not output: |
| 360 return None | 361 return None |
| 361 if not output.startswith('package:'): | 362 if not all([s.startswith('package:') for s in output]): |
| 362 raise device_errors.CommandFailedError('pm path returned: %r' % output, | 363 raise device_errors.CommandFailedError( |
| 363 str(self)) | 364 'pm path returned: %r' % '\n'.join(output), str(self)) |
| 364 return output[len('package:'):] | 365 return [s[len('package:'):] for s in output] |
|
perezju
2015/06/18 09:30:17
This is walking over the output twice and building
mikecase (-- gone --)
2015/06/18 19:22:20
Now only iterate through list once.
Removed specia
| |
| 365 | 366 |
| 366 @decorators.WithTimeoutAndRetriesFromInstance() | 367 @decorators.WithTimeoutAndRetriesFromInstance() |
| 367 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): | 368 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): |
| 368 """Get the data directory on the device for the given package. | 369 """Get the data directory on the device for the given package. |
| 369 | 370 |
| 370 Args: | 371 Args: |
| 371 package: Name of the package. | 372 package: Name of the package. |
| 372 | 373 |
| 373 Returns: | 374 Returns: |
| 374 The package's data directory, or None if the package doesn't exist on the | 375 The package's data directory, or None if the package doesn't exist on the |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 406 def sd_card_ready(): | 407 def sd_card_ready(): |
| 407 try: | 408 try: |
| 408 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], | 409 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], |
| 409 check_return=True) | 410 check_return=True) |
| 410 return True | 411 return True |
| 411 except device_errors.AdbCommandFailedError: | 412 except device_errors.AdbCommandFailedError: |
| 412 return False | 413 return False |
| 413 | 414 |
| 414 def pm_ready(): | 415 def pm_ready(): |
| 415 try: | 416 try: |
| 416 return self.GetApplicationPath('android') | 417 return self.GetApplicationPaths('android') |
| 417 except device_errors.CommandFailedError: | 418 except device_errors.CommandFailedError: |
| 418 return False | 419 return False |
| 419 | 420 |
| 420 def boot_completed(): | 421 def boot_completed(): |
| 421 return self.GetProp('sys.boot_completed') == '1' | 422 return self.GetProp('sys.boot_completed') == '1' |
| 422 | 423 |
| 423 def wifi_enabled(): | 424 def wifi_enabled(): |
| 424 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], | 425 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], |
| 425 check_return=False) | 426 check_return=False) |
| 426 | 427 |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 476 reinstall: A boolean indicating if we should keep any existing app data. | 477 reinstall: A boolean indicating if we should keep any existing app data. |
| 477 timeout: timeout in seconds | 478 timeout: timeout in seconds |
| 478 retries: number of retries | 479 retries: number of retries |
| 479 | 480 |
| 480 Raises: | 481 Raises: |
| 481 CommandFailedError if the installation fails. | 482 CommandFailedError if the installation fails. |
| 482 CommandTimeoutError if the installation times out. | 483 CommandTimeoutError if the installation times out. |
| 483 DeviceUnreachableError on missing device. | 484 DeviceUnreachableError on missing device. |
| 484 """ | 485 """ |
| 485 package_name = apk_helper.GetPackageName(apk_path) | 486 package_name = apk_helper.GetPackageName(apk_path) |
| 486 device_path = self.GetApplicationPath(package_name) | 487 device_paths = self.GetApplicationPaths(package_name) |
| 487 if device_path is not None: | 488 if device_paths: |
| 488 (files_to_push, _) = self._GetChangedAndStaleFiles( | 489 (files_to_push, _) = self._GetChangedAndStaleFiles( |
|
perezju
2015/06/18 09:30:17
maybe add a warning if device_paths has more than
mikecase (-- gone --)
2015/06/18 19:22:20
Done
| |
| 489 apk_path, device_path) | 490 apk_path, device_paths[0]) |
| 490 should_install = bool(files_to_push) | 491 should_install = bool(files_to_push) |
| 491 if should_install and not reinstall: | 492 if should_install and not reinstall: |
| 492 self.adb.Uninstall(package_name) | 493 self.adb.Uninstall(package_name) |
| 493 else: | 494 else: |
| 494 should_install = True | 495 should_install = True |
| 495 if should_install: | 496 if should_install: |
| 496 self.adb.Install(apk_path, reinstall=reinstall) | 497 self.adb.Install(apk_path, reinstall=reinstall) |
| 497 | 498 |
| 499 @decorators.WithTimeoutAndRetriesDefaults( | |
| 500 INSTALL_DEFAULT_TIMEOUT, | |
| 501 INSTALL_DEFAULT_RETRIES) | |
| 502 def InstallSplitApk(self, base_apk, split_apks, reinstall=False, | |
|
perezju
2015/06/18 09:30:17
This is duplicating a lot of the code in Install.
mikecase (-- gone --)
2015/06/18 19:22:20
Added some more to the InstallSplitApk function so
| |
| 503 timeout=None, retries=None): | |
| 504 """Install a split APK. | |
| 505 | |
| 506 Noop if all of the APK splits are already installed. | |
| 507 | |
| 508 Args: | |
| 509 base_apk: A string of the path to the base APK. | |
| 510 split_apks: A list of strings of paths of all of the APK splits. | |
| 511 reinstall: A boolean indicating if we should keep any existing app data. | |
| 512 timeout: timeout in seconds | |
| 513 retries: number of retries | |
| 514 | |
| 515 Raises: | |
| 516 CommandFailedError if the installation fails. | |
| 517 CommandTimeoutError if the installation times out. | |
| 518 DeviceUnreachableError on missing device. | |
| 519 DeviceVersionError if device SDK is less than Android L. | |
| 520 """ | |
| 521 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | |
| 522 | |
| 523 required_splits = split_select_wrapper.SelectSplits( | |
| 524 self, base_apk, split_apks) | |
| 525 package_name = apk_helper.GetPackageName(base_apk) | |
| 526 device_apk_paths = self.GetApplicationPaths(package_name) | |
| 527 | |
| 528 if device_apk_paths: | |
| 529 device_checksums = md5sum.CalculateDeviceMd5Sums( | |
| 530 device_apk_paths, self).values() | |
| 531 host_checksums = md5sum.CalculateHostMd5Sums( | |
| 532 [base_apk] + required_splits).values() | |
| 533 should_install = sorted(host_checksums) != sorted(device_checksums) | |
| 534 if should_install and not reinstall: | |
| 535 self.adb.Uninstall(package_name) | |
| 536 else: | |
| 537 should_install = True | |
| 538 if should_install: | |
| 539 self.adb.InstallMultiple( | |
|
agrieve
2015/06/18 14:01:08
It would be great to only install the splits that
mikecase (-- gone --)
2015/06/18 19:22:20
Done.
| |
| 540 [base_apk] + required_splits, reinstall=reinstall) | |
| 541 | |
| 542 def _CheckSdkLevel(self, required_sdk_level): | |
| 543 """Raises an exception if the device does not have the required SDK level. | |
| 544 """ | |
| 545 if self.build_version_sdk < required_sdk_level: | |
| 546 raise device_errors.DeviceVersionError( | |
| 547 ('Requires SDK level %s, device is SDK level %s' % | |
| 548 (required_sdk_level, self.build_version_sdk)), | |
| 549 device_serial=self.adb.GetDeviceSerial()) | |
| 550 | |
| 551 | |
| 498 @decorators.WithTimeoutAndRetriesFromInstance() | 552 @decorators.WithTimeoutAndRetriesFromInstance() |
| 499 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, | 553 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, |
| 500 as_root=False, single_line=False, large_output=False, | 554 as_root=False, single_line=False, large_output=False, |
| 501 timeout=None, retries=None): | 555 timeout=None, retries=None): |
| 502 """Run an ADB shell command. | 556 """Run an ADB shell command. |
| 503 | 557 |
| 504 The command to run |cmd| should be a sequence of program arguments or else | 558 The command to run |cmd| should be a sequence of program arguments or else |
| 505 a single string. | 559 a single string. |
| 506 | 560 |
| 507 When |cmd| is a sequence, it is assumed to contain the name of the command | 561 When |cmd| is a sequence, it is assumed to contain the name of the command |
| (...skipping 291 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 799 | 853 |
| 800 Raises: | 854 Raises: |
| 801 CommandTimeoutError on timeout. | 855 CommandTimeoutError on timeout. |
| 802 DeviceUnreachableError on missing device. | 856 DeviceUnreachableError on missing device. |
| 803 """ | 857 """ |
| 804 # Check that the package exists before clearing it for android builds below | 858 # Check that the package exists before clearing it for android builds below |
| 805 # JB MR2. Necessary because calling pm clear on a package that doesn't exist | 859 # JB MR2. Necessary because calling pm clear on a package that doesn't exist |
| 806 # may never return. | 860 # may never return. |
| 807 if ((self.build_version_sdk >= | 861 if ((self.build_version_sdk >= |
| 808 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) | 862 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) |
| 809 or self.GetApplicationPath(package)): | 863 or self.GetApplicationPaths(package)): |
| 810 self.RunShellCommand(['pm', 'clear', package], check_return=True) | 864 self.RunShellCommand(['pm', 'clear', package], check_return=True) |
| 811 | 865 |
| 812 @decorators.WithTimeoutAndRetriesFromInstance() | 866 @decorators.WithTimeoutAndRetriesFromInstance() |
| 813 def SendKeyEvent(self, keycode, timeout=None, retries=None): | 867 def SendKeyEvent(self, keycode, timeout=None, retries=None): |
| 814 """Sends a keycode to the device. | 868 """Sends a keycode to the device. |
| 815 | 869 |
| 816 See the pylib.constants.keyevent module for suitable keycode values. | 870 See the pylib.constants.keyevent module for suitable keycode values. |
| 817 | 871 |
| 818 Args: | 872 Args: |
| 819 keycode: A integer keycode to send to the device. | 873 keycode: A integer keycode to send to the device. |
| (...skipping 466 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1286 | 1340 |
| 1287 # Next, check the current runtime value is what we need, and | 1341 # Next, check the current runtime value is what we need, and |
| 1288 # if not, set it and report that a reboot is required. | 1342 # if not, set it and report that a reboot is required. |
| 1289 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) | 1343 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) |
| 1290 if new_value != value: | 1344 if new_value != value: |
| 1291 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) | 1345 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) |
| 1292 return True | 1346 return True |
| 1293 else: | 1347 else: |
| 1294 return False | 1348 return False |
| 1295 | 1349 |
| 1350 @property | |
| 1351 def langauge_setting(self): | |
| 1352 """Returns the language setting on the device.""" | |
| 1353 return self.GetProp('persist.sys.language', cache=False) | |
| 1354 | |
| 1355 @property | |
| 1356 def country_setting(self): | |
| 1357 """Returns the country setting on the device.""" | |
| 1358 return self.GetProp('persist.sys.country', cache=False) | |
| 1359 | |
| 1360 @property | |
| 1361 def screen_density(self): | |
| 1362 """Returns the screen density of the device.""" | |
| 1363 DPI_TO_DENSITY = { | |
|
perezju
2015/06/18 09:30:16
not sure, but maybe this dict should be a private
mikecase (-- gone --)
2015/06/18 19:22:20
Leaving this here for now. Nothing else needs this
| |
| 1364 120: 'ldpi', | |
| 1365 160: 'mdpi', | |
| 1366 240: 'hdpi', | |
| 1367 320: 'xhdpi', | |
| 1368 480: 'xxhdpi', | |
| 1369 } | |
| 1370 dpi = int(self.GetProp('ro.sf.lcd_density', cache=True)) | |
| 1371 return DPI_TO_DENSITY.get(dpi, 'tvdpi') | |
| 1296 | 1372 |
| 1297 @property | 1373 @property |
| 1298 def build_description(self): | 1374 def build_description(self): |
| 1299 """Returns the build description of the system. | 1375 """Returns the build description of the system. |
| 1300 | 1376 |
| 1301 For example: | 1377 For example: |
| 1302 nakasi-user 4.4.4 KTU84P 1227136 release-keys | 1378 nakasi-user 4.4.4 KTU84P 1227136 release-keys |
| 1303 """ | 1379 """ |
| 1304 return self.GetProp('ro.build.description', cache=True) | 1380 return self.GetProp('ro.build.description', cache=True) |
| 1305 | 1381 |
| (...skipping 320 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1626 @classmethod | 1702 @classmethod |
| 1627 def HealthyDevices(cls): | 1703 def HealthyDevices(cls): |
| 1628 blacklist = device_blacklist.ReadBlacklist() | 1704 blacklist = device_blacklist.ReadBlacklist() |
| 1629 def blacklisted(adb): | 1705 def blacklisted(adb): |
| 1630 if adb.GetDeviceSerial() in blacklist: | 1706 if adb.GetDeviceSerial() in blacklist: |
| 1631 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) | 1707 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) |
| 1632 return True | 1708 return True |
| 1633 return False | 1709 return False |
| 1634 | 1710 |
| 1635 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() | 1711 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() |
| 1636 if not blacklisted(adb)] | 1712 if not blacklisted(adb)] |
| 1637 | |
| OLD | NEW |