1#!/usr/bin/env python3.4 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.sl4a_lib import event_dispatcher 23from acts.controllers.sl4a_lib import rpc_connection 24from acts.controllers.sl4a_lib import rpc_client 25from acts.controllers.sl4a_lib import sl4a_ports 26 27SOCKET_TIMEOUT = 60 28 29# The SL4A Session UID when a UID has not been received yet. 30UNKNOWN_UID = -1 31 32 33class Sl4aSession(object): 34 """An object that tracks the state of an SL4A Session. 35 36 Attributes: 37 _event_dispatcher: The EventDispatcher instance, if any, for this 38 session. 39 _terminate_lock: A lock that prevents race conditions for multiple 40 threads calling terminate() 41 _terminated: A bool that stores whether or not this session has been 42 terminated. Terminated sessions cannot be restarted. 43 adb: A reference to the AndroidDevice's AdbProxy. 44 log: The logger for this Sl4aSession 45 server_port: The SL4A server port this session is established on. 46 uid: The uid that corresponds the the SL4A Server's session id. This 47 value is only unique during the lifetime of the SL4A apk. 48 """ 49 50 def __init__(self, 51 adb, 52 host_port, 53 device_port, 54 get_server_port_func, 55 on_error_callback, 56 max_connections=None): 57 """Creates an SL4A Session. 58 59 Args: 60 adb: A reference to the adb proxy 61 get_server_port_func: A lambda (int) that returns the corrected 62 server port. The int passed in hints at which port to use, if 63 possible. 64 host_port: The port the host machine uses to connect to the SL4A 65 server for its first connection. 66 device_port: The SL4A server port to be used as a hint for which 67 SL4A server to connect to. 68 """ 69 self._event_dispatcher = None 70 self._terminate_lock = threading.Lock() 71 self._terminated = False 72 self.adb = adb 73 74 def _log_formatter(message): 75 return '[SL4A Session|%s|%s] %s' % (self.adb.serial, self.uid, 76 message) 77 78 self.log = logger.create_logger(_log_formatter) 79 80 self.server_port = device_port 81 self.uid = UNKNOWN_UID 82 self.obtain_server_port = get_server_port_func 83 self._on_error_callback = on_error_callback 84 85 connection_creator = self._rpc_connection_creator(host_port) 86 self.rpc_client = rpc_client.RpcClient( 87 self.uid, 88 self.adb.serial, 89 self.diagnose_failure, 90 connection_creator, 91 max_connections=max_connections) 92 93 def _rpc_connection_creator(self, host_port): 94 def create_client(uid): 95 return self._create_rpc_connection( 96 ports=sl4a_ports.Sl4aPorts(host_port, 0, self.server_port), 97 uid=uid) 98 99 return create_client 100 101 @property 102 def is_alive(self): 103 return not self._terminated 104 105 def _create_rpc_connection(self, ports=None, uid=UNKNOWN_UID): 106 """Creates an RPC Connection with the specified ports. 107 108 Args: 109 ports: A Sl4aPorts object or a tuple of (host/client_port, 110 forwarded_port, device/server_port). If any of these are 111 zero, the OS will determine their values during connection. 112 113 Note that these ports are only suggestions. If they are not 114 available, the a different port will be selected. 115 uid: The UID of the SL4A Session. To create a new session, use 116 UNKNOWN_UID. 117 Returns: 118 An Sl4aClient. 119 """ 120 if ports is None: 121 ports = sl4a_ports.Sl4aPorts(0, 0, 0) 122 # Open a new server if a server cannot be inferred. 123 ports.server_port = self.obtain_server_port(ports.server_port) 124 self.server_port = ports.server_port 125 # Forward the device port to the host. 126 ports.forwarded_port = int(self.adb.tcp_forward(0, ports.server_port)) 127 client_socket, fd = self._create_client_side_connection(ports) 128 client = rpc_connection.RpcConnection( 129 self.adb, ports, client_socket, fd, uid=uid) 130 client.open() 131 if uid == UNKNOWN_UID: 132 self.uid = client.uid 133 return client 134 135 def diagnose_failure(self, connection): 136 """Diagnoses any problems related to the SL4A session.""" 137 self._on_error_callback(self, connection) 138 139 def get_event_dispatcher(self): 140 """Returns the EventDispatcher for this Sl4aSession.""" 141 if self._event_dispatcher is None: 142 self._event_dispatcher = event_dispatcher.EventDispatcher( 143 self, self.rpc_client) 144 return self._event_dispatcher 145 146 def _create_client_side_connection(self, ports): 147 """Creates and connects the client socket to the forward device port. 148 149 Args: 150 ports: A Sl4aPorts object or a tuple of (host_port, 151 forwarded_port, device_port). 152 153 Returns: 154 A tuple of (socket, socket_file_descriptor). 155 """ 156 client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 157 client_socket.settimeout(SOCKET_TIMEOUT) 158 client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 159 if ports.client_port != 0: 160 try: 161 client_socket.bind((socket.gethostname(), ports.client_port)) 162 except OSError as e: 163 # If the port is in use, log and ask for any open port. 164 if e.errno == errno.EADDRINUSE: 165 self.log.warning( 166 'Port %s is already in use on the host. ' 167 'Generating a random port.' % ports.client_port) 168 ports.client_port = 0 169 return self._create_client_side_connection(ports) 170 raise 171 172 # Verify and obtain the port opened by SL4A. 173 try: 174 # Connect to the port that has been forwarded to the device. 175 client_socket.connect(('127.0.0.1', ports.forwarded_port)) 176 except socket.timeout: 177 raise rpc_client.Sl4aConnectionError( 178 'SL4A has not connected over the specified port within the ' 179 'timeout of %s seconds.' % SOCKET_TIMEOUT) 180 except socket.error as e: 181 # In extreme, unlikely cases, a socket error with 182 # errno.EADDRNOTAVAIL can be raised when a desired host_port is 183 # taken by a separate program between the bind and connect calls. 184 # Note that if host_port is set to zero, there is no bind before 185 # the connection is made, so this error will never be thrown. 186 if e.errno == errno.EADDRNOTAVAIL: 187 ports.client_port = 0 188 return self._create_client_side_connection(ports) 189 raise 190 ports.client_port = client_socket.getsockname()[1] 191 return client_socket, client_socket.makefile(mode='brw') 192 193 def terminate(self): 194 """Terminates the session. 195 196 The return of process execution is blocked on completion of all events 197 being processed by handlers in the Event Dispatcher. 198 """ 199 with self._terminate_lock: 200 if not self._terminated: 201 self.log.debug('Terminating Session.') 202 self.rpc_client.closeSl4aSession() 203 # Must be set after closeSl4aSession so the rpc_client does not 204 # think the session has closed. 205 self._terminated = True 206 if self._event_dispatcher: 207 self._event_dispatcher.close() 208 self.rpc_client.terminate() 209