blob: 531ae065b03d7004d9556bd15496e646d3387d24 [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.
Ang Li93420002016-05-10 19:11:44 -070016"""
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
Ang Lie2139f12016-05-12 17:39:06 -070031
Ang Li93420002016-05-10 19:11:44 -070032class SL4AException(Exception):
33 pass
34
Ang Lie2139f12016-05-12 17:39:06 -070035
Ang Li93420002016-05-10 19:11:44 -070036class SL4AAPIError(SL4AException):
37 """Raised when remote API reports an error."""
38
Ang Lie2139f12016-05-12 17:39:06 -070039
Ang Li93420002016-05-10 19:11:44 -070040class 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
Ang Lie2139f12016-05-12 17:39:06 -070046
Ang Li93420002016-05-10 19:11:44 -070047def IDCounter():
48 i = 0
49 while True:
50 yield i
51 i += 1
52
Ang Lie2139f12016-05-12 17:39:06 -070053
Ang Li93420002016-05-10 19:11:44 -070054class Android(object):
55 COUNTER = IDCounter()
56
57 _SOCKET_CONNECT_TIMEOUT = 60
58
Ang Lie2139f12016-05-12 17:39:06 -070059 def __init__(self,
60 cmd='initiate',
61 uid=-1,
62 port=PORT,
63 addr=HOST,
64 timeout=None):
Ang Li93420002016-05-10 19:11:44 -070065 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(
Ang Lie2139f12016-05-12 17:39:06 -070072 (addr, port), max(1, timeout_time - time.time()))
Ang Li93420002016-05-10 19:11:44 -070073 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:
Ang Lie2139f12016-05-12 17:39:06 -070091 raise SL4AProtocolError(
92 SL4AProtocolError.NO_RESPONSE_FROM_HANDSHAKE)
Ang Li93420002016-05-10 19:11:44 -070093 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
Ang Lie2139f12016-05-12 17:39:06 -0700107 self.client.write(json.dumps({'cmd': command,
108 'uid': uid}).encode("utf8") + b'\n')
Ang Li93420002016-05-10 19:11:44 -0700109 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()
Ang Lie2139f12016-05-12 17:39:06 -0700116 data = {'id': apiid, 'method': method, 'params': args}
Ang Li93420002016-05-10 19:11:44 -0700117 request = json.dumps(data)
Ang Lie2139f12016-05-12 17:39:06 -0700118 self.client.write(request.encode("utf8") + b'\n')
Ang Li93420002016-05-10 19:11:44 -0700119 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)
Ang Lie2139f12016-05-12 17:39:06 -0700133
Ang Li93420002016-05-10 19:11:44 -0700134 return rpc_call