212 lines
7.3 KiB
Python
212 lines
7.3 KiB
Python
|
#!/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."""
|
||
|
|
||
|
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_m2crypto
|
||
|
|
||
|
rsa_signer = sign_m2crypto.M2CryptoSigner
|
||
|
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
|
||
|
|
||
|
|
||
|
def Devices(args):
|
||
|
"""Lists the available devices.
|
||
|
|
||
|
Mimics 'adb devices' output:
|
||
|
List of devices attached
|
||
|
015DB7591102001A device 1,2
|
||
|
"""
|
||
|
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
|
||
|
|
||
|
|
||
|
def List(device, device_path):
|
||
|
"""Prints a directory listing.
|
||
|
|
||
|
Args:
|
||
|
device_path: Directory to list.
|
||
|
"""
|
||
|
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):
|
||
|
return device.Logcat(
|
||
|
device, ' '.join(options), timeout_ms=0)
|
||
|
|
||
|
|
||
|
def Shell(device, *command):
|
||
|
"""Runs a command on the device and prints the stdout.
|
||
|
|
||
|
Args:
|
||
|
command: Command to run on the target.
|
||
|
"""
|
||
|
if command:
|
||
|
return device.StreamingShell(' '.join(command))
|
||
|
else:
|
||
|
# 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
|
||
|
elif cmd == 'exit':
|
||
|
break
|
||
|
else:
|
||
|
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()
|
||
|
|
||
|
|
||
|
def main():
|
||
|
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 M2Crypto, 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())
|