2525import io
2626import os
2727import socket
28+ import posixpath
2829
2930from adb import adb_protocol
3031from adb import common
@@ -54,52 +55,127 @@ class AdbCommands(object):
5455 protocol_handler = adb_protocol .AdbMessage
5556 filesync_handler = filesync_protocol .FilesyncProtocol
5657
57- @classmethod
58- def ConnectDevice (
59- cls , port_path = None , serial = None , default_timeout_ms = None , ** kwargs ):
60- """Convenience function to get an adb device from usb path or serial.
58+ def __init__ (self ):
59+
60+ self .__reset ()
61+
62+ def __reset (self ):
63+ self .build_props = None
64+ self ._handle = None
65+ self ._device_state = None
66+
67+ # Connection table tracks each open AdbConnection objects per service type for program functions
68+ # that choose to persist an AdbConnection object for their functionality, using
69+ # self._get_service_connection
70+ self ._service_connections = {}
71+
72+ def _get_service_connection (self , service , service_command = None , create = True , timeout_ms = None ):
73+ """
74+ Based on the service, get the AdbConnection for that service or create one if it doesnt exist
75+
76+ :param service:
77+ :param service_command: Additional service parameters to append
78+ :param create: If False, dont create a connection if it does not exist
79+ :return:
80+ """
81+
82+ connection = self ._service_connections .get (service , None )
83+
84+ if connection :
85+ return connection
86+
87+ if not connection and not create :
88+ return None
89+
90+ if service_command :
91+ destination_str = b'%s:%s' % (service , service_command )
92+ else :
93+ destination_str = service
94+
95+ connection = self .protocol_handler .Open (
96+ self ._handle , destination = destination_str , timeout_ms = timeout_ms )
97+
98+ self ._service_connections .update ({service : connection })
99+
100+ return connection
101+
102+ def ConnectDevice (self , port_path = None , serial = None , default_timeout_ms = None , ** kwargs ):
103+ """Convenience function to setup a transport handle for the adb device from
104+ usb path or serial then connect to it.
61105
62106 Args:
63107 port_path: The filename of usb port to use.
64108 serial: The serial number of the device to use.
65109 default_timeout_ms: The default timeout in milliseconds to use.
110+ kwargs: handle: Device handle to use (instance of common.TcpHandle or common.UsbHandle)
111+ banner: Connection banner to pass to the remote device
112+ rsa_keys: List of AuthSigner subclass instances to be used for
113+ authentication. The device can either accept one of these via the Sign
114+ method, or we will send the result of GetPublicKey from the first one
115+ if the device doesn't accept any of them.
116+ auth_timeout_ms: Timeout to wait for when sending a new public key. This
117+ is only relevant when we send a new public key. The device shows a
118+ dialog and this timeout is how long to wait for that dialog. If used
119+ in automation, this should be low to catch such a case as a failure
120+ quickly; while in interactive settings it should be high to allow
121+ users to accept the dialog. We default to automation here, so it's low
122+ by default.
66123
67124 If serial specifies a TCP address:port, then a TCP connection is
68125 used instead of a USB connection.
69126 """
70- if serial and b':' in serial :
71- handle = common .TcpHandle (serial , timeout_ms = default_timeout_ms )
127+
128+ # If there isnt a handle override (used by tests), build one here
129+ if 'handle' in kwargs :
130+ self ._handle = kwargs .pop ('handle' )
131+ elif serial and b':' in serial :
132+ self ._handle = common .TcpHandle (serial , timeout_ms = default_timeout_ms )
72133 else :
73- handle = common .UsbHandle .FindAndOpen (
134+ self . _handle = common .UsbHandle .FindAndOpen (
74135 DeviceIsAvailable , port_path = port_path , serial = serial ,
75136 timeout_ms = default_timeout_ms )
76- return cls .Connect (handle , ** kwargs )
77137
78- def __init__ ( self , handle , device_state ):
79- self . handle = handle
80- self . _device_state = device_state
138+ self . _Connect ( ** kwargs )
139+
140+ return self
81141
82142 def Close (self ):
83- self .handle .Close ()
143+ for conn in list (self ._service_connections .values ()):
144+ if conn :
145+ try :
146+ conn .Close ()
147+ except :
148+ pass
84149
85- @classmethod
86- def Connect (cls , usb , banner = None , ** kwargs ):
150+ if self ._handle :
151+ self ._handle .Close ()
152+
153+ self .__reset ()
154+
155+ def _Connect (self , banner = None , ** kwargs ):
87156 """Connect to the device.
88157
89158 Args:
90- usb: UsbHandle or TcpHandle instance to use.
91159 banner: See protocol_handler.Connect.
92- **kwargs: See protocol_handler.Connect for kwargs. Includes rsa_keys,
93- and auth_timeout_ms.
160+ **kwargs: See protocol_handler.Connect and adb_commands.ConnectDevice for kwargs.
161+ Includes handle, rsa_keys, and auth_timeout_ms.
94162 Returns:
95163 An instance of this class if the device connected successfully.
96164 """
165+
97166 if not banner :
98167 banner = socket .gethostname ().encode ()
99- device_state = cls .protocol_handler .Connect (usb , banner = banner , ** kwargs )
168+
169+ conn_str = self .protocol_handler .Connect (self ._handle , banner = banner , ** kwargs )
170+
100171 # Remove banner and colons after device state (state::banner)
101- device_state = device_state .split (b':' )[0 ]
102- return cls (usb , device_state )
172+ parts = conn_str .split (b'::' )
173+ self ._device_state = parts [0 ]
174+
175+ # Break out the build prop info
176+ self .build_props = str (parts [1 ].split (b';' ))
177+
178+ return True
103179
104180 @classmethod
105181 def Devices (cls ):
@@ -129,13 +205,13 @@ def Install(self, apk_path, destination_dir='', timeout_ms=None, replace_existin
129205 if not destination_dir :
130206 destination_dir = '/data/local/tmp/'
131207 basename = os .path .basename (apk_path )
132- destination_path = destination_dir + basename
208+ destination_path = posixpath . join ( destination_dir , basename )
133209 self .Push (apk_path , destination_path , timeout_ms = timeout_ms , progress_callback = transfer_progress_callback )
134210
135211 cmd = ['pm install' ]
136212 if replace_existing :
137213 cmd .append ('-r' )
138- cmd .append ('"%s"' % destination_path )
214+ cmd .append ('"{}"' . format ( destination_path ) )
139215 return self .Shell (' ' .join (cmd ), timeout_ms = timeout_ms )
140216
141217 def Uninstall (self , package_name , keep_data = False , timeout_ms = None ):
@@ -167,50 +243,56 @@ def Push(self, source_file, device_filename, mtime='0', timeout_ms=None, progres
167243 progress_callback: callback method that accepts filename, bytes_written and total_bytes,
168244 total_bytes will be -1 for file-like objects
169245 """
170- should_close = False
171246 if isinstance (source_file , str ):
172247 if os .path .isdir (source_file ):
173248 self .Shell ("mkdir " + device_filename )
174249 for f in os .listdir (source_file ):
175250 self .Push (os .path .join (source_file , f ), device_filename + '/' + f , progress_callback = progress_callback )
176251 return
177252 source_file = open (source_file , "rb" )
178- should_close = True
179253
180- connection = self .protocol_handler .Open (
181- self .handle , destination = b'sync:' , timeout_ms = timeout_ms )
182- self .filesync_handler .Push (connection , source_file , device_filename ,
254+ with source_file :
255+ connection = self .protocol_handler .Open (
256+ self ._handle , destination = b'sync:' , timeout_ms = timeout_ms )
257+ self .filesync_handler .Push (connection , source_file , device_filename ,
183258 mtime = int (mtime ), progress_callback = progress_callback )
184- if should_close :
185- source_file .close ()
186259 connection .Close ()
187260
188- def Pull (self , device_filename , dest_file = '' , progress_callback = None , timeout_ms = None ):
261+ def Pull (self , device_filename , dest_file = None , timeout_ms = None , progress_callback = None ):
189262 """Pull a file from the device.
190263
191264 Args:
192265 device_filename: Filename on the device to pull.
193266 dest_file: If set, a filename or writable file-like object.
194267 timeout_ms: Expected timeout for any part of the pull.
268+ progress_callback: callback method that accepts filename, bytes_written and total_bytes,
269+ total_bytes will be -1 for file-like objects
195270
196271 Returns:
197- The file data if dest_file is not set.
272+ The file data if dest_file is not set. Otherwise, True if the destination file exists
198273 """
199274 if not dest_file :
200275 dest_file = io .BytesIO ()
201276 elif isinstance (dest_file , str ):
202- dest_file = open (dest_file , 'wb' )
203- connection = self .protocol_handler .Open (
204- self .handle , destination = b'sync:' ,
205- timeout_ms = timeout_ms )
206- self .filesync_handler .Pull (connection , device_filename , dest_file , progress_callback )
207- connection .Close ()
277+ dest_file = open (dest_file , 'w' )
278+ else :
279+ raise ValueError ("destfile is of unknown type" )
280+
281+ conn = self .protocol_handler .Open (
282+ self ._handle , destination = b'sync:' , timeout_ms = timeout_ms )
283+
284+ self .filesync_handler .Pull (conn , device_filename , dest_file , progress_callback )
285+
286+ conn .Close ()
208287 if isinstance (dest_file , io .BytesIO ):
209288 return dest_file .getvalue ()
289+ else :
290+ dest_file .close ()
291+ return os .path .exists (dest_file )
210292
211293 def Stat (self , device_filename ):
212294 """Get a file's stat() information."""
213- connection = self .protocol_handler .Open (self .handle , destination = b'sync:' )
295+ connection = self .protocol_handler .Open (self ._handle , destination = b'sync:' )
214296 mode , size , mtime = self .filesync_handler .Stat (
215297 connection , device_filename )
216298 connection .Close ()
@@ -222,7 +304,7 @@ def List(self, device_path):
222304 Args:
223305 device_path: Directory to list.
224306 """
225- connection = self .protocol_handler .Open (self .handle , destination = b'sync:' )
307+ connection = self .protocol_handler .Open (self ._handle , destination = b'sync:' )
226308 listing = self .filesync_handler .List (connection , device_path )
227309 connection .Close ()
228310 return listing
@@ -233,32 +315,37 @@ def Reboot(self, destination=b''):
233315 Args:
234316 destination: Specify 'bootloader' for fastboot.
235317 """
236- self .protocol_handler .Open (self .handle , b'reboot:%s' % destination )
318+ self .protocol_handler .Open (self ._handle , b'reboot:%s' % destination )
237319
238320 def RebootBootloader (self ):
239321 """Reboot device into fastboot."""
240322 self .Reboot (b'bootloader' )
241323
242324 def Remount (self ):
243325 """Remount / as read-write."""
244- return self .protocol_handler .Command (self .handle , service = b'remount' )
326+ return self .protocol_handler .Command (self ._handle , service = b'remount' )
245327
246328 def Root (self ):
247329 """Restart adbd as root on the device."""
248- return self .protocol_handler .Command (self .handle , service = b'root' )
330+ return self .protocol_handler .Command (self ._handle , service = b'root' )
249331
250332 def EnableVerity (self ):
251333 """Re-enable dm-verity checking on userdebug builds"""
252- return self .protocol_handler .Command (self .handle , service = b'enable-verity' )
334+ return self .protocol_handler .Command (self ._handle , service = b'enable-verity' )
253335
254336 def DisableVerity (self ):
255337 """Disable dm-verity checking on userdebug builds"""
256- return self .protocol_handler .Command (self .handle , service = b'disable-verity' )
338+ return self .protocol_handler .Command (self ._handle , service = b'disable-verity' )
257339
258340 def Shell (self , command , timeout_ms = None ):
259- """Run command on the device, returning the output."""
341+ """Run command on the device, returning the output.
342+
343+ Args:
344+ command: Shell command to run
345+ timeout_ms: Maximum time to allow the command to run.
346+ """
260347 return self .protocol_handler .Command (
261- self .handle , service = b'shell' , command = command ,
348+ self ._handle , service = b'shell' , command = command ,
262349 timeout_ms = timeout_ms )
263350
264351 def StreamingShell (self , command , timeout_ms = None ):
@@ -272,13 +359,34 @@ def StreamingShell(self, command, timeout_ms=None):
272359 The responses from the shell command.
273360 """
274361 return self .protocol_handler .StreamingCommand (
275- self .handle , service = b'shell' , command = command ,
362+ self ._handle , service = b'shell' , command = command ,
276363 timeout_ms = timeout_ms )
277364
278365 def Logcat (self , options , timeout_ms = None ):
279366 """Run 'shell logcat' and stream the output to stdout.
280367
281368 Args:
282369 options: Arguments to pass to 'logcat'.
370+ timeout_ms: Maximum time to allow the command to run.
283371 """
284372 return self .StreamingShell ('logcat %s' % options , timeout_ms )
373+
374+ def InteractiveShell (self , cmd = None , strip_cmd = True , delim = None , strip_delim = True ):
375+ """Get stdout from the currently open interactive shell and optionally run a command
376+ on the device, returning all output.
377+
378+ Args:
379+ command: Optional. Command to run on the target.
380+ strip_cmd: Optional (default True). Strip command name from stdout.
381+ delim: Optional. Delimiter to look for in the output to know when to stop expecting more output
382+ (usually the shell prompt)
383+ strip_delim: Optional (default True): Strip the provided delimiter from the output
384+
385+ Returns:
386+ The stdout from the shell command.
387+ """
388+ conn = self ._get_service_connection (b'shell:' )
389+
390+ return self .protocol_handler .InteractiveShellCommand (
391+ conn , cmd = cmd , strip_cmd = strip_cmd ,
392+ delim = delim , strip_delim = strip_delim )
0 commit comments