blob: 7d1dd9476584db909a5f0e94b371f81971fd55bf [file] [log] [blame]
# Copyright 2018 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module for the snippet management service."""
from mobly.controllers.android_device_lib import errors
from mobly.controllers.android_device_lib import snippet_client
from mobly.controllers.android_device_lib import snippet_client_v2
from mobly.controllers.android_device_lib.services import base_service
MISSING_SNIPPET_CLIENT_MSG = 'No snippet client is registered with name "%s".'
class Error(errors.ServiceError):
"""Root error type for snippet management service."""
SERVICE_TYPE = 'SnippetManagementService'
class SnippetManagementService(base_service.BaseService):
"""Management service of snippet clients.
This service manages all the snippet clients associated with an Android
device.
"""
def __init__(self, device, configs=None):
del configs # Unused param.
self._device = device
self._is_alive = False
self._snippet_clients = {}
super().__init__(device)
self._use_client_v2_switch = None
def _is_using_client_v2(self):
"""Is this service using snippet client V2.
Do not call this function in the constructor, as this function depends on
the device configuration and the device object will load its configuration
right after constructing the services.
NOTE: This is a transient function when we are migrating the snippet client
from v1 to v2. It will be removed after the migration is completed.
"""
if self._use_client_v2_switch is None:
device_dimensions = getattr(self._device, 'dimensions', {})
self._use_client_v2_switch = (device_dimensions.get(
'use_mobly_snippet_client_v2', 'false').lower() == 'true')
return self._use_client_v2_switch
@property
def is_alive(self):
"""True if any client is running, False otherwise."""
return any([client.is_alive for client in self._snippet_clients.values()])
def get_snippet_client(self, name):
"""Gets the snippet client managed under a given name.
Args:
name: string, the name of the snippet client under management.
Returns:
SnippetClient.
"""
if name in self._snippet_clients:
return self._snippet_clients[name]
def add_snippet_client(self, name, package):
"""Adds a snippet client to the management.
Args:
name: string, the attribute name to which to attach the snippet
client. E.g. `name='maps'` attaches the snippet client to
`ad.maps`.
package: string, the package name of the snippet apk to connect to.
Raises:
Error, if a duplicated name or package is passed in.
"""
# Should not load snippet with the same name more than once.
if name in self._snippet_clients:
raise Error(
self, 'Name "%s" is already registered with package "%s", it cannot '
'be used again.' % (name, self._snippet_clients[name].client.package))
# Should not load the same snippet package more than once.
for snippet_name, client in self._snippet_clients.items():
if package == client.package:
raise Error(
self, 'Snippet package "%s" has already been loaded under name'
' "%s".' % (package, snippet_name))
if self._is_using_client_v2():
client = snippet_client_v2.SnippetClientV2(package=package,
ad=self._device)
client.initialize()
else:
client = snippet_client.SnippetClient(package=package, ad=self._device)
client.start_app_and_connect()
self._snippet_clients[name] = client
def remove_snippet_client(self, name):
"""Removes a snippet client from management.
Args:
name: string, the name of the snippet client to remove.
Raises:
Error: if no snippet client is managed under the specified name.
"""
if name not in self._snippet_clients:
raise Error(self._device, MISSING_SNIPPET_CLIENT_MSG % name)
client = self._snippet_clients.pop(name)
if self._is_using_client_v2():
client.stop()
else:
client.stop_app()
def start(self):
"""Starts all the snippet clients under management."""
for client in self._snippet_clients.values():
if not client.is_alive:
self._device.log.debug('Starting SnippetClient<%s>.', client.package)
if self._is_using_client_v2():
client.initialize()
else:
client.start_app_and_connect()
else:
self._device.log.debug(
'Not startng SnippetClient<%s> because it is already alive.',
client.package)
def stop(self):
"""Stops all the snippet clients under management."""
for client in self._snippet_clients.values():
if client.is_alive:
self._device.log.debug('Stopping SnippetClient<%s>.', client.package)
if self._is_using_client_v2():
client.stop()
else:
client.stop_app()
else:
self._device.log.debug(
'Not stopping SnippetClient<%s> because it is not alive.',
client.package)
def pause(self):
"""Pauses all the snippet clients under management.
This clears the host port of a client because a new port will be
allocated in `resume`.
"""
for client in self._snippet_clients.values():
self._device.log.debug('Pausing SnippetClient<%s>.', client.package)
if self._is_using_client_v2():
client.close_connection()
else:
client.disconnect()
def resume(self):
"""Resumes all paused snippet clients."""
for client in self._snippet_clients.values():
if not client.is_alive:
self._device.log.debug('Resuming SnippetClient<%s>.', client.package)
if self._is_using_client_v2():
client.restore_server_connection()
else:
client.restore_app_connection()
else:
self._device.log.debug('Not resuming SnippetClient<%s>.',
client.package)
def __getattr__(self, name):
client = self.get_snippet_client(name)
if client:
return client
return self.__getattribute__(name)