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