1*90c8c64dSAndroid Build Coastguard Worker# 2*90c8c64dSAndroid Build Coastguard Worker# Copyright (C) 2016 The Android Open Source Project 3*90c8c64dSAndroid Build Coastguard Worker# 4*90c8c64dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 5*90c8c64dSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 6*90c8c64dSAndroid Build Coastguard Worker# You may obtain a copy of the License at 7*90c8c64dSAndroid Build Coastguard Worker# 8*90c8c64dSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 9*90c8c64dSAndroid Build Coastguard Worker# 10*90c8c64dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 11*90c8c64dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 12*90c8c64dSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13*90c8c64dSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 14*90c8c64dSAndroid Build Coastguard Worker# limitations under the License. 15*90c8c64dSAndroid Build Coastguard Worker# 16*90c8c64dSAndroid Build Coastguard Worker"""Provides functionality to interact with a device via `fastboot`.""" 17*90c8c64dSAndroid Build Coastguard Worker 18*90c8c64dSAndroid Build Coastguard Workerimport os 19*90c8c64dSAndroid Build Coastguard Workerimport re 20*90c8c64dSAndroid Build Coastguard Workerimport subprocess 21*90c8c64dSAndroid Build Coastguard Worker 22*90c8c64dSAndroid Build Coastguard Worker 23*90c8c64dSAndroid Build Coastguard Workerclass FastbootError(Exception): 24*90c8c64dSAndroid Build Coastguard Worker """Something went wrong interacting with fastboot.""" 25*90c8c64dSAndroid Build Coastguard Worker 26*90c8c64dSAndroid Build Coastguard Worker 27*90c8c64dSAndroid Build Coastguard Workerclass FastbootDevice(object): 28*90c8c64dSAndroid Build Coastguard Worker """Class to interact with a fastboot device.""" 29*90c8c64dSAndroid Build Coastguard Worker 30*90c8c64dSAndroid Build Coastguard Worker # Prefix for INFO-type messages when printed by fastboot. If we want 31*90c8c64dSAndroid Build Coastguard Worker # to parse the output from an INFO message we need to strip this off. 32*90c8c64dSAndroid Build Coastguard Worker INFO_PREFIX = '(bootloader) ' 33*90c8c64dSAndroid Build Coastguard Worker 34*90c8c64dSAndroid Build Coastguard Worker def __init__(self, path='fastboot'): 35*90c8c64dSAndroid Build Coastguard Worker """Initialization. 36*90c8c64dSAndroid Build Coastguard Worker 37*90c8c64dSAndroid Build Coastguard Worker Args: 38*90c8c64dSAndroid Build Coastguard Worker path: path to the fastboot executable to test with. 39*90c8c64dSAndroid Build Coastguard Worker 40*90c8c64dSAndroid Build Coastguard Worker Raises: 41*90c8c64dSAndroid Build Coastguard Worker FastbootError: Failed to find a device in fastboot mode. 42*90c8c64dSAndroid Build Coastguard Worker """ 43*90c8c64dSAndroid Build Coastguard Worker self.path = path 44*90c8c64dSAndroid Build Coastguard Worker 45*90c8c64dSAndroid Build Coastguard Worker # Make sure the fastboot executable is available. 46*90c8c64dSAndroid Build Coastguard Worker try: 47*90c8c64dSAndroid Build Coastguard Worker _subprocess_check_output([self.path, '--version']) 48*90c8c64dSAndroid Build Coastguard Worker except OSError: 49*90c8c64dSAndroid Build Coastguard Worker raise FastbootError('Could not execute `{}`'.format(self.path)) 50*90c8c64dSAndroid Build Coastguard Worker 51*90c8c64dSAndroid Build Coastguard Worker # Make sure exactly 1 fastboot device is available if <specific device> 52*90c8c64dSAndroid Build Coastguard Worker # was not given as an argument. Do not try to find an adb device and 53*90c8c64dSAndroid Build Coastguard Worker # put it in fastboot mode, it would be too easy to accidentally 54*90c8c64dSAndroid Build Coastguard Worker # download to the wrong device. 55*90c8c64dSAndroid Build Coastguard Worker if not self._check_single_device(): 56*90c8c64dSAndroid Build Coastguard Worker raise FastbootError('Requires exactly 1 device in fastboot mode') 57*90c8c64dSAndroid Build Coastguard Worker 58*90c8c64dSAndroid Build Coastguard Worker def _check_single_device(self): 59*90c8c64dSAndroid Build Coastguard Worker """Returns True if there is exactly one fastboot device attached. 60*90c8c64dSAndroid Build Coastguard Worker When ANDROID_SERIAL is set it checks that the device is available. 61*90c8c64dSAndroid Build Coastguard Worker """ 62*90c8c64dSAndroid Build Coastguard Worker 63*90c8c64dSAndroid Build Coastguard Worker if 'ANDROID_SERIAL' in os.environ: 64*90c8c64dSAndroid Build Coastguard Worker try: 65*90c8c64dSAndroid Build Coastguard Worker self.getvar('product') 66*90c8c64dSAndroid Build Coastguard Worker return True 67*90c8c64dSAndroid Build Coastguard Worker except subprocess.CalledProcessError: 68*90c8c64dSAndroid Build Coastguard Worker return False 69*90c8c64dSAndroid Build Coastguard Worker devices = _subprocess_check_output([self.path, 'devices']).splitlines() 70*90c8c64dSAndroid Build Coastguard Worker return len(devices) == 1 and devices[0].split()[1] == 'fastboot' 71*90c8c64dSAndroid Build Coastguard Worker 72*90c8c64dSAndroid Build Coastguard Worker def getvar(self, name): 73*90c8c64dSAndroid Build Coastguard Worker """Calls `fastboot getvar`. 74*90c8c64dSAndroid Build Coastguard Worker 75*90c8c64dSAndroid Build Coastguard Worker To query all variables (fastboot getvar all) use getvar_all() 76*90c8c64dSAndroid Build Coastguard Worker instead. 77*90c8c64dSAndroid Build Coastguard Worker 78*90c8c64dSAndroid Build Coastguard Worker Args: 79*90c8c64dSAndroid Build Coastguard Worker name: variable name to access. 80*90c8c64dSAndroid Build Coastguard Worker 81*90c8c64dSAndroid Build Coastguard Worker Returns: 82*90c8c64dSAndroid Build Coastguard Worker String value of variable |name| or None if not found. 83*90c8c64dSAndroid Build Coastguard Worker """ 84*90c8c64dSAndroid Build Coastguard Worker try: 85*90c8c64dSAndroid Build Coastguard Worker output = _subprocess_check_output([self.path, 'getvar', name], 86*90c8c64dSAndroid Build Coastguard Worker stderr=subprocess.STDOUT).splitlines() 87*90c8c64dSAndroid Build Coastguard Worker except subprocess.CalledProcessError: 88*90c8c64dSAndroid Build Coastguard Worker return None 89*90c8c64dSAndroid Build Coastguard Worker # Output format is <name>:<whitespace><value>. 90*90c8c64dSAndroid Build Coastguard Worker out = 0 91*90c8c64dSAndroid Build Coastguard Worker if output[0] == "< waiting for any device >": 92*90c8c64dSAndroid Build Coastguard Worker out = 1 93*90c8c64dSAndroid Build Coastguard Worker result = re.search(r'{}:\s*(.*)'.format(name), output[out]) 94*90c8c64dSAndroid Build Coastguard Worker if result: 95*90c8c64dSAndroid Build Coastguard Worker return result.group(1) 96*90c8c64dSAndroid Build Coastguard Worker else: 97*90c8c64dSAndroid Build Coastguard Worker return None 98*90c8c64dSAndroid Build Coastguard Worker 99*90c8c64dSAndroid Build Coastguard Worker def getvar_all(self): 100*90c8c64dSAndroid Build Coastguard Worker """Calls `fastboot getvar all`. 101*90c8c64dSAndroid Build Coastguard Worker 102*90c8c64dSAndroid Build Coastguard Worker Returns: 103*90c8c64dSAndroid Build Coastguard Worker A {name, value} dictionary of variables. 104*90c8c64dSAndroid Build Coastguard Worker """ 105*90c8c64dSAndroid Build Coastguard Worker output = _subprocess_check_output([self.path, 'getvar', 'all'], 106*90c8c64dSAndroid Build Coastguard Worker stderr=subprocess.STDOUT).splitlines() 107*90c8c64dSAndroid Build Coastguard Worker all_vars = {} 108*90c8c64dSAndroid Build Coastguard Worker for line in output: 109*90c8c64dSAndroid Build Coastguard Worker result = re.search(r'(.*):\s*(.*)', line) 110*90c8c64dSAndroid Build Coastguard Worker if result: 111*90c8c64dSAndroid Build Coastguard Worker var_name = result.group(1) 112*90c8c64dSAndroid Build Coastguard Worker 113*90c8c64dSAndroid Build Coastguard Worker # `getvar all` works by sending one INFO message per variable 114*90c8c64dSAndroid Build Coastguard Worker # so we need to strip out the info prefix string. 115*90c8c64dSAndroid Build Coastguard Worker if var_name.startswith(self.INFO_PREFIX): 116*90c8c64dSAndroid Build Coastguard Worker var_name = var_name[len(self.INFO_PREFIX):] 117*90c8c64dSAndroid Build Coastguard Worker 118*90c8c64dSAndroid Build Coastguard Worker # In addition to returning all variables the bootloader may 119*90c8c64dSAndroid Build Coastguard Worker # also think it's supposed to query a return a variable named 120*90c8c64dSAndroid Build Coastguard Worker # "all", so ignore this line if so. Fastboot also prints a 121*90c8c64dSAndroid Build Coastguard Worker # summary line that we want to ignore. 122*90c8c64dSAndroid Build Coastguard Worker if var_name != 'all' and 'total time' not in var_name: 123*90c8c64dSAndroid Build Coastguard Worker all_vars[var_name] = result.group(2) 124*90c8c64dSAndroid Build Coastguard Worker return all_vars 125*90c8c64dSAndroid Build Coastguard Worker 126*90c8c64dSAndroid Build Coastguard Worker def flashall(self, wipe_user=True, slot=None, skip_secondary=False, quiet=True): 127*90c8c64dSAndroid Build Coastguard Worker """Calls `fastboot [-w] flashall`. 128*90c8c64dSAndroid Build Coastguard Worker 129*90c8c64dSAndroid Build Coastguard Worker Args: 130*90c8c64dSAndroid Build Coastguard Worker wipe_user: whether to set the -w flag or not. 131*90c8c64dSAndroid Build Coastguard Worker slot: slot to flash if device supports A/B, otherwise default will be used. 132*90c8c64dSAndroid Build Coastguard Worker skip_secondary: on A/B devices, flashes only the primary images if true. 133*90c8c64dSAndroid Build Coastguard Worker quiet: True to hide output, false to send it to stdout. 134*90c8c64dSAndroid Build Coastguard Worker """ 135*90c8c64dSAndroid Build Coastguard Worker func = (_subprocess_check_output if quiet else subprocess.check_call) 136*90c8c64dSAndroid Build Coastguard Worker command = [self.path, 'flashall'] 137*90c8c64dSAndroid Build Coastguard Worker if slot: 138*90c8c64dSAndroid Build Coastguard Worker command.extend(['--slot', slot]) 139*90c8c64dSAndroid Build Coastguard Worker if skip_secondary: 140*90c8c64dSAndroid Build Coastguard Worker command.append("--skip-secondary") 141*90c8c64dSAndroid Build Coastguard Worker if wipe_user: 142*90c8c64dSAndroid Build Coastguard Worker command.append('-w') 143*90c8c64dSAndroid Build Coastguard Worker func(command, stderr=subprocess.STDOUT) 144*90c8c64dSAndroid Build Coastguard Worker 145*90c8c64dSAndroid Build Coastguard Worker def flash(self, partition='cache', img=None, slot=None, quiet=True): 146*90c8c64dSAndroid Build Coastguard Worker """Calls `fastboot flash`. 147*90c8c64dSAndroid Build Coastguard Worker 148*90c8c64dSAndroid Build Coastguard Worker Args: 149*90c8c64dSAndroid Build Coastguard Worker partition: which partition to flash. 150*90c8c64dSAndroid Build Coastguard Worker img: path to .img file, otherwise the default will be used. 151*90c8c64dSAndroid Build Coastguard Worker slot: slot to flash if device supports A/B, otherwise default will be used. 152*90c8c64dSAndroid Build Coastguard Worker quiet: True to hide output, false to send it to stdout. 153*90c8c64dSAndroid Build Coastguard Worker """ 154*90c8c64dSAndroid Build Coastguard Worker func = (_subprocess_check_output if quiet else subprocess.check_call) 155*90c8c64dSAndroid Build Coastguard Worker command = [self.path, 'flash', partition] 156*90c8c64dSAndroid Build Coastguard Worker if img: 157*90c8c64dSAndroid Build Coastguard Worker command.append(img) 158*90c8c64dSAndroid Build Coastguard Worker if slot: 159*90c8c64dSAndroid Build Coastguard Worker command.extend(['--slot', slot]) 160*90c8c64dSAndroid Build Coastguard Worker if skip_secondary: 161*90c8c64dSAndroid Build Coastguard Worker command.append("--skip-secondary") 162*90c8c64dSAndroid Build Coastguard Worker func(command, stderr=subprocess.STDOUT) 163*90c8c64dSAndroid Build Coastguard Worker 164*90c8c64dSAndroid Build Coastguard Worker def reboot(self, bootloader=False): 165*90c8c64dSAndroid Build Coastguard Worker """Calls `fastboot reboot [bootloader]`. 166*90c8c64dSAndroid Build Coastguard Worker 167*90c8c64dSAndroid Build Coastguard Worker Args: 168*90c8c64dSAndroid Build Coastguard Worker bootloader: True to reboot back to the bootloader. 169*90c8c64dSAndroid Build Coastguard Worker """ 170*90c8c64dSAndroid Build Coastguard Worker command = [self.path, 'reboot'] 171*90c8c64dSAndroid Build Coastguard Worker if bootloader: 172*90c8c64dSAndroid Build Coastguard Worker command.append('bootloader') 173*90c8c64dSAndroid Build Coastguard Worker _subprocess_check_output(command, stderr=subprocess.STDOUT) 174*90c8c64dSAndroid Build Coastguard Worker 175*90c8c64dSAndroid Build Coastguard Worker def set_active(self, slot): 176*90c8c64dSAndroid Build Coastguard Worker """Calls `fastboot set_active <slot>`. 177*90c8c64dSAndroid Build Coastguard Worker 178*90c8c64dSAndroid Build Coastguard Worker Args: 179*90c8c64dSAndroid Build Coastguard Worker slot: The slot to set as the current slot.""" 180*90c8c64dSAndroid Build Coastguard Worker command = [self.path, 'set_active', slot] 181*90c8c64dSAndroid Build Coastguard Worker _subprocess_check_output(command, stderr=subprocess.STDOUT) 182*90c8c64dSAndroid Build Coastguard Worker 183*90c8c64dSAndroid Build Coastguard Worker# If necessary, modifies subprocess.check_output() or subprocess.Popen() args 184*90c8c64dSAndroid Build Coastguard Worker# to run the subprocess via Windows PowerShell to work-around an issue in 185*90c8c64dSAndroid Build Coastguard Worker# Python 2's subprocess class on Windows where it doesn't support Unicode. 186*90c8c64dSAndroid Build Coastguard Workerdef _get_subprocess_args(args): 187*90c8c64dSAndroid Build Coastguard Worker # Only do this slow work-around if Unicode is in the cmd line on Windows. 188*90c8c64dSAndroid Build Coastguard Worker # PowerShell takes 600-700ms to startup on a 2013-2014 machine, which is 189*90c8c64dSAndroid Build Coastguard Worker # very slow. 190*90c8c64dSAndroid Build Coastguard Worker if os.name != 'nt' or all(not isinstance(arg, unicode) for arg in args[0]): 191*90c8c64dSAndroid Build Coastguard Worker return args 192*90c8c64dSAndroid Build Coastguard Worker 193*90c8c64dSAndroid Build Coastguard Worker def escape_arg(arg): 194*90c8c64dSAndroid Build Coastguard Worker # Escape for the parsing that the C Runtime does in Windows apps. In 195*90c8c64dSAndroid Build Coastguard Worker # particular, this will take care of double-quotes. 196*90c8c64dSAndroid Build Coastguard Worker arg = subprocess.list2cmdline([arg]) 197*90c8c64dSAndroid Build Coastguard Worker # Escape single-quote with another single-quote because we're about 198*90c8c64dSAndroid Build Coastguard Worker # to... 199*90c8c64dSAndroid Build Coastguard Worker arg = arg.replace(u"'", u"''") 200*90c8c64dSAndroid Build Coastguard Worker # ...put the arg in a single-quoted string for PowerShell to parse. 201*90c8c64dSAndroid Build Coastguard Worker arg = u"'" + arg + u"'" 202*90c8c64dSAndroid Build Coastguard Worker return arg 203*90c8c64dSAndroid Build Coastguard Worker 204*90c8c64dSAndroid Build Coastguard Worker # Escape command line args. 205*90c8c64dSAndroid Build Coastguard Worker argv = map(escape_arg, args[0]) 206*90c8c64dSAndroid Build Coastguard Worker # Cause script errors (such as adb not found) to stop script immediately 207*90c8c64dSAndroid Build Coastguard Worker # with an error. 208*90c8c64dSAndroid Build Coastguard Worker ps_code = u'$ErrorActionPreference = "Stop"\r\n' 209*90c8c64dSAndroid Build Coastguard Worker # Add current directory to the PATH var, to match cmd.exe/CreateProcess() 210*90c8c64dSAndroid Build Coastguard Worker # behavior. 211*90c8c64dSAndroid Build Coastguard Worker ps_code += u'$env:Path = ".;" + $env:Path\r\n' 212*90c8c64dSAndroid Build Coastguard Worker # Precede by &, the PowerShell call operator, and separate args by space. 213*90c8c64dSAndroid Build Coastguard Worker ps_code += u'& ' + u' '.join(argv) 214*90c8c64dSAndroid Build Coastguard Worker # Make the PowerShell exit code the exit code of the subprocess. 215*90c8c64dSAndroid Build Coastguard Worker ps_code += u'\r\nExit $LastExitCode' 216*90c8c64dSAndroid Build Coastguard Worker # Encode as UTF-16LE (without Byte-Order-Mark) which Windows natively 217*90c8c64dSAndroid Build Coastguard Worker # understands. 218*90c8c64dSAndroid Build Coastguard Worker ps_code = ps_code.encode('utf-16le') 219*90c8c64dSAndroid Build Coastguard Worker 220*90c8c64dSAndroid Build Coastguard Worker # Encode the PowerShell command as base64 and use the special 221*90c8c64dSAndroid Build Coastguard Worker # -EncodedCommand option that base64 decodes. Base64 is just plain ASCII, 222*90c8c64dSAndroid Build Coastguard Worker # so it should have no problem passing through Win32 CreateProcessA() 223*90c8c64dSAndroid Build Coastguard Worker # (which python erroneously calls instead of CreateProcessW()). 224*90c8c64dSAndroid Build Coastguard Worker return (['powershell.exe', '-NoProfile', '-NonInteractive', 225*90c8c64dSAndroid Build Coastguard Worker '-EncodedCommand', base64.b64encode(ps_code)],) + args[1:] 226*90c8c64dSAndroid Build Coastguard Worker 227*90c8c64dSAndroid Build Coastguard Worker# Call this instead of subprocess.check_output() to work-around issue in Python 228*90c8c64dSAndroid Build Coastguard Worker# 2's subprocess class on Windows where it doesn't support Unicode. 229*90c8c64dSAndroid Build Coastguard Workerdef _subprocess_check_output(*args, **kwargs): 230*90c8c64dSAndroid Build Coastguard Worker try: 231*90c8c64dSAndroid Build Coastguard Worker return subprocess.check_output(*_get_subprocess_args(args), **kwargs) 232*90c8c64dSAndroid Build Coastguard Worker except subprocess.CalledProcessError as e: 233*90c8c64dSAndroid Build Coastguard Worker # Show real command line instead of the powershell.exe command line. 234*90c8c64dSAndroid Build Coastguard Worker raise subprocess.CalledProcessError(e.returncode, args[0], 235*90c8c64dSAndroid Build Coastguard Worker output=e.output) 236