Source code for mobly.controllers.android_device_lib.sl4a_client

# Copyright 2016 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.
"""JSON RPC interface to android scripting engine."""

import time

from mobly import utils
from mobly.controllers.android_device_lib import event_dispatcher
from mobly.controllers.android_device_lib import jsonrpc_client_base

_APP_NAME = 'SL4A'
_DEVICE_SIDE_PORT = 8080
_LAUNCH_CMD = (
    'am start -a com.googlecode.android_scripting.action.LAUNCH_SERVER '
    '--ei com.googlecode.android_scripting.extra.USE_SERVICE_PORT %s '
    'com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher')
# Maximum time to wait for the app to start on the device (10 minutes).
# TODO: This timeout is set high in order to allow for retries in
# start_app_and_connect. Decrease it when the call to connect() has the option
# for a quicker timeout than the default _cmd() timeout.
# TODO: Evaluate whether the high timeout still makes sense for sl4a. It was
# designed for user snippets which could be very slow to start depending on the
# size of the snippet and main apps. sl4a can probably use a much smaller value.
_APP_START_WAIT_TIME = 2 * 60


[docs]class Sl4aClient(jsonrpc_client_base.JsonRpcClientBase): """A client for interacting with SL4A using Mobly Snippet Lib. Extra public attributes: ed: Event dispatcher instance for this sl4a client. """ def __init__(self, ad): """Initializes an Sl4aClient. Args: ad: AndroidDevice object. """ super().__init__(app_name=_APP_NAME, ad=ad) self._ad = ad self.ed = None self._adb = ad.adb
[docs] def start_app_and_connect(self): """Overrides superclass.""" # Check that sl4a is installed out = self._adb.shell('pm list package') if not utils.grep('com.googlecode.android_scripting', out): raise jsonrpc_client_base.AppStartError( self._ad, '%s is not installed on %s' % (_APP_NAME, self._adb.serial)) self.disable_hidden_api_blacklist() # sl4a has problems connecting after disconnection, so kill the apk and # try connecting again. try: self.stop_app() except Exception as e: self.log.warning(e) # Launch the app self.device_port = _DEVICE_SIDE_PORT self._adb.shell(_LAUNCH_CMD % self.device_port) # Try to start the connection (not restore the connectivity). # The function name restore_app_connection is used here is for the # purpose of reusing the same code as it does when restoring the # connection. And we do not want to come up with another function # name to complicate the API. Change the name if necessary. self.restore_app_connection()
[docs] def restore_app_connection(self, port=None): """Restores the sl4a after device got disconnected. Instead of creating new instance of the client: - Uses the given port (or find a new available host_port if none is given). - Tries to connect to remote server with selected port. Args: port: If given, this is the host port from which to connect to remote device port. If not provided, find a new available port as host port. Raises: AppRestoreConnectionError: When the app was not able to be started. """ self.host_port = port or utils.get_available_host_port() self._retry_connect() self.ed = self._start_event_client()
[docs] def stop_app(self): """Overrides superclass.""" try: if self._conn: # Be polite; let the dest know we're shutting down. try: self.closeSl4aSession() except Exception: self.log.exception('Failed to gracefully shut down %s.', self.app_name) # Close the socket connection. self.disconnect() self.stop_event_dispatcher() # Terminate the app self._adb.shell('am force-stop com.googlecode.android_scripting') finally: # Always clean up the adb port self.clear_host_port()
[docs] def stop_event_dispatcher(self): # Close Event Dispatcher if self.ed: try: self.ed.clean_up() except Exception: self.log.exception('Failed to shutdown sl4a event dispatcher.') self.ed = None
def _retry_connect(self): self._adb.forward(['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port]) expiration_time = time.perf_counter() + _APP_START_WAIT_TIME started = False while time.perf_counter() < expiration_time: self.log.debug('Attempting to start %s.', self.app_name) try: self.connect() started = True break except Exception: self.log.debug('%s is not yet running, retrying', self.app_name, exc_info=True) time.sleep(1) if not started: raise jsonrpc_client_base.AppRestoreConnectionError( self._ad, '%s failed to connect for %s at host port %s, ' 'device port %s' % (self.app_name, self._adb.serial, self.host_port, self.device_port)) def _start_event_client(self): # Start an EventDispatcher for the current sl4a session event_client = Sl4aClient(self._ad) event_client.host_port = self.host_port event_client.device_port = self.device_port event_client.connect(uid=self.uid, cmd=jsonrpc_client_base.JsonRpcCommand.CONTINUE) ed = event_dispatcher.EventDispatcher(event_client) ed.start() return ed