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 max_connections=None): 59 """Creates an SL4A Session. 60 61 Args: 62 adb: A reference to the adb proxy 63 get_server_port_func: A lambda (int) that returns the corrected 64 server port. The int passed in hints at which port to use, if 65 possible. 66 host_port: The port the host machine uses to connect to the SL4A 67 server for its first connection. 68 device_port: The SL4A server port to be used as a hint for which 69 SL4A server to connect to. 70 """ 71 self._event_dispatcher = None 72 self._terminate_lock = threading.Lock() 73 self._terminated = False 74 self.adb = adb 75 76 def _log_formatter(message): 77 return '[SL4A Session|%s|%s] %s' % (self.adb.serial, self.uid, 78 message) 79 80 self.log = logger.create_logger(_log_formatter) 81 82 self.server_port = device_port 83 self.uid = UNKNOWN_UID 84 self.obtain_server_port = get_server_port_func 85 self._on_error_callback = on_error_callback 86 87 connection_creator = self._rpc_connection_creator(host_port) 88 self.rpc_client = rpc_client.RpcClient( 89 self.uid, 90 self.adb.serial, 91 self.diagnose_failure, 92 connection_creator, 93 max_connections=max_connections) 94 95 def _rpc_connection_creator(self, host_port): 96 def create_client(uid): 97 return self._create_rpc_connection( 98 ports=sl4a_ports.Sl4aPorts(host_port, 0, self.server_port), 99 uid=uid) 100 101 return create_client 102 103 @property 104 def is_alive(self): 105 return not self._terminated 106 107 def _create_forwarded_port(self, server_port, hinted_port=0): 108 """Creates a forwarded port to the specified server port. 109 110 Args: 111 server_port: (int) The port to forward to. 112 hinted_port: (int) The port to use for forwarding, if available. 113 Otherwise, the chosen port will be random. 114 Returns: 115 The chosen forwarded port. 116 117 Raises AdbError if the version of ADB is too old, or the command fails. 118 """ 119 if self.adb.get_version_number() < 37 and hinted_port == 0: 120 self.log.error( 121 'The current version of ADB does not automatically provide a ' 122 'port to forward. Please upgrade ADB to version 1.0.37 or ' 123 'higher.') 124 raise Sl4aStartError('Unable to forward a port to the device.') 125 else: 126 try: 127 return self.adb.tcp_forward(hinted_port, server_port) 128 except AdbError as e: 129 if 'cannot bind listener' in e.stderr: 130 self.log.warning( 131 'Unable to use %s to forward to device port %s due to: ' 132 '"%s". Attempting to choose a random port instead.' % 133 (hinted_port, server_port, e.stderr)) 134 # Call this method again, but this time with no hinted port. 135 return self._create_forwarded_port(server_port) 136 raise e 137 138 def _create_rpc_connection(self, ports=None, uid=UNKNOWN_UID): 139 """Creates an RPC Connection with the specified ports. 140 141 Args: 142 ports: A Sl4aPorts object or a tuple of (host/client_port, 143 forwarded_port, device/server_port). If any of these are 144 zero, the OS will determine their values during connection. 145 146 Note that these ports are only suggestions. If they are not 147 available, the a different port will be selected. 148 uid: The UID of the SL4A Session. To create a new session, use 149 UNKNOWN_UID. 150 Returns: 151 An Sl4aClient. 152 """ 153 if ports is None: 154 ports = sl4a_ports.Sl4aPorts(0, 0, 0) 155 # Open a new server if a server cannot be inferred. 156 ports.server_port = self.obtain_server_port(ports.server_port) 157 self.server_port = ports.server_port 158 # Forward the device port to the host. 159 ports.forwarded_port = self._create_forwarded_port(ports.server_port) 160 client_socket, fd = self._create_client_side_connection(ports) 161 client = rpc_connection.RpcConnection( 162 self.adb, ports, client_socket, fd, uid=uid) 163 client.open() 164 if uid == UNKNOWN_UID: 165 self.uid = client.uid 166 return client 167 168 def diagnose_failure(self, connection): 169 """Diagnoses any problems related to the SL4A session.""" 170 self._on_error_callback(self, connection) 171 172 def get_event_dispatcher(self): 173 """Returns the EventDispatcher for this Sl4aSession.""" 174 if self._event_dispatcher is None: 175 self._event_dispatcher = event_dispatcher.EventDispatcher( 176 self.adb.serial, self.rpc_client) 177 return self._event_dispatcher 178 179 def _create_client_side_connection(self, ports): 180 """Creates and connects the client socket to the forward device port. 181 182 Args: 183 ports: A Sl4aPorts object or a tuple of (host_port, 184 forwarded_port, device_port). 185 186 Returns: 187 A tuple of (socket, socket_file_descriptor). 188 """ 189 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 190 client_socket.settimeout(SOCKET_TIMEOUT) 191 client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 192 if ports.client_port != 0: 193 try: 194 client_socket.bind((socket.gethostname(), ports.client_port)) 195 except OSError as e: 196 # If the port is in use, log and ask for any open port. 197 if e.errno == errno.EADDRINUSE: 198 self.log.warning( 199 'Port %s is already in use on the host. ' 200 'Generating a random port.' % ports.client_port) 201 ports.client_port = 0 202 return self._create_client_side_connection(ports) 203 raise 204 205 # Verify and obtain the port opened by SL4A. 206 try: 207 # Connect to the port that has been forwarded to the device. 208 client_socket.connect(('127.0.0.1', ports.forwarded_port)) 209 except socket.timeout: 210 raise rpc_client.Sl4aConnectionError( 211 'SL4A has not connected over the specified port within the ' 212 'timeout of %s seconds.' % SOCKET_TIMEOUT) 213 except socket.error as e: 214 # In extreme, unlikely cases, a socket error with 215 # errno.EADDRNOTAVAIL can be raised when a desired host_port is 216 # taken by a separate program between the bind and connect calls. 217 # Note that if host_port is set to zero, there is no bind before 218 # the connection is made, so this error will never be thrown. 219 if e.errno == errno.EADDRNOTAVAIL: 220 ports.client_port = 0 221 return self._create_client_side_connection(ports) 222 raise 223 ports.client_port = client_socket.getsockname()[1] 224 return client_socket, client_socket.makefile(mode='brw') 225 226 def terminate(self): 227 """Terminates the session. 228 229 The return of process execution is blocked on completion of all events 230 being processed by handlers in the Event Dispatcher. 231 """ 232 with self._terminate_lock: 233 if not self._terminated: 234 self.log.debug('Terminating Session.') 235 try: 236 self.rpc_client.closeSl4aSession() 237 except Exception as e: 238 if "SL4A session has already been terminated" not in str( 239 e): 240 self.log.warning(e) 241 # Must be set after closeSl4aSession so the rpc_client does not 242 # think the session has closed. 243 self._terminated = True 244 if self._event_dispatcher: 245 try: 246 self._event_dispatcher.close() 247 except Exception as e: 248 self.log.warning(e) 249 try: 250 self.rpc_client.terminate() 251 except Exception as e: 252 self.log.warning(e) 253