Home | History | Annotate | Download | only in tools
      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