| OLD | NEW |
| 1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 # Copyright (c) 2013 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 """Module containing utilities for apk packages.""" | 5 """Module containing utilities for apk packages.""" |
| 6 | 6 |
| 7 import itertools | 7 import itertools |
| 8 import re | 8 import re |
| 9 | 9 |
| 10 from devil import base_error | 10 from devil import base_error |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 70 if manifest_key in node: | 70 if manifest_key in node: |
| 71 raise base_error.BaseError( | 71 raise base_error.BaseError( |
| 72 "A single attribute should have one key and one value") | 72 "A single attribute should have one key and one value") |
| 73 else: | 73 else: |
| 74 node[manifest_key] = m.group(2) or m.group(3) | 74 node[manifest_key] = m.group(2) or m.group(3) |
| 75 continue | 75 continue |
| 76 | 76 |
| 77 return parsed_manifest | 77 return parsed_manifest |
| 78 | 78 |
| 79 | 79 |
| 80 def _ParseNumericKey(obj, key, default=0): | |
| 81 val = obj.get(key) | |
| 82 if val is None: | |
| 83 return default | |
| 84 return int(val, 0) | |
| 85 | |
| 86 | |
| 87 class _ExportedActivity(object): | |
| 88 def __init__(self, name): | |
| 89 self.name = name | |
| 90 self.actions = set() | |
| 91 self.categories = set() | |
| 92 self.schemes = set() | |
| 93 | |
| 94 | |
| 95 def _IterateExportedActivities(manifest_info): | |
| 96 app_node = manifest_info['manifest'][0]['application'][0] | |
| 97 activities = app_node.get('activity', []) + app_node.get('activity-alias', []) | |
| 98 for activity_node in activities: | |
| 99 if not _ParseNumericKey(activity_node, 'android:exported'): | |
| 100 continue | |
| 101 activity = _ExportedActivity(activity_node.get('android:name')) | |
| 102 # Merge all intent-filters into a single set because there is not | |
| 103 # currently a need to keep them separate. | |
| 104 for intent_filter in activity_node.get('intent-filter', []): | |
| 105 for action in intent_filter.get('action', []): | |
| 106 activity.actions.add(action.get('android:name')) | |
| 107 for category in intent_filter.get('category', []): | |
| 108 activity.categories.add(category.get('android:name')) | |
| 109 for data in intent_filter.get('data', []): | |
| 110 activity.schemes.add(data.get('android:scheme')) | |
| 111 yield activity | |
| 112 | |
| 113 | |
| 114 class ApkHelper(object): | 80 class ApkHelper(object): |
| 115 | 81 |
| 116 def __init__(self, path): | 82 def __init__(self, path): |
| 117 self._apk_path = path | 83 self._apk_path = path |
| 118 self._manifest = None | 84 self._manifest = None |
| 119 | 85 |
| 120 @property | 86 @property |
| 121 def path(self): | 87 def path(self): |
| 122 return self._apk_path | 88 return self._apk_path |
| 123 | 89 |
| 124 def GetActivityName(self): | 90 def GetActivityName(self): |
| 125 """Returns the name of the first launcher Activity in the apk.""" | 91 """Returns the name of the Activity in the apk.""" |
| 126 manifest_info = self._GetManifest() | 92 manifest_info = self._GetManifest() |
| 127 for activity in _IterateExportedActivities(manifest_info): | 93 try: |
| 128 if ('android.intent.action.MAIN' in activity.actions and | 94 activity = ( |
| 129 'android.intent.category.LAUNCHER' in activity.categories): | 95 manifest_info['manifest'][0]['application'][0]['activity'][0] |
| 130 return self._ResolveName(activity.name) | 96 ['android:name']) |
| 131 return None | 97 except KeyError: |
| 132 | 98 return None |
| 133 def GetViewActivityName(self): | 99 if '.' not in activity: |
| 134 """Returns name of the first action=View Activity that can handle http.""" | 100 activity = '%s.%s' % (self.GetPackageName(), activity) |
| 135 manifest_info = self._GetManifest() | 101 elif activity.startswith('.'): |
| 136 for activity in _IterateExportedActivities(manifest_info): | 102 activity = '%s%s' % (self.GetPackageName(), activity) |
| 137 if ('android.intent.action.VIEW' in activity.actions and | 103 return activity |
| 138 'http' in activity.schemes): | |
| 139 return self._ResolveName(activity.name) | |
| 140 return None | |
| 141 | 104 |
| 142 def GetInstrumentationName( | 105 def GetInstrumentationName( |
| 143 self, default='android.test.InstrumentationTestRunner'): | 106 self, default='android.test.InstrumentationTestRunner'): |
| 144 """Returns the name of the Instrumentation in the apk.""" | 107 """Returns the name of the Instrumentation in the apk.""" |
| 145 all_instrumentations = self.GetAllInstrumentations(default=default) | 108 all_instrumentations = self.GetAllInstrumentations(default=default) |
| 146 if len(all_instrumentations) != 1: | 109 if len(all_instrumentations) != 1: |
| 147 raise base_error.BaseError( | 110 raise base_error.BaseError( |
| 148 'There is more than one instrumentation. Expected one.') | 111 'There is more than one instrumentation. Expected one.') |
| 149 else: | 112 else: |
| 150 return self._ResolveName(all_instrumentations[0]['android:name']) | 113 return all_instrumentations[0]['android:name'] |
| 151 | 114 |
| 152 def GetAllInstrumentations( | 115 def GetAllInstrumentations( |
| 153 self, default='android.test.InstrumentationTestRunner'): | 116 self, default='android.test.InstrumentationTestRunner'): |
| 154 """Returns a list of all Instrumentations in the apk.""" | 117 """Returns a list of all Instrumentations in the apk.""" |
| 155 try: | 118 try: |
| 156 return self._GetManifest()['manifest'][0]['instrumentation'] | 119 return self._GetManifest()['manifest'][0]['instrumentation'] |
| 157 except KeyError: | 120 except KeyError: |
| 158 return [{'android:name': default}] | 121 return [{'android:name': default}] |
| 159 | 122 |
| 160 def GetPackageName(self): | 123 def GetPackageName(self): |
| (...skipping 21 matching lines...) Expand all Loading... |
| 182 return None | 145 return None |
| 183 | 146 |
| 184 def HasIsolatedProcesses(self): | 147 def HasIsolatedProcesses(self): |
| 185 """Returns whether any services exist that use isolatedProcess=true.""" | 148 """Returns whether any services exist that use isolatedProcess=true.""" |
| 186 manifest_info = self._GetManifest() | 149 manifest_info = self._GetManifest() |
| 187 try: | 150 try: |
| 188 applications = manifest_info['manifest'][0].get('application', []) | 151 applications = manifest_info['manifest'][0].get('application', []) |
| 189 services = itertools.chain( | 152 services = itertools.chain( |
| 190 *(application.get('service', []) for application in applications)) | 153 *(application.get('service', []) for application in applications)) |
| 191 return any( | 154 return any( |
| 192 _ParseNumericKey(s, 'android:isolatedProcess') for s in services) | 155 int(s.get('android:isolatedProcess', '0'), 0) |
| 156 for s in services) |
| 193 except KeyError: | 157 except KeyError: |
| 194 return False | 158 return False |
| 195 | 159 |
| 196 def _GetManifest(self): | 160 def _GetManifest(self): |
| 197 if not self._manifest: | 161 if not self._manifest: |
| 198 self._manifest = _ParseManifestFromApk(self._apk_path) | 162 self._manifest = _ParseManifestFromApk(self._apk_path) |
| 199 return self._manifest | 163 return self._manifest |
| 200 | 164 |
| 201 def _ResolveName(self, name): | |
| 202 name = name.lstrip('.') | |
| 203 if '.' not in name: | |
| 204 return '%s.%s' % (self.GetPackageName(), name) | |
| 205 return name | |
| OLD | NEW |