Skip to content

Commit 854be63

Browse files
greateggsgregfahhem
authored andcommitted
AdbCommands structure change, Pycryptodome support, python3 fixes (google#82)
* Added pycharm .idea/ folder to gitignore * Added authsigner for pycryptodome * Added support for retrieving the reason for a command failure * Revised AdbCommands model to be an instantiated class. Fixed misc bugs and python3 compatibility issues * Merge adb_test and CONTRIBUTORS from master * Additional formatting cleanup for common_cli * Fixed formatting in filesync_protocol * common.py merges with master and spacing fixes * Fixed spacing issues in adb_protocol. Removed unused code * Added future to the requirements for py3 compatibility * Fixed spacing issues in adb_debug * Fixed up adb_commands spacing * Minor spacing change to requirements list * Updated InteractiveShellCommand to handle changing delimiters, still a bit more work to do * Dynamic detection of rsa library in setup.py * Improved user experience for adb_debug * Shortened InteractiveShell function and parameters to less than 80 characters. Minor fix to handling partial delimiter * adb_commands: Exposing handle paramter to allow caller to specify their own device handle if desired (used in tests) * Migrated fastboot to use object oriented interface. Updated fastboot tests. Consolidated interface to mimic object adbcommands * AdbCommands: Improved handle interface and passing to internal functions * Updated adb_test.py to conform to new AdbCommand object model * Updated adb_test to support py2/3. Fixed exit bug in adb_commands * added .DS_Store files to gitignore * Fixed minor syntax issue in function declaration * Fixed merge issues * removed reliance on future library * Improved FastbootCommands.ConnectDevice documentation. Fixed bug in fastboot_debug where chunk_kb was not being passed through * FilesyncProtocol: Simplified handling of utf-8 decoding errors * Imported __future__.print_function for python2/3 compatible printing to stderr * AdbProtocol: Reverted reliance on future.iteritems * Made handle a protected member, removed unused code, fixed line spacing, made build_prop a public property, improved documentation for ConnectDevice * Simplified logic in fastboot.ConnectDevice * setup.py: Removed duplicate version string for M2Crypto requirements * Fixed check for bytes in filesync_protocol * Updated documentation around self._service_connections * Additional docstrings for progress_callback parameter * Updated adb_commands._Connect to be a protected member that could be called externally. Fixed documentation around the method and its relationship to ConnectDevice() * Updated contributors
1 parent c3f93fa commit 854be63

14 files changed

Lines changed: 554 additions & 159 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ adb.egg-info/
55
.tox/
66
/adb.zip
77
/fastboot.zip
8+
.idea/
9+
*.DS_Store*

CONTRIBUTORS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ Marc-Antoine Ruel <maruel@chromium.org>
66
Max Borghino <fmborghino@gmail.com>
77
Mohammad Abu-Garbeyyeh <github@mohammadag.com>
88
Josip Delic <delijati@gmail.com>
9+
Greg E. <greateggsgreg@gmail.com>
10+

adb/adb_commands.py

Lines changed: 155 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import io
2626
import os
2727
import socket
28+
import posixpath
2829

2930
from adb import adb_protocol
3031
from 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

Comments
 (0)