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 itertools | 12 import itertools |
| 13 import json |
13 import logging | 14 import logging |
14 import multiprocessing | 15 import multiprocessing |
15 import os | 16 import os |
16 import posixpath | 17 import posixpath |
17 import re | 18 import re |
18 import shutil | 19 import shutil |
19 import tempfile | 20 import tempfile |
20 import time | 21 import time |
21 import zipfile | 22 import zipfile |
22 | 23 |
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
156 _MAX_ADB_OUTPUT_LENGTH = 32768 | 157 _MAX_ADB_OUTPUT_LENGTH = 32768 |
157 _LAUNCHER_FOCUSED_RE = re.compile( | 158 _LAUNCHER_FOCUSED_RE = re.compile( |
158 r'\s*mCurrentFocus.*(Launcher|launcher).*') | 159 r'\s*mCurrentFocus.*(Launcher|launcher).*') |
159 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') | 160 _VALID_SHELL_VARIABLE = re.compile('^[a-zA-Z_][a-zA-Z0-9_]*$') |
160 | 161 |
161 LOCAL_PROPERTIES_PATH = posixpath.join('/', 'data', 'local.prop') | 162 LOCAL_PROPERTIES_PATH = posixpath.join('/', 'data', 'local.prop') |
162 | 163 |
163 # Property in /data/local.prop that controls Java assertions. | 164 # Property in /data/local.prop that controls Java assertions. |
164 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' | 165 JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions' |
165 | 166 |
166 def __init__(self, device, default_timeout=_DEFAULT_TIMEOUT, | 167 def __init__(self, device, enable_device_files_cache=False, |
| 168 default_timeout=_DEFAULT_TIMEOUT, |
167 default_retries=_DEFAULT_RETRIES): | 169 default_retries=_DEFAULT_RETRIES): |
168 """DeviceUtils constructor. | 170 """DeviceUtils constructor. |
169 | 171 |
170 Args: | 172 Args: |
171 device: Either a device serial, an existing AdbWrapper instance, or an | 173 device: Either a device serial, an existing AdbWrapper instance, or an |
172 an existing AndroidCommands instance. | 174 an existing AndroidCommands instance. |
| 175 enable_device_files_cache: For PushChangedFiles(), cache checksums of |
| 176 pushed files rather than recomputing them on a subsequent call. |
173 default_timeout: An integer containing the default number of seconds to | 177 default_timeout: An integer containing the default number of seconds to |
174 wait for an operation to complete if no explicit value | 178 wait for an operation to complete if no explicit value is provided. |
175 is provided. | |
176 default_retries: An integer containing the default number or times an | 179 default_retries: An integer containing the default number or times an |
177 operation should be retried on failure if no explicit | 180 operation should be retried on failure if no explicit value is provided. |
178 value is provided. | |
179 """ | 181 """ |
180 self.adb = None | 182 self.adb = None |
181 if isinstance(device, basestring): | 183 if isinstance(device, basestring): |
182 self.adb = adb_wrapper.AdbWrapper(device) | 184 self.adb = adb_wrapper.AdbWrapper(device) |
183 elif isinstance(device, adb_wrapper.AdbWrapper): | 185 elif isinstance(device, adb_wrapper.AdbWrapper): |
184 self.adb = device | 186 self.adb = device |
185 else: | 187 else: |
186 raise ValueError('Unsupported device value: %r' % device) | 188 raise ValueError('Unsupported device value: %r' % device) |
187 self._commands_installed = None | 189 self._commands_installed = None |
188 self._default_timeout = default_timeout | 190 self._default_timeout = default_timeout |
189 self._default_retries = default_retries | 191 self._default_retries = default_retries |
| 192 self._enable_device_files_cache = enable_device_files_cache |
190 self._cache = {} | 193 self._cache = {} |
191 self._client_caches = {} | 194 self._client_caches = {} |
192 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) | 195 assert hasattr(self, decorators.DEFAULT_TIMEOUT_ATTR) |
193 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) | 196 assert hasattr(self, decorators.DEFAULT_RETRIES_ATTR) |
194 | 197 |
195 self._ClearCache() | 198 self._ClearCache() |
196 | 199 |
197 def __eq__(self, other): | 200 def __eq__(self, other): |
198 """Checks whether |other| refers to the same device as |self|. | 201 """Checks whether |other| refers to the same device as |self|. |
199 | 202 |
(...skipping 861 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1061 | 1064 |
1062 Raises: | 1065 Raises: |
1063 CommandFailedError on failure. | 1066 CommandFailedError on failure. |
1064 CommandTimeoutError on timeout. | 1067 CommandTimeoutError on timeout. |
1065 DeviceUnreachableError on missing device. | 1068 DeviceUnreachableError on missing device. |
1066 """ | 1069 """ |
1067 | 1070 |
1068 all_changed_files = [] | 1071 all_changed_files = [] |
1069 all_stale_files = [] | 1072 all_stale_files = [] |
1070 missing_dirs = [] | 1073 missing_dirs = [] |
| 1074 cache_commit_funcs = [] |
1071 for h, d in host_device_tuples: | 1075 for h, d in host_device_tuples: |
1072 changed_files, up_to_date_files, stale_files = ( | 1076 changed_files, up_to_date_files, stale_files, cache_commit_func = ( |
1073 self._GetChangedAndStaleFiles(h, d, delete_device_stale)) | 1077 self._GetChangedAndStaleFiles(h, d, delete_device_stale)) |
1074 all_changed_files += changed_files | 1078 all_changed_files += changed_files |
1075 all_stale_files += stale_files | 1079 all_stale_files += stale_files |
| 1080 cache_commit_funcs.append(cache_commit_func) |
1076 if (os.path.isdir(h) and changed_files and not up_to_date_files | 1081 if (os.path.isdir(h) and changed_files and not up_to_date_files |
1077 and not stale_files): | 1082 and not stale_files): |
1078 missing_dirs.append(d) | 1083 missing_dirs.append(d) |
1079 | 1084 |
1080 if delete_device_stale and all_stale_files: | 1085 if delete_device_stale and all_stale_files: |
1081 self.RunShellCommand(['rm', '-f'] + all_stale_files, | 1086 self.RunShellCommand(['rm', '-f'] + all_stale_files, |
1082 check_return=True) | 1087 check_return=True) |
1083 | 1088 |
1084 if all_changed_files: | 1089 if all_changed_files: |
1085 if missing_dirs: | 1090 if missing_dirs: |
1086 self.RunShellCommand(['mkdir', '-p'] + missing_dirs, check_return=True) | 1091 self.RunShellCommand(['mkdir', '-p'] + missing_dirs, check_return=True) |
1087 self._PushFilesImpl(host_device_tuples, all_changed_files) | 1092 self._PushFilesImpl(host_device_tuples, all_changed_files) |
| 1093 for func in cache_commit_funcs: |
| 1094 func() |
1088 | 1095 |
1089 def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False): | 1096 def _GetChangedAndStaleFiles(self, host_path, device_path, track_stale=False): |
1090 """Get files to push and delete | 1097 """Get files to push and delete |
1091 | 1098 |
1092 Args: | 1099 Args: |
1093 host_path: an absolute path of a file or directory on the host | 1100 host_path: an absolute path of a file or directory on the host |
1094 device_path: an absolute path of a file or directory on the device | 1101 device_path: an absolute path of a file or directory on the device |
1095 track_stale: whether to bother looking for stale files (slower) | 1102 track_stale: whether to bother looking for stale files (slower) |
1096 | 1103 |
1097 Returns: | 1104 Returns: |
1098 a three-element tuple | 1105 a three-element tuple |
1099 1st element: a list of (host_files_path, device_files_path) tuples to push | 1106 1st element: a list of (host_files_path, device_files_path) tuples to push |
1100 2nd element: a list of host_files_path that are up-to-date | 1107 2nd element: a list of host_files_path that are up-to-date |
1101 3rd element: a list of stale files under device_path, or [] when | 1108 3rd element: a list of stale files under device_path, or [] when |
1102 track_stale == False | 1109 track_stale == False |
1103 """ | 1110 """ |
1104 try: | 1111 try: |
| 1112 # Length calculations below assume no trailing /. |
| 1113 host_path = host_path.rstrip('/') |
| 1114 device_path = device_path.rstrip('/') |
| 1115 |
1105 specific_device_paths = [device_path] | 1116 specific_device_paths = [device_path] |
1106 if not track_stale and os.path.isdir(host_path): | 1117 ignore_other_files = not track_stale and os.path.isdir(host_path) |
| 1118 if ignore_other_files: |
1107 specific_device_paths = [] | 1119 specific_device_paths = [] |
1108 for root, _, filenames in os.walk(host_path): | 1120 for root, _, filenames in os.walk(host_path): |
1109 relative_dir = root[len(host_path) + 1:] | 1121 relative_dir = root[len(host_path) + 1:] |
1110 specific_device_paths.extend( | 1122 specific_device_paths.extend( |
1111 posixpath.join(device_path, relative_dir, f) for f in filenames) | 1123 posixpath.join(device_path, relative_dir, f) for f in filenames) |
1112 | 1124 |
| 1125 def device_sums_helper(): |
| 1126 if self._enable_device_files_cache: |
| 1127 cache_entry = self._cache['device_path_checksums'].get(device_path) |
| 1128 if cache_entry and cache_entry[0] == ignore_other_files: |
| 1129 return dict(cache_entry[1]) |
| 1130 |
| 1131 sums = md5sum.CalculateDeviceMd5Sums(specific_device_paths, self) |
| 1132 |
| 1133 if self._enable_device_files_cache: |
| 1134 cache_entry = [ignore_other_files, sums] |
| 1135 self._cache['device_path_checksums'][device_path] = cache_entry |
| 1136 return dict(sums) |
| 1137 |
1113 host_checksums, device_checksums = reraiser_thread.RunAsync(( | 1138 host_checksums, device_checksums = reraiser_thread.RunAsync(( |
1114 lambda: md5sum.CalculateHostMd5Sums([host_path]), | 1139 lambda: md5sum.CalculateHostMd5Sums([host_path]), |
1115 lambda: md5sum.CalculateDeviceMd5Sums(specific_device_paths, self))) | 1140 device_sums_helper)) |
1116 except EnvironmentError as e: | 1141 except EnvironmentError as e: |
1117 logging.warning('Error calculating md5: %s', e) | 1142 logging.warning('Error calculating md5: %s', e) |
1118 return ([(host_path, device_path)], [], []) | 1143 return ([(host_path, device_path)], [], [], lambda: 0) |
1119 | 1144 |
1120 to_push = [] | 1145 to_push = [] |
1121 up_to_date = [] | 1146 up_to_date = [] |
1122 to_delete = [] | 1147 to_delete = [] |
1123 if os.path.isfile(host_path): | 1148 if os.path.isfile(host_path): |
1124 host_checksum = host_checksums.get(host_path) | 1149 host_checksum = host_checksums.get(host_path) |
1125 device_checksum = device_checksums.get(device_path) | 1150 device_checksum = device_checksums.get(device_path) |
1126 if host_checksum == device_checksum: | 1151 if host_checksum == device_checksum: |
1127 up_to_date.append(host_path) | 1152 up_to_date.append(host_path) |
1128 else: | 1153 else: |
1129 to_push.append((host_path, device_path)) | 1154 to_push.append((host_path, device_path)) |
1130 else: | 1155 else: |
1131 for host_abs_path, host_checksum in host_checksums.iteritems(): | 1156 for host_abs_path, host_checksum in host_checksums.iteritems(): |
1132 device_abs_path = posixpath.join( | 1157 device_abs_path = posixpath.join( |
1133 device_path, os.path.relpath(host_abs_path, host_path)) | 1158 device_path, os.path.relpath(host_abs_path, host_path)) |
1134 device_checksum = device_checksums.pop(device_abs_path, None) | 1159 device_checksum = device_checksums.pop(device_abs_path, None) |
1135 if device_checksum == host_checksum: | 1160 if device_checksum == host_checksum: |
1136 up_to_date.append(host_abs_path) | 1161 up_to_date.append(host_abs_path) |
1137 else: | 1162 else: |
1138 to_push.append((host_abs_path, device_abs_path)) | 1163 to_push.append((host_abs_path, device_abs_path)) |
1139 to_delete = device_checksums.keys() | 1164 to_delete = device_checksums.keys() |
1140 return (to_push, up_to_date, to_delete) | 1165 |
| 1166 def cache_commit_func(): |
| 1167 if not self._enable_device_files_cache: |
| 1168 return |
| 1169 new_sums = {posixpath.join(device_path, path[len(host_path) + 1:]): val |
| 1170 for path, val in host_checksums.iteritems()} |
| 1171 cache_entry = [ignore_other_files, new_sums] |
| 1172 self._cache['device_path_checksums'][device_path] = cache_entry |
| 1173 |
| 1174 return (to_push, up_to_date, to_delete, cache_commit_func) |
1141 | 1175 |
1142 def _ComputeDeviceChecksumsForApks(self, package_name): | 1176 def _ComputeDeviceChecksumsForApks(self, package_name): |
1143 ret = self._cache['package_apk_checksums'].get(package_name) | 1177 ret = self._cache['package_apk_checksums'].get(package_name) |
1144 if ret is None: | 1178 if ret is None: |
1145 device_paths = self._GetApplicationPathsInternal(package_name) | 1179 device_paths = self._GetApplicationPathsInternal(package_name) |
1146 file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self) | 1180 file_to_checksums = md5sum.CalculateDeviceMd5Sums(device_paths, self) |
1147 ret = set(file_to_checksums.values()) | 1181 ret = set(file_to_checksums.values()) |
1148 self._cache['package_apk_checksums'][package_name] = ret | 1182 self._cache['package_apk_checksums'][package_name] = ret |
1149 return ret | 1183 return ret |
1150 | 1184 |
(...skipping 762 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1913 """Clears all caches.""" | 1947 """Clears all caches.""" |
1914 for client in self._client_caches: | 1948 for client in self._client_caches: |
1915 self._client_caches[client].clear() | 1949 self._client_caches[client].clear() |
1916 self._cache = { | 1950 self._cache = { |
1917 # Map of packageId -> list of on-device .apk paths | 1951 # Map of packageId -> list of on-device .apk paths |
1918 'package_apk_paths': {}, | 1952 'package_apk_paths': {}, |
1919 # Map of packageId -> set of on-device .apk checksums | 1953 # Map of packageId -> set of on-device .apk checksums |
1920 'package_apk_checksums': {}, | 1954 'package_apk_checksums': {}, |
1921 # Map of property_name -> value | 1955 # Map of property_name -> value |
1922 'getprop': {}, | 1956 'getprop': {}, |
| 1957 # Map of device_path -> [ignore_other_files, map of path->checksum] |
| 1958 'device_path_checksums': {}, |
1923 } | 1959 } |
1924 | 1960 |
| 1961 def LoadCacheData(self, data): |
| 1962 """Initializes the cache from data created using DumpCacheData.""" |
| 1963 obj = json.loads(data) |
| 1964 self._cache['package_apk_paths'] = obj.get('package_apk_paths', {}) |
| 1965 package_apk_checksums = obj.get('package_apk_checksums', {}) |
| 1966 for k, v in package_apk_checksums.iteritems(): |
| 1967 package_apk_checksums[k] = set(v) |
| 1968 self._cache['package_apk_checksums'] = package_apk_checksums |
| 1969 device_path_checksums = obj.get('device_path_checksums', {}) |
| 1970 self._cache['device_path_checksums'] = device_path_checksums |
| 1971 |
| 1972 def DumpCacheData(self): |
| 1973 """Dumps the current cache state to a string.""" |
| 1974 obj = {} |
| 1975 obj['package_apk_paths'] = self._cache['package_apk_paths'] |
| 1976 obj['package_apk_checksums'] = self._cache['package_apk_checksums'] |
| 1977 # JSON can't handle sets. |
| 1978 for k, v in obj['package_apk_checksums'].iteritems(): |
| 1979 obj['package_apk_checksums'][k] = list(v) |
| 1980 obj['device_path_checksums'] = self._cache['device_path_checksums'] |
| 1981 return json.dumps(obj, separators=(',', ':')) |
| 1982 |
1925 @classmethod | 1983 @classmethod |
1926 def parallel(cls, devices, async=False): | 1984 def parallel(cls, devices, async=False): |
1927 """Creates a Parallelizer to operate over the provided list of devices. | 1985 """Creates a Parallelizer to operate over the provided list of devices. |
1928 | 1986 |
1929 If |devices| is either |None| or an empty list, the Parallelizer will | 1987 If |devices| is either |None| or an empty list, the Parallelizer will |
1930 operate over all attached devices that have not been blacklisted. | 1988 operate over all attached devices that have not been blacklisted. |
1931 | 1989 |
1932 Args: | 1990 Args: |
1933 devices: A list of either DeviceUtils instances or objects from | 1991 devices: A list of either DeviceUtils instances or objects from |
1934 from which DeviceUtils instances can be constructed. If None, | 1992 from which DeviceUtils instances can be constructed. If None, |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1982 if ('android.permission.WRITE_EXTERNAL_STORAGE' in permissions | 2040 if ('android.permission.WRITE_EXTERNAL_STORAGE' in permissions |
1983 and 'android.permission.READ_EXTERNAL_STORAGE' not in permissions): | 2041 and 'android.permission.READ_EXTERNAL_STORAGE' not in permissions): |
1984 permissions.append('android.permission.READ_EXTERNAL_STORAGE') | 2042 permissions.append('android.permission.READ_EXTERNAL_STORAGE') |
1985 cmd = ';'.join('pm grant %s %s' %(package, p) for p in permissions) | 2043 cmd = ';'.join('pm grant %s %s' %(package, p) for p in permissions) |
1986 if cmd: | 2044 if cmd: |
1987 output = self.RunShellCommand(cmd) | 2045 output = self.RunShellCommand(cmd) |
1988 if output: | 2046 if output: |
1989 logging.warning('Possible problem when granting permissions. Blacklist ' | 2047 logging.warning('Possible problem when granting permissions. Blacklist ' |
1990 'may need to be updated.') | 2048 'may need to be updated.') |
1991 logging.warning(output) | 2049 logging.warning(output) |
OLD | NEW |