blob: cdd1412f79014f40e7d80ecc634b86b9092dfd13 [file] [log] [blame]
Ang Li93420002016-05-10 19:11:44 -07001#/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