forked from google/python-adb
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathadb_commands.py
More file actions
424 lines (339 loc) · 15.7 KB
/
adb_commands.py
File metadata and controls
424 lines (339 loc) · 15.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
# Copyright 2014 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""A libusb1-based ADB reimplementation.
ADB was giving us trouble with its client/server architecture, which is great
for users and developers, but not so great for reliable scripting. This will
allow us to more easily catch errors as Python exceptions instead of checking
random exit codes, and all the other great benefits from not going through
subprocess and a network socket.
All timeouts are in milliseconds.
"""
import io
import os
import socket
import posixpath
from adb import adb_protocol
from adb import common
from adb import filesync_protocol
try:
file # Python 2
except NameError: # Python 3
file = io.IOBase
# From adb.h
CLASS = 0xFF
SUBCLASS = 0x42
PROTOCOL = 0x01
# pylint: disable=invalid-name
DeviceIsAvailable = common.InterfaceMatcher(CLASS, SUBCLASS, PROTOCOL)
try:
# Imported locally to keep compatibility with previous code.
from adb.sign_m2crypto import M2CryptoSigner
except ImportError:
# Ignore this error when M2Crypto is not installed, there are other options.
pass
class AdbCommands(object):
"""Exposes adb-like methods for use.
Some methods are more-pythonic and/or have more options.
"""
protocol_handler = adb_protocol.AdbMessage
filesync_handler = filesync_protocol.FilesyncProtocol
def __init__(self):
self.__reset()
def __reset(self):
self.build_props = None
self._handle = None
self._device_state = None
# Connection table tracks each open AdbConnection objects per service type for program functions
# that choose to persist an AdbConnection object for their functionality, using
# self._get_service_connection
self._service_connections = {}
def _get_service_connection(self, service, service_command=None, create=True, timeout_ms=None):
"""
Based on the service, get the AdbConnection for that service or create one if it doesnt exist
:param service:
:param service_command: Additional service parameters to append
:param create: If False, dont create a connection if it does not exist
:return:
"""
connection = self._service_connections.get(service, None)
if connection:
return connection
if not connection and not create:
return None
if service_command:
destination_str = b'%s:%s' % (service, service_command)
else:
destination_str = service
connection = self.protocol_handler.Open(
self._handle, destination=destination_str, timeout_ms=timeout_ms)
self._service_connections.update({service: connection})
return connection
def ConnectDevice(self, port_path=None, serial=None, default_timeout_ms=None, **kwargs):
"""Convenience function to setup a transport handle for the adb device from
usb path or serial then connect to it.
Args:
port_path: The filename of usb port to use.
serial: The serial number of the device to use.
default_timeout_ms: The default timeout in milliseconds to use.
kwargs: handle: Device handle to use (instance of common.TcpHandle or common.UsbHandle)
banner: Connection banner to pass to the remote device
rsa_keys: List of AuthSigner subclass instances to be used for
authentication. The device can either accept one of these via the Sign
method, or we will send the result of GetPublicKey from the first one
if the device doesn't accept any of them.
auth_timeout_ms: Timeout to wait for when sending a new public key. This
is only relevant when we send a new public key. The device shows a
dialog and this timeout is how long to wait for that dialog. If used
in automation, this should be low to catch such a case as a failure
quickly; while in interactive settings it should be high to allow
users to accept the dialog. We default to automation here, so it's low
by default.
If serial specifies a TCP address:port, then a TCP connection is
used instead of a USB connection.
"""
# If there isnt a handle override (used by tests), build one here
if 'handle' in kwargs:
self._handle = kwargs.pop('handle')
else:
# if necessary, convert serial to a unicode string
if isinstance(serial, (bytes, bytearray)):
serial = serial.decode('utf-8')
if serial and ':' in serial:
self._handle = common.TcpHandle(serial, timeout_ms=default_timeout_ms)
else:
self._handle = common.UsbHandle.FindAndOpen(
DeviceIsAvailable, port_path=port_path, serial=serial,
timeout_ms=default_timeout_ms)
self._Connect(**kwargs)
return self
def Close(self):
for conn in list(self._service_connections.values()):
if conn:
try:
conn.Close()
except:
pass
if self._handle:
self._handle.Close()
self.__reset()
def _Connect(self, banner=None, **kwargs):
"""Connect to the device.
Args:
banner: See protocol_handler.Connect.
**kwargs: See protocol_handler.Connect and adb_commands.ConnectDevice for kwargs.
Includes handle, rsa_keys, and auth_timeout_ms.
Returns:
An instance of this class if the device connected successfully.
"""
if not banner:
banner = socket.gethostname().encode()
conn_str = self.protocol_handler.Connect(self._handle, banner=banner, **kwargs)
# Remove banner and colons after device state (state::banner)
parts = conn_str.split(b'::')
self._device_state = parts[0]
# Break out the build prop info
self.build_props = str(parts[1].split(b';'))
return True
@classmethod
def Devices(cls):
"""Get a generator of UsbHandle for devices available."""
return common.UsbHandle.FindDevices(DeviceIsAvailable)
def GetState(self):
return self._device_state
def Install(self, apk_path, destination_dir='', replace_existing=True,
grant_permissions=False, timeout_ms=None, transfer_progress_callback=None):
"""Install an apk to the device.
Doesn't support verifier file, instead allows destination directory to be
overridden.
Args:
apk_path: Local path to apk to install.
destination_dir: Optional destination directory. Use /system/app/ for
persistent applications.
replace_existing: whether to replace existing application
grant_permissions: If True, grant all permissions to the app specified in its manifest
timeout_ms: Expected timeout for pushing and installing.
transfer_progress_callback: callback method that accepts filename, bytes_written and total_bytes of APK transfer
Returns:
The pm install output.
"""
if not destination_dir:
destination_dir = '/data/local/tmp/'
basename = os.path.basename(apk_path)
destination_path = posixpath.join(destination_dir, basename)
self.Push(apk_path, destination_path, timeout_ms=timeout_ms, progress_callback=transfer_progress_callback)
cmd = ['pm install']
if grant_permissions:
cmd.append('-g')
if replace_existing:
cmd.append('-r')
cmd.append('"{}"'.format(destination_path))
ret = self.Shell(' '.join(cmd), timeout_ms=timeout_ms)
# Remove the apk
rm_cmd = ['rm', destination_path]
rmret = self.Shell(' '.join(rm_cmd), timeout_ms=timeout_ms)
return ret
def Uninstall(self, package_name, keep_data=False, timeout_ms=None):
"""Removes a package from the device.
Args:
package_name: Package name of target package.
keep_data: whether to keep the data and cache directories
timeout_ms: Expected timeout for pushing and installing.
Returns:
The pm uninstall output.
"""
cmd = ['pm uninstall']
if keep_data:
cmd.append('-k')
cmd.append('"%s"' % package_name)
return self.Shell(' '.join(cmd), timeout_ms=timeout_ms)
def Push(self, source_file, device_filename, mtime='0', timeout_ms=None, progress_callback=None, st_mode=None):
"""Push a file or directory to the device.
Args:
source_file: Either a filename, a directory or file-like object to push to
the device.
device_filename: Destination on the device to write to.
mtime: Optional, modification time to set on the file.
timeout_ms: Expected timeout for any part of the push.
st_mode: stat mode for filename
progress_callback: callback method that accepts filename, bytes_written and total_bytes,
total_bytes will be -1 for file-like objects
"""
if isinstance(source_file, str):
if os.path.isdir(source_file):
self.Shell("mkdir " + device_filename)
for f in os.listdir(source_file):
self.Push(os.path.join(source_file, f), device_filename + '/' + f,
progress_callback=progress_callback)
return
source_file = open(source_file, "rb")
with source_file:
connection = self.protocol_handler.Open(
self._handle, destination=b'sync:', timeout_ms=timeout_ms)
kwargs={}
if st_mode is not None:
kwargs['st_mode'] = st_mode
self.filesync_handler.Push(connection, source_file, device_filename,
mtime=int(mtime), progress_callback=progress_callback, **kwargs)
connection.Close()
def Pull(self, device_filename, dest_file=None, timeout_ms=None, progress_callback=None):
"""Pull a file from the device.
Args:
device_filename: Filename on the device to pull.
dest_file: If set, a filename or writable file-like object.
timeout_ms: Expected timeout for any part of the pull.
progress_callback: callback method that accepts filename, bytes_written and total_bytes,
total_bytes will be -1 for file-like objects
Returns:
The file data if dest_file is not set. Otherwise, True if the destination file exists
"""
if not dest_file:
dest_file = io.BytesIO()
elif isinstance(dest_file, str):
dest_file = open(dest_file, 'wb')
elif isinstance(dest_file, file):
pass
else:
raise ValueError("destfile is of unknown type")
conn = self.protocol_handler.Open(
self._handle, destination=b'sync:', timeout_ms=timeout_ms)
self.filesync_handler.Pull(conn, device_filename, dest_file, progress_callback)
conn.Close()
if isinstance(dest_file, io.BytesIO):
return dest_file.getvalue()
else:
dest_file.close()
if hasattr(dest_file, 'name'):
return os.path.exists(dest_file.name)
# We don't know what the path is, so we just assume it exists.
return True
def Stat(self, device_filename):
"""Get a file's stat() information."""
connection = self.protocol_handler.Open(self._handle, destination=b'sync:')
mode, size, mtime = self.filesync_handler.Stat(
connection, device_filename)
connection.Close()
return mode, size, mtime
def List(self, device_path):
"""Return a directory listing of the given path.
Args:
device_path: Directory to list.
"""
connection = self.protocol_handler.Open(self._handle, destination=b'sync:')
listing = self.filesync_handler.List(connection, device_path)
connection.Close()
return listing
def Reboot(self, destination=b''):
"""Reboot the device.
Args:
destination: Specify 'bootloader' for fastboot.
"""
self.protocol_handler.Open(self._handle, b'reboot:%s' % destination)
def RebootBootloader(self):
"""Reboot device into fastboot."""
self.Reboot(b'bootloader')
def Remount(self):
"""Remount / as read-write."""
return self.protocol_handler.Command(self._handle, service=b'remount')
def Root(self):
"""Restart adbd as root on the device."""
return self.protocol_handler.Command(self._handle, service=b'root')
def EnableVerity(self):
"""Re-enable dm-verity checking on userdebug builds"""
return self.protocol_handler.Command(self._handle, service=b'enable-verity')
def DisableVerity(self):
"""Disable dm-verity checking on userdebug builds"""
return self.protocol_handler.Command(self._handle, service=b'disable-verity')
def Shell(self, command, timeout_ms=None):
"""Run command on the device, returning the output.
Args:
command: Shell command to run
timeout_ms: Maximum time to allow the command to run.
"""
return self.protocol_handler.Command(
self._handle, service=b'shell', command=command,
timeout_ms=timeout_ms)
def StreamingShell(self, command, timeout_ms=None):
"""Run command on the device, yielding each line of output.
Args:
command: Command to run on the target.
timeout_ms: Maximum time to allow the command to run.
Yields:
The responses from the shell command.
"""
return self.protocol_handler.StreamingCommand(
self._handle, service=b'shell', command=command,
timeout_ms=timeout_ms)
def Logcat(self, options, timeout_ms=None):
"""Run 'shell logcat' and stream the output to stdout.
Args:
options: Arguments to pass to 'logcat'.
timeout_ms: Maximum time to allow the command to run.
"""
return self.StreamingShell('logcat %s' % options, timeout_ms)
def InteractiveShell(self, cmd=None, strip_cmd=True, delim=None, strip_delim=True):
"""Get stdout from the currently open interactive shell and optionally run a command
on the device, returning all output.
Args:
cmd: Optional. Command to run on the target.
strip_cmd: Optional (default True). Strip command name from stdout.
delim: Optional. Delimiter to look for in the output to know when to stop expecting more output
(usually the shell prompt)
strip_delim: Optional (default True): Strip the provided delimiter from the output
Returns:
The stdout from the shell command.
"""
conn = self._get_service_connection(b'shell:')
return self.protocol_handler.InteractiveShellCommand(
conn, cmd=cmd, strip_cmd=strip_cmd,
delim=delim, strip_delim=strip_delim)