1 #!/usr/bin/env python 2 # Copyright 2017 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """A script to open the unlock bootloader on-screen prompt on all devices.""" 7 8 import argparse 9 import logging 10 import os 11 import subprocess 12 import sys 13 import time 14 15 if __name__ == '__main__': 16 sys.path.append( 17 os.path.abspath(os.path.join(os.path.dirname(__file__), 18 '..', '..', '..'))) 19 20 from devil import devil_env 21 from devil.android import device_errors 22 from devil.android.sdk import adb_wrapper 23 from devil.android.sdk import fastboot 24 from devil.android.tools import script_common 25 from devil.utils import parallelizer 26 27 28 def reboot_into_bootloader(filter_devices): 29 # Reboot all devices into bootloader if they aren't there already. 30 rebooted_devices = set() 31 for d in adb_wrapper.AdbWrapper.Devices(desired_state=None): 32 if filter_devices and str(d) not in filter_devices: 33 continue 34 state = d.GetState() 35 if state == 'device': 36 logging.info('Booting %s to bootloader.', d) 37 try: 38 d.Reboot(to_bootloader=True) 39 rebooted_devices.add(str(d)) 40 except (device_errors.AdbCommandFailedError, 41 device_errors.DeviceUnreachableError): 42 logging.exception('Unable to reboot device %s', d) 43 else: 44 logging.error('Unable to reboot device %s: %s', d, state) 45 46 # Wait for the rebooted devices to show up in fastboot. 47 if rebooted_devices: 48 logging.info('Waiting for devices to reboot...') 49 timeout = 60 50 start = time.time() 51 while True: 52 time.sleep(5) 53 fastbooted_devices = set([str(d) for d in fastboot.Fastboot.Devices()]) 54 if rebooted_devices <= set(fastbooted_devices): 55 logging.info('All devices in fastboot.') 56 break 57 if time.time() - start > timeout: 58 logging.error('Timed out waiting for %s to reboot.', 59 rebooted_devices - set(fastbooted_devices)) 60 break 61 62 63 def unlock_bootloader(d): 64 # Unlock the phones. 65 unlocking_processes = [] 66 logging.info('Unlocking %s...', d) 67 # The command to unlock the bootloader could be either of the following 68 # depending on the android version and/or oem. Can't really tell which is 69 # needed, so just try both. 70 # pylint: disable=protected-access 71 cmd_old = [d._fastboot_path.read(), '-s', str(d), 'oem', 'unlock'] 72 cmd_new = [d._fastboot_path.read(), '-s', str(d), 'flashing', 'unlock'] 73 unlocking_processes.append( 74 subprocess.Popen( 75 cmd_old, stdout=subprocess.PIPE, stderr=subprocess.PIPE)) 76 unlocking_processes.append( 77 subprocess.Popen( 78 cmd_new, stdout=subprocess.PIPE, stderr=subprocess.PIPE)) 79 80 # Give the unlocking command time to finish and/or open the on-screen prompt. 81 logging.info('Sleeping for 5 seconds...') 82 time.sleep(5) 83 84 leftover_pids = [] 85 for p in unlocking_processes: 86 p.poll() 87 rc = p.returncode 88 # If the command succesfully opened the unlock prompt on the screen, the 89 # fastboot command process will hang and wait for a response. We still 90 # need to read its stdout/stderr, so use os.read so that we don't 91 # have to wait for EOF to be written. 92 out = os.read(p.stderr.fileno(), 1024).strip().lower() 93 if not rc: 94 if out == '...' or out == '< waiting for device >': 95 logging.info('Device %s is waiting for confirmation.', d) 96 else: 97 logging.error( 98 'Device %s is hanging, but not waiting for confirmation: %s', 99 d, out) 100 leftover_pids.append(p.pid) 101 else: 102 if 'unknown command' in out: 103 # Of the two unlocking commands, this was likely the wrong one. 104 continue 105 elif 'already unlocked' in out: 106 logging.info('Device %s already unlocked.', d) 107 elif 'unlock is not allowed' in out: 108 logging.error("Device %s is oem locked. Can't unlock bootloader.", d) 109 else: 110 logging.error('Device %s in unknown state: "%s"', d, out) 111 break 112 113 if leftover_pids: 114 logging.warning('Processes %s left over after unlocking.', leftover_pids) 115 116 return 0 117 118 119 def main(): 120 logging.getLogger().setLevel(logging.INFO) 121 122 parser = argparse.ArgumentParser() 123 script_common.AddDeviceArguments(parser) 124 parser.add_argument('--adb-path', 125 help='Absolute path to the adb binary to use.') 126 args = parser.parse_args() 127 128 devil_dynamic_config = devil_env.EmptyConfig() 129 if args.adb_path: 130 devil_dynamic_config['dependencies'].update( 131 devil_env.LocalConfigItem( 132 'adb', devil_env.GetPlatform(), args.adb_path)) 133 devil_env.config.Initialize(configs=[devil_dynamic_config]) 134 135 reboot_into_bootloader(args.devices) 136 devices = [ 137 d for d in fastboot.Fastboot.Devices() if not args.devices or 138 str(d) in args.devices] 139 parallel_devices = parallelizer.Parallelizer(devices) 140 parallel_devices.pMap(unlock_bootloader).pGet(None) 141 return 0 142 143 144 if __name__ == '__main__': 145 sys.exit(main()) 146