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