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 |
| 11 import calendar | 11 import calendar |
| 12 import collections | 12 import collections |
| 13 import fnmatch | |
| 13 import itertools | 14 import itertools |
| 14 import json | 15 import json |
| 15 import logging | 16 import logging |
| 16 import multiprocessing | 17 import multiprocessing |
| 17 import os | 18 import os |
| 18 import posixpath | 19 import posixpath |
| 19 import pprint | 20 import pprint |
| 20 import random | 21 import random |
| 21 import re | 22 import re |
| 22 import shutil | 23 import shutil |
| 23 import stat | 24 import stat |
| 24 import tempfile | 25 import tempfile |
| 25 import time | 26 import time |
| 26 import threading | 27 import threading |
| 27 import uuid | 28 import uuid |
| 28 import zipfile | 29 import zipfile |
| 29 | 30 |
| 30 from devil import base_error | 31 from devil import base_error |
| 31 from devil import devil_env | 32 from devil import devil_env |
| 32 from devil.utils import cmd_helper | 33 from devil.utils import cmd_helper |
| 33 from devil.android import apk_helper | 34 from devil.android import apk_helper |
| 34 from devil.android import device_signal | 35 from devil.android import device_signal |
| 35 from devil.android import decorators | 36 from devil.android import decorators |
| 36 from devil.android import device_errors | 37 from devil.android import device_errors |
| 37 from devil.android import device_temp_file | 38 from devil.android import device_temp_file |
| 38 from devil.android import install_commands | 39 from devil.android import install_commands |
| 39 from devil.android import logcat_monitor | 40 from devil.android import logcat_monitor |
| 40 from devil.android import md5sum | 41 from devil.android import md5sum |
| 41 from devil.android.constants import chrome | |
| 42 from devil.android.sdk import adb_wrapper | 42 from devil.android.sdk import adb_wrapper |
| 43 from devil.android.sdk import intent | 43 from devil.android.sdk import intent |
| 44 from devil.android.sdk import keyevent | 44 from devil.android.sdk import keyevent |
| 45 from devil.android.sdk import split_select | 45 from devil.android.sdk import split_select |
| 46 from devil.android.sdk import version_codes | 46 from devil.android.sdk import version_codes |
| 47 from devil.utils import host_utils | 47 from devil.utils import host_utils |
| 48 from devil.utils import parallelizer | 48 from devil.utils import parallelizer |
| 49 from devil.utils import reraiser_thread | 49 from devil.utils import reraiser_thread |
| 50 from devil.utils import timeout_retry | 50 from devil.utils import timeout_retry |
| 51 from devil.utils import zip_utils | 51 from devil.utils import zip_utils |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 65 trap '' TERM | 65 trap '' TERM |
| 66 trap '' PIPE | 66 trap '' PIPE |
| 67 function restart() { | 67 function restart() { |
| 68 stop adbd | 68 stop adbd |
| 69 start adbd | 69 start adbd |
| 70 } | 70 } |
| 71 restart & | 71 restart & |
| 72 """ | 72 """ |
| 73 | 73 |
| 74 # Not all permissions can be set. | 74 # Not all permissions can be set. |
| 75 _PERMISSIONS_BLACKLIST = [ | 75 _PERMISSIONS_BLACKLIST_RE = re.compile('|'.join(fnmatch.translate(p) for p in [ |
| 76 'android.permission.ACCESS_LOCATION_EXTRA_COMMANDS', | 76 'android.permission.ACCESS_LOCATION_EXTRA_COMMANDS', |
| 77 'android.permission.ACCESS_MOCK_LOCATION', | 77 'android.permission.ACCESS_MOCK_LOCATION', |
| 78 'android.permission.ACCESS_NETWORK_STATE', | 78 'android.permission.ACCESS_NETWORK_STATE', |
| 79 'android.permission.ACCESS_NOTIFICATION_POLICY', | 79 'android.permission.ACCESS_NOTIFICATION_POLICY', |
| 80 'android.permission.ACCESS_WIFI_STATE', | 80 'android.permission.ACCESS_WIFI_STATE', |
| 81 'android.permission.AUTHENTICATE_ACCOUNTS', | 81 'android.permission.AUTHENTICATE_ACCOUNTS', |
| 82 'android.permission.BLUETOOTH', | 82 'android.permission.BLUETOOTH', |
| 83 'android.permission.BLUETOOTH_ADMIN', | 83 'android.permission.BLUETOOTH_ADMIN', |
| 84 'android.permission.BROADCAST_STICKY', | 84 'android.permission.BROADCAST_STICKY', |
| 85 'android.permission.CHANGE_NETWORK_STATE', | 85 'android.permission.CHANGE_NETWORK_STATE', |
| 86 'android.permission.CHANGE_WIFI_MULTICAST_STATE', | 86 'android.permission.CHANGE_WIFI_MULTICAST_STATE', |
| 87 'android.permission.CHANGE_WIFI_STATE', | 87 'android.permission.CHANGE_WIFI_STATE', |
| 88 'android.permission.DISABLE_KEYGUARD', | 88 'android.permission.DISABLE_KEYGUARD', |
| 89 'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION', | 89 'android.permission.DOWNLOAD_WITHOUT_NOTIFICATION', |
| 90 'android.permission.EXPAND_STATUS_BAR', | 90 'android.permission.EXPAND_STATUS_BAR', |
| 91 'android.permission.GET_PACKAGE_SIZE', | 91 'android.permission.GET_PACKAGE_SIZE', |
| 92 'android.permission.INSTALL_SHORTCUT', | 92 'android.permission.INSTALL_SHORTCUT', |
| 93 'android.permission.INJECT_EVENTS', | |
| 93 'android.permission.INTERNET', | 94 'android.permission.INTERNET', |
| 94 'android.permission.KILL_BACKGROUND_PROCESSES', | 95 'android.permission.KILL_BACKGROUND_PROCESSES', |
| 95 'android.permission.MANAGE_ACCOUNTS', | 96 'android.permission.MANAGE_ACCOUNTS', |
| 96 'android.permission.MODIFY_AUDIO_SETTINGS', | 97 'android.permission.MODIFY_AUDIO_SETTINGS', |
| 97 'android.permission.NFC', | 98 'android.permission.NFC', |
| 98 'android.permission.READ_SYNC_SETTINGS', | 99 'android.permission.READ_SYNC_SETTINGS', |
| 99 'android.permission.READ_SYNC_STATS', | 100 'android.permission.READ_SYNC_STATS', |
| 100 'android.permission.RECEIVE_BOOT_COMPLETED', | 101 'android.permission.RECEIVE_BOOT_COMPLETED', |
| 101 'android.permission.RECORD_VIDEO', | 102 'android.permission.RECORD_VIDEO', |
| 102 'android.permission.REORDER_TASKS', | 103 'android.permission.REORDER_TASKS', |
| 103 'android.permission.REQUEST_INSTALL_PACKAGES', | 104 'android.permission.REQUEST_INSTALL_PACKAGES', |
| 105 'android.permission.RESTRICTED_VR_ACCESS', | |
| 104 'android.permission.RUN_INSTRUMENTATION', | 106 'android.permission.RUN_INSTRUMENTATION', |
| 105 'android.permission.SET_ALARM', | 107 'android.permission.SET_ALARM', |
| 106 'android.permission.SET_TIME_ZONE', | 108 'android.permission.SET_TIME_ZONE', |
| 107 'android.permission.SET_WALLPAPER', | 109 'android.permission.SET_WALLPAPER', |
| 108 'android.permission.SET_WALLPAPER_HINTS', | 110 'android.permission.SET_WALLPAPER_HINTS', |
| 109 'android.permission.TRANSMIT_IR', | 111 'android.permission.TRANSMIT_IR', |
| 110 'android.permission.USE_CREDENTIALS', | 112 'android.permission.USE_CREDENTIALS', |
| 111 'android.permission.USE_FINGERPRINT', | 113 'android.permission.USE_FINGERPRINT', |
| 112 'android.permission.VIBRATE', | 114 'android.permission.VIBRATE', |
| 113 'android.permission.WAKE_LOCK', | 115 'android.permission.WAKE_LOCK', |
| 114 'android.permission.WRITE_SYNC_SETTINGS', | 116 'android.permission.WRITE_SYNC_SETTINGS', |
| 115 'com.android.browser.permission.READ_HISTORY_BOOKMARKS', | 117 'com.android.browser.permission.READ_HISTORY_BOOKMARKS', |
| 116 'com.android.browser.permission.WRITE_HISTORY_BOOKMARKS', | 118 'com.android.browser.permission.WRITE_HISTORY_BOOKMARKS', |
| 117 'com.android.launcher.permission.INSTALL_SHORTCUT', | 119 'com.android.launcher.permission.INSTALL_SHORTCUT', |
| 118 'com.chrome.permission.DEVICE_EXTRAS', | 120 'com.chrome.permission.DEVICE_EXTRAS', |
| 119 'com.google.android.apps.now.CURRENT_ACCOUNT_ACCESS', | 121 'com.google.android.apps.now.CURRENT_ACCOUNT_ACCESS', |
| 120 'com.google.android.c2dm.permission.RECEIVE', | 122 'com.google.android.c2dm.permission.RECEIVE', |
| 121 'com.google.android.providers.gsf.permission.READ_GSERVICES', | 123 'com.google.android.providers.gsf.permission.READ_GSERVICES', |
| 122 'com.sec.enterprise.knox.MDM_CONTENT_PROVIDER', | 124 'com.sec.enterprise.knox.MDM_CONTENT_PROVIDER', |
| 123 ] | 125 '*.permission.C2D_MESSAGE', |
| 124 for package_info in chrome.PACKAGE_INFO.itervalues(): | 126 '*.permission.READ_WRITE_BOOKMARK_FOLDERS', |
| 125 _PERMISSIONS_BLACKLIST.extend([ | 127 '*.TOS_ACKED', |
| 126 '%s.permission.C2D_MESSAGE' % package_info.package, | 128 ])) |
| 127 '%s.permission.READ_WRITE_BOOKMARK_FOLDERS' % package_info.package, | 129 _SHELL_OUTPUT_SEPARATOR = '~X~' |
| 128 '%s.TOS_ACKED' % package_info.package]) | |
| 129 | 130 |
| 130 _CURRENT_FOCUS_CRASH_RE = re.compile( | 131 _CURRENT_FOCUS_CRASH_RE = re.compile( |
| 131 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') | 132 r'\s*mCurrentFocus.*Application (Error|Not Responding): (\S+)}') |
| 132 | 133 |
| 133 _GETPROP_RE = re.compile(r'\[(.*?)\]: \[(.*?)\]') | 134 _GETPROP_RE = re.compile(r'\[(.*?)\]: \[(.*?)\]') |
| 134 | 135 |
| 135 # Regex to parse the long (-l) output of 'ls' command, c.f. | 136 # Regex to parse the long (-l) output of 'ls' command, c.f. |
| 136 # https://github.com/landley/toybox/blob/master/toys/posix/ls.c#L446 | 137 # https://github.com/landley/toybox/blob/master/toys/posix/ls.c#L446 |
| 137 _LONG_LS_OUTPUT_RE = re.compile( | 138 _LONG_LS_OUTPUT_RE = re.compile( |
| 138 r'(?P<st_mode>[\w-]{10})\s+' # File permissions | 139 r'(?P<st_mode>[\w-]{10})\s+' # File permissions |
| (...skipping 702 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 841 self._cache['package_apk_paths'].pop(package_name, 0) | 842 self._cache['package_apk_paths'].pop(package_name, 0) |
| 842 self._cache['package_apk_checksums'].pop(package_name, 0) | 843 self._cache['package_apk_checksums'].pop(package_name, 0) |
| 843 if split_apks: | 844 if split_apks: |
| 844 partial = package_name if len(apks_to_install) < len(all_apks) else None | 845 partial = package_name if len(apks_to_install) < len(all_apks) else None |
| 845 self.adb.InstallMultiple( | 846 self.adb.InstallMultiple( |
| 846 apks_to_install, partial=partial, reinstall=reinstall, | 847 apks_to_install, partial=partial, reinstall=reinstall, |
| 847 allow_downgrade=allow_downgrade) | 848 allow_downgrade=allow_downgrade) |
| 848 else: | 849 else: |
| 849 self.adb.Install( | 850 self.adb.Install( |
| 850 base_apk.path, reinstall=reinstall, allow_downgrade=allow_downgrade) | 851 base_apk.path, reinstall=reinstall, allow_downgrade=allow_downgrade) |
| 851 if (permissions is None | |
| 852 and self.build_version_sdk >= version_codes.MARSHMALLOW): | |
| 853 permissions = base_apk.GetPermissions() | |
| 854 self.GrantPermissions(package_name, permissions) | |
| 855 # Upon success, we know the device checksums, but not their paths. | |
| 856 if host_checksums is not None: | |
| 857 self._cache['package_apk_checksums'][package_name] = host_checksums | |
| 858 else: | 852 else: |
| 859 # Running adb install terminates running instances of the app, so to be | 853 # Running adb install terminates running instances of the app, so to be |
| 860 # consistent, we explicitly terminate it when skipping the install. | 854 # consistent, we explicitly terminate it when skipping the install. |
| 861 self.ForceStop(package_name) | 855 self.ForceStop(package_name) |
| 862 | 856 |
| 857 if (permissions is None | |
| 858 and self.build_version_sdk >= version_codes.MARSHMALLOW): | |
| 859 permissions = base_apk.GetPermissions() | |
| 860 self.GrantPermissions(package_name, permissions) | |
| 861 # Upon success, we know the device checksums, but not their paths. | |
| 862 if host_checksums is not None: | |
| 863 self._cache['package_apk_checksums'][package_name] = host_checksums | |
| 864 | |
| 863 @decorators.WithTimeoutAndRetriesFromInstance() | 865 @decorators.WithTimeoutAndRetriesFromInstance() |
| 864 def Uninstall(self, package_name, keep_data=False, timeout=None, | 866 def Uninstall(self, package_name, keep_data=False, timeout=None, |
| 865 retries=None): | 867 retries=None): |
| 866 """Remove the app |package_name| from the device. | 868 """Remove the app |package_name| from the device. |
| 867 | 869 |
| 868 This is a no-op if the app is not already installed. | 870 This is a no-op if the app is not already installed. |
| 869 | 871 |
| 870 Args: | 872 Args: |
| 871 package_name: The package to uninstall. | 873 package_name: The package to uninstall. |
| 872 keep_data: (optional) Whether to keep the data and cache directories. | 874 keep_data: (optional) Whether to keep the data and cache directories. |
| (...skipping 1780 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 2653 self.RunShellCommand( | 2655 self.RunShellCommand( |
| 2654 ['source', script.name], check_return=True, as_root=True) | 2656 ['source', script.name], check_return=True, as_root=True) |
| 2655 self.adb.WaitForDevice() | 2657 self.adb.WaitForDevice() |
| 2656 | 2658 |
| 2657 @decorators.WithTimeoutAndRetriesFromInstance() | 2659 @decorators.WithTimeoutAndRetriesFromInstance() |
| 2658 def GrantPermissions(self, package, permissions, timeout=None, retries=None): | 2660 def GrantPermissions(self, package, permissions, timeout=None, retries=None): |
| 2659 # Permissions only need to be set on M and above because of the changes to | 2661 # Permissions only need to be set on M and above because of the changes to |
| 2660 # the permission model. | 2662 # the permission model. |
| 2661 if not permissions or self.build_version_sdk < version_codes.MARSHMALLOW: | 2663 if not permissions or self.build_version_sdk < version_codes.MARSHMALLOW: |
| 2662 return | 2664 return |
| 2663 logger.info('Setting permissions for %s.', package) | 2665 |
| 2664 permissions = [p for p in permissions if p not in _PERMISSIONS_BLACKLIST] | 2666 permissions = set( |
| 2667 p for p in permissions if not _PERMISSIONS_BLACKLIST_RE.match(p)) | |
| 2668 | |
| 2665 if ('android.permission.WRITE_EXTERNAL_STORAGE' in permissions | 2669 if ('android.permission.WRITE_EXTERNAL_STORAGE' in permissions |
| 2666 and 'android.permission.READ_EXTERNAL_STORAGE' not in permissions): | 2670 and 'android.permission.READ_EXTERNAL_STORAGE' not in permissions): |
| 2667 permissions.append('android.permission.READ_EXTERNAL_STORAGE') | 2671 permissions.add('android.permission.READ_EXTERNAL_STORAGE') |
| 2668 cmd = '&&'.join('pm grant %s %s' % (package, p) for p in permissions) | 2672 |
| 2669 if cmd: | 2673 script = ';'.join([ |
| 2670 output = self.RunShellCommand(cmd, shell=True, check_return=True) | 2674 'p={package}', |
| 2671 if output: | 2675 'for q in {permissions}', |
| 2672 logger.warning('Possible problem when granting permissions. Blacklist ' | 2676 'do pm grant "$p" "$q"', |
| 2673 'may need to be updated.') | 2677 'echo "{sep}$q{sep}$?{sep}"', |
| 2674 for line in output: | 2678 'done' |
| 2675 logger.warning(' %s', line) | 2679 ]).format( |
| 2680 package=cmd_helper.SingleQuote(package), | |
| 2681 permissions=' '.join( | |
| 2682 cmd_helper.SingleQuote(p) for p in sorted(permissions)), | |
| 2683 sep=_SHELL_OUTPUT_SEPARATOR) | |
| 2684 | |
| 2685 logger.info('Setting permissions for %s.', package) | |
| 2686 res = self.RunShellCommand( | |
|
jbudorick
2017/07/17 15:31:38
nit: might be worth using large_output=True here,
perezju
2017/07/17 16:08:38
Thought about that, but needs another fix. Current
| |
| 2687 script, shell=True, raw_output=True, check_return=True) | |
| 2688 res = res.split(_SHELL_OUTPUT_SEPARATOR) | |
| 2689 failures = [ | |
| 2690 (permission, output.strip()) | |
| 2691 for permission, status, output in zip(res[1::3], res[2::3], res[0::3]) | |
| 2692 if int(status)] | |
| 2693 | |
| 2694 if failures: | |
| 2695 logger.warning( | |
| 2696 'Failed to grant some permissions. Blacklist may need to be updated?') | |
| 2697 for permission, output in failures: | |
| 2698 # Try to grab the relevant error message from the output. | |
| 2699 m = re.search(r'java\.lang\.\w+Exception: .*$', output, re.MULTILINE) | |
|
jbudorick
2017/07/17 15:31:38
nit: compile the regex as a module-scope constant
perezju
2017/07/17 16:08:38
Done.
| |
| 2700 if m: | |
| 2701 error_msg = m.group(0) | |
| 2702 elif len(output) > 200: | |
| 2703 error_msg = repr(output[:200]) + ' (truncated)' | |
| 2704 else: | |
| 2705 error_msg = repr(output) | |
| 2706 logger.warning('- %s: %s', permission, error_msg) | |
| 2676 | 2707 |
| 2677 @decorators.WithTimeoutAndRetriesFromInstance() | 2708 @decorators.WithTimeoutAndRetriesFromInstance() |
| 2678 def IsScreenOn(self, timeout=None, retries=None): | 2709 def IsScreenOn(self, timeout=None, retries=None): |
| 2679 """Determines if screen is on. | 2710 """Determines if screen is on. |
| 2680 | 2711 |
| 2681 Dumpsys input_method exposes screen on/off state. Below is an explination of | 2712 Dumpsys input_method exposes screen on/off state. Below is an explination of |
| 2682 the states. | 2713 the states. |
| 2683 | 2714 |
| 2684 Pre-L: | 2715 Pre-L: |
| 2685 On: mScreenOn=true | 2716 On: mScreenOn=true |
| (...skipping 29 matching lines...) Expand all Loading... | |
| 2715 on: bool to decide state to switch to. True = on False = off. | 2746 on: bool to decide state to switch to. True = on False = off. |
| 2716 """ | 2747 """ |
| 2717 def screen_test(): | 2748 def screen_test(): |
| 2718 return self.IsScreenOn() == on | 2749 return self.IsScreenOn() == on |
| 2719 | 2750 |
| 2720 if screen_test(): | 2751 if screen_test(): |
| 2721 logger.info('Screen already in expected state.') | 2752 logger.info('Screen already in expected state.') |
| 2722 return | 2753 return |
| 2723 self.SendKeyEvent(keyevent.KEYCODE_POWER) | 2754 self.SendKeyEvent(keyevent.KEYCODE_POWER) |
| 2724 timeout_retry.WaitFor(screen_test, wait_period=1) | 2755 timeout_retry.WaitFor(screen_test, wait_period=1) |
| OLD | NEW |