1#/usr/bin/env python3.4 2# 3# Copyright (C) 2009 Google Inc. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); you may not 6# use this file except in compliance with the License. You may obtain a copy of 7# 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, WITHOUT 13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14# License for the specific language governing permissions and limitations under 15# the License. 16 17""" 18JSON RPC interface to android scripting engine. 19""" 20 21from builtins import str 22 23import json 24import os 25import socket 26import threading 27import time 28 29HOST = os.environ.get('AP_HOST', None) 30PORT = os.environ.get('AP_PORT', 9999) 31 32class SL4AException(Exception): 33 pass 34 35class SL4AAPIError(SL4AException): 36 """Raised when remote API reports an error.""" 37 38class SL4AProtocolError(SL4AException): 39 """Raised when there is some error in exchanging data with server on device.""" 40 NO_RESPONSE_FROM_HANDSHAKE = "No response from handshake." 41 NO_RESPONSE_FROM_SERVER = "No response from server." 42 MISMATCHED_API_ID = "Mismatched API id." 43 44def IDCounter(): 45 i = 0 46 while True: 47 yield i 48 i += 1 49 50class Android(object): 51 COUNTER = IDCounter() 52 53 _SOCKET_CONNECT_TIMEOUT = 60 54 55 def __init__(self, cmd='initiate', uid=-1, port=PORT, addr=HOST, timeout=None): 56 self.lock = threading.RLock() 57 self.client = None # prevent close errors on connect failure 58 self.uid = None 59 timeout_time = time.time() + self._SOCKET_CONNECT_TIMEOUT 60 while True: 61 try: 62 self.conn = socket.create_connection( 63 (addr, port), max(1,timeout_time - time.time())) 64 self.conn.settimeout(timeout) 65 break 66 except (TimeoutError, socket.timeout): 67 print("Failed to create socket connection!") 68 raise 69 except (socket.error, IOError): 70 # TODO: optimize to only forgive some errors here 71 # error values are OS-specific so this will require 72 # additional tuning to fail faster 73 if time.time() + 1 >= timeout_time: 74 print("Failed to create socket connection!") 75 raise 76 time.sleep(1) 77 78 self.client = self.conn.makefile(mode="brw") 79 80 resp = self._cmd(cmd, uid) 81 if not resp: 82 raise SL4AProtocolError(SL4AProtocolError.NO_RESPONSE_FROM_HANDSHAKE) 83 result = json.loads(str(resp, encoding="utf8")) 84 if result['status']: 85 self.uid = result['uid'] 86 else: 87 self.uid = -1 88 89 def close(self): 90 if self.conn is not None: 91 self.conn.close() 92 self.conn = None 93 94 def _cmd(self, command, uid=None): 95 if not uid: 96 uid = self.uid 97 self.client.write( 98 json.dumps({'cmd': command, 'uid': uid}) 99 .encode("utf8")+b'\n') 100 self.client.flush() 101 return self.client.readline() 102 103 def _rpc(self, method, *args): 104 self.lock.acquire() 105 apiid = next(Android.COUNTER) 106 self.lock.release() 107 data = {'id': apiid, 108 'method': method, 109 'params': args} 110 request = json.dumps(data) 111 self.client.write(request.encode("utf8")+b'\n') 112 self.client.flush() 113 response = self.client.readline() 114 if not response: 115 raise SL4AProtocolError(SL4AProtocolError.NO_RESPONSE_FROM_SERVER) 116 result = json.loads(str(response, encoding="utf8")) 117 if result['error']: 118 raise SL4AAPIError(result['error']) 119 if result['id'] != apiid: 120 raise SL4AProtocolError(SL4AProtocolError.MISMATCHED_API_ID) 121 return result['result'] 122 123 def __getattr__(self, name): 124 def rpc_call(*args): 125 return self._rpc(name, *args) 126 return rpc_call 127