We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Log snippet client calls at debug level
Right now there is no log for calls made to snippet client side, which makes it difficult to debug should a snippet client call hangs.

On debug level, we should log messages sent and received by the client, same way we log messages received and sent by snippet server on devices.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mobly/controllers/android_device_lib/jsonrpc_client_base.py`
Content:
```
1 # Copyright 2016 Google Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14 """Base class for clients that communicate with apps over a JSON RPC interface.
15 
16 The JSON protocol expected by this module is:
17 
18 .. code-block:: json
19 
20     Request:
21     {
22         "id": <monotonically increasing integer containing the ID of 
23                this request>
24         "method": <string containing the name of the method to execute>
25         "params": <JSON array containing the arguments to the method>
26     }
27 
28     Response:
29     {
30         "id": <int id of request that this response maps to>,
31         "result": <Arbitrary JSON object containing the result of
32                    executing the method. If the method could not be
33                    executed or returned void, contains 'null'.>,
34         "error": <String containing the error thrown by executing the
35                   method. If no error occurred, contains 'null'.>
36         "callback": <String that represents a callback ID used to
37                      identify events associated with a particular
38                      CallbackHandler object.>
39     }
40 """
41 
42 from builtins import str
43 
44 import json
45 import socket
46 import threading
47 
48 from mobly.controllers.android_device_lib import callback_handler
49 from mobly.controllers.android_device_lib import errors
50 
51 # UID of the 'unknown' jsonrpc session. Will cause creation of a new session.
52 UNKNOWN_UID = -1
53 
54 # Maximum time to wait for the socket to open on the device.
55 _SOCKET_CONNECTION_TIMEOUT = 60
56 
57 # Maximum time to wait for a response message on the socket.
58 _SOCKET_READ_TIMEOUT = callback_handler.MAX_TIMEOUT
59 
60 
61 class Error(errors.DeviceError):
62     pass
63 
64 
65 class AppStartError(Error):
66     """Raised when the app is not able to be started."""
67 
68 
69 class AppRestoreConnectionError(Error):
70     """Raised when failed to restore app from disconnection."""
71 
72 
73 class ApiError(Error):
74     """Raised when remote API reports an error."""
75 
76 
77 class ProtocolError(Error):
78     """Raised when there is some error in exchanging data with server."""
79     NO_RESPONSE_FROM_HANDSHAKE = 'No response from handshake.'
80     NO_RESPONSE_FROM_SERVER = 'No response from server.'
81     MISMATCHED_API_ID = 'Mismatched API id.'
82 
83 
84 class JsonRpcCommand(object):
85     """Commands that can be invoked on all jsonrpc clients.
86 
87     INIT: Initializes a new session.
88     CONTINUE: Creates a connection.
89     """
90     INIT = 'initiate'
91     CONTINUE = 'continue'
92 
93 
94 class JsonRpcClientBase(object):
95     """Base class for jsonrpc clients that connect to remote servers.
96 
97     Connects to a remote device running a jsonrpc-compatible app. Before opening
98     a connection a port forward must be setup to go over usb. This be done using
99     adb.forward([local, remote]). Once the port has been forwarded it can be
100     used in this object as the port of communication.
101 
102     Attributes:
103         host_port: (int) The host port of this RPC client.
104         device_port: (int) The device port of this RPC client.
105         app_name: (str) The user-visible name of the app being communicated
106                   with.
107         uid: (int) The uid of this session.
108     """
109 
110     def __init__(self, app_name, ad):
111         """
112         Args:
113             app_name: (str) The user-visible name of the app being communicated
114                 with.
115             ad: (AndroidDevice) The device object associated with a client.
116         """
117         self.host_port = None
118         self.device_port = None
119         self.app_name = app_name
120         self._ad = ad
121         self.log = self._ad.log
122         self.uid = None
123         self._client = None  # prevent close errors on connect failure
124         self._conn = None
125         self._counter = None
126         self._lock = threading.Lock()
127         self._event_client = None
128 
129     def __del__(self):
130         self.disconnect()
131 
132     # Methods to be implemented by subclasses.
133 
134     def start_app_and_connect(self):
135         """Starts the server app on the android device and connects to it.
136 
137         After this, the self.host_port and self.device_port attributes must be
138         set.
139 
140         Must be implemented by subclasses.
141 
142         Raises:
143             AppStartError: When the app was not able to be started.
144         """
145         raise NotImplementedError()
146 
147     def stop_app(self):
148         """Kills any running instance of the app.
149 
150         Must be implemented by subclasses.
151         """
152         raise NotImplementedError()
153 
154     def restore_app_connection(self, port=None):
155         """Reconnects to the app after device USB was disconnected.
156 
157         Instead of creating new instance of the client:
158           - Uses the given port (or finds a new available host_port if none is
159             given).
160           - Tries to connect to remote server with selected port.
161 
162         Must be implemented by subclasses.
163 
164         Args:
165           port: If given, this is the host port from which to connect to remote
166               device port. If not provided, find a new available port as host
167               port.
168 
169         Raises:
170             AppRestoreConnectionError: When the app was not able to be
171             reconnected.
172         """
173         raise NotImplementedError()
174 
175     def _start_event_client(self):
176         """Starts a separate JsonRpc client to the same session for propagating
177         events.
178 
179         This is an optional function that should only implement if the client
180         utilizes the snippet event mechanism.
181 
182         Returns:
183             A JsonRpc Client object that connects to the same session as the
184             one on which this function is called.
185         """
186         raise NotImplementedError()
187 
188     # Rest of the client methods.
189 
190     def connect(self, uid=UNKNOWN_UID, cmd=JsonRpcCommand.INIT):
191         """Opens a connection to a JSON RPC server.
192 
193         Opens a connection to a remote client. The connection attempt will time
194         out if it takes longer than _SOCKET_CONNECTION_TIMEOUT seconds. Each
195         subsequent operation over this socket will time out after
196         _SOCKET_READ_TIMEOUT seconds as well.
197 
198         Args:
199             uid: int, The uid of the session to join, or UNKNOWN_UID to start a
200                 new session.
201             cmd: JsonRpcCommand, The command to use for creating the connection.
202 
203         Raises:
204             IOError: Raised when the socket times out from io error
205             socket.timeout: Raised when the socket waits to long for connection.
206             ProtocolError: Raised when there is an error in the protocol.
207         """
208         self._counter = self._id_counter()
209         self._conn = socket.create_connection(('localhost', self.host_port),
210                                               _SOCKET_CONNECTION_TIMEOUT)
211         self._conn.settimeout(_SOCKET_READ_TIMEOUT)
212         self._client = self._conn.makefile(mode='brw')
213 
214         resp = self._cmd(cmd, uid)
215         if not resp:
216             raise ProtocolError(self._ad,
217                                 ProtocolError.NO_RESPONSE_FROM_HANDSHAKE)
218         result = json.loads(str(resp, encoding='utf8'))
219         if result['status']:
220             self.uid = result['uid']
221         else:
222             self.uid = UNKNOWN_UID
223 
224     def disconnect(self):
225         """Close the connection to the remote client."""
226         if self._conn:
227             self._conn.close()
228             self._conn = None
229 
230     def _client_send(self, msg):
231         """Sends an Rpc message through the connection.
232 
233         Args:
234             msg: string, the message to send.
235 
236         Raises:
237             Error: a socket error occurred during the send.
238         """
239         try:
240             self._client.write(msg.encode("utf8") + b'\n')
241             self._client.flush()
242         except socket.error as e:
243             raise Error(
244                 self._ad,
245                 'Encountered socket error "%s" sending RPC message "%s"' %
246                 (e, msg))
247 
248     def _client_receive(self):
249         """Receives the server's response of an Rpc message.
250 
251         Returns:
252             Raw byte string of the response.
253 
254         Raises:
255             Error: a socket error occurred during the read.
256         """
257         try:
258             return self._client.readline()
259         except socket.error as e:
260             raise Error(
261                 self._ad,
262                 'Encountered socket error reading RPC response "%s"' % e)
263 
264     def _cmd(self, command, uid=None):
265         """Send a command to the server.
266 
267         Args:
268             command: str, The name of the command to execute.
269             uid: int, the uid of the session to send the command to.
270 
271         Returns:
272             The line that was written back.
273         """
274         if not uid:
275             uid = self.uid
276         self._client_send(json.dumps({'cmd': command, 'uid': uid}))
277         return self._client_receive()
278 
279     def _rpc(self, method, *args):
280         """Sends an rpc to the app.
281 
282         Args:
283             method: str, The name of the method to execute.
284             args: any, The args of the method.
285 
286         Returns:
287             The result of the rpc.
288 
289         Raises:
290             ProtocolError: Something went wrong with the protocol.
291             ApiError: The rpc went through, however executed with errors.
292         """
293         with self._lock:
294             apiid = next(self._counter)
295             data = {'id': apiid, 'method': method, 'params': args}
296             request = json.dumps(data)
297             self._client_send(request)
298             response = self._client_receive()
299         if not response:
300             raise ProtocolError(self._ad,
301                                 ProtocolError.NO_RESPONSE_FROM_SERVER)
302         result = json.loads(str(response, encoding="utf8"))
303         if result['error']:
304             raise ApiError(self._ad, result['error'])
305         if result['id'] != apiid:
306             raise ProtocolError(self._ad, ProtocolError.MISMATCHED_API_ID)
307         if result.get('callback') is not None:
308             if self._event_client is None:
309                 self._event_client = self._start_event_client()
310             return callback_handler.CallbackHandler(
311                 callback_id=result['callback'],
312                 event_client=self._event_client,
313                 ret_value=result['result'],
314                 method_name=method,
315                 ad=self._ad)
316         return result['result']
317 
318     def disable_hidden_api_blacklist(self):
319         """If necessary and possible, disables hidden api blacklist."""
320         version_codename = self._ad.adb.getprop('ro.build.version.codename')
321         sdk_version = int(self._ad.adb.getprop('ro.build.version.sdk'))
322         # we check version_codename in addition to sdk_version because P builds
323         # in development report sdk_version 27, but still enforce the blacklist.
324         if self._ad.is_rootable and (sdk_version >= 28
325                                      or version_codename == 'P'):
326             self._ad.adb.shell(
327                 'settings put global hidden_api_blacklist_exemptions "*"')
328 
329     def __getattr__(self, name):
330         """Wrapper for python magic to turn method calls into RPC calls."""
331 
332         def rpc_call(*args):
333             return self._rpc(name, *args)
334 
335         return rpc_call
336 
337     def _id_counter(self):
338         i = 0
339         while True:
340             yield i
341             i += 1
342
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mobly/controllers/android_device_lib/jsonrpc_client_base.py b/mobly/controllers/android_device_lib/jsonrpc_client_base.py
--- a/mobly/controllers/android_device_lib/jsonrpc_client_base.py
+++ b/mobly/controllers/android_device_lib/jsonrpc_client_base.py
@@ -19,7 +19,7 @@
 
     Request:
     {
-        "id": <monotonically increasing integer containing the ID of 
+        "id": <monotonically increasing integer containing the ID of
                this request>
         "method": <string containing the name of the method to execute>
         "params": <JSON array containing the arguments to the method>
@@ -239,6 +239,7 @@
         try:
             self._client.write(msg.encode("utf8") + b'\n')
             self._client.flush()
+            self.log.debug('Snippet sent %s.', msg)
         except socket.error as e:
             raise Error(
                 self._ad,
@@ -255,7 +256,9 @@
             Error: a socket error occurred during the read.
         """
         try:
-            return self._client.readline()
+            response = self._client.readline()
+            self.log.debug('Snippet received: %s', response)
+            return response
         except socket.error as e:
             raise Error(
                 self._ad,
@@ -299,7 +302,7 @@
         if not response:
             raise ProtocolError(self._ad,
                                 ProtocolError.NO_RESPONSE_FROM_SERVER)
-        result = json.loads(str(response, encoding="utf8"))
+        result = json.loads(str(response, encoding='utf8'))
         if result['error']:
             raise ApiError(self._ad, result['error'])
         if result['id'] != apiid:

{"golden_diff": "diff --git a/mobly/controllers/android_device_lib/jsonrpc_client_base.py b/mobly/controllers/android_device_lib/jsonrpc_client_base.py\n--- a/mobly/controllers/android_device_lib/jsonrpc_client_base.py\n+++ b/mobly/controllers/android_device_lib/jsonrpc_client_base.py\n@@ -19,7 +19,7 @@\n \n     Request:\n     {\n-        \"id\": <monotonically increasing integer containing the ID of \n+        \"id\": <monotonically increasing integer containing the ID of\n                this request>\n         \"method\": <string containing the name of the method to execute>\n         \"params\": <JSON array containing the arguments to the method>\n@@ -239,6 +239,7 @@\n         try:\n             self._client.write(msg.encode(\"utf8\") + b'\\n')\n             self._client.flush()\n+            self.log.debug('Snippet sent %s.', msg)\n         except socket.error as e:\n             raise Error(\n                 self._ad,\n@@ -255,7 +256,9 @@\n             Error: a socket error occurred during the read.\n         \"\"\"\n         try:\n-            return self._client.readline()\n+            response = self._client.readline()\n+            self.log.debug('Snippet received: %s', response)\n+            return response\n         except socket.error as e:\n             raise Error(\n                 self._ad,\n@@ -299,7 +302,7 @@\n         if not response:\n             raise ProtocolError(self._ad,\n                                 ProtocolError.NO_RESPONSE_FROM_SERVER)\n-        result = json.loads(str(response, encoding=\"utf8\"))\n+        result = json.loads(str(response, encoding='utf8'))\n         if result['error']:\n             raise ApiError(self._ad, result['error'])\n         if result['id'] != apiid:\n", "issue": "Log snippet client calls at debug level\nRight now there is no log for calls made to snippet client side, which makes it difficult to debug should a snippet client call hangs.\r\n\r\nOn debug level, we should log messages sent and received by the client, same way we log messages received and sent by snippet server on devices.\n", "before_files": [{"content": "# Copyright 2016 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Base class for clients that communicate with apps over a JSON RPC interface.\n\nThe JSON protocol expected by this module is:\n\n.. code-block:: json\n\n    Request:\n    {\n        \"id\": <monotonically increasing integer containing the ID of \n               this request>\n        \"method\": <string containing the name of the method to execute>\n        \"params\": <JSON array containing the arguments to the method>\n    }\n\n    Response:\n    {\n        \"id\": <int id of request that this response maps to>,\n        \"result\": <Arbitrary JSON object containing the result of\n                   executing the method. If the method could not be\n                   executed or returned void, contains 'null'.>,\n        \"error\": <String containing the error thrown by executing the\n                  method. If no error occurred, contains 'null'.>\n        \"callback\": <String that represents a callback ID used to\n                     identify events associated with a particular\n                     CallbackHandler object.>\n    }\n\"\"\"\n\nfrom builtins import str\n\nimport json\nimport socket\nimport threading\n\nfrom mobly.controllers.android_device_lib import callback_handler\nfrom mobly.controllers.android_device_lib import errors\n\n# UID of the 'unknown' jsonrpc session. Will cause creation of a new session.\nUNKNOWN_UID = -1\n\n# Maximum time to wait for the socket to open on the device.\n_SOCKET_CONNECTION_TIMEOUT = 60\n\n# Maximum time to wait for a response message on the socket.\n_SOCKET_READ_TIMEOUT = callback_handler.MAX_TIMEOUT\n\n\nclass Error(errors.DeviceError):\n    pass\n\n\nclass AppStartError(Error):\n    \"\"\"Raised when the app is not able to be started.\"\"\"\n\n\nclass AppRestoreConnectionError(Error):\n    \"\"\"Raised when failed to restore app from disconnection.\"\"\"\n\n\nclass ApiError(Error):\n    \"\"\"Raised when remote API reports an error.\"\"\"\n\n\nclass ProtocolError(Error):\n    \"\"\"Raised when there is some error in exchanging data with server.\"\"\"\n    NO_RESPONSE_FROM_HANDSHAKE = 'No response from handshake.'\n    NO_RESPONSE_FROM_SERVER = 'No response from server.'\n    MISMATCHED_API_ID = 'Mismatched API id.'\n\n\nclass JsonRpcCommand(object):\n    \"\"\"Commands that can be invoked on all jsonrpc clients.\n\n    INIT: Initializes a new session.\n    CONTINUE: Creates a connection.\n    \"\"\"\n    INIT = 'initiate'\n    CONTINUE = 'continue'\n\n\nclass JsonRpcClientBase(object):\n    \"\"\"Base class for jsonrpc clients that connect to remote servers.\n\n    Connects to a remote device running a jsonrpc-compatible app. Before opening\n    a connection a port forward must be setup to go over usb. This be done using\n    adb.forward([local, remote]). Once the port has been forwarded it can be\n    used in this object as the port of communication.\n\n    Attributes:\n        host_port: (int) The host port of this RPC client.\n        device_port: (int) The device port of this RPC client.\n        app_name: (str) The user-visible name of the app being communicated\n                  with.\n        uid: (int) The uid of this session.\n    \"\"\"\n\n    def __init__(self, app_name, ad):\n        \"\"\"\n        Args:\n            app_name: (str) The user-visible name of the app being communicated\n                with.\n            ad: (AndroidDevice) The device object associated with a client.\n        \"\"\"\n        self.host_port = None\n        self.device_port = None\n        self.app_name = app_name\n        self._ad = ad\n        self.log = self._ad.log\n        self.uid = None\n        self._client = None  # prevent close errors on connect failure\n        self._conn = None\n        self._counter = None\n        self._lock = threading.Lock()\n        self._event_client = None\n\n    def __del__(self):\n        self.disconnect()\n\n    # Methods to be implemented by subclasses.\n\n    def start_app_and_connect(self):\n        \"\"\"Starts the server app on the android device and connects to it.\n\n        After this, the self.host_port and self.device_port attributes must be\n        set.\n\n        Must be implemented by subclasses.\n\n        Raises:\n            AppStartError: When the app was not able to be started.\n        \"\"\"\n        raise NotImplementedError()\n\n    def stop_app(self):\n        \"\"\"Kills any running instance of the app.\n\n        Must be implemented by subclasses.\n        \"\"\"\n        raise NotImplementedError()\n\n    def restore_app_connection(self, port=None):\n        \"\"\"Reconnects to the app after device USB was disconnected.\n\n        Instead of creating new instance of the client:\n          - Uses the given port (or finds a new available host_port if none is\n            given).\n          - Tries to connect to remote server with selected port.\n\n        Must be implemented by subclasses.\n\n        Args:\n          port: If given, this is the host port from which to connect to remote\n              device port. If not provided, find a new available port as host\n              port.\n\n        Raises:\n            AppRestoreConnectionError: When the app was not able to be\n            reconnected.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _start_event_client(self):\n        \"\"\"Starts a separate JsonRpc client to the same session for propagating\n        events.\n\n        This is an optional function that should only implement if the client\n        utilizes the snippet event mechanism.\n\n        Returns:\n            A JsonRpc Client object that connects to the same session as the\n            one on which this function is called.\n        \"\"\"\n        raise NotImplementedError()\n\n    # Rest of the client methods.\n\n    def connect(self, uid=UNKNOWN_UID, cmd=JsonRpcCommand.INIT):\n        \"\"\"Opens a connection to a JSON RPC server.\n\n        Opens a connection to a remote client. The connection attempt will time\n        out if it takes longer than _SOCKET_CONNECTION_TIMEOUT seconds. Each\n        subsequent operation over this socket will time out after\n        _SOCKET_READ_TIMEOUT seconds as well.\n\n        Args:\n            uid: int, The uid of the session to join, or UNKNOWN_UID to start a\n                new session.\n            cmd: JsonRpcCommand, The command to use for creating the connection.\n\n        Raises:\n            IOError: Raised when the socket times out from io error\n            socket.timeout: Raised when the socket waits to long for connection.\n            ProtocolError: Raised when there is an error in the protocol.\n        \"\"\"\n        self._counter = self._id_counter()\n        self._conn = socket.create_connection(('localhost', self.host_port),\n                                              _SOCKET_CONNECTION_TIMEOUT)\n        self._conn.settimeout(_SOCKET_READ_TIMEOUT)\n        self._client = self._conn.makefile(mode='brw')\n\n        resp = self._cmd(cmd, uid)\n        if not resp:\n            raise ProtocolError(self._ad,\n                                ProtocolError.NO_RESPONSE_FROM_HANDSHAKE)\n        result = json.loads(str(resp, encoding='utf8'))\n        if result['status']:\n            self.uid = result['uid']\n        else:\n            self.uid = UNKNOWN_UID\n\n    def disconnect(self):\n        \"\"\"Close the connection to the remote client.\"\"\"\n        if self._conn:\n            self._conn.close()\n            self._conn = None\n\n    def _client_send(self, msg):\n        \"\"\"Sends an Rpc message through the connection.\n\n        Args:\n            msg: string, the message to send.\n\n        Raises:\n            Error: a socket error occurred during the send.\n        \"\"\"\n        try:\n            self._client.write(msg.encode(\"utf8\") + b'\\n')\n            self._client.flush()\n        except socket.error as e:\n            raise Error(\n                self._ad,\n                'Encountered socket error \"%s\" sending RPC message \"%s\"' %\n                (e, msg))\n\n    def _client_receive(self):\n        \"\"\"Receives the server's response of an Rpc message.\n\n        Returns:\n            Raw byte string of the response.\n\n        Raises:\n            Error: a socket error occurred during the read.\n        \"\"\"\n        try:\n            return self._client.readline()\n        except socket.error as e:\n            raise Error(\n                self._ad,\n                'Encountered socket error reading RPC response \"%s\"' % e)\n\n    def _cmd(self, command, uid=None):\n        \"\"\"Send a command to the server.\n\n        Args:\n            command: str, The name of the command to execute.\n            uid: int, the uid of the session to send the command to.\n\n        Returns:\n            The line that was written back.\n        \"\"\"\n        if not uid:\n            uid = self.uid\n        self._client_send(json.dumps({'cmd': command, 'uid': uid}))\n        return self._client_receive()\n\n    def _rpc(self, method, *args):\n        \"\"\"Sends an rpc to the app.\n\n        Args:\n            method: str, The name of the method to execute.\n            args: any, The args of the method.\n\n        Returns:\n            The result of the rpc.\n\n        Raises:\n            ProtocolError: Something went wrong with the protocol.\n            ApiError: The rpc went through, however executed with errors.\n        \"\"\"\n        with self._lock:\n            apiid = next(self._counter)\n            data = {'id': apiid, 'method': method, 'params': args}\n            request = json.dumps(data)\n            self._client_send(request)\n            response = self._client_receive()\n        if not response:\n            raise ProtocolError(self._ad,\n                                ProtocolError.NO_RESPONSE_FROM_SERVER)\n        result = json.loads(str(response, encoding=\"utf8\"))\n        if result['error']:\n            raise ApiError(self._ad, result['error'])\n        if result['id'] != apiid:\n            raise ProtocolError(self._ad, ProtocolError.MISMATCHED_API_ID)\n        if result.get('callback') is not None:\n            if self._event_client is None:\n                self._event_client = self._start_event_client()\n            return callback_handler.CallbackHandler(\n                callback_id=result['callback'],\n                event_client=self._event_client,\n                ret_value=result['result'],\n                method_name=method,\n                ad=self._ad)\n        return result['result']\n\n    def disable_hidden_api_blacklist(self):\n        \"\"\"If necessary and possible, disables hidden api blacklist.\"\"\"\n        version_codename = self._ad.adb.getprop('ro.build.version.codename')\n        sdk_version = int(self._ad.adb.getprop('ro.build.version.sdk'))\n        # we check version_codename in addition to sdk_version because P builds\n        # in development report sdk_version 27, but still enforce the blacklist.\n        if self._ad.is_rootable and (sdk_version >= 28\n                                     or version_codename == 'P'):\n            self._ad.adb.shell(\n                'settings put global hidden_api_blacklist_exemptions \"*\"')\n\n    def __getattr__(self, name):\n        \"\"\"Wrapper for python magic to turn method calls into RPC calls.\"\"\"\n\n        def rpc_call(*args):\n            return self._rpc(name, *args)\n\n        return rpc_call\n\n    def _id_counter(self):\n        i = 0\n        while True:\n            yield i\n            i += 1\n", "path": "mobly/controllers/android_device_lib/jsonrpc_client_base.py"}], "after_files": [{"content": "# Copyright 2016 Google Inc.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\"\"\"Base class for clients that communicate with apps over a JSON RPC interface.\n\nThe JSON protocol expected by this module is:\n\n.. code-block:: json\n\n    Request:\n    {\n        \"id\": <monotonically increasing integer containing the ID of\n               this request>\n        \"method\": <string containing the name of the method to execute>\n        \"params\": <JSON array containing the arguments to the method>\n    }\n\n    Response:\n    {\n        \"id\": <int id of request that this response maps to>,\n        \"result\": <Arbitrary JSON object containing the result of\n                   executing the method. If the method could not be\n                   executed or returned void, contains 'null'.>,\n        \"error\": <String containing the error thrown by executing the\n                  method. If no error occurred, contains 'null'.>\n        \"callback\": <String that represents a callback ID used to\n                     identify events associated with a particular\n                     CallbackHandler object.>\n    }\n\"\"\"\n\nfrom builtins import str\n\nimport json\nimport socket\nimport threading\n\nfrom mobly.controllers.android_device_lib import callback_handler\nfrom mobly.controllers.android_device_lib import errors\n\n# UID of the 'unknown' jsonrpc session. Will cause creation of a new session.\nUNKNOWN_UID = -1\n\n# Maximum time to wait for the socket to open on the device.\n_SOCKET_CONNECTION_TIMEOUT = 60\n\n# Maximum time to wait for a response message on the socket.\n_SOCKET_READ_TIMEOUT = callback_handler.MAX_TIMEOUT\n\n\nclass Error(errors.DeviceError):\n    pass\n\n\nclass AppStartError(Error):\n    \"\"\"Raised when the app is not able to be started.\"\"\"\n\n\nclass AppRestoreConnectionError(Error):\n    \"\"\"Raised when failed to restore app from disconnection.\"\"\"\n\n\nclass ApiError(Error):\n    \"\"\"Raised when remote API reports an error.\"\"\"\n\n\nclass ProtocolError(Error):\n    \"\"\"Raised when there is some error in exchanging data with server.\"\"\"\n    NO_RESPONSE_FROM_HANDSHAKE = 'No response from handshake.'\n    NO_RESPONSE_FROM_SERVER = 'No response from server.'\n    MISMATCHED_API_ID = 'Mismatched API id.'\n\n\nclass JsonRpcCommand(object):\n    \"\"\"Commands that can be invoked on all jsonrpc clients.\n\n    INIT: Initializes a new session.\n    CONTINUE: Creates a connection.\n    \"\"\"\n    INIT = 'initiate'\n    CONTINUE = 'continue'\n\n\nclass JsonRpcClientBase(object):\n    \"\"\"Base class for jsonrpc clients that connect to remote servers.\n\n    Connects to a remote device running a jsonrpc-compatible app. Before opening\n    a connection a port forward must be setup to go over usb. This be done using\n    adb.forward([local, remote]). Once the port has been forwarded it can be\n    used in this object as the port of communication.\n\n    Attributes:\n        host_port: (int) The host port of this RPC client.\n        device_port: (int) The device port of this RPC client.\n        app_name: (str) The user-visible name of the app being communicated\n                  with.\n        uid: (int) The uid of this session.\n    \"\"\"\n\n    def __init__(self, app_name, ad):\n        \"\"\"\n        Args:\n            app_name: (str) The user-visible name of the app being communicated\n                with.\n            ad: (AndroidDevice) The device object associated with a client.\n        \"\"\"\n        self.host_port = None\n        self.device_port = None\n        self.app_name = app_name\n        self._ad = ad\n        self.log = self._ad.log\n        self.uid = None\n        self._client = None  # prevent close errors on connect failure\n        self._conn = None\n        self._counter = None\n        self._lock = threading.Lock()\n        self._event_client = None\n\n    def __del__(self):\n        self.disconnect()\n\n    # Methods to be implemented by subclasses.\n\n    def start_app_and_connect(self):\n        \"\"\"Starts the server app on the android device and connects to it.\n\n        After this, the self.host_port and self.device_port attributes must be\n        set.\n\n        Must be implemented by subclasses.\n\n        Raises:\n            AppStartError: When the app was not able to be started.\n        \"\"\"\n        raise NotImplementedError()\n\n    def stop_app(self):\n        \"\"\"Kills any running instance of the app.\n\n        Must be implemented by subclasses.\n        \"\"\"\n        raise NotImplementedError()\n\n    def restore_app_connection(self, port=None):\n        \"\"\"Reconnects to the app after device USB was disconnected.\n\n        Instead of creating new instance of the client:\n          - Uses the given port (or finds a new available host_port if none is\n            given).\n          - Tries to connect to remote server with selected port.\n\n        Must be implemented by subclasses.\n\n        Args:\n          port: If given, this is the host port from which to connect to remote\n              device port. If not provided, find a new available port as host\n              port.\n\n        Raises:\n            AppRestoreConnectionError: When the app was not able to be\n            reconnected.\n        \"\"\"\n        raise NotImplementedError()\n\n    def _start_event_client(self):\n        \"\"\"Starts a separate JsonRpc client to the same session for propagating\n        events.\n\n        This is an optional function that should only implement if the client\n        utilizes the snippet event mechanism.\n\n        Returns:\n            A JsonRpc Client object that connects to the same session as the\n            one on which this function is called.\n        \"\"\"\n        raise NotImplementedError()\n\n    # Rest of the client methods.\n\n    def connect(self, uid=UNKNOWN_UID, cmd=JsonRpcCommand.INIT):\n        \"\"\"Opens a connection to a JSON RPC server.\n\n        Opens a connection to a remote client. The connection attempt will time\n        out if it takes longer than _SOCKET_CONNECTION_TIMEOUT seconds. Each\n        subsequent operation over this socket will time out after\n        _SOCKET_READ_TIMEOUT seconds as well.\n\n        Args:\n            uid: int, The uid of the session to join, or UNKNOWN_UID to start a\n                new session.\n            cmd: JsonRpcCommand, The command to use for creating the connection.\n\n        Raises:\n            IOError: Raised when the socket times out from io error\n            socket.timeout: Raised when the socket waits to long for connection.\n            ProtocolError: Raised when there is an error in the protocol.\n        \"\"\"\n        self._counter = self._id_counter()\n        self._conn = socket.create_connection(('localhost', self.host_port),\n                                              _SOCKET_CONNECTION_TIMEOUT)\n        self._conn.settimeout(_SOCKET_READ_TIMEOUT)\n        self._client = self._conn.makefile(mode='brw')\n\n        resp = self._cmd(cmd, uid)\n        if not resp:\n            raise ProtocolError(self._ad,\n                                ProtocolError.NO_RESPONSE_FROM_HANDSHAKE)\n        result = json.loads(str(resp, encoding='utf8'))\n        if result['status']:\n            self.uid = result['uid']\n        else:\n            self.uid = UNKNOWN_UID\n\n    def disconnect(self):\n        \"\"\"Close the connection to the remote client.\"\"\"\n        if self._conn:\n            self._conn.close()\n            self._conn = None\n\n    def _client_send(self, msg):\n        \"\"\"Sends an Rpc message through the connection.\n\n        Args:\n            msg: string, the message to send.\n\n        Raises:\n            Error: a socket error occurred during the send.\n        \"\"\"\n        try:\n            self._client.write(msg.encode(\"utf8\") + b'\\n')\n            self._client.flush()\n            self.log.debug('Snippet sent %s.', msg)\n        except socket.error as e:\n            raise Error(\n                self._ad,\n                'Encountered socket error \"%s\" sending RPC message \"%s\"' %\n                (e, msg))\n\n    def _client_receive(self):\n        \"\"\"Receives the server's response of an Rpc message.\n\n        Returns:\n            Raw byte string of the response.\n\n        Raises:\n            Error: a socket error occurred during the read.\n        \"\"\"\n        try:\n            response = self._client.readline()\n            self.log.debug('Snippet received: %s', response)\n            return response\n        except socket.error as e:\n            raise Error(\n                self._ad,\n                'Encountered socket error reading RPC response \"%s\"' % e)\n\n    def _cmd(self, command, uid=None):\n        \"\"\"Send a command to the server.\n\n        Args:\n            command: str, The name of the command to execute.\n            uid: int, the uid of the session to send the command to.\n\n        Returns:\n            The line that was written back.\n        \"\"\"\n        if not uid:\n            uid = self.uid\n        self._client_send(json.dumps({'cmd': command, 'uid': uid}))\n        return self._client_receive()\n\n    def _rpc(self, method, *args):\n        \"\"\"Sends an rpc to the app.\n\n        Args:\n            method: str, The name of the method to execute.\n            args: any, The args of the method.\n\n        Returns:\n            The result of the rpc.\n\n        Raises:\n            ProtocolError: Something went wrong with the protocol.\n            ApiError: The rpc went through, however executed with errors.\n        \"\"\"\n        with self._lock:\n            apiid = next(self._counter)\n            data = {'id': apiid, 'method': method, 'params': args}\n            request = json.dumps(data)\n            self._client_send(request)\n            response = self._client_receive()\n        if not response:\n            raise ProtocolError(self._ad,\n                                ProtocolError.NO_RESPONSE_FROM_SERVER)\n        result = json.loads(str(response, encoding='utf8'))\n        if result['error']:\n            raise ApiError(self._ad, result['error'])\n        if result['id'] != apiid:\n            raise ProtocolError(self._ad, ProtocolError.MISMATCHED_API_ID)\n        if result.get('callback') is not None:\n            if self._event_client is None:\n                self._event_client = self._start_event_client()\n            return callback_handler.CallbackHandler(\n                callback_id=result['callback'],\n                event_client=self._event_client,\n                ret_value=result['result'],\n                method_name=method,\n                ad=self._ad)\n        return result['result']\n\n    def disable_hidden_api_blacklist(self):\n        \"\"\"If necessary and possible, disables hidden api blacklist.\"\"\"\n        version_codename = self._ad.adb.getprop('ro.build.version.codename')\n        sdk_version = int(self._ad.adb.getprop('ro.build.version.sdk'))\n        # we check version_codename in addition to sdk_version because P builds\n        # in development report sdk_version 27, but still enforce the blacklist.\n        if self._ad.is_rootable and (sdk_version >= 28\n                                     or version_codename == 'P'):\n            self._ad.adb.shell(\n                'settings put global hidden_api_blacklist_exemptions \"*\"')\n\n    def __getattr__(self, name):\n        \"\"\"Wrapper for python magic to turn method calls into RPC calls.\"\"\"\n\n        def rpc_call(*args):\n            return self._rpc(name, *args)\n\n        return rpc_call\n\n    def _id_counter(self):\n        i = 0\n        while True:\n            yield i\n            i += 1\n", "path": "mobly/controllers/android_device_lib/jsonrpc_client_base.py"}]}