• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 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"""JSON RPC interface to android scripting engine."""
15
16import time
17
18from mobly import utils
19from mobly.controllers.android_device_lib import event_dispatcher
20from mobly.controllers.android_device_lib import jsonrpc_client_base
21
22_APP_NAME = 'SL4A'
23_DEVICE_SIDE_PORT = 8080
24_LAUNCH_CMD = (
25    'am start -a com.googlecode.android_scripting.action.LAUNCH_SERVER '
26    '--ei com.googlecode.android_scripting.extra.USE_SERVICE_PORT %s '
27    'com.googlecode.android_scripting/.activity.ScriptingLayerServiceLauncher')
28# Maximum time to wait for the app to start on the device (10 minutes).
29# TODO: This timeout is set high in order to allow for retries in
30# start_app_and_connect. Decrease it when the call to connect() has the option
31# for a quicker timeout than the default _cmd() timeout.
32# TODO: Evaluate whether the high timeout still makes sense for sl4a. It was
33# designed for user snippets which could be very slow to start depending on the
34# size of the snippet and main apps. sl4a can probably use a much smaller value.
35_APP_START_WAIT_TIME = 2 * 60
36
37
38class Sl4aClient(jsonrpc_client_base.JsonRpcClientBase):
39  """A client for interacting with SL4A using Mobly Snippet Lib.
40
41  Extra public attributes:
42  ed: Event dispatcher instance for this sl4a client.
43  """
44
45  def __init__(self, ad):
46    """Initializes an Sl4aClient.
47
48    Args:
49      ad: AndroidDevice object.
50    """
51    super().__init__(app_name=_APP_NAME, ad=ad)
52    self._ad = ad
53    self.ed = None
54    self._adb = ad.adb
55
56  def start_app_and_connect(self):
57    """Overrides superclass."""
58    # Check that sl4a is installed
59    out = self._adb.shell('pm list package')
60    if not utils.grep('com.googlecode.android_scripting', out):
61      raise jsonrpc_client_base.AppStartError(
62          self._ad, '%s is not installed on %s' % (_APP_NAME, self._adb.serial))
63    self.disable_hidden_api_blacklist()
64
65    # sl4a has problems connecting after disconnection, so kill the apk and
66    # try connecting again.
67    try:
68      self.stop_app()
69    except Exception as e:
70      self.log.warning(e)
71
72    # Launch the app
73    self.device_port = _DEVICE_SIDE_PORT
74    self._adb.shell(_LAUNCH_CMD % self.device_port)
75
76    # Try to start the connection (not restore the connectivity).
77    # The function name restore_app_connection is used here is for the
78    # purpose of reusing the same code as it does when restoring the
79    # connection. And we do not want to come up with another function
80    # name to complicate the API. Change the name if necessary.
81    self.restore_app_connection()
82
83  def restore_app_connection(self, port=None):
84    """Restores the sl4a after device got disconnected.
85
86    Instead of creating new instance of the client:
87      - Uses the given port (or find a new available host_port if none is
88      given).
89      - Tries to connect to remote server with selected port.
90
91    Args:
92      port: If given, this is the host port from which to connect to remote
93        device port. If not provided, find a new available port as host
94        port.
95
96    Raises:
97      AppRestoreConnectionError: When the app was not able to be started.
98    """
99    self.host_port = port or utils.get_available_host_port()
100    self._retry_connect()
101    self.ed = self._start_event_client()
102
103  def stop_app(self):
104    """Overrides superclass."""
105    try:
106      if self._conn:
107        # Be polite; let the dest know we're shutting down.
108        try:
109          self.closeSl4aSession()
110        except Exception:
111          self.log.exception('Failed to gracefully shut down %s.',
112                             self.app_name)
113
114        # Close the socket connection.
115        self.disconnect()
116        self.stop_event_dispatcher()
117
118      # Terminate the app
119      self._adb.shell('am force-stop com.googlecode.android_scripting')
120    finally:
121      # Always clean up the adb port
122      self.clear_host_port()
123
124  def stop_event_dispatcher(self):
125    # Close Event Dispatcher
126    if self.ed:
127      try:
128        self.ed.clean_up()
129      except Exception:
130        self.log.exception('Failed to shutdown sl4a event dispatcher.')
131      self.ed = None
132
133  def _retry_connect(self):
134    self._adb.forward(['tcp:%d' % self.host_port, 'tcp:%d' % self.device_port])
135    expiration_time = time.perf_counter() + _APP_START_WAIT_TIME
136    started = False
137    while time.perf_counter() < expiration_time:
138      self.log.debug('Attempting to start %s.', self.app_name)
139      try:
140        self.connect()
141        started = True
142        break
143      except Exception:
144        self.log.debug('%s is not yet running, retrying',
145                       self.app_name,
146                       exc_info=True)
147      time.sleep(1)
148    if not started:
149      raise jsonrpc_client_base.AppRestoreConnectionError(
150          self._ad, '%s failed to connect for %s at host port %s, '
151          'device port %s' %
152          (self.app_name, self._adb.serial, self.host_port, self.device_port))
153
154  def _start_event_client(self):
155    # Start an EventDispatcher for the current sl4a session
156    event_client = Sl4aClient(self._ad)
157    event_client.host_port = self.host_port
158    event_client.device_port = self.device_port
159    event_client.connect(uid=self.uid,
160                         cmd=jsonrpc_client_base.JsonRpcCommand.CONTINUE)
161    ed = event_dispatcher.EventDispatcher(event_client)
162    ed.start()
163    return ed
164