Home | History | Annotate | Download | only in adb
      1 #
      2 # Copyright (C) 2015 The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 #
     16 import atexit
     17 import base64
     18 import logging
     19 import os
     20 import re
     21 import subprocess
     22 
     23 
     24 class FindDeviceError(RuntimeError):
     25     pass
     26 
     27 
     28 class DeviceNotFoundError(FindDeviceError):
     29     def __init__(self, serial):
     30         self.serial = serial
     31         super(DeviceNotFoundError, self).__init__(
     32             'No device with serial {}'.format(serial))
     33 
     34 
     35 class NoUniqueDeviceError(FindDeviceError):
     36     def __init__(self):
     37         super(NoUniqueDeviceError, self).__init__('No unique device')
     38 
     39 
     40 class ShellError(RuntimeError):
     41     def __init__(self, cmd, stdout, stderr, exit_code):
     42         super(ShellError, self).__init__(
     43             '`{0}` exited with code {1}'.format(cmd, exit_code))
     44         self.cmd = cmd
     45         self.stdout = stdout
     46         self.stderr = stderr
     47         self.exit_code = exit_code
     48 
     49 
     50 def get_devices(adb_path='adb'):
     51     with open(os.devnull, 'wb') as devnull:
     52         subprocess.check_call([adb_path, 'start-server'], stdout=devnull,
     53                               stderr=devnull)
     54     out = split_lines(
     55         subprocess.check_output([adb_path, 'devices']).decode('utf-8'))
     56 
     57     # The first line of `adb devices` just says "List of attached devices", so
     58     # skip that.
     59     devices = []
     60     for line in out[1:]:
     61         if not line.strip():
     62             continue
     63         if 'offline' in line:
     64             continue
     65 
     66         serial, _ = re.split(r'\s+', line, maxsplit=1)
     67         devices.append(serial)
     68     return devices
     69 
     70 
     71 def _get_unique_device(product=None, adb_path='adb'):
     72     devices = get_devices(adb_path=adb_path)
     73     if len(devices) != 1:
     74         raise NoUniqueDeviceError()
     75     return AndroidDevice(devices[0], product, adb_path)
     76 
     77 
     78 def _get_device_by_serial(serial, product=None, adb_path='adb'):
     79     for device in get_devices(adb_path=adb_path):
     80         if device == serial:
     81             return AndroidDevice(serial, product, adb_path)
     82     raise DeviceNotFoundError(serial)
     83 
     84 
     85 def get_device(serial=None, product=None, adb_path='adb'):
     86     """Get a uniquely identified AndroidDevice if one is available.
     87 
     88     Raises:
     89         DeviceNotFoundError:
     90             The serial specified by `serial` or $ANDROID_SERIAL is not
     91             connected.
     92 
     93         NoUniqueDeviceError:
     94             Neither `serial` nor $ANDROID_SERIAL was set, and the number of
     95             devices connected to the system is not 1. Having 0 connected
     96             devices will also result in this error.
     97 
     98     Returns:
     99         An AndroidDevice associated with the first non-None identifier in the
    100         following order of preference:
    101 
    102         1) The `serial` argument.
    103         2) The environment variable $ANDROID_SERIAL.
    104         3) The single device connnected to the system.
    105     """
    106     if serial is not None:
    107         return _get_device_by_serial(serial, product, adb_path)
    108 
    109     android_serial = os.getenv('ANDROID_SERIAL')
    110     if android_serial is not None:
    111         return _get_device_by_serial(android_serial, product, adb_path)
    112 
    113     return _get_unique_device(product, adb_path=adb_path)
    114 
    115 
    116 def _get_device_by_type(flag, adb_path):
    117     with open(os.devnull, 'wb') as devnull:
    118         subprocess.check_call([adb_path, 'start-server'], stdout=devnull,
    119                               stderr=devnull)
    120     try:
    121         serial = subprocess.check_output(
    122             [adb_path, flag, 'get-serialno']).decode('utf-8').strip()
    123     except subprocess.CalledProcessError:
    124         raise RuntimeError('adb unexpectedly returned nonzero')
    125     if serial == 'unknown':
    126         raise NoUniqueDeviceError()
    127     return _get_device_by_serial(serial, adb_path=adb_path)
    128 
    129 
    130 def get_usb_device(adb_path='adb'):
    131     """Get the unique USB-connected AndroidDevice if it is available.
    132 
    133     Raises:
    134         NoUniqueDeviceError:
    135             0 or multiple devices are connected via USB.
    136 
    137     Returns:
    138         An AndroidDevice associated with the unique USB-connected device.
    139     """
    140     return _get_device_by_type('-d', adb_path=adb_path)
    141 
    142 
    143 def get_emulator_device(adb_path='adb'):
    144     """Get the unique emulator AndroidDevice if it is available.
    145 
    146     Raises:
    147         NoUniqueDeviceError:
    148             0 or multiple emulators are running.
    149 
    150     Returns:
    151         An AndroidDevice associated with the unique running emulator.
    152     """
    153     return _get_device_by_type('-e', adb_path=adb_path)
    154 
    155 
    156 # If necessary, modifies subprocess.check_output() or subprocess.Popen() args
    157 # to run the subprocess via Windows PowerShell to work-around an issue in
    158 # Python 2's subprocess class on Windows where it doesn't support Unicode.
    159 def _get_subprocess_args(args):
    160     # Only do this slow work-around if Unicode is in the cmd line on Windows.
    161     # PowerShell takes 600-700ms to startup on a 2013-2014 machine, which is
    162     # very slow.
    163     if os.name != 'nt' or all(not isinstance(arg, unicode) for arg in args[0]):
    164         return args
    165 
    166     def escape_arg(arg):
    167         # Escape for the parsing that the C Runtime does in Windows apps. In
    168         # particular, this will take care of double-quotes.
    169         arg = subprocess.list2cmdline([arg])
    170         # Escape single-quote with another single-quote because we're about
    171         # to...
    172         arg = arg.replace(u"'", u"''")
    173         # ...put the arg in a single-quoted string for PowerShell to parse.
    174         arg = u"'" + arg + u"'"
    175         return arg
    176 
    177     # Escape command line args.
    178     argv = map(escape_arg, args[0])
    179     # Cause script errors (such as adb not found) to stop script immediately
    180     # with an error.
    181     ps_code = u'$ErrorActionPreference = "Stop"\r\n'
    182     # Add current directory to the PATH var, to match cmd.exe/CreateProcess()
    183     # behavior.
    184     ps_code += u'$env:Path = ".;" + $env:Path\r\n'
    185     # Precede by &, the PowerShell call operator, and separate args by space.
    186     ps_code += u'& ' + u' '.join(argv)
    187     # Make the PowerShell exit code the exit code of the subprocess.
    188     ps_code += u'\r\nExit $LastExitCode'
    189     # Encode as UTF-16LE (without Byte-Order-Mark) which Windows natively
    190     # understands.
    191     ps_code = ps_code.encode('utf-16le')
    192 
    193     # Encode the PowerShell command as base64 and use the special
    194     # -EncodedCommand option that base64 decodes. Base64 is just plain ASCII,
    195     # so it should have no problem passing through Win32 CreateProcessA()
    196     # (which python erroneously calls instead of CreateProcessW()).
    197     return (['powershell.exe', '-NoProfile', '-NonInteractive',
    198              '-EncodedCommand', base64.b64encode(ps_code)],) + args[1:]
    199 
    200 
    201 # Call this instead of subprocess.check_output() to work-around issue in Python
    202 # 2's subprocess class on Windows where it doesn't support Unicode.
    203 def _subprocess_check_output(*args, **kwargs):
    204     try:
    205         return subprocess.check_output(*_get_subprocess_args(args), **kwargs)
    206     except subprocess.CalledProcessError as e:
    207         # Show real command line instead of the powershell.exe command line.
    208         raise subprocess.CalledProcessError(e.returncode, args[0],
    209                                             output=e.output)
    210 
    211 
    212 # Call this instead of subprocess.Popen(). Like _subprocess_check_output().
    213 def _subprocess_Popen(*args, **kwargs):
    214     return subprocess.Popen(*_get_subprocess_args(args), **kwargs)
    215 
    216 
    217 def split_lines(s):
    218     """Splits lines in a way that works even on Windows and old devices.
    219 
    220     Windows will see \r\n instead of \n, old devices do the same, old devices
    221     on Windows will see \r\r\n.
    222     """
    223     # rstrip is used here to workaround a difference between splineslines and
    224     # re.split:
    225     # >>> 'foo\n'.splitlines()
    226     # ['foo']
    227     # >>> re.split(r'\n', 'foo\n')
    228     # ['foo', '']
    229     return re.split(r'[\r\n]+', s.rstrip())
    230 
    231 
    232 def version(adb_path=None):
    233     """Get the version of adb (in terms of ADB_SERVER_VERSION)."""
    234 
    235     adb_path = adb_path if adb_path is not None else ['adb']
    236     version_output = subprocess.check_output(adb_path + ['version'])
    237     version_output = version_output.decode('utf-8')
    238     pattern = r'^Android Debug Bridge version 1.0.(\d+)$'
    239     result = re.match(pattern, version_output.splitlines()[0])
    240     if not result:
    241         return 0
    242     return int(result.group(1))
    243 
    244 
    245 class AndroidDevice(object):
    246     # Delimiter string to indicate the start of the exit code.
    247     _RETURN_CODE_DELIMITER = 'x'
    248 
    249     # Follow any shell command with this string to get the exit
    250     # status of a program since this isn't propagated by adb.
    251     #
    252     # The delimiter is needed because `printf 1; echo $?` would print
    253     # "10", and we wouldn't be able to distinguish the exit code.
    254     _RETURN_CODE_PROBE = [';', 'echo', '{0}$?'.format(_RETURN_CODE_DELIMITER)]
    255 
    256     # Maximum search distance from the output end to find the delimiter.
    257     # adb on Windows returns \r\n even if adbd returns \n. Some old devices
    258     # seem to actually return \r\r\n.
    259     _RETURN_CODE_SEARCH_LENGTH = len(
    260         '{0}255\r\r\n'.format(_RETURN_CODE_DELIMITER))
    261 
    262     def __init__(self, serial, product=None, adb_path='adb'):
    263         self.serial = serial
    264         self.product = product
    265         self.adb_path = adb_path
    266         self.adb_cmd = [adb_path]
    267 
    268         if self.serial is not None:
    269             self.adb_cmd.extend(['-s', serial])
    270         if self.product is not None:
    271             self.adb_cmd.extend(['-p', product])
    272         self._linesep = None
    273         self._features = None
    274 
    275     @property
    276     def linesep(self):
    277         if self._linesep is None:
    278             self._linesep = subprocess.check_output(
    279                 self.adb_cmd + ['shell', 'echo']).decode('utf-8')
    280         return self._linesep
    281 
    282     @property
    283     def features(self):
    284         if self._features is None:
    285             try:
    286                 self._features = split_lines(self._simple_call(['features']))
    287             except subprocess.CalledProcessError:
    288                 self._features = []
    289         return self._features
    290 
    291     def has_shell_protocol(self):
    292         return version(self.adb_cmd) >= 35 and 'shell_v2' in self.features
    293 
    294     def _make_shell_cmd(self, user_cmd):
    295         command = self.adb_cmd + ['shell'] + user_cmd
    296         if not self.has_shell_protocol():
    297             command += self._RETURN_CODE_PROBE
    298         return command
    299 
    300     def _parse_shell_output(self, out):
    301         """Finds the exit code string from shell output.
    302 
    303         Args:
    304             out: Shell output string.
    305 
    306         Returns:
    307             An (exit_code, output_string) tuple. The output string is
    308             cleaned of any additional stuff we appended to find the
    309             exit code.
    310 
    311         Raises:
    312             RuntimeError: Could not find the exit code in |out|.
    313         """
    314         search_text = out
    315         if len(search_text) > self._RETURN_CODE_SEARCH_LENGTH:
    316             # We don't want to search over massive amounts of data when we know
    317             # the part we want is right at the end.
    318             search_text = search_text[-self._RETURN_CODE_SEARCH_LENGTH:]
    319         partition = search_text.rpartition(self._RETURN_CODE_DELIMITER)
    320         if partition[1] == '':
    321             raise RuntimeError('Could not find exit status in shell output.')
    322         result = int(partition[2])
    323         # partition[0] won't contain the full text if search_text was
    324         # truncated, pull from the original string instead.
    325         out = out[:-len(partition[1]) - len(partition[2])]
    326         return result, out
    327 
    328     def _simple_call(self, cmd):
    329         logging.info(' '.join(self.adb_cmd + cmd))
    330         return _subprocess_check_output(
    331             self.adb_cmd + cmd, stderr=subprocess.STDOUT).decode('utf-8')
    332 
    333     def shell(self, cmd):
    334         """Calls `adb shell`
    335 
    336         Args:
    337             cmd: command to execute as a list of strings.
    338 
    339         Returns:
    340             A (stdout, stderr) tuple. Stderr may be combined into stdout
    341             if the device doesn't support separate streams.
    342 
    343         Raises:
    344             ShellError: the exit code was non-zero.
    345         """
    346         exit_code, stdout, stderr = self.shell_nocheck(cmd)
    347         if exit_code != 0:
    348             raise ShellError(cmd, stdout, stderr, exit_code)
    349         return stdout, stderr
    350 
    351     def shell_nocheck(self, cmd):
    352         """Calls `adb shell`
    353 
    354         Args:
    355             cmd: command to execute as a list of strings.
    356 
    357         Returns:
    358             An (exit_code, stdout, stderr) tuple. Stderr may be combined
    359             into stdout if the device doesn't support separate streams.
    360         """
    361         cmd = self._make_shell_cmd(cmd)
    362         logging.info(' '.join(cmd))
    363         p = _subprocess_Popen(
    364             cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    365         stdout, stderr = p.communicate()
    366         stdout = stdout.decode('utf-8')
    367         stderr = stderr.decode('utf-8')
    368         if self.has_shell_protocol():
    369             exit_code = p.returncode
    370         else:
    371             exit_code, stdout = self._parse_shell_output(stdout)
    372         return exit_code, stdout, stderr
    373 
    374     def shell_popen(self, cmd, kill_atexit=True, preexec_fn=None,
    375                     creationflags=0, **kwargs):
    376         """Calls `adb shell` and returns a handle to the adb process.
    377 
    378         This function provides direct access to the subprocess used to run the
    379         command, without special return code handling. Users that need the
    380         return value must retrieve it themselves.
    381 
    382         Args:
    383             cmd: Array of command arguments to execute.
    384             kill_atexit: Whether to kill the process upon exiting.
    385             preexec_fn: Argument forwarded to subprocess.Popen.
    386             creationflags: Argument forwarded to subprocess.Popen.
    387             **kwargs: Arguments forwarded to subprocess.Popen.
    388 
    389         Returns:
    390             subprocess.Popen handle to the adb shell instance
    391         """
    392 
    393         command = self.adb_cmd + ['shell'] + cmd
    394 
    395         # Make sure a ctrl-c in the parent script doesn't kill gdbserver.
    396         if os.name == 'nt':
    397             creationflags |= subprocess.CREATE_NEW_PROCESS_GROUP
    398         else:
    399             if preexec_fn is None:
    400                 preexec_fn = os.setpgrp
    401             elif preexec_fn is not os.setpgrp:
    402                 fn = preexec_fn
    403                 def _wrapper():
    404                     fn()
    405                     os.setpgrp()
    406                 preexec_fn = _wrapper
    407 
    408         p = _subprocess_Popen(command, creationflags=creationflags,
    409                               preexec_fn=preexec_fn, **kwargs)
    410 
    411         if kill_atexit:
    412             atexit.register(p.kill)
    413 
    414         return p
    415 
    416     def install(self, filename, replace=False):
    417         cmd = ['install']
    418         if replace:
    419             cmd.append('-r')
    420         cmd.append(filename)
    421         return self._simple_call(cmd)
    422 
    423     def push(self, local, remote, sync=False):
    424         """Transfer a local file or directory to the device.
    425 
    426         Args:
    427             local: The local file or directory to transfer.
    428             remote: The remote path to which local should be transferred.
    429             sync: If True, only transfers files that are newer on the host than
    430                   those on the device. If False, transfers all files.
    431 
    432         Returns:
    433             Exit status of the push command.
    434         """
    435         cmd = ['push']
    436         if sync:
    437             cmd.append('--sync')
    438         cmd.extend([local, remote])
    439         return self._simple_call(cmd)
    440 
    441     def pull(self, remote, local):
    442         return self._simple_call(['pull', remote, local])
    443 
    444     def sync(self, directory=None):
    445         cmd = ['sync']
    446         if directory is not None:
    447             cmd.append(directory)
    448         return self._simple_call(cmd)
    449 
    450     def tcpip(self, port):
    451         return self._simple_call(['tcpip', port])
    452 
    453     def usb(self):
    454         return self._simple_call(['usb'])
    455 
    456     def reboot(self):
    457         return self._simple_call(['reboot'])
    458 
    459     def remount(self):
    460         return self._simple_call(['remount'])
    461 
    462     def root(self):
    463         return self._simple_call(['root'])
    464 
    465     def unroot(self):
    466         return self._simple_call(['unroot'])
    467 
    468     def connect(self, host):
    469         return self._simple_call(['connect', host])
    470 
    471     def disconnect(self, host):
    472         return self._simple_call(['disconnect', host])
    473 
    474     def forward(self, local, remote):
    475         return self._simple_call(['forward', local, remote])
    476 
    477     def forward_list(self):
    478         return self._simple_call(['forward', '--list'])
    479 
    480     def forward_no_rebind(self, local, remote):
    481         return self._simple_call(['forward', '--no-rebind', local, remote])
    482 
    483     def forward_remove(self, local):
    484         return self._simple_call(['forward', '--remove', local])
    485 
    486     def forward_remove_all(self):
    487         return self._simple_call(['forward', '--remove-all'])
    488 
    489     def reverse(self, remote, local):
    490         return self._simple_call(['reverse', remote, local])
    491 
    492     def reverse_list(self):
    493         return self._simple_call(['reverse', '--list'])
    494 
    495     def reverse_no_rebind(self, local, remote):
    496         return self._simple_call(['reverse', '--no-rebind', local, remote])
    497 
    498     def reverse_remove_all(self):
    499         return self._simple_call(['reverse', '--remove-all'])
    500 
    501     def reverse_remove(self, remote):
    502         return self._simple_call(['reverse', '--remove', remote])
    503 
    504     def wait(self):
    505         return self._simple_call(['wait-for-device'])
    506 
    507     def get_prop(self, prop_name):
    508         output = split_lines(self.shell(['getprop', prop_name])[0])
    509         if len(output) != 1:
    510             raise RuntimeError('Too many lines in getprop output:\n' +
    511                                '\n'.join(output))
    512         value = output[0]
    513         if not value.strip():
    514             return None
    515         return value
    516 
    517     def set_prop(self, prop_name, value):
    518         self.shell(['setprop', prop_name, value])
    519