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