Home | History | Annotate | Download | only in sl4a_lib
      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.
     16 import socket
     17 import threading
     18 
     19 import errno
     20 
     21 from acts import logger
     22 from acts.controllers.sl4a_lib import event_dispatcher
     23 from acts.controllers.sl4a_lib import rpc_connection
     24 from acts.controllers.sl4a_lib import rpc_client
     25 from acts.controllers.sl4a_lib import sl4a_ports
     26 
     27 SOCKET_TIMEOUT = 60
     28 
     29 # The SL4A Session UID when a UID has not been received yet.
     30 UNKNOWN_UID = -1
     31 
     32 
     33 class 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