| 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 |
| 11 import collections | 11 import collections |
| 12 import contextlib | 12 import contextlib |
| 13 import itertools | 13 import itertools |
| 14 import logging | 14 import logging |
| 15 import multiprocessing | 15 import multiprocessing |
| 16 import os | 16 import os |
| 17 import posixpath | 17 import posixpath |
| 18 import re | 18 import re |
| 19 import shutil | 19 import shutil |
| 20 import sys | 20 import sys |
| 21 import tempfile | 21 import tempfile |
| 22 import time | 22 import time |
| 23 import zipfile | 23 import zipfile |
| 24 | 24 |
| 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.constants import keyevent |
| 29 from pylib.device import adb_wrapper | 30 from pylib.device import adb_wrapper |
| 30 from pylib.device import decorators | 31 from pylib.device import decorators |
| 31 from pylib.device import device_blacklist | 32 from pylib.device import device_blacklist |
| 32 from pylib.device import device_errors | 33 from pylib.device import device_errors |
| 33 from pylib.device import intent | 34 from pylib.device import intent |
| 34 from pylib.device import logcat_monitor | 35 from pylib.device import logcat_monitor |
| 35 from pylib.device.commands import install_commands | 36 from pylib.device.commands import install_commands |
| 37 from pylib.sdk import split_select |
| 36 from pylib.utils import apk_helper | 38 from pylib.utils import apk_helper |
| 37 from pylib.utils import base_error | 39 from pylib.utils import base_error |
| 38 from pylib.utils import device_temp_file | 40 from pylib.utils import device_temp_file |
| 39 from pylib.utils import host_utils | 41 from pylib.utils import host_utils |
| 40 from pylib.utils import md5sum | 42 from pylib.utils import md5sum |
| 41 from pylib.utils import parallelizer | 43 from pylib.utils import parallelizer |
| 42 from pylib.utils import timeout_retry | 44 from pylib.utils import timeout_retry |
| 43 from pylib.utils import zip_utils | 45 from pylib.utils import zip_utils |
| 44 | 46 |
| 45 _DEFAULT_TIMEOUT = 30 | 47 _DEFAULT_TIMEOUT = 30 |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 128 # makes sure that the last line is also terminated, and is more memory | 130 # makes sure that the last line is also terminated, and is more memory |
| 129 # efficient than first appending an end-line to each line and then joining | 131 # efficient than first appending an end-line to each line and then joining |
| 130 # all of them together. | 132 # all of them together. |
| 131 return ''.join(s for line in lines for s in (line, '\n')) | 133 return ''.join(s for line in lines for s in (line, '\n')) |
| 132 | 134 |
| 133 | 135 |
| 134 class DeviceUtils(object): | 136 class DeviceUtils(object): |
| 135 | 137 |
| 136 _MAX_ADB_COMMAND_LENGTH = 512 | 138 _MAX_ADB_COMMAND_LENGTH = 512 |
| 137 _MAX_ADB_OUTPUT_LENGTH = 32768 | 139 _MAX_ADB_OUTPUT_LENGTH = 32768 |
| 140 _LAUNCHER_FOCUSED_RE = re.compile( |
| 141 '\s*mCurrentFocus.*(Launcher|launcher).*') |
| 138 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') | 142 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') |
| 139 | 143 |
| 140 # Property in /data/local.prop that controls Java assertions. | 144 # Property in /data/local.prop that controls Java assertions. |
| 141 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' | 145 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' |
| 142 | 146 |
| 143 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, | 147 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, |
| 144 default_retries=_DEFAULT_RETRIES): | 148 default_retries=_DEFAULT_RETRIES): |
| 145 """DeviceUtils constructor. | 149 """DeviceUtils constructor. |
| 146 | 150 |
| 147 Args: | 151 Args: |
| (...skipping 184 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 332 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', | 336 value = self.RunShellCommand('echo $EXTERNAL_STORAGE', |
| 333 single_line=True, | 337 single_line=True, |
| 334 check_return=True) | 338 check_return=True) |
| 335 if not value: | 339 if not value: |
| 336 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', | 340 raise device_errors.CommandFailedError('$EXTERNAL_STORAGE is not set', |
| 337 str(self)) | 341 str(self)) |
| 338 self._cache['external_storage'] = value | 342 self._cache['external_storage'] = value |
| 339 return value | 343 return value |
| 340 | 344 |
| 341 @decorators.WithTimeoutAndRetriesFromInstance() | 345 @decorators.WithTimeoutAndRetriesFromInstance() |
| 342 def GetApplicationPath(self, package, timeout=None, retries=None): | 346 def GetApplicationPaths(self, package, timeout=None, retries=None): |
| 343 """Get the path of the installed apk on the device for the given package. | 347 """Get the paths of the installed apks on the device for the given package. |
| 344 | 348 |
| 345 Args: | 349 Args: |
| 346 package: Name of the package. | 350 package: Name of the package. |
| 347 | 351 |
| 348 Returns: | 352 Returns: |
| 349 Path to the apk on the device if it exists, None otherwise. | 353 List of paths to the apks on the device for the given package. |
| 350 """ | 354 """ |
| 351 # 'pm path' is liable to incorrectly exit with a nonzero number starting | 355 # 'pm path' is liable to incorrectly exit with a nonzero number starting |
| 352 # in Lollipop. | 356 # in Lollipop. |
| 353 # TODO(jbudorick): Check if this is fixed as new Android versions are | 357 # TODO(jbudorick): Check if this is fixed as new Android versions are |
| 354 # released to put an upper bound on this. | 358 # released to put an upper bound on this. |
| 355 should_check_return = (self.build_version_sdk < | 359 should_check_return = (self.build_version_sdk < |
| 356 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) | 360 constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
| 357 output = self.RunShellCommand(['pm', 'path', package], single_line=True, | 361 output = self.RunShellCommand( |
| 358 check_return=should_check_return) | 362 ['pm', 'path', package], check_return=should_check_return) |
| 359 if not output: | 363 apks = [] |
| 360 return None | 364 for line in output: |
| 361 if not output.startswith('package:'): | 365 if not line.startswith('package:'): |
| 362 raise device_errors.CommandFailedError('pm path returned: %r' % output, | 366 raise device_errors.CommandFailedError( |
| 363 str(self)) | 367 'pm path returned: %r' % '\n'.join(output), str(self)) |
| 364 return output[len('package:'):] | 368 apks.append(line[len('package:'):]) |
| 369 return apks |
| 370 |
| 371 @decorators.WithTimeoutAndRetriesFromInstance() |
| 372 def GetApplicationDataDirectory(self, package, timeout=None, retries=None): |
| 373 """Get the data directory on the device for the given package. |
| 374 |
| 375 Args: |
| 376 package: Name of the package. |
| 377 |
| 378 Returns: |
| 379 The package's data directory, or None if the package doesn't exist on the |
| 380 device. |
| 381 """ |
| 382 try: |
| 383 output = self._RunPipedShellCommand( |
| 384 'pm dump %s | grep dataDir=' % cmd_helper.SingleQuote(package)) |
| 385 for line in output: |
| 386 _, _, dataDir = line.partition('dataDir=') |
| 387 if dataDir: |
| 388 return dataDir |
| 389 except device_errors.CommandFailedError: |
| 390 logging.exception('Could not find data directory for %s', package) |
| 391 return None |
| 365 | 392 |
| 366 @decorators.WithTimeoutAndRetriesFromInstance() | 393 @decorators.WithTimeoutAndRetriesFromInstance() |
| 367 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): | 394 def WaitUntilFullyBooted(self, wifi=False, timeout=None, retries=None): |
| 368 """Wait for the device to fully boot. | 395 """Wait for the device to fully boot. |
| 369 | 396 |
| 370 This means waiting for the device to boot, the package manager to be | 397 This means waiting for the device to boot, the package manager to be |
| 371 available, and the SD card to be ready. It can optionally mean waiting | 398 available, and the SD card to be ready. It can optionally mean waiting |
| 372 for wifi to come up, too. | 399 for wifi to come up, too. |
| 373 | 400 |
| 374 Args: | 401 Args: |
| 375 wifi: A boolean indicating if we should wait for wifi to come up or not. | 402 wifi: A boolean indicating if we should wait for wifi to come up or not. |
| 376 timeout: timeout in seconds | 403 timeout: timeout in seconds |
| 377 retries: number of retries | 404 retries: number of retries |
| 378 | 405 |
| 379 Raises: | 406 Raises: |
| 380 CommandFailedError on failure. | 407 CommandFailedError on failure. |
| 381 CommandTimeoutError if one of the component waits times out. | 408 CommandTimeoutError if one of the component waits times out. |
| 382 DeviceUnreachableError if the device becomes unresponsive. | 409 DeviceUnreachableError if the device becomes unresponsive. |
| 383 """ | 410 """ |
| 384 def sd_card_ready(): | 411 def sd_card_ready(): |
| 385 try: | 412 try: |
| 386 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], | 413 self.RunShellCommand(['test', '-d', self.GetExternalStoragePath()], |
| 387 check_return=True) | 414 check_return=True) |
| 388 return True | 415 return True |
| 389 except device_errors.AdbCommandFailedError: | 416 except device_errors.AdbCommandFailedError: |
| 390 return False | 417 return False |
| 391 | 418 |
| 392 def pm_ready(): | 419 def pm_ready(): |
| 393 try: | 420 try: |
| 394 return self.GetApplicationPath('android') | 421 return self.GetApplicationPaths('android') |
| 395 except device_errors.CommandFailedError: | 422 except device_errors.CommandFailedError: |
| 396 return False | 423 return False |
| 397 | 424 |
| 398 def boot_completed(): | 425 def boot_completed(): |
| 399 return self.GetProp('sys.boot_completed') == '1' | 426 return self.GetProp('sys.boot_completed') == '1' |
| 400 | 427 |
| 401 def wifi_enabled(): | 428 def wifi_enabled(): |
| 402 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], | 429 return 'Wi-Fi is enabled' in self.RunShellCommand(['dumpsys', 'wifi'], |
| 403 check_return=False) | 430 check_return=False) |
| 404 | 431 |
| (...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 454 reinstall: A boolean indicating if we should keep any existing app data. | 481 reinstall: A boolean indicating if we should keep any existing app data. |
| 455 timeout: timeout in seconds | 482 timeout: timeout in seconds |
| 456 retries: number of retries | 483 retries: number of retries |
| 457 | 484 |
| 458 Raises: | 485 Raises: |
| 459 CommandFailedError if the installation fails. | 486 CommandFailedError if the installation fails. |
| 460 CommandTimeoutError if the installation times out. | 487 CommandTimeoutError if the installation times out. |
| 461 DeviceUnreachableError on missing device. | 488 DeviceUnreachableError on missing device. |
| 462 """ | 489 """ |
| 463 package_name = apk_helper.GetPackageName(apk_path) | 490 package_name = apk_helper.GetPackageName(apk_path) |
| 464 device_path = self.GetApplicationPath(package_name) | 491 device_paths = self.GetApplicationPaths(package_name) |
| 465 if device_path is not None: | 492 if device_paths: |
| 466 should_install = bool(self._GetChangedFilesImpl(apk_path, device_path)) | 493 if len(device_paths) > 1: |
| 494 logging.warning( |
| 495 'Installing single APK (%s) when split APKs (%s) are currently ' |
| 496 'installed.', apk_path, ' '.join(device_paths)) |
| 497 (files_to_push, _) = self._GetChangedAndStaleFiles( |
| 498 apk_path, device_paths[0]) |
| 499 should_install = bool(files_to_push) |
| 467 if should_install and not reinstall: | 500 if should_install and not reinstall: |
| 468 self.adb.Uninstall(package_name) | 501 self.adb.Uninstall(package_name) |
| 469 else: | 502 else: |
| 470 should_install = True | 503 should_install = True |
| 471 if should_install: | 504 if should_install: |
| 472 self.adb.Install(apk_path, reinstall=reinstall) | 505 self.adb.Install(apk_path, reinstall=reinstall) |
| 473 | 506 |
| 507 @decorators.WithTimeoutAndRetriesDefaults( |
| 508 INSTALL_DEFAULT_TIMEOUT, |
| 509 INSTALL_DEFAULT_RETRIES) |
| 510 def InstallSplitApk(self, base_apk, split_apks, reinstall=False, |
| 511 timeout=None, retries=None): |
| 512 """Install a split APK. |
| 513 |
| 514 Noop if all of the APK splits are already installed. |
| 515 |
| 516 Args: |
| 517 base_apk: A string of the path to the base APK. |
| 518 split_apks: A list of strings of paths of all of the APK splits. |
| 519 reinstall: A boolean indicating if we should keep any existing app data. |
| 520 timeout: timeout in seconds |
| 521 retries: number of retries |
| 522 |
| 523 Raises: |
| 524 CommandFailedError if the installation fails. |
| 525 CommandTimeoutError if the installation times out. |
| 526 DeviceUnreachableError on missing device. |
| 527 DeviceVersionError if device SDK is less than Android L. |
| 528 """ |
| 529 self._CheckSdkLevel(constants.ANDROID_SDK_VERSION_CODES.LOLLIPOP) |
| 530 |
| 531 all_apks = [base_apk] + split_select.SelectSplits( |
| 532 self, base_apk, split_apks) |
| 533 package_name = apk_helper.GetPackageName(base_apk) |
| 534 device_apk_paths = self.GetApplicationPaths(package_name) |
| 535 |
| 536 if device_apk_paths: |
| 537 partial_install_package = package_name |
| 538 device_checksums = md5sum.CalculateDeviceMd5Sums(device_apk_paths, self) |
| 539 host_checksums = md5sum.CalculateHostMd5Sums(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: |
| 543 self.adb.Uninstall(package_name) |
| 544 partial_install_package = None |
| 545 apks_to_install = all_apks |
| 546 else: |
| 547 partial_install_package = None |
| 548 apks_to_install = all_apks |
| 549 if apks_to_install: |
| 550 self.adb.InstallMultiple( |
| 551 apks_to_install, partial=partial_install_package, reinstall=reinstall) |
| 552 |
| 553 def _CheckSdkLevel(self, required_sdk_level): |
| 554 """Raises an exception if the device does not have the required SDK level. |
| 555 """ |
| 556 if self.build_version_sdk < required_sdk_level: |
| 557 raise device_errors.DeviceVersionError( |
| 558 ('Requires SDK level %s, device is SDK level %s' % |
| 559 (required_sdk_level, self.build_version_sdk)), |
| 560 device_serial=self.adb.GetDeviceSerial()) |
| 561 |
| 562 |
| 474 @decorators.WithTimeoutAndRetriesFromInstance() | 563 @decorators.WithTimeoutAndRetriesFromInstance() |
| 475 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, | 564 def RunShellCommand(self, cmd, check_return=False, cwd=None, env=None, |
| 476 as_root=False, single_line=False, large_output=False, | 565 as_root=False, single_line=False, large_output=False, |
| 477 timeout=None, retries=None): | 566 timeout=None, retries=None): |
| 478 """Run an ADB shell command. | 567 """Run an ADB shell command. |
| 479 | 568 |
| 480 The command to run |cmd| should be a sequence of program arguments or else | 569 The command to run |cmd| should be a sequence of program arguments or else |
| 481 a single string. | 570 a single string. |
| 482 | 571 |
| 483 When |cmd| is a sequence, it is assumed to contain the name of the command | 572 When |cmd| is a sequence, it is assumed to contain the name of the command |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 550 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: | 639 with device_temp_file.DeviceTempFile(self.adb, suffix='.sh') as script: |
| 551 self._WriteFileWithPush(script.name, cmd) | 640 self._WriteFileWithPush(script.name, cmd) |
| 552 logging.info('Large shell command will be run from file: %s ...', | 641 logging.info('Large shell command will be run from file: %s ...', |
| 553 cmd[:100]) | 642 cmd[:100]) |
| 554 return handle_check_return('sh %s' % script.name_quoted) | 643 return handle_check_return('sh %s' % script.name_quoted) |
| 555 | 644 |
| 556 def handle_large_output(cmd, large_output_mode): | 645 def handle_large_output(cmd, large_output_mode): |
| 557 if large_output_mode: | 646 if large_output_mode: |
| 558 with device_temp_file.DeviceTempFile(self.adb) as large_output_file: | 647 with device_temp_file.DeviceTempFile(self.adb) as large_output_file: |
| 559 cmd = '%s > %s' % (cmd, large_output_file.name) | 648 cmd = '%s > %s' % (cmd, large_output_file.name) |
| 560 logging.info('Large output mode enabled. Will write output to device ' | 649 logging.debug('Large output mode enabled. Will write output to ' |
| 561 'and read results from file.') | 650 'device and read results from file.') |
| 562 handle_large_command(cmd) | 651 handle_large_command(cmd) |
| 563 return self.ReadFile(large_output_file.name, force_pull=True) | 652 return self.ReadFile(large_output_file.name, force_pull=True) |
| 564 else: | 653 else: |
| 565 try: | 654 try: |
| 566 return handle_large_command(cmd) | 655 return handle_large_command(cmd) |
| 567 except device_errors.AdbCommandFailedError as exc: | 656 except device_errors.AdbCommandFailedError as exc: |
| 568 if exc.status is None: | 657 if exc.status is None: |
| 569 logging.exception('No output found for %s', cmd) | 658 logging.exception('No output found for %s', cmd) |
| 570 logging.warning('Attempting to run in large_output mode.') | 659 logging.warning('Attempting to run in large_output mode.') |
| 571 logging.warning('Use RunShellCommand(..., large_output=True) for ' | 660 logging.warning('Use RunShellCommand(..., large_output=True) for ' |
| (...skipping 135 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 707 extras = {} | 796 extras = {} |
| 708 | 797 |
| 709 cmd = ['am', 'instrument'] | 798 cmd = ['am', 'instrument'] |
| 710 if finish: | 799 if finish: |
| 711 cmd.append('-w') | 800 cmd.append('-w') |
| 712 if raw: | 801 if raw: |
| 713 cmd.append('-r') | 802 cmd.append('-r') |
| 714 for k, v in extras.iteritems(): | 803 for k, v in extras.iteritems(): |
| 715 cmd.extend(['-e', str(k), str(v)]) | 804 cmd.extend(['-e', str(k), str(v)]) |
| 716 cmd.append(component) | 805 cmd.append(component) |
| 717 return self.RunShellCommand(cmd, check_return=True) | 806 return self.RunShellCommand(cmd, check_return=True, large_output=True) |
| 718 | 807 |
| 719 @decorators.WithTimeoutAndRetriesFromInstance() | 808 @decorators.WithTimeoutAndRetriesFromInstance() |
| 720 def BroadcastIntent(self, intent_obj, timeout=None, retries=None): | 809 def BroadcastIntent(self, intent_obj, timeout=None, retries=None): |
| 721 """Send a broadcast intent. | 810 """Send a broadcast intent. |
| 722 | 811 |
| 723 Args: | 812 Args: |
| 724 intent: An Intent to broadcast. | 813 intent: An Intent to broadcast. |
| 725 timeout: timeout in seconds | 814 timeout: timeout in seconds |
| 726 retries: number of retries | 815 retries: number of retries |
| 727 | 816 |
| 728 Raises: | 817 Raises: |
| 729 CommandTimeoutError on timeout. | 818 CommandTimeoutError on timeout. |
| 730 DeviceUnreachableError on missing device. | 819 DeviceUnreachableError on missing device. |
| 731 """ | 820 """ |
| 732 cmd = ['am', 'broadcast'] + intent_obj.am_args | 821 cmd = ['am', 'broadcast'] + intent_obj.am_args |
| 733 self.RunShellCommand(cmd, check_return=True) | 822 self.RunShellCommand(cmd, check_return=True) |
| 734 | 823 |
| 735 @decorators.WithTimeoutAndRetriesFromInstance() | 824 @decorators.WithTimeoutAndRetriesFromInstance() |
| 736 def GoHome(self, timeout=None, retries=None): | 825 def GoHome(self, timeout=None, retries=None): |
| 737 """Return to the home screen. | 826 """Return to the home screen and obtain launcher focus. |
| 827 |
| 828 This command launches the home screen and attempts to obtain |
| 829 launcher focus until the timeout is reached. |
| 738 | 830 |
| 739 Args: | 831 Args: |
| 740 timeout: timeout in seconds | 832 timeout: timeout in seconds |
| 741 retries: number of retries | 833 retries: number of retries |
| 742 | 834 |
| 743 Raises: | 835 Raises: |
| 744 CommandTimeoutError on timeout. | 836 CommandTimeoutError on timeout. |
| 745 DeviceUnreachableError on missing device. | 837 DeviceUnreachableError on missing device. |
| 746 """ | 838 """ |
| 839 def is_launcher_focused(): |
| 840 output = self.RunShellCommand(['dumpsys', 'window', 'windows'], |
| 841 check_return=True, large_output=True) |
| 842 return any(self._LAUNCHER_FOCUSED_RE.match(l) for l in output) |
| 843 |
| 844 def dismiss_popups(): |
| 845 # There is a dialog present; attempt to get rid of it. |
| 846 # Not all dialogs can be dismissed with back. |
| 847 self.SendKeyEvent(keyevent.KEYCODE_ENTER) |
| 848 self.SendKeyEvent(keyevent.KEYCODE_BACK) |
| 849 return is_launcher_focused() |
| 850 |
| 851 # If Home is already focused, return early to avoid unnecessary work. |
| 852 if is_launcher_focused(): |
| 853 return |
| 854 |
| 747 self.StartActivity( | 855 self.StartActivity( |
| 748 intent.Intent(action='android.intent.action.MAIN', | 856 intent.Intent(action='android.intent.action.MAIN', |
| 749 category='android.intent.category.HOME'), | 857 category='android.intent.category.HOME'), |
| 750 blocking=True) | 858 blocking=True) |
| 751 | 859 |
| 860 if not is_launcher_focused(): |
| 861 timeout_retry.WaitFor(dismiss_popups, wait_period=1) |
| 862 |
| 752 @decorators.WithTimeoutAndRetriesFromInstance() | 863 @decorators.WithTimeoutAndRetriesFromInstance() |
| 753 def ForceStop(self, package, timeout=None, retries=None): | 864 def ForceStop(self, package, timeout=None, retries=None): |
| 754 """Close the application. | 865 """Close the application. |
| 755 | 866 |
| 756 Args: | 867 Args: |
| 757 package: A string containing the name of the package to stop. | 868 package: A string containing the name of the package to stop. |
| 758 timeout: timeout in seconds | 869 timeout: timeout in seconds |
| 759 retries: number of retries | 870 retries: number of retries |
| 760 | 871 |
| 761 Raises: | 872 Raises: |
| (...skipping 13 matching lines...) Expand all Loading... |
| 775 | 886 |
| 776 Raises: | 887 Raises: |
| 777 CommandTimeoutError on timeout. | 888 CommandTimeoutError on timeout. |
| 778 DeviceUnreachableError on missing device. | 889 DeviceUnreachableError on missing device. |
| 779 """ | 890 """ |
| 780 # Check that the package exists before clearing it for android builds below | 891 # Check that the package exists before clearing it for android builds below |
| 781 # JB MR2. Necessary because calling pm clear on a package that doesn't exist | 892 # JB MR2. Necessary because calling pm clear on a package that doesn't exist |
| 782 # may never return. | 893 # may never return. |
| 783 if ((self.build_version_sdk >= | 894 if ((self.build_version_sdk >= |
| 784 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) | 895 constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2) |
| 785 or self.GetApplicationPath(package)): | 896 or self.GetApplicationPaths(package)): |
| 786 self.RunShellCommand(['pm', 'clear', package], check_return=True) | 897 self.RunShellCommand(['pm', 'clear', package], check_return=True) |
| 787 | 898 |
| 788 @decorators.WithTimeoutAndRetriesFromInstance() | 899 @decorators.WithTimeoutAndRetriesFromInstance() |
| 789 def SendKeyEvent(self, keycode, timeout=None, retries=None): | 900 def SendKeyEvent(self, keycode, timeout=None, retries=None): |
| 790 """Sends a keycode to the device. | 901 """Sends a keycode to the device. |
| 791 | 902 |
| 792 See the pylib.constants.keyevent module for suitable keycode values. | 903 See the pylib.constants.keyevent module for suitable keycode values. |
| 793 | 904 |
| 794 Args: | 905 Args: |
| 795 keycode: A integer keycode to send to the device. | 906 keycode: A integer keycode to send to the device. |
| 796 timeout: timeout in seconds | 907 timeout: timeout in seconds |
| 797 retries: number of retries | 908 retries: number of retries |
| 798 | 909 |
| 799 Raises: | 910 Raises: |
| 800 CommandTimeoutError on timeout. | 911 CommandTimeoutError on timeout. |
| 801 DeviceUnreachableError on missing device. | 912 DeviceUnreachableError on missing device. |
| 802 """ | 913 """ |
| 803 self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')], | 914 self.RunShellCommand(['input', 'keyevent', format(keycode, 'd')], |
| 804 check_return=True) | 915 check_return=True) |
| 805 | 916 |
| 806 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT | 917 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT = 10 * _DEFAULT_TIMEOUT |
| 807 PUSH_CHANGED_FILES_DEFAULT_RETRIES = _DEFAULT_RETRIES | 918 PUSH_CHANGED_FILES_DEFAULT_RETRIES = _DEFAULT_RETRIES |
| 808 | 919 |
| 809 @decorators.WithTimeoutAndRetriesDefaults( | 920 @decorators.WithTimeoutAndRetriesDefaults( |
| 810 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT, | 921 PUSH_CHANGED_FILES_DEFAULT_TIMEOUT, |
| 811 PUSH_CHANGED_FILES_DEFAULT_RETRIES) | 922 PUSH_CHANGED_FILES_DEFAULT_RETRIES) |
| 812 def PushChangedFiles(self, host_device_tuples, timeout=None, | 923 def PushChangedFiles(self, host_device_tuples, timeout=None, |
| 813 retries=None): | 924 retries=None, delete_device_stale=False): |
| 814 """Push files to the device, skipping files that don't need updating. | 925 """Push files to the device, skipping files that don't need updating. |
| 815 | 926 |
| 927 When a directory is pushed, it is traversed recursively on the host and |
| 928 all files in it are pushed to the device as needed. |
| 929 Additionally, if delete_device_stale option is True, |
| 930 files that exist on the device but don't exist on the host are deleted. |
| 931 |
| 816 Args: | 932 Args: |
| 817 host_device_tuples: A list of (host_path, device_path) tuples, where | 933 host_device_tuples: A list of (host_path, device_path) tuples, where |
| 818 |host_path| is an absolute path of a file or directory on the host | 934 |host_path| is an absolute path of a file or directory on the host |
| 819 that should be minimially pushed to the device, and |device_path| is | 935 that should be minimially pushed to the device, and |device_path| is |
| 820 an absolute path of the destination on the device. | 936 an absolute path of the destination on the device. |
| 821 timeout: timeout in seconds | 937 timeout: timeout in seconds |
| 822 retries: number of retries | 938 retries: number of retries |
| 939 delete_device_stale: option to delete stale files on device |
| 823 | 940 |
| 824 Raises: | 941 Raises: |
| 825 CommandFailedError on failure. | 942 CommandFailedError on failure. |
| 826 CommandTimeoutError on timeout. | 943 CommandTimeoutError on timeout. |
| 827 DeviceUnreachableError on missing device. | 944 DeviceUnreachableError on missing device. |
| 828 """ | 945 """ |
| 829 | 946 |
| 830 files = [] | 947 all_changed_files = [] |
| 948 all_stale_files = [] |
| 831 for h, d in host_device_tuples: | 949 for h, d in host_device_tuples: |
| 832 if os.path.isdir(h): | 950 if os.path.isdir(h): |
| 833 self.RunShellCommand(['mkdir', '-p', d], check_return=True) | 951 self.RunShellCommand(['mkdir', '-p', d], check_return=True) |
| 834 files += self._GetChangedFilesImpl(h, d) | 952 (changed_files, stale_files) = self._GetChangedAndStaleFiles(h, d) |
| 953 all_changed_files += changed_files |
| 954 all_stale_files += stale_files |
| 835 | 955 |
| 836 if not files: | 956 if delete_device_stale: |
| 957 self.RunShellCommand(['rm', '-f'] + all_stale_files, |
| 958 check_return=True) |
| 959 |
| 960 if not all_changed_files: |
| 837 return | 961 return |
| 838 | 962 |
| 963 self._PushFilesImpl(host_device_tuples, all_changed_files) |
| 964 |
| 965 def _GetChangedAndStaleFiles(self, host_path, device_path): |
| 966 """Get files to push and delete |
| 967 |
| 968 Args: |
| 969 host_path: an absolute path of a file or directory on the host |
| 970 device_path: an absolute path of a file or directory on the device |
| 971 |
| 972 Returns: |
| 973 a two-element tuple |
| 974 1st element: a list of (host_files_path, device_files_path) tuples to push |
| 975 2nd element: a list of stale files under device_path |
| 976 """ |
| 977 real_host_path = os.path.realpath(host_path) |
| 978 try: |
| 979 real_device_path = self.RunShellCommand( |
| 980 ['realpath', device_path], single_line=True, check_return=True) |
| 981 except device_errors.CommandFailedError: |
| 982 real_device_path = None |
| 983 if not real_device_path: |
| 984 return ([(host_path, device_path)], []) |
| 985 |
| 986 try: |
| 987 host_checksums = md5sum.CalculateHostMd5Sums([real_host_path]) |
| 988 device_checksums = md5sum.CalculateDeviceMd5Sums( |
| 989 [real_device_path], self) |
| 990 except EnvironmentError as e: |
| 991 logging.warning('Error calculating md5: %s', e) |
| 992 return ([(host_path, device_path)], []) |
| 993 |
| 994 if os.path.isfile(host_path): |
| 995 host_checksum = host_checksums.get(real_host_path) |
| 996 device_checksum = device_checksums.get(real_device_path) |
| 997 if host_checksum != device_checksum: |
| 998 return ([(host_path, device_path)], []) |
| 999 else: |
| 1000 return ([], []) |
| 1001 else: |
| 1002 to_push = [] |
| 1003 for host_abs_path, host_checksum in host_checksums.iteritems(): |
| 1004 device_abs_path = '%s/%s' % ( |
| 1005 real_device_path, os.path.relpath(host_abs_path, real_host_path)) |
| 1006 device_checksum = device_checksums.pop(device_abs_path, None) |
| 1007 if device_checksum != host_checksum: |
| 1008 to_push.append((host_abs_path, device_abs_path)) |
| 1009 to_delete = device_checksums.keys() |
| 1010 return (to_push, to_delete) |
| 1011 |
| 1012 def _PushFilesImpl(self, host_device_tuples, files): |
| 839 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) | 1013 size = sum(host_utils.GetRecursiveDiskUsage(h) for h, _ in files) |
| 840 file_count = len(files) | 1014 file_count = len(files) |
| 841 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) | 1015 dir_size = sum(host_utils.GetRecursiveDiskUsage(h) |
| 842 for h, _ in host_device_tuples) | 1016 for h, _ in host_device_tuples) |
| 843 dir_file_count = 0 | 1017 dir_file_count = 0 |
| 844 for h, _ in host_device_tuples: | 1018 for h, _ in host_device_tuples: |
| 845 if os.path.isdir(h): | 1019 if os.path.isdir(h): |
| 846 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) | 1020 dir_file_count += sum(len(f) for _r, _d, f in os.walk(h)) |
| 847 else: | 1021 else: |
| 848 dir_file_count += 1 | 1022 dir_file_count += 1 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 859 dir_push_duration < zip_duration or not self._commands_installed): | 1033 dir_push_duration < zip_duration or not self._commands_installed): |
| 860 self._PushChangedFilesIndividually(host_device_tuples) | 1034 self._PushChangedFilesIndividually(host_device_tuples) |
| 861 elif push_duration < zip_duration or not self._commands_installed: | 1035 elif push_duration < zip_duration or not self._commands_installed: |
| 862 self._PushChangedFilesIndividually(files) | 1036 self._PushChangedFilesIndividually(files) |
| 863 else: | 1037 else: |
| 864 self._PushChangedFilesZipped(files) | 1038 self._PushChangedFilesZipped(files) |
| 865 self.RunShellCommand( | 1039 self.RunShellCommand( |
| 866 ['chmod', '-R', '777'] + [d for _, d in host_device_tuples], | 1040 ['chmod', '-R', '777'] + [d for _, d in host_device_tuples], |
| 867 as_root=True, check_return=True) | 1041 as_root=True, check_return=True) |
| 868 | 1042 |
| 869 def _GetChangedFilesImpl(self, host_path, device_path): | |
| 870 real_host_path = os.path.realpath(host_path) | |
| 871 try: | |
| 872 real_device_path = self.RunShellCommand( | |
| 873 ['realpath', device_path], single_line=True, check_return=True) | |
| 874 except device_errors.CommandFailedError: | |
| 875 real_device_path = None | |
| 876 if not real_device_path: | |
| 877 return [(host_path, device_path)] | |
| 878 | |
| 879 try: | |
| 880 host_checksums = md5sum.CalculateHostMd5Sums([real_host_path]) | |
| 881 device_paths_to_md5 = ( | |
| 882 real_device_path if os.path.isfile(real_host_path) | |
| 883 else ('%s/%s' % (real_device_path, os.path.relpath(p, real_host_path)) | |
| 884 for p in host_checksums.iterkeys())) | |
| 885 device_checksums = md5sum.CalculateDeviceMd5Sums( | |
| 886 device_paths_to_md5, self) | |
| 887 except EnvironmentError as e: | |
| 888 logging.warning('Error calculating md5: %s', e) | |
| 889 return [(host_path, device_path)] | |
| 890 | |
| 891 if os.path.isfile(host_path): | |
| 892 host_checksum = host_checksums.get(real_host_path) | |
| 893 device_checksum = device_checksums.get(real_device_path) | |
| 894 if host_checksum != device_checksum: | |
| 895 return [(host_path, device_path)] | |
| 896 else: | |
| 897 return [] | |
| 898 else: | |
| 899 to_push = [] | |
| 900 for host_abs_path, host_checksum in host_checksums.iteritems(): | |
| 901 device_abs_path = '%s/%s' % ( | |
| 902 real_device_path, os.path.relpath(host_abs_path, real_host_path)) | |
| 903 if (device_checksums.get(device_abs_path) != host_checksum): | |
| 904 to_push.append((host_abs_path, device_abs_path)) | |
| 905 return to_push | |
| 906 | |
| 907 def _InstallCommands(self): | 1043 def _InstallCommands(self): |
| 908 if self._commands_installed is None: | 1044 if self._commands_installed is None: |
| 909 try: | 1045 try: |
| 910 if not install_commands.Installed(self): | 1046 if not install_commands.Installed(self): |
| 911 install_commands.InstallCommands(self) | 1047 install_commands.InstallCommands(self) |
| 912 self._commands_installed = True | 1048 self._commands_installed = True |
| 913 except Exception as e: | 1049 except Exception as e: |
| 914 logging.warning('unzip not available: %s' % str(e)) | 1050 logging.warning('unzip not available: %s' % str(e)) |
| 915 self._commands_installed = False | 1051 self._commands_installed = False |
| 916 | 1052 |
| (...skipping 320 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1237 | 1373 |
| 1238 # Next, check the current runtime value is what we need, and | 1374 # Next, check the current runtime value is what we need, and |
| 1239 # if not, set it and report that a reboot is required. | 1375 # if not, set it and report that a reboot is required. |
| 1240 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) | 1376 value = self.GetProp(self.JAVA_ASSERT_PROPERTY) |
| 1241 if new_value != value: | 1377 if new_value != value: |
| 1242 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) | 1378 self.SetProp(self.JAVA_ASSERT_PROPERTY, new_value) |
| 1243 return True | 1379 return True |
| 1244 else: | 1380 else: |
| 1245 return False | 1381 return False |
| 1246 | 1382 |
| 1383 @property |
| 1384 def language(self): |
| 1385 """Returns the language setting on the device.""" |
| 1386 return self.GetProp('persist.sys.language', cache=False) |
| 1387 |
| 1388 @property |
| 1389 def country(self): |
| 1390 """Returns the country setting on the device.""" |
| 1391 return self.GetProp('persist.sys.country', cache=False) |
| 1392 |
| 1393 @property |
| 1394 def screen_density(self): |
| 1395 """Returns the screen density of the device.""" |
| 1396 DPI_TO_DENSITY = { |
| 1397 120: 'ldpi', |
| 1398 160: 'mdpi', |
| 1399 240: 'hdpi', |
| 1400 320: 'xhdpi', |
| 1401 480: 'xxhdpi', |
| 1402 640: 'xxxhdpi', |
| 1403 } |
| 1404 dpi = int(self.GetProp('ro.sf.lcd_density', cache=True)) |
| 1405 return DPI_TO_DENSITY.get(dpi, 'tvdpi') |
| 1247 | 1406 |
| 1248 @property | 1407 @property |
| 1249 def build_description(self): | 1408 def build_description(self): |
| 1250 """Returns the build description of the system. | 1409 """Returns the build description of the system. |
| 1251 | 1410 |
| 1252 For example: | 1411 For example: |
| 1253 nakasi-user 4.4.4 KTU84P 1227136 release-keys | 1412 nakasi-user 4.4.4 KTU84P 1227136 release-keys |
| 1254 """ | 1413 """ |
| 1255 return self.GetProp('ro.build.description', cache=True) | 1414 return self.GetProp('ro.build.description', cache=True) |
| 1256 | 1415 |
| (...skipping 321 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1578 def HealthyDevices(cls): | 1737 def HealthyDevices(cls): |
| 1579 blacklist = device_blacklist.ReadBlacklist() | 1738 blacklist = device_blacklist.ReadBlacklist() |
| 1580 def blacklisted(adb): | 1739 def blacklisted(adb): |
| 1581 if adb.GetDeviceSerial() in blacklist: | 1740 if adb.GetDeviceSerial() in blacklist: |
| 1582 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) | 1741 logging.warning('Device %s is blacklisted.', adb.GetDeviceSerial()) |
| 1583 return True | 1742 return True |
| 1584 return False | 1743 return False |
| 1585 | 1744 |
| 1586 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() | 1745 return [cls(adb) for adb in adb_wrapper.AdbWrapper.Devices() |
| 1587 if not blacklisted(adb)] | 1746 if not blacklisted(adb)] |
| 1588 | |
| OLD | NEW |