1# Copyright 2018 Google Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"""Module for the manager of services.""" 15# TODO(xpconanfan: move the device errors to a more generic location so 16# other device controllers like iOS can share it. 17import collections 18import inspect 19 20from mobly import expects 21from mobly.controllers.android_device_lib import errors 22from mobly.controllers.android_device_lib.services import base_service 23 24 25class Error(errors.DeviceError): 26 """Root error type for this module.""" 27 28 29class ServiceManager: 30 """Manager for services of AndroidDevice. 31 32 A service is a long running process that involves an Android device, like 33 adb logcat or Snippet. 34 """ 35 36 def __init__(self, device): 37 self._service_objects = collections.OrderedDict() 38 self._device = device 39 40 def has_service_by_name(self, name): 41 """Checks if the manager has a service registered with a specific name. 42 43 Args: 44 name: string, the name to look for. 45 46 Returns: 47 True if a service is registered with the specified name, False 48 otherwise. 49 """ 50 return name in self._service_objects 51 52 @property 53 def is_any_alive(self): 54 """True if any service is alive; False otherwise.""" 55 for service in self._service_objects.values(): 56 if service.is_alive: 57 return True 58 return False 59 60 def register(self, alias, service_class, configs=None, start_service=True): 61 """Registers a service. 62 63 This will create a service instance, starts the service, and adds the 64 instance to the mananger. 65 66 Args: 67 alias: string, the alias for this instance. 68 service_class: class, the service class to instantiate. 69 configs: (optional) config object to pass to the service class's 70 constructor. 71 start_service: bool, whether to start the service instance or not. 72 Default is True. 73 """ 74 if not inspect.isclass(service_class): 75 raise Error(self._device, '"%s" is not a class!' % service_class) 76 if not issubclass(service_class, base_service.BaseService): 77 raise Error(self._device, 78 'Class %s is not a subclass of BaseService!' % service_class) 79 if alias in self._service_objects: 80 raise Error(self._device, 81 'A service is already registered with alias "%s".' % alias) 82 service_obj = service_class(self._device, configs) 83 service_obj.alias = alias 84 if start_service: 85 service_obj.start() 86 self._service_objects[alias] = service_obj 87 88 def unregister(self, alias): 89 """Unregisters a service instance. 90 91 Stops a service and removes it from the manager. 92 93 Args: 94 alias: string, the alias of the service instance to unregister. 95 """ 96 if alias not in self._service_objects: 97 raise Error(self._device, 98 'No service is registered with alias "%s".' % alias) 99 service_obj = self._service_objects.pop(alias) 100 if service_obj.is_alive: 101 with expects.expect_no_raises('Failed to stop service instance "%s".' % 102 alias): 103 service_obj.stop() 104 105 def for_each(self, func): 106 """Executes a function with all registered services. 107 108 Args: 109 func: function, the function to execute. This function should take 110 a service object as args. 111 """ 112 aliases = list(self._service_objects.keys()) 113 for alias in aliases: 114 with expects.expect_no_raises('Failed to execute "%s" for service "%s".' % 115 (func.__name__, alias)): 116 func(self._service_objects[alias]) 117 118 def list_live_services(self): 119 """Lists the aliases of all the services that are alive. 120 121 Order of this list is determined by the order the services are 122 registered in. 123 124 Returns: 125 list of strings, the aliases of the services that are running. 126 """ 127 aliases = [] 128 self.for_each(lambda service: aliases.append(service.alias) 129 if service.is_alive else None) 130 return aliases 131 132 def create_output_excerpts_all(self, test_info): 133 """Creates output excerpts from all services. 134 135 This calls `create_output_excerpts` on all registered services. 136 137 Args: 138 test_info: RuntimeTestInfo, the test info associated with the scope 139 of the excerpts. 140 141 Returns: 142 Dict, keys are the names of the services, values are the paths to 143 the excerpt files created by the corresponding services. 144 """ 145 excerpt_paths = {} 146 147 def create_output_excerpts_for_one(service): 148 if not service.is_alive: 149 return 150 paths = service.create_output_excerpts(test_info) 151 excerpt_paths[service.alias] = paths 152 153 self.for_each(create_output_excerpts_for_one) 154 return excerpt_paths 155 156 def unregister_all(self): 157 """Safely unregisters all active instances. 158 159 Errors occurred here will be recorded but not raised. 160 """ 161 aliases = list(self._service_objects.keys()) 162 for alias in aliases: 163 self.unregister(alias) 164 165 def start_all(self): 166 """Starts all inactive service instances. 167 168 Services will be started in the order they were registered. 169 """ 170 for alias, service in self._service_objects.items(): 171 if not service.is_alive: 172 with expects.expect_no_raises('Failed to start service "%s".' % alias): 173 service.start() 174 175 def start_services(self, service_alises): 176 """Starts the specified services. 177 178 Services will be started in the order specified by the input list. 179 No-op for services that are already running. 180 181 Args: 182 service_alises: list of strings, the aliases of services to start. 183 """ 184 for name in service_alises: 185 if name not in self._service_objects: 186 raise Error( 187 self._device, 188 'No service is registered under the name "%s", cannot start.' % 189 name) 190 service = self._service_objects[name] 191 if not service.is_alive: 192 service.start() 193 194 def stop_all(self): 195 """Stops all active service instances. 196 197 Services will be stopped in the reverse order they were registered. 198 """ 199 # OrdereDict#items does not return a sequence in Python 3.4, so we have 200 # to do a list conversion here. 201 for alias, service in reversed(list(self._service_objects.items())): 202 if service.is_alive: 203 with expects.expect_no_raises('Failed to stop service "%s".' % alias): 204 service.stop() 205 206 def pause_all(self): 207 """Pauses all service instances. 208 209 Services will be paused in the reverse order they were registered. 210 """ 211 # OrdereDict#items does not return a sequence in Python 3.4, so we have 212 # to do a list conversion here. 213 for alias, service in reversed(list(self._service_objects.items())): 214 with expects.expect_no_raises('Failed to pause service "%s".' % alias): 215 service.pause() 216 217 def resume_all(self): 218 """Resumes all service instances. 219 220 Services will be resumed in the order they were registered. 221 """ 222 for alias, service in self._service_objects.items(): 223 with expects.expect_no_raises('Failed to resume service "%s".' % alias): 224 service.resume() 225 226 def resume_services(self, service_alises): 227 """Resumes the specified services. 228 229 Services will be resumed in the order specified by the input list. 230 231 Args: 232 service_alises: list of strings, the names of services to start. 233 """ 234 for name in service_alises: 235 if name not in self._service_objects: 236 raise Error( 237 self._device, 238 'No service is registered under the name "%s", cannot resume.' % 239 name) 240 service = self._service_objects[name] 241 service.resume() 242 243 def __getattr__(self, name): 244 """Syntactic sugar to enable direct access of service objects by alias. 245 246 Args: 247 name: string, the alias a service object was registered under. 248 """ 249 if self.has_service_by_name(name): 250 return self._service_objects[name] 251 return self.__getattribute__(name) 252