20
20
import grpc
21
21
import grpc .aio
22
22
import logging
23
- import os
24
23
import portpicker
25
24
import re
26
25
import shlex
26
+ import subprocess
27
27
import threading
28
28
import types
29
29
@@ -118,6 +118,7 @@ class AndroidPandoraServer(PandoraServer[AndroidDevice]):
118
118
_port : int
119
119
_logger : logging .Logger
120
120
_handler : logging .Handler
121
+ _adb_shell : subprocess .Popen [bytes ]
121
122
122
123
def start (self ) -> PandoraClient :
123
124
"""Sets up and starts the Pandora server on the Android device."""
@@ -140,27 +141,36 @@ def start(self) -> PandoraClient:
140
141
141
142
# Forward all logging to ADB logs
142
143
adb = self .device .adb
144
+ self ._adb_shell = subprocess .Popen (['adb' , '-s' , adb .serial , 'shell' ], stdin = subprocess .PIPE )
145
+
146
+ # This regex match all ANSI escape sequences (colors, style, ..).
147
+ ansi_escape = re .compile (r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])' )
143
148
144
149
class AdbLoggingHandler (logging .Handler ):
150
+ LOGGING_TO_ANDROID_LEVELS = {
151
+ logging .FATAL : 'f' ,
152
+ logging .ERROR : 'e' ,
153
+ logging .WARN : 'w' ,
154
+ logging .INFO : 'i' ,
155
+ logging .DEBUG : 'd' ,
156
+ logging .NOTSET : 'd' ,
157
+ }
158
+
159
+ def __init__ (self , adb_shell : subprocess .Popen [bytes ]) -> None :
160
+ self .adb_shell = adb_shell
161
+
145
162
def emit (self , record : logging .LogRecord ) -> None :
146
163
if record .levelno <= logging .DEBUG :
147
164
return
148
- ansi_escape = re .compile (r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])' )
149
- msg = self .format (record )
150
- msg = ansi_escape .sub ('' , msg )
151
- level = {
152
- logging .FATAL : 'f' ,
153
- logging .ERROR : 'e' ,
154
- logging .WARN : 'w' ,
155
- logging .INFO : 'i' ,
156
- logging .DEBUG : 'd' ,
157
- logging .NOTSET : 'd' ,
158
- }[record .levelno ]
159
- for msg in msg .splitlines ():
160
- os .system (f'adb -s { adb .serial } shell "log -t Avatar -p { level } { shlex .quote (msg )} "' )
165
+ # Format and remove all ANSI escape sequences.
166
+ msg = ansi_escape .sub ('' , self .format (record ))
167
+ level = AdbLoggingHandler .LOGGING_TO_ANDROID_LEVELS [record .levelno ]
168
+ assert self .adb_shell .stdin
169
+ self .adb_shell .stdin .write (f'log -t Avatar -p { level } { shlex .quote (msg )} \n ' .encode ('utf-8' ))
170
+ self .adb_shell .stdin .flush ()
161
171
162
172
self ._logger = logging .getLogger ()
163
- self ._handler = AdbLoggingHandler ()
173
+ self ._handler = AdbLoggingHandler (self . _adb_shell )
164
174
self ._logger .addHandler (self ._handler )
165
175
166
176
return PandoraClient (f'localhost:{ self ._port } ' , 'android' )
@@ -176,6 +186,9 @@ def stop(self) -> None:
176
186
177
187
# Remove ADB logging handler
178
188
self ._logger .removeHandler (self ._handler )
189
+ assert self ._adb_shell .stdin
190
+ self ._adb_shell .stdin .close ()
191
+ self ._adb_shell .wait ()
179
192
180
193
self .device .adb .forward (['--remove' , f'tcp:{ self ._port } ' ]) # type: ignore
181
194
self ._instrumentation .join ()
0 commit comments