2020import grpc
2121import grpc .aio
2222import logging
23- import os
2423import portpicker
2524import re
2625import shlex
26+ import subprocess
2727import threading
2828import types
2929
@@ -118,6 +118,7 @@ class AndroidPandoraServer(PandoraServer[AndroidDevice]):
118118 _port : int
119119 _logger : logging .Logger
120120 _handler : logging .Handler
121+ _adb_shell : subprocess .Popen [bytes ]
121122
122123 def start (self ) -> PandoraClient :
123124 """Sets up and starts the Pandora server on the Android device."""
@@ -140,27 +141,36 @@ def start(self) -> PandoraClient:
140141
141142 # Forward all logging to ADB logs
142143 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-?]*[ -/]*[@-~])' )
143148
144149 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+
145162 def emit (self , record : logging .LogRecord ) -> None :
146163 if record .levelno <= logging .DEBUG :
147164 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 ()
161171
162172 self ._logger = logging .getLogger ()
163- self ._handler = AdbLoggingHandler ()
173+ self ._handler = AdbLoggingHandler (self . _adb_shell )
164174 self ._logger .addHandler (self ._handler )
165175
166176 return PandoraClient (f'localhost:{ self ._port } ' , 'android' )
@@ -176,6 +186,9 @@ def stop(self) -> None:
176186
177187 # Remove ADB logging handler
178188 self ._logger .removeHandler (self ._handler )
189+ assert self ._adb_shell .stdin
190+ self ._adb_shell .stdin .close ()
191+ self ._adb_shell .wait ()
179192
180193 self .device .adb .forward (['--remove' , f'tcp:{ self ._port } ' ]) # type: ignore
181194 self ._instrumentation .join ()
0 commit comments