#!/usr/bin/env python
# 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.
"""Daemon-less ADB client in python.
.. rubric:: Contents
* :func:`Devices`
* :func:`List`
* :func:`Logcat`
* :func:`Shell`
"""
import argparse
import functools
import logging
import os
import stat
import sys
import time
from adb import adb_commands
from adb import common_cli
try:
from adb import sign_cryptography
rsa_signer = sign_cryptography.CryptographySigner
except ImportError:
try:
from adb import sign_pythonrsa
rsa_signer = sign_pythonrsa.PythonRSASigner.FromRSAKeyPath
except ImportError:
try:
from adb import sign_pycryptodome
rsa_signer = sign_pycryptodome.PycryptodomeAuthSigner
except ImportError:
rsa_signer = None
[docs]def Devices(args):
"""Lists the available devices.
Mimics ``adb devices`` output:
::
List of devices attached
015DB7591102001A device 1,2
.. seealso:: :meth:`adb.adb_commands.AdbCommands.Devices`
.. image:: _static/adb.adb_debug.Devices.CALLER_GRAPH.svg
Parameters
----------
args : argparse.ArgumentParser
CLI arguments; see :func:`adb.common_cli.GetDeviceArguments`.
Returns
-------
int
0
"""
for d in adb_commands.AdbCommands.Devices():
if args.output_port_path:
print('%s\tdevice\t%s' % (d.serial_number, ','.join(str(p) for p in d.port_path)))
else:
print('%s\tdevice' % d.serial_number)
return 0
[docs]def List(device, device_path):
"""Prints a directory listing.
.. seealso:: :meth:`adb.adb_commands.AdbCommands.List`
Parameters
----------
device : adb.adb_commands.AdbCommands
TODO
device_path : str, bytes
Directory to list.
Yields
------
str
A formatted listing for a file in ``device_path``
"""
files = device.List(device_path)
files.sort(key=lambda x: x.filename)
maxname = max(len(f.filename) for f in files)
maxsize = max(len(str(f.size)) for f in files)
for f in files:
mode = (('d' if stat.S_ISDIR(f.mode) else '-') +
('r' if f.mode & stat.S_IRUSR else '-') +
('w' if f.mode & stat.S_IWUSR else '-') +
('x' if f.mode & stat.S_IXUSR else '-') +
('r' if f.mode & stat.S_IRGRP else '-') +
('w' if f.mode & stat.S_IWGRP else '-') +
('x' if f.mode & stat.S_IXGRP else '-') +
('r' if f.mode & stat.S_IROTH else '-') +
('w' if f.mode & stat.S_IWOTH else '-') +
('x' if f.mode & stat.S_IXOTH else '-'))
t = time.gmtime(f.mtime)
yield '%s %*d %04d-%02d-%02d %02d:%02d:%02d %-*s\n' % (
mode, maxsize, f.size,
t.tm_year, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec,
maxname, f.filename)
@functools.wraps(adb_commands.AdbCommands.Logcat)
def Logcat(device, *options):
"""Run ``shell logcat`` and stream the output to stdout.
.. seealso:: :meth:`adb.adb_commands.AdbCommands.Logcat`
Parameters
----------
device : adb.adb_commands.AdbCommands
TODO
options : list[str]
Arguments to pass to ``logcat``
Returns
-------
generator
The responses from the ``logcat`` command
"""
return device.Logcat(device, ' '.join(options), timeout_ms=0)
[docs]def Shell(device, *command):
"""Runs a command on the device and prints the stdout.
.. seealso:: :meth:`adb.adb_commands.AdbCommands.StreamingShell`, :meth:`adb.adb_commands.AdbCommands.InteractiveShell`
Parameters
----------
device : adb.adb_commands.AdbCommands
TODO
command : list[str]
Command to run on the target.
"""
if command:
return device.StreamingShell(' '.join(command))
# Retrieve the initial terminal prompt to use as a delimiter for future reads
terminal_prompt = device.InteractiveShell()
print(terminal_prompt.decode('utf-8'))
# Accept user input in a loop and write that into the interactive shells stdin, then print output
while True:
cmd = input('> ')
if not cmd:
continue
if cmd == 'exit':
break
stdout = device.InteractiveShell(cmd, strip_cmd=True, delim=terminal_prompt, strip_delim=True)
if stdout:
if isinstance(stdout, bytes):
stdout = stdout.decode('utf-8')
print(stdout)
device.Close()
[docs]def main():
"""TODO"""
common = common_cli.GetCommonArguments()
common.add_argument('--rsa_key_path', action='append', default=[], metavar='~/.android/adbkey',
help='RSA key(s) to use, use multiple times to load mulitple keys')
common.add_argument('--auth_timeout_s', default=60., metavar='60', type=int,
help='Seconds to wait for the dialog to be accepted when using authenticated ADB.')
device = common_cli.GetDeviceArguments()
parents = [common, device]
parser = argparse.ArgumentParser(description=sys.modules[__name__].__doc__, parents=[common])
subparsers = parser.add_subparsers(title='Commands', dest='command_name')
subparser = subparsers.add_parser(name='help', help='Prints the commands available')
subparser = subparsers.add_parser(name='devices', help='Lists the available devices', parents=[common])
subparser.add_argument('--output_port_path', action='store_true', help='Outputs the port_path alongside the serial')
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Install)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Uninstall)
common_cli.MakeSubparser(subparsers, parents, List)
common_cli.MakeSubparser(subparsers, parents, Logcat)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Push,
{'source_file': 'Filename or directory to push to the device.'})
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Pull,
{'dest_file': 'Filename to write to on the host, if not specified, prints the content to stdout.'})
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Reboot)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.RebootBootloader)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Remount)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.Root)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.EnableVerity)
common_cli.MakeSubparser(subparsers, parents, adb_commands.AdbCommands.DisableVerity)
common_cli.MakeSubparser(subparsers, parents, Shell)
if len(sys.argv) == 1:
parser.print_help()
return 2
args = parser.parse_args()
if args.verbose:
logging.basicConfig(level=logging.DEBUG)
if not args.rsa_key_path:
default = os.path.expanduser('~/.android/adbkey')
if os.path.isfile(default):
args.rsa_key_path = [default]
if args.rsa_key_path and not rsa_signer:
parser.error('Please install either cryptography, python-rsa, or PycryptoDome')
# Hacks so that the generated doc is nicer.
if args.command_name == 'devices':
return Devices(args)
if args.command_name == 'help':
parser.print_help()
return 0
if args.command_name == 'logcat':
args.positional = args.options
elif args.command_name == 'shell':
args.positional = args.command
return common_cli.StartCli(args,
adb_commands.AdbCommands,
auth_timeout_ms=int(args.auth_timeout_s * 1000),
rsa_keys=[rsa_signer(path) for path in args.rsa_key_path])
if __name__ == '__main__':
sys.exit(main())