├── .gitignore ├── LICENSE ├── PKGBUILD ├── README.rst ├── docs ├── basics.rst ├── conf.py ├── index.rst ├── install.rst └── introduction.rst ├── pyand ├── ADB.py ├── Fastboot.py └── __init__.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Sphinx 31 | build 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | pkgname=python2-pyand-git 2 | pkgver=r54.f90701b 3 | pkgrel=2 4 | pkgdesc="A Python wrapper library for ADB and Fastboot" 5 | arch=('any') 6 | url="https://github.com/ardevd/pyand" 7 | license=('MIT') 8 | depends=('python2') 9 | makedepends=('git' 'python2-setuptools') 10 | # The git repo is detected by the 'git:' or 'git+' beginning. The branch 11 | # '$pkgname' is then checked out upon cloning, expediating versioning: 12 | source=($pkgname::git+'https://github.com/ardevd/pyand.git') 13 | # Because the sources are not static, skip Git checksum: 14 | md5sums=('SKIP') 15 | pkgver() { 16 | cd "$srcdir/$pkgname" 17 | # Use the tag of the last commit 18 | printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" 19 | } 20 | 21 | package() { 22 | export PYTHONPATH="$pkgdir/usr/lib/python2.7/site-packages" 23 | cd "$srcdir/" 24 | install -m755 -d "${pkgdir}/usr/share/licenses/${pkgname}" 25 | install -m755 "${srcdir}/${pkgname}/LICENSE" "${pkgdir}/usr/share/licenses/${pkgname}/" 26 | mkdir -p "$pkgdir/usr/lib/python2.7/site-packages" 27 | easy_install-2.7 --prefix "$pkgdir/usr" python2-pyand-git 28 | } 29 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pyand: Python library for adb and fastboot 2 | ========================================= 3 | 4 | pyand is a simple Python library for Python 2.7 that allows you to easily work with adb and fastboot connected Android devices. 5 | 6 | Existing Python modules for adb are somewhat outdated or broken, hence why I went ahead and wrote pyand. I also wanted more than just adb hence why I added in support for Fastboot as well. Usage is simple and should be pretty intuitive for anyone used to working with adb and fastboot. 7 | 8 | .. code-block:: pycon 9 | 10 | >>> from pyand import ADB, Fastboot 11 | >>> adb = ADB() 12 | >>> adb.get_devices() 13 | {0: '15901aabbccdd124', 1: 'abc1951124de1241'} 14 | >>> adb.set_target_by_id(1) 15 | '[+] Target device set: abc1951124de1241' 16 | >>> adb.get_model() 17 | 'Nexus_5' 18 | >>> adb.set_system_rw() 19 | 'remount succeeded' 20 | >>> adb.reboot(2) 21 | >>> fb = Fastboot() 22 | >>> fb.get_devices() 23 | {0: 'abc1951124de1241'} 24 | ... 25 | 26 | 27 | pyand will eventually let you do pretty much anything you could possibly do with adb and fastboot, but its still under development and not entirely done yet. 28 | 29 | Requirements 30 | ======= 31 | * Linux is currently the only supported operating system. pyand is reported to function on Windows and OSX as well but I havent done extensive testing on those platforms.. 32 | * Python 2.7 is the recommended version of Python as Python 3.x is not currently supported. 33 | * Fastboot and ADB is also required and should ideally be in your $PATH. If its not in your $PATH you will have to specify the path when you instantiate the object. 34 | 35 | `The Android SDK `_ is a good way of getting a hold of up-to-date binaries. 36 | 37 | Documentation 38 | ==== 39 | The pyand documentation is currently being written. You can find the latest documentation `here `_. 40 | 41 | Installation 42 | ====== 43 | There are currently two recommended ways of installing pyand. 44 | 45 | easy_install 46 | ------- 47 | If you have easy_install for Python-2.7 installed, you can use it to install pyand pretty easily. 48 | 49 | .. code-block:: 50 | 51 | $ git clone https://github.com/ardevd/pyand 52 | $ sudo easy_install-2.7 pyand 53 | 54 | 55 | AUR PKGBUILD 56 | -------- 57 | There is also an officially supported PKGBUILD available. You can grab the PKGBUILD from the github repo. 58 | 59 | Credits 60 | ======== 61 | 62 | pyand spawned from `pyadb `_ so thanks to Chema Garcia for writing it as it gave me a great starting point for pyand. 63 | 64 | -------------------------------------------------------------------------------- /docs/basics.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | Basics 4 | =============== 5 | This section covers the most basic concepts in pyand 6 | 7 | Devices 8 | -------------- 9 | You can easily use adb to get a list of the currently connected devices.:: 10 | 11 | >> from pyand import ADB 12 | >> adb = ADB() 13 | >> adb.get_devices() 14 | {0: '12601aabbccdd124', 1: 'abc1551124de1241'} 15 | 16 | Notice how ``get_devices()`` returns a dictionary with an index and the device identifier. 17 | 18 | Setting a target device 19 | ----------------------- 20 | In order to interact with a device you need to tell pyand which device you want to interact with. You do this by specifying either the device index from the dictionary returned from ``get_devices()`` or by the device id.:: 21 | 22 | >> adb.get_devices() 23 | {0: '12601aabbccdd124', 1: 'abc1551124de1241'} 24 | >> adb.set_target_by_id(1) 25 | [+] Target device set: abc1551124de1241 26 | >> adb.set_target_by_name('abc1551124de1241') 27 | [+] Target device set: abc1551124de1241 28 | 29 | 30 | Getting device state 31 | -------------------- 32 | Note how that dictionary returned by ``get_devices()`` does not indicate the state of the device(s). You can use ``get_state()`` for that. Remember to set a device target first.:: 33 | 34 | >> adb.get_state() 35 | unauthorized 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pyand documentation build configuration file, created by 4 | # sphinx-quickstart on Tue Jun 6 01:39:59 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | # import os 20 | # import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | # needs_sphinx = '1.0' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [] 34 | 35 | # Add any paths that contain templates here, relative to this directory. 36 | templates_path = ['_templates'] 37 | 38 | # The suffix(es) of source filenames. 39 | # You can specify multiple suffix as a list of string: 40 | # 41 | # source_suffix = ['.rst', '.md'] 42 | source_suffix = '.rst' 43 | 44 | # The master toctree document. 45 | master_doc = 'index' 46 | 47 | # General information about the project. 48 | project = u'pyand' 49 | copyright = u'2017, ardevd' 50 | author = u'ardevd' 51 | 52 | # The version info for the project you're documenting, acts as replacement for 53 | # |version| and |release|, also used in various other places throughout the 54 | # built documents. 55 | # 56 | # The short X.Y version. 57 | version = u'1.0.0' 58 | # The full version, including alpha/beta/rc tags. 59 | release = u'1.0.0' 60 | 61 | # The language for content autogenerated by Sphinx. Refer to documentation 62 | # for a list of supported languages. 63 | # 64 | # This is also used if you do content translation via gettext catalogs. 65 | # Usually you set "language" from the command line for these cases. 66 | language = None 67 | 68 | # List of patterns, relative to source directory, that match files and 69 | # directories to ignore when looking for source files. 70 | # This patterns also effect to html_static_path and html_extra_path 71 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 72 | 73 | # The name of the Pygments (syntax highlighting) style to use. 74 | pygments_style = 'sphinx' 75 | 76 | # If true, `todo` and `todoList` produce output, else they produce nothing. 77 | todo_include_todos = False 78 | 79 | 80 | # -- Options for HTML output ---------------------------------------------- 81 | 82 | # The theme to use for HTML and HTML Help pages. See the documentation for 83 | # a list of builtin themes. 84 | # 85 | html_theme = 'default' 86 | 87 | # Theme options are theme-specific and customize the look and feel of a theme 88 | # further. For a list of options available for each theme, see the 89 | # documentation. 90 | # 91 | # html_theme_options = {} 92 | 93 | # Add any paths that contain custom static files (such as style sheets) here, 94 | # relative to this directory. They are copied after the builtin static files, 95 | # so a file named "default.css" will overwrite the builtin "default.css". 96 | html_static_path = ['_static'] 97 | 98 | 99 | # -- Options for HTMLHelp output ------------------------------------------ 100 | 101 | # Output file base name for HTML help builder. 102 | htmlhelp_basename = 'pyanddoc' 103 | 104 | 105 | # -- Options for LaTeX output --------------------------------------------- 106 | 107 | latex_elements = { 108 | # The paper size ('letterpaper' or 'a4paper'). 109 | # 110 | # 'papersize': 'letterpaper', 111 | 112 | # The font size ('10pt', '11pt' or '12pt'). 113 | # 114 | # 'pointsize': '10pt', 115 | 116 | # Additional stuff for the LaTeX preamble. 117 | # 118 | # 'preamble': '', 119 | 120 | # Latex figure (float) alignment 121 | # 122 | # 'figure_align': 'htbp', 123 | } 124 | 125 | # Grouping the document tree into LaTeX files. List of tuples 126 | # (source start file, target name, title, 127 | # author, documentclass [howto, manual, or own class]). 128 | latex_documents = [ 129 | (master_doc, 'pyand.tex', u'pyand Documentation', 130 | u'ardevd', 'manual'), 131 | ] 132 | 133 | 134 | # -- Options for manual page output --------------------------------------- 135 | 136 | # One entry per manual page. List of tuples 137 | # (source start file, name, description, authors, manual section). 138 | man_pages = [ 139 | (master_doc, 'pyand', u'pyand Documentation', 140 | [author], 1) 141 | ] 142 | 143 | 144 | # -- Options for Texinfo output ------------------------------------------- 145 | 146 | # Grouping the document tree into Texinfo files. List of tuples 147 | # (source start file, target name, title, author, 148 | # dir menu entry, description, category) 149 | texinfo_documents = [ 150 | (master_doc, 'pyand', u'pyand Documentation', 151 | author, 'pyand', 'One line description of project.', 152 | 'Miscellaneous'), 153 | ] 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyand documentation master file, created by 2 | sphinx-quickstart on Tue Jun 6 01:39:59 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | pyand: Interface with adb and fastboot in Python 7 | ================================= 8 | :Release: |version| 9 | :Date: |today| 10 | 11 | pyand is a simple Python library for Python 2.7 that allows you to easily work with adb and fastboot connected Android devices. Usage is pretty simple and should be intuitive for anyone familiar with Python as well as adb and fastboot. 12 | 13 | Although this documentation describes the most important methods and classes it is not a complete API reference. 14 | 15 | .. toctree:: 16 | :numbered: 17 | :maxdepth: 2 18 | :caption: Contents: 19 | 20 | install 21 | introduction 22 | basics 23 | 24 | Indices and tables 25 | ================== 26 | 27 | * :ref:`search` 28 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Installation 4 | ============ 5 | Installation is simple. Pyand is a simple Python module that enables developers to interact with ADB and Fastboot. 6 | 7 | There are currently two recommended ways of installing pyand. 8 | 9 | 10 | Dependencies 11 | ------------ 12 | * Python 2 (2.6 or later) 13 | 14 | * ADB and Fastboot available on the system 15 | 16 | 17 | easy_install 18 | ------- 19 | If you have easy_install for Python-2.7 installed, you can use it to install pyand pretty easily.:: 20 | 21 | $ git clone https://github.com/ardevd/pyand 22 | $ sudo easy_install-2.7 pyand 23 | 24 | 25 | AUR PKGBUILD 26 | -------- 27 | There is also an officially supported PKGBUILD available. You can grab the PKGBUILD from the GitHub repo. 28 | 29 | -------------------------------------------------------------------------------- /docs/introduction.rst: -------------------------------------------------------------------------------- 1 | .. _introduction: 2 | 3 | Getting Started 4 | =============== 5 | 6 | Simple Example 7 | -------------- 8 | 9 | pyand exposes two classes. ADB and Fastboot. Usage is simple and to verify that everything is set up correctly we can import ADB and Fastboot and check for connected devices:: 10 | 11 | >>> from pyand import ADB, Fastboot 12 | >>> adb = ADB() 13 | >>> adb.get_devices() 14 | {0: '15901aabbccdd124', 1: 'abc1951124de1241'} 15 | >>> adb.set_target_by_id(1) 16 | '[+] Target device set: abc1951124de1241' 17 | >>> adb.get_model() 18 | 'Nexus_5' 19 | >>> adb.set_system_rw() 20 | 'remount succeeded' 21 | >>> adb.reboot(2) 22 | >>> fb = Fastboot() 23 | >>> fb.get_devices() 24 | {0: 'abc1951124de1241'} 25 | 26 | To start we have to import the ADB and Fastboot classes to be able to access the associated methods. As long as the ADB and Fastboot binaries are in your $PATH the above example should work. Otherwise you will have to specify the path to binary when instantiating the object.:: 27 | 28 | >>> adb = ADB('~/android-sdk/platform-tools/adb') 29 | 30 | Furthermore, notice how we set a device target. Since you can have several devices connected it's important for pyand to know which device you want to interact with. 31 | -------------------------------------------------------------------------------- /pyand/ADB.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | 4 | try: 5 | import sys 6 | import subprocess 7 | import re 8 | import platform 9 | from os import popen3 as pipe 10 | except ImportError as e: 11 | print "[!] Required module missing. %s" % e.args[0] 12 | sys.exit(-1) 13 | 14 | 15 | class ADB(object): 16 | 17 | __adb_path = None 18 | __output = None 19 | __error = None 20 | __devices = None 21 | __target = None 22 | 23 | # reboot modes 24 | REBOOT_NORMAL = 0 25 | REBOOT_RECOVERY = 1 26 | REBOOT_BOOTLOADER = 2 27 | 28 | # default TCP/IP port 29 | DEFAULT_TCP_PORT = 5555 30 | # default TCP/IP host 31 | DEFAULT_TCP_HOST = "localhost" 32 | 33 | def __init__(self, adb_path="adb"): 34 | # By default we assume adb is in $PATH 35 | self.__adb_path = adb_path 36 | if not self.check_path(): 37 | self.__error = "[!] adb path not valid" 38 | 39 | def __clean__(self): 40 | self.__output = None 41 | self.__error = None 42 | 43 | def __read_output__(self, fd): 44 | ret = '' 45 | while 1: 46 | line = fd.readline() 47 | if not line: 48 | break 49 | ret += line 50 | 51 | if len(ret) == 0: 52 | ret = None 53 | 54 | return ret 55 | 56 | def __build_command__(self, cmd): 57 | """ 58 | Build command parameters 59 | """ 60 | if self.__devices is not None and len(self.__devices) > 1 and self.__target is None: 61 | self.__error = "[!] Must set target device first" 62 | return None 63 | 64 | if type(cmd) is tuple: 65 | a = list(cmd) 66 | elif type(cmd) is list: 67 | a = cmd 68 | else: 69 | # All arguments must be single list items 70 | a = cmd.split(" ") 71 | 72 | a.insert(0, self.__adb_path) 73 | if self.__target is not None: 74 | # add target device arguments to the command 75 | a.insert(1, '-s') 76 | a.insert(2, self.__target) 77 | 78 | return a 79 | 80 | def run_cmd(self, cmd): 81 | """ 82 | Run a command against the adb tool ($ adb ) 83 | """ 84 | self.__clean__() 85 | 86 | if self.__adb_path is None: 87 | self.__error = "[!] ADB path not set" 88 | return False 89 | 90 | try: 91 | args = self.__build_command__(cmd) 92 | if args is None: 93 | return 94 | cmdp = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 95 | self.__output, self.__error = cmdp.communicate() 96 | retcode = cmdp.wait() 97 | if "device unauthorized" in self.__output: 98 | self.__error = "[-] Device unauthorized" 99 | return False 100 | return self.__output.rstrip('\n') 101 | except OSError, e: 102 | self.__error = str(e) 103 | 104 | return 105 | 106 | def get_version(self): 107 | """ 108 | Returns ADB tool version 109 | adb version 110 | """ 111 | ret = self.run_cmd("version") 112 | try: 113 | pattern = re.compile(r"version\s(.+)") 114 | version = pattern.findall(ret)[0] 115 | except: 116 | version = None 117 | return version 118 | 119 | def check_path(self): 120 | """ 121 | Verify if adb path is valid 122 | """ 123 | 124 | if self.get_version() is None: 125 | print "[-] adb executable not found" 126 | return False 127 | return True 128 | 129 | def set_adb_path(self, adb_path): 130 | """ 131 | Set the ADB tool path 132 | """ 133 | self.__adb_path = adb_path 134 | self.check_path() 135 | 136 | def get_adb_path(self): 137 | """ 138 | Returns the ADB tool path 139 | """ 140 | return self.__adb_path 141 | 142 | def start_server(self): 143 | """ 144 | Starts the ADB server 145 | adb start-server 146 | """ 147 | self.run_cmd('start-server') 148 | return self.__output 149 | 150 | def kill_server(self): 151 | """ 152 | Kills the ADB server 153 | adb kill-server 154 | """ 155 | self.run_cmd('kill-server') 156 | 157 | def restart_server(self): 158 | """ 159 | Restarts the ADB server 160 | """ 161 | self.kill_server() 162 | return self.start_server() 163 | 164 | def restore_file(self, file_name): 165 | """ 166 | Restore device contents from the backup archive 167 | adb restore 168 | """ 169 | self.run_cmd('restore %s' % file_name) 170 | return self.__output 171 | 172 | def wait_for_device(self): 173 | """ 174 | Block operations until device is online 175 | adb wait-for-device 176 | """ 177 | self.run_cmd('wait-for-device') 178 | return self.__output 179 | 180 | def get_help(self): 181 | """ 182 | Returns ADB help 183 | adb help 184 | """ 185 | self.run_cmd('help') 186 | return self.__output 187 | 188 | def get_devices(self): 189 | """ 190 | Return a dictionary of connected devices along with an incremented Id. 191 | adb devices 192 | """ 193 | error = 0 194 | # Clear existing list of devices 195 | self.__devices = None 196 | self.run_cmd("devices") 197 | device_dict = {} 198 | if self.__error is not None: 199 | return None 200 | try: 201 | n = 0 202 | output_list = self.__output.split("\n") 203 | # Split on \r if we are on Windows 204 | if platform.system().lower == "windows": 205 | output_list = self.__output.split("\r") 206 | 207 | for line in output_list: 208 | pattern = re.compile(r"([^\s]+)\t+.+$") 209 | device = pattern.findall(line) 210 | if device: 211 | device_dict[n] = device[0] 212 | n += 1 213 | except: 214 | self.__devices = None 215 | error = 1 216 | self.__devices = device_dict 217 | return self.__devices 218 | 219 | def set_target_by_name(self, device): 220 | """ 221 | Specify the device name to target 222 | example: set_target_device('emulator-5554') 223 | """ 224 | if device is None or self.__devices is None or device not in self.__devices.values(): 225 | 226 | self.__error = 'Must get device list first' 227 | print "[!] Device not found in device list" 228 | return False 229 | self.__target = device 230 | return "[+] Target device set: %s" % self.get_target_device() 231 | 232 | def set_target_by_id(self, device): 233 | """ 234 | Specify the device ID to target. 235 | The ID should be one from the device list. 236 | """ 237 | if device is None or self.__devices is None or device not in self.__devices: 238 | self.__error = 'Must get device list first' 239 | print "[!] Device not found in device list" 240 | return False 241 | self.__target = self.__devices[device] 242 | return "[+] Target device set: %s" % self.get_target_device() 243 | 244 | def get_target_device(self): 245 | """ 246 | Returns the selected device to work with 247 | """ 248 | if self.__target is None: 249 | print "[*] No device target set" 250 | 251 | return self.__target 252 | 253 | def get_state(self): 254 | """ 255 | Get ADB state. Returns either offline | offline | device 256 | adb get-state 257 | """ 258 | return self.run_cmd('get-state') 259 | 260 | def get_model(self): 261 | """ 262 | Get Model name from target device 263 | """ 264 | self.run_cmd("devices -l") 265 | device_model = "" 266 | if self.__error is not None: 267 | return self.__error 268 | try: 269 | for line in self.__output.split("\n"): 270 | if line.startswith(self.__target): 271 | pattern = r"model:(.+)\sdevice" 272 | pat = re.compile(pattern) 273 | device_model = pat.findall(line) 274 | device_model = re.sub("[\[\]\'{\}<>]", '', str(device_model)) 275 | except Exception as e: 276 | return "[-] Error: %s" % e.args[0] 277 | 278 | return device_model 279 | 280 | def get_serialno(self): 281 | """ 282 | Get serialno from target device 283 | adb get-serialno 284 | """ 285 | return self.run_cmd('get-serialno') 286 | 287 | def reboot_device(self,mode=0): 288 | """ 289 | Reboot the target device 290 | Specify mode to reboot normally, recovery or bootloader 291 | adb reboot 292 | """ 293 | if mode not in (self.REBOOT_NORMAL, self.REBOOT_RECOVERY, self.REBOOT_BOOTLOADER): 294 | self.__error = "mode must be REBOOT_NORMAL/REBOOT_RECOVERY/REBOOT_BOOTLOADER" 295 | return self.__output 296 | 297 | cmd_str = "reboot" 298 | if mode == self.REBOOT_RECOVERY: 299 | cmd_str += " recovery" 300 | elif mode == self.REBOOT_BOOTLOADER: 301 | cmd_str += " bootloader" 302 | 303 | return self.run_cmd(cmd_str) 304 | 305 | def set_adb_root(self, mode): 306 | """ 307 | restarts the adbd daemon with root permissions 308 | adb root 309 | """ 310 | return self.run_cmd('root') 311 | 312 | def set_system_rw(self): 313 | """ 314 | Mounts /system as rw 315 | adb remount 316 | """ 317 | self.run_cmd("remount") 318 | return self.__output 319 | 320 | def get_remote_file(self, remote, local): 321 | """ 322 | Pulls a remote file 323 | adb pull remote local 324 | """ 325 | self.run_cmd('pull \"%s\" \"%s\"' % (remote, local)) 326 | if "bytes in" in self.__error: 327 | self.__output = self.__error 328 | self.__error = None 329 | return self.__output 330 | 331 | def push_local_file(self, local, remote): 332 | """ 333 | Push a local file 334 | adb push local remote 335 | """ 336 | self.run_cmd('push \"%s\" \"%s\"' % (local, remote)) 337 | return self.__output 338 | 339 | def shell_command(self,cmd): 340 | """ 341 | Executes a shell command 342 | adb shell 343 | """ 344 | self.run_cmd('shell %s' % cmd) 345 | return self.__output 346 | 347 | def listen_usb(self): 348 | """ 349 | Restarts the adbd daemon listening on USB 350 | adb usb 351 | """ 352 | self.run_cmd("usb") 353 | return self.__output 354 | 355 | def listen_tcp(self,port=DEFAULT_TCP_PORT): 356 | """ 357 | Restarts the adbd daemon listening on the specified port 358 | adb tcpip 359 | """ 360 | self.run_cmd("tcpip %s" % port) 361 | return self.__output 362 | 363 | def get_bugreport(self): 364 | """ 365 | Return all information from the device that should be included in a bug report 366 | adb bugreport 367 | """ 368 | self.run_cmd("bugreport") 369 | return self.__output 370 | 371 | def get_jdwp(self): 372 | """ 373 | List PIDs of processes hosting a JDWP transport 374 | adb jdwp 375 | """ 376 | return self.run_cmd("jdwp") 377 | 378 | def get_logcat(self,lcfilter=""): 379 | """ 380 | View device log 381 | adb logcat 382 | """ 383 | self.run_cmd("logcat %s" % lcfilter) 384 | return self.__output 385 | 386 | def run_emulator(self,cmd=""): 387 | """ 388 | Run emulator console command 389 | """ 390 | self.run_cmd("emu %s" % cmd) 391 | return self.__output 392 | 393 | def connect_remote (self,host=DEFAULT_TCP_HOST,port=DEFAULT_TCP_PORT): 394 | """ 395 | Connect to a device via TCP/IP 396 | adb connect host:port 397 | """ 398 | self.run_cmd("connect %s:%s" % ( host , port ) ) 399 | return self.__output 400 | 401 | def disconnect_remote (self , host=DEFAULT_TCP_HOST , port=DEFAULT_TCP_PORT): 402 | """ 403 | Disconnect from a TCP/IP device 404 | adb disconnect host:port 405 | """ 406 | self.run_cmd("disconnect %s:%s" % ( host , port ) ) 407 | return self.__output 408 | 409 | def ppp_over_usb(self,tty=None,params=""): 410 | """ 411 | Run PPP over USB 412 | adb ppp 413 | """ 414 | if tty is None: 415 | return self.__output 416 | 417 | cmd = "ppp %s" % tty 418 | if params != "": 419 | cmd += " %s" % params 420 | 421 | self.run_cmd(cmd) 422 | return self.__output 423 | 424 | def sync_directory(self,directory=""): 425 | """ 426 | Copy host->device only if changed (-l means list but don't copy) 427 | adb sync 428 | """ 429 | self.run_cmd("sync %s" % directory ) 430 | return self.__output 431 | 432 | def forward_socket(self,local=None,remote=None): 433 | """ 434 | Forward socket connections 435 | adb forward 436 | """ 437 | if local is None or remote is None: 438 | return self.__output 439 | self.run_cmd("forward %s %s" % (local,remote) ) 440 | return self.__output 441 | 442 | def uninstall(self,package=None,keepdata=False): 443 | """ 444 | Remove this app package from the device 445 | adb uninstall [-k] package 446 | """ 447 | if package is None: 448 | return self.__output 449 | cmd = "uninstall %s" % (package if keepdata is True else "-k %s" % package ) 450 | self.run_cmd(cmd) 451 | return self.__output 452 | 453 | def install(self,pkgapp=None,fwdlock=False,reinstall=False,sdcard=False): 454 | """ 455 | Push this package file to the device and install it 456 | adb install [-l] [-r] [-s] 457 | -l -> forward-lock the app 458 | -r -> reinstall the app, keeping its data 459 | -s -> install on sdcard instead of internal storage 460 | """ 461 | 462 | if pkgapp is None: 463 | return self.__output 464 | 465 | cmd = "install" 466 | if fwdlock is True: 467 | cmd += " -l " 468 | if reinstall is True: 469 | cmd += " -r " 470 | if sdcard is True: 471 | cmd += " -s " 472 | 473 | self.run_cmd("%s %s" % (cmd , pkgapp) ) 474 | return self.__output 475 | 476 | def find_binary(self,name=None): 477 | """ 478 | Look for a binary file on the device 479 | """ 480 | 481 | self.shell_command("which %s" % name) 482 | 483 | if self.__output is None: # not found 484 | self.__error = "'%s' was not found" % name 485 | elif self.__output.strip() == "which: not found": # which binary not available 486 | self.__output = None 487 | self.__error = "which binary not found" 488 | else: 489 | self.__output = self.__output.strip() 490 | 491 | return self.__output 492 | 493 | def wake_device(self): 494 | return self.run_cmd('shell input keyevent 26') 495 | 496 | def sideload(self, otapackage=None): 497 | if otapackage is None: 498 | return self.__output 499 | self.run_cmd("sideload %s" % otapackage) 500 | return self.__output 501 | 502 | def get_devpath(self): 503 | return self.run_cmd('get-devpath') 504 | -------------------------------------------------------------------------------- /pyand/Fastboot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | try: 4 | import sys 5 | import subprocess 6 | from os import popen3 as pipe 7 | except ImportError, e: 8 | print "[!] Required module missing. %s" % e.args[0] 9 | sys.exit(-1) 10 | 11 | class Fastboot(object): 12 | 13 | __fastboot_path = None 14 | __output = None 15 | __error = None 16 | __devices = None 17 | __target = None 18 | 19 | def __init__(self, fastboot_path="fastboot"): 20 | """ 21 | By default we assume fastboot is in $PATH. 22 | Alternatively, the path to fasboot can be supplied. 23 | """ 24 | self.__fastboot_path = fastboot_path 25 | if not self.check_path(): 26 | self.__error = "[!] fastboot path not valid." 27 | 28 | def __clean__(self): 29 | self.__output = None 30 | self.__error = None 31 | 32 | def __read_output__(self, fd): 33 | ret = "" 34 | while 1: 35 | line = fd.readline() 36 | if not line: 37 | break 38 | ret += line 39 | 40 | if len(ret) == 0: 41 | ret = None 42 | 43 | return ret 44 | 45 | def __build_command__(self, cmd): 46 | """ 47 | Build command parameters for Fastboot command 48 | """ 49 | if self.__devices is not None and len(self.__devices) > 1 and self.__target is None: 50 | self.__error = "[!] Must set target device first" 51 | return None 52 | 53 | if type(cmd) is tuple: 54 | a = list(cmd) 55 | elif type(cmd) is list: 56 | a = cmd 57 | else: 58 | a = cmd.split(" ") 59 | a.insert(0, self.__fastboot_path) 60 | if self.__target is not None: 61 | # add target device arguments to the command 62 | a.insert(1, '-s') 63 | a.insert(2, self.__target) 64 | 65 | return a 66 | 67 | def run_cmd(self, cmd): 68 | """ 69 | Run a command against the fastboot tool ($ fastboot ) 70 | """ 71 | self.__clean__() 72 | 73 | if self.__fastboot_path is None: 74 | self.__error = "[!] Fastboot path not set" 75 | return False 76 | 77 | try: 78 | args = self.__build_command__(cmd) 79 | if args is None: 80 | return 81 | cmdp = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) 82 | self.__output, self.__error = cmdp.communicate() 83 | retcode = cmdp.wait() 84 | return self.__output 85 | except OSError, e: 86 | self.__error = str(e) 87 | 88 | return 89 | 90 | def check_path(self): 91 | """ 92 | Check if the Fastboot path is valid 93 | """ 94 | if self.run_cmd("help") is None: 95 | print "[-] fastboot executable not found" 96 | return False 97 | return True 98 | 99 | def set_fastboot_path(self, fastboot_path): 100 | """ 101 | Set the Fastboot tool path 102 | """ 103 | self.__fastboot_path = fastboot_path 104 | self.check_path() 105 | 106 | def get_fastboot_path(self): 107 | """ 108 | Returns the Fastboot tool path 109 | """ 110 | return self.__fastboot_path_path 111 | 112 | def get_devices(self): 113 | """ 114 | Return a dictionary of fastboot connected devices along with an incremented Id. 115 | fastboot devices 116 | """ 117 | error = 0 118 | # Clear existing list of devices 119 | self.__devices = None 120 | self.run_cmd("devices") 121 | if self.__error is not None: 122 | return '' 123 | try: 124 | device_list = self.__output.replace('fastboot','').split() 125 | 126 | if device_list[1:] == ['no','permissions']: 127 | error = 2 128 | self.__devices = None 129 | except: 130 | self.__devices = None 131 | error = 1 132 | i = 0 133 | device_dict = {} 134 | for device in device_list: 135 | # Add list to dictionary with incrementing ID 136 | device_dict[i] = device 137 | i += 1 138 | self.__devices = device_dict 139 | return self.__devices 140 | 141 | def set_target_by_name(self, device): 142 | """ 143 | Specify the device name to target 144 | example: set_target_device('emulator-5554') 145 | """ 146 | if device is None or not device in self.__devices.values(): 147 | 148 | self.__error = 'Must get device list first' 149 | print "[!] Device not found in device list" 150 | return False 151 | self.__target = device 152 | return "[+] Target device set: %s" % self.get_target_device() 153 | 154 | def set_target_by_id(self, device): 155 | """ 156 | Specify the device ID to target. 157 | The ID should be one from the device list. 158 | """ 159 | if device is None or not device in self.__devices: 160 | self.__error = 'Must get device list first' 161 | print "[!] Device not found in device list" 162 | return False 163 | self.__target = self.__devices[device] 164 | return "[+] Target device set: %s" % self.get_target_device() 165 | 166 | def get_target_device(self): 167 | """ 168 | Returns the selected device to work with 169 | """ 170 | if self.__target == None: 171 | print "[*] No device target set" 172 | 173 | return self.__target 174 | 175 | def flash_all(self, wipe=False): 176 | """ 177 | flash boot + recovery + system. Optionally wipe everything 178 | """ 179 | if wipe: 180 | self.run_cmd('-w flashall') 181 | else: 182 | self.run_cmd('flashall') 183 | 184 | def format(self, partition): 185 | """ 186 | Format the specified partition 187 | """ 188 | self.run_cmd('format %s' % partition) 189 | return self.__output 190 | 191 | def reboot_device(self): 192 | """ 193 | Reboot the device normally 194 | """ 195 | self.run_cmd('reboot') 196 | return self.__output 197 | 198 | def reboot_device_bootloader(self): 199 | """ 200 | Reboot the device into bootloader 201 | """ 202 | self.run_cmd('reboot-bootloader') 203 | return self.__output 204 | 205 | def oem_unlock(self): 206 | """ 207 | unlock bootloader 208 | """ 209 | self.run_cmd('oem unlock') 210 | return self.__output 211 | 212 | def oem_lock(self): 213 | """ 214 | lock bootloader 215 | """ 216 | self.run_cmd('oem lock') 217 | return self.__output 218 | -------------------------------------------------------------------------------- /pyand/__init__.py: -------------------------------------------------------------------------------- 1 | from ADB import ADB 2 | from Fastboot import Fastboot 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from distutils.core import setup 3 | 4 | def read(fname): 5 | return open(os.path.join(os.path.dirname(__file__), fname)).read() 6 | 7 | setup( 8 | name = "pyand", 9 | version = "0.9.1.2", 10 | author = "ardevd", 11 | author_email = "no-reply@unknown.com", 12 | description = ("Python wrapper for ADB and Fastboot"), 13 | license = "MIT", 14 | keywords = "python android adb fastboot", 15 | url = "https://github.com/ardevd/pyand", 16 | packages=['pyand'], 17 | long_description=read('README.rst'), 18 | classifiers=[ 19 | "Development Status :: 4 - Beta", 20 | "Topic :: Utilities", 21 | "Programming Language :: Python", 22 | ], 23 | ) 24 | --------------------------------------------------------------------------------