1#!/usr/bin/env python3 2# 3# Copyright 2018 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16import socket 17import threading 18 19import errno 20 21from acts import logger 22from acts.controllers.adb_lib.error import AdbError 23from acts.controllers.sl4a_lib import event_dispatcher 24from acts.controllers.sl4a_lib import rpc_connection 25from acts.controllers.sl4a_lib import rpc_client 26from acts.controllers.sl4a_lib import sl4a_ports 27from acts.controllers.sl4a_lib.rpc_client import Sl4aStartError 28 29SOCKET_TIMEOUT = 60 30 31# The SL4A Session UID when a UID has not been received yet. 32UNKNOWN_UID = -1 33 34 35class Sl4aSession(object): 36 """An object that tracks the state of an SL4A Session. 37 38 Attributes: 39 _event_dispatcher: The EventDispatcher instance, if any, for this 40 session. 41 _terminate_lock: A lock that prevents race conditions for multiple 42 threads calling terminate() 43 _terminated: A bool that stores whether or not this session has been 44 terminated. Terminated sessions cannot be restarted. 45 adb: A reference to the AndroidDevice's AdbProxy. 46 log: The logger for this Sl4aSession 47 server_port: The SL4A server port this session is established on. 48 uid: The uid that corresponds the the SL4A Server's session id. This 49 value is only unique during the lifetime of the SL4A apk. 50 """ 51 52 def __init__(self, 53 adb, 54 host_port, 55 device_port, 56 get_server_port_func, 57 on_error_callback, 58 forwarded_port=0, 59 max_connections=None): 60 """Creates an SL4A Session. 61 62 Args: 63 adb: A reference to the adb proxy 64 get_server_port_func: A lambda (int) that returns the corrected 65 server port. The int passed in hints at which port to use, if 66 possible. 67 host_port: The port the host machine uses to connect to the SL4A 68 server for its first connection. 69 device_port: The SL4A server port to be used as a hint for which 70 SL4A server to connect to. 71 forwarded_port: The server port on host machine forwarded by adb 72 from Android device to accept SL4A connection 73 """ 74 self._event_dispatcher = None 75 self._terminate_lock = threading.Lock() 76 self._terminated = False 77 self.adb = adb 78 79 def _log_formatter(message): 80 return '[SL4A Session|%s|%s] %s' % (self.adb.serial, self.uid, 81 message) 82 83 self.log = logger.create_logger(_log_formatter) 84 85 self.forwarded_port = forwarded_port 86 self.server_port = device_port 87 self.uid = UNKNOWN_UID 88 self.obtain_server_port = get_server_port_func 89 self._on_error_callback = on_error_callback 90 91 connection_creator = self._rpc_connection_creator(host_port) 92 self.rpc_client = rpc_client.RpcClient(self.uid, 93 self.adb.serial, 94 self.diagnose_failure, 95 connection_creator, 96 max_connections=max_connections) 97 98 def _rpc_connection_creator(self, host_port): 99 def create_client(uid): 100 return self._create_rpc_connection(ports=sl4a_ports.Sl4aPorts( 101 host_port, self.forwarded_port, self.server_port), 102 uid=uid) 103 104 return create_client 105 106 @property 107 def is_alive(self): 108 return not self._terminated 109 110 def _create_forwarded_port(self, server_port, hinted_port=0): 111 """Creates a forwarded port to the specified server port. 112 113 Args: 114 server_port: (int) The port to forward to. 115 hinted_port: (int) The port to use for forwarding, if available. 116 Otherwise, the chosen port will be random. 117 Returns: 118 The chosen forwarded port. 119 120 Raises AdbError if the version of ADB is too old, or the command fails. 121 """ 122 if self.adb.get_version_number() < 37 and hinted_port == 0: 123 self.log.error( 124 'The current version of ADB does not automatically provide a ' 125 'port to forward. Please upgrade ADB to version 1.0.37 or ' 126 'higher.') 127 raise Sl4aStartError('Unable to forward a port to the device.') 128 else: 129 try: 130 return self.adb.tcp_forward(hinted_port, server_port) 131 except AdbError as e: 132 if 'cannot bind listener' in e.stderr: 133 self.log.warning( 134 'Unable to use %s to forward to device port %s due to: ' 135 '"%s". Attempting to choose a random port instead.' % 136 (hinted_port, server_port, e.stderr)) 137 # Call this method again, but this time with no hinted port. 138 return self._create_forwarded_port(server_port) 139 raise e 140 141 def _create_rpc_connection(self, ports=None, uid=UNKNOWN_UID): 142 """Creates an RPC Connection with the specified ports. 143 144 Args: 145 ports: A Sl4aPorts object or a tuple of (host/client_port, 146 forwarded_port, device/server_port). If any of these are 147 zero, the OS will determine their values during connection. 148 149 Note that these ports are only suggestions. If they are not 150 available, the a different port will be selected. 151 uid: The UID of the SL4A Session. To create a new session, use 152 UNKNOWN_UID. 153 Returns: 154 An Sl4aClient. 155 """ 156 if ports is None: 157 ports = sl4a_ports.Sl4aPorts(0, 0, 0) 158 # Open a new server if a server cannot be inferred. 159 ports.server_port = self.obtain_server_port(ports.server_port) 160 self.server_port = ports.server_port 161 # Forward the device port to the host. 162 ports.forwarded_port = self._create_forwarded_port( 163 ports.server_port, hinted_port=ports.forwarded_port) 164 client_socket, fd = self._create_client_side_connection(ports) 165 client = rpc_connection.RpcConnection(self.adb, 166 ports, 167 client_socket, 168 fd, 169 uid=uid) 170 client.open() 171 if uid == UNKNOWN_UID: 172 self.uid = client.uid 173 return client 174 175 def diagnose_failure(self, connection): 176 """Diagnoses any problems related to the SL4A session.""" 177 self._on_error_callback(self, connection) 178 179 def get_event_dispatcher(self): 180 """Returns the EventDispatcher for this Sl4aSession.""" 181 if self._event_dispatcher is None: 182 self._event_dispatcher = event_dispatcher.EventDispatcher( 183 self.adb.serial, self.rpc_client) 184 return self._event_dispatcher 185 186 def _create_client_side_connection(self, ports): 187 """Creates and connects the client socket to the forward device port. 188 189 Args: 190 ports: A Sl4aPorts object or a tuple of (host_port, 191 forwarded_port, device_port). 192 193 Returns: 194 A tuple of (socket, socket_file_descriptor). 195 """ 196 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 197 client_socket.settimeout(SOCKET_TIMEOUT) 198 client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 199 if ports.client_port != 0: 200 try: 201 client_socket.bind((socket.gethostname(), ports.client_port)) 202 except OSError as e: 203 # If the port is in use, log and ask for any open port. 204 if e.errno == errno.EADDRINUSE: 205 self.log.warning('Port %s is already in use on the host. ' 206 'Generating a random port.' % 207 ports.client_port) 208 ports.client_port = 0 209 return self._create_client_side_connection(ports) 210 raise 211 212 # Verify and obtain the port opened by SL4A. 213 try: 214 # Connect to the port that has been forwarded to the device. 215 client_socket.connect(('127.0.0.1', ports.forwarded_port)) 216 except socket.timeout: 217 raise rpc_client.Sl4aConnectionError( 218 'SL4A has not connected over the specified port within the ' 219 'timeout of %s seconds.' % SOCKET_TIMEOUT) 220 except socket.error as e: 221 # In extreme, unlikely cases, a socket error with 222 # errno.EADDRNOTAVAIL can be raised when a desired host_port is 223 # taken by a separate program between the bind and connect calls. 224 # Note that if host_port is set to zero, there is no bind before 225 # the connection is made, so this error will never be thrown. 226 if e.errno == errno.EADDRNOTAVAIL: 227 ports.client_port = 0 228 return self._create_client_side_connection(ports) 229 raise 230 ports.client_port = client_socket.getsockname()[1] 231 return client_socket, client_socket.makefile(mode='brw') 232 233 def terminate(self): 234 """Terminates the session. 235 236 The return of process execution is blocked on completion of all events 237 being processed by handlers in the Event Dispatcher. 238 """ 239 with self._terminate_lock: 240 if not self._terminated: 241 self.log.debug('Terminating Session.') 242 try: 243 self.rpc_client.closeSl4aSession() 244 except Exception as e: 245 if "SL4A session has already been terminated" not in str( 246 e): 247 self.log.warning(e) 248 # Must be set after closeSl4aSession so the rpc_client does not 249 # think the session has closed. 250 self._terminated = True 251 if self._event_dispatcher: 252 try: 253 self._event_dispatcher.close() 254 except Exception as e: 255 self.log.warning(e) 256 try: 257 self.rpc_client.terminate() 258 except Exception as e: 259 self.log.warning(e) 260