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 |