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 json 17import socket 18import threading 19 20from acts import logger 21from acts.controllers.sl4a_lib import rpc_client 22 23# The Session UID when a UID has not been received yet. 24UNKNOWN_UID = -1 25 26 27class Sl4aConnectionCommand(object): 28 """Commands that can be invoked on the sl4a client. 29 30 INIT: Initializes a new sessions in sl4a. 31 CONTINUE: Creates a connection. 32 """ 33 INIT = 'initiate' 34 CONTINUE = 'continue' 35 36 37class RpcConnection(object): 38 """A single RPC Connection thread. 39 40 Attributes: 41 _client_socket: The socket this connection uses. 42 _socket_file: The file created over the _client_socket. 43 _ticket_counter: The counter storing the current ticket number. 44 _ticket_lock: A lock on the ticket counter to prevent ticket collisions. 45 adb: A reference to the AdbProxy of the AndroidDevice. Used for logging. 46 log: The logger for this RPC Client. 47 ports: The Sl4aPorts object that stores the ports this connection uses. 48 uid: The SL4A session ID. 49 """ 50 51 def __init__(self, adb, ports, client_socket, socket_fd, uid=UNKNOWN_UID): 52 self._client_socket = client_socket 53 self._socket_file = socket_fd 54 self._ticket_counter = 0 55 self._ticket_lock = threading.Lock() 56 self.adb = adb 57 self.uid = uid 58 59 def _log_formatter(message): 60 """Defines the formatting used in the logger.""" 61 return '[SL4A Client|%s|%s|%s] %s' % (self.adb.serial, 62 self.ports.client_port, 63 self.uid, message) 64 65 self.log = logger.create_logger(_log_formatter) 66 67 self.ports = ports 68 self.set_timeout(rpc_client.SOCKET_TIMEOUT) 69 70 def open(self): 71 if self.uid != UNKNOWN_UID: 72 start_command = Sl4aConnectionCommand.CONTINUE 73 else: 74 start_command = Sl4aConnectionCommand.INIT 75 76 self._initiate_handshake(start_command) 77 78 def _initiate_handshake(self, start_command): 79 """Establishes a connection with the SL4A server. 80 81 Args: 82 start_command: The command to send. See Sl4aConnectionCommand. 83 """ 84 try: 85 resp = self._cmd(start_command) 86 except socket.timeout as e: 87 self.log.error('Failed to open socket connection: %s', e) 88 raise 89 if not resp: 90 raise rpc_client.Sl4aProtocolError( 91 rpc_client.Sl4aProtocolError.NO_RESPONSE_FROM_HANDSHAKE) 92 result = json.loads(str(resp, encoding='utf8')) 93 if result['status']: 94 self.uid = result['uid'] 95 else: 96 self.log.warning( 97 'UID not received for connection %s.' % self.ports) 98 self.uid = UNKNOWN_UID 99 self.log.debug('Created connection over: %s.' % self.ports) 100 101 def _cmd(self, command): 102 """Sends an session protocol command to SL4A to establish communication. 103 104 Args: 105 command: The name of the command to execute. 106 107 Returns: 108 The line that was written back. 109 """ 110 self.send_request(json.dumps({'cmd': command, 'uid': self.uid})) 111 return self.get_response() 112 113 def get_new_ticket(self): 114 """Returns a ticket for a new request.""" 115 with self._ticket_lock: 116 self._ticket_counter += 1 117 ticket = self._ticket_counter 118 return ticket 119 120 def set_timeout(self, timeout): 121 """Sets the socket's wait for response timeout.""" 122 self._client_socket.settimeout(timeout) 123 124 def send_request(self, request): 125 """Sends a request over the connection.""" 126 self._socket_file.write(request.encode('utf8') + b'\n') 127 self._socket_file.flush() 128 self.log.debug('Sent: ' + request) 129 130 def get_response(self): 131 """Returns the first response sent back to the client.""" 132 data = self._socket_file.readline() 133 self.log.debug('Received: ' + data.decode('utf8', errors='replace')) 134 return data 135 136 def close(self): 137 """Closes the connection gracefully.""" 138 self._client_socket.close() 139 self.adb.remove_tcp_forward(self.ports.forwarded_port) 140