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 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' % (self.adb.serial, self.uid, 62 message) 63 64 self.log = logger.create_logger(_log_formatter) 65 66 self.ports = ports 67 self.set_timeout(rpc_client.SOCKET_TIMEOUT) 68 69 def open(self): 70 if self.uid != UNKNOWN_UID: 71 start_command = Sl4aConnectionCommand.CONTINUE 72 else: 73 start_command = Sl4aConnectionCommand.INIT 74 75 self._initiate_handshake(start_command) 76 77 def _initiate_handshake(self, start_command): 78 """Establishes a connection with the SL4A server. 79 80 Args: 81 start_command: The command to send. See Sl4aConnectionCommand. 82 """ 83 try: 84 resp = self._cmd(start_command) 85 except socket.timeout as e: 86 self.log.error('Failed to open socket connection: %s', e) 87 raise 88 if not resp: 89 raise rpc_client.Sl4aProtocolError( 90 rpc_client.Sl4aProtocolError.NO_RESPONSE_FROM_HANDSHAKE) 91 result = json.loads(str(resp, encoding='utf8')) 92 if result['status']: 93 self.uid = result['uid'] 94 else: 95 self.log.warning( 96 'UID not received for connection %s.' % self.ports) 97 self.uid = UNKNOWN_UID 98 self.log.debug('Created connection over: %s.' % self.ports) 99 100 def _cmd(self, command): 101 """Sends an session protocol command to SL4A to establish communication. 102 103 Args: 104 command: The name of the command to execute. 105 106 Returns: 107 The line that was written back. 108 """ 109 self.send_request(json.dumps({'cmd': command, 'uid': self.uid})) 110 return self.get_response() 111 112 def get_new_ticket(self): 113 """Returns a ticket for a new request.""" 114 with self._ticket_lock: 115 self._ticket_counter += 1 116 ticket = self._ticket_counter 117 return ticket 118 119 def set_timeout(self, timeout): 120 """Sets the socket's wait for response timeout.""" 121 self._client_socket.settimeout(timeout) 122 123 def send_request(self, request): 124 """Sends a request over the connection.""" 125 self._socket_file.write(request.encode('utf8') + b'\n') 126 self._socket_file.flush() 127 128 def get_response(self): 129 """Returns the first response sent back to the client.""" 130 data = self._socket_file.readline() 131 return data 132 133 def close(self): 134 """Closes the connection gracefully.""" 135 self._client_socket.close() 136 self.adb.remove_tcp_forward(self.ports.forwarded_port) 137