Chromium Code Reviews| Index: build/android/devil/android/fastboot_utils.py |
| diff --git a/build/android/devil/android/fastboot_utils.py b/build/android/devil/android/fastboot_utils.py |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..587f42fd02ce0bec28a2521c5afa9c65d30c0bbc |
| --- /dev/null |
| +++ b/build/android/devil/android/fastboot_utils.py |
| @@ -0,0 +1,245 @@ |
| +# Copyright 2015 The Chromium Authors. All rights reserved. |
| +# Use of this source code is governed by a BSD-style license that can be |
| +# found in the LICENSE file. |
| + |
| +"""Provides a variety of device interactions based on fastboot.""" |
| +# pylint: disable=unused-argument |
| + |
| +import contextlib |
| +import fnmatch |
| +import logging |
| +import os |
| +import re |
| + |
| +from devil.android import decorators |
| +from devil.android import device_errors |
| +from devil.android.sdk import fastboot |
| +from devil.utils import timeout_retry |
| + |
| +_DEFAULT_TIMEOUT = 30 |
| +_DEFAULT_RETRIES = 3 |
| +_FASTBOOT_REBOOT_TIMEOUT = 10 * _DEFAULT_TIMEOUT |
| +ALL_PARTITIONS = [ |
| + 'bootloader', |
| + 'radio', |
| + 'boot', |
| + 'recovery', |
| + 'system', |
| + 'userdata', |
| + 'cache', |
| +] |
| + |
| +class FastbootUtils(object): |
| + |
| + _FASTBOOT_WAIT_TIME = 1 |
| + _RESTART_WHEN_FLASHING = ['bootloader', 'radio'] |
| + _BOARD_VERIFICATION_FILE = 'android-info.txt' |
| + _FLASH_IMAGE_FILES = { |
| + 'bootloader': 'bootloader*.img', |
| + 'radio': 'radio*.img', |
| + 'boot': 'boot.img', |
| + 'recovery': 'recovery.img', |
| + 'system': 'system.img', |
| + 'userdata': 'userdata.img', |
| + 'cache': 'cache.img', |
| + } |
| + |
| + def __init__(self, device, fastbooter=None, default_timeout=_DEFAULT_TIMEOUT, |
| + default_retries=_DEFAULT_RETRIES): |
| + """FastbootUtils constructor. |
| + |
| + Example Usage to flash a device: |
| + fastboot = fastboot_utils.FastbootUtils(device) |
| + fastboot.FlashDevice('/path/to/build/directory') |
| + |
| + Args: |
| + device: A DeviceUtils instance. |
| + fastbooter: Optional fastboot object. If none is passed, one will |
| + be created. |
| + default_timeout: An integer containing the default number of seconds to |
| + wait for an operation to complete if no explicit value is provided. |
| + default_retries: An integer containing the default number or times an |
| + operation should be retried on failure if no explicit value is provided. |
| + """ |
| + self._device = device |
| + self._board = device.product_board |
| + self._serial = str(device) |
| + self._default_timeout = default_timeout |
| + self._default_retries = default_retries |
| + if fastbooter: |
| + self.fastboot = fastbooter |
| + else: |
| + self.fastboot = fastboot.Fastboot(self._serial) |
| + |
| + @decorators.WithTimeoutAndRetriesFromInstance() |
| + def WaitForFastbootMode(self, timeout=None, retries=None): |
| + """Wait for device to boot into fastboot mode. |
| + |
| + This waits for the device serial to show up in fastboot devices output. |
| + """ |
| + def fastboot_mode(): |
| + return self._serial in self.fastboot.Devices() |
| + |
| + timeout_retry.WaitFor(fastboot_mode, wait_period=self._FASTBOOT_WAIT_TIME) |
| + |
| + @decorators.WithTimeoutAndRetriesFromInstance( |
| + min_default_timeout=_FASTBOOT_REBOOT_TIMEOUT) |
| + def EnableFastbootMode(self, timeout=None, retries=None): |
| + """Reboots phone into fastboot mode. |
| + |
| + Roots phone if needed, then reboots phone into fastboot mode and waits. |
| + """ |
| + self._device.EnableRoot() |
| + self._device.adb.Reboot(to_bootloader=True) |
| + self.WaitForFastbootMode() |
| + |
| + @decorators.WithTimeoutAndRetriesFromInstance( |
| + min_default_timeout=_FASTBOOT_REBOOT_TIMEOUT) |
| + def Reboot(self, bootloader=False, timeout=None, retries=None): |
| + """Reboots out of fastboot mode. |
| + |
| + It reboots the phone either back into fastboot, or to a regular boot. It |
| + then blocks until the device is ready. |
| + |
| + Args: |
| + bootloader: If set to True, reboots back into bootloader. |
| + """ |
| + if bootloader: |
| + self.fastboot.RebootBootloader() |
| + self.WaitForFastbootMode() |
| + else: |
| + self.fastboot.Reboot() |
| + self._device.WaitUntilFullyBooted(timeout=_FASTBOOT_REBOOT_TIMEOUT) |
| + |
| + def _VerifyBoard(self, directory): |
| + """Validate as best as possible that the android build matches the device. |
| + |
| + Goes through build files and checks if the board name is mentioned in the |
| + |self._BOARD_VERIFICATION_FILE| or in the build archive. |
| + |
| + Args: |
| + directory: directory where build files are located. |
| + """ |
| + files = os.listdir(directory) |
| + board_regex = re.compile(r'require board=(\w+)') |
| + if self._BOARD_VERIFICATION_FILE in files: |
| + with open(os.path.join(directory, self._BOARD_VERIFICATION_FILE)) as f: |
| + for line in f: |
| + m = board_regex.match(line) |
| + if m: |
| + board_name = m.group(1) |
| + if board_name == self._board: |
| + return True |
| + elif board_name: |
| + return False |
| + else: |
| + logging.warning('No board type found in %s.', |
| + self._BOARD_VERIFICATION_FILE) |
| + else: |
| + logging.warning('%s not found. Unable to use it to verify device.', |
| + self._BOARD_VERIFICATION_FILE) |
| + |
| + zip_regex = re.compile(r'.*%s.*\.zip' % re.escape(self._board)) |
| + for f in files: |
| + if zip_regex.match(f): |
| + return True |
| + |
| + return False |
| + |
| + def _FindAndVerifyPartitionsAndImages(self, partitions, directory): |
| + """Validate partitions and images. |
| + |
| + Validate all partition names and partition directories. Cannot stop mid |
| + flash so its important to validate everything first. |
| + |
| + Args: |
| + Partitions: partitions to be tested. |
| + directory: directory containing the images. |
| + |
| + Returns: |
| + Dictionary with exact partition, image name mapping. |
| + """ |
| + files = os.listdir(directory) |
| + |
| + def find_file(pattern): |
| + for filename in files: |
| + if fnmatch.fnmatch(filename, pattern): |
| + return os.path.join(directory, filename) |
| + raise device_errors.FastbootCommandFailedError( |
| + 'Failed to flash device. Counld not find image for %s.', pattern) |
| + |
| + return {name: find_file(self._FLASH_IMAGE_FILES[name]) |
| + for name in partitions} |
| + |
| + def _FlashPartitions(self, partitions, directory, wipe=False, force=False): |
| + """Flashes all given partiitons with all given images. |
| + |
| + Args: |
| + partitions: List of partitions to flash. |
| + directory: Directory where all partitions can be found. |
| + wipe: If set to true, will automatically detect if cache and userdata |
| + partitions are sent, and if so ignore them. |
| + force: boolean to decide to ignore board name safety checks. |
| + |
| + Raises: |
| + device_errors.CommandFailedError(): If image cannot be found or if bad |
| + partition name is give. |
| + """ |
| + if not self._VerifyBoard(directory): |
| + if force: |
| + logging.warning('Could not verify build is meant to be installed on ' |
| + 'the current device type, but force flag is set. ' |
| + 'Flashing device. Possibly dangerous operation.') |
| + else: |
| + raise device_errors.CommandFailedError( |
| + 'Could not verify build is meant to be installed on the current ' |
| + 'device type. Run again with force=True to force flashing with an ' |
| + 'unverified board.') |
| + |
| + flash_image_files = self._FindAndVerifyPartitionsAndImages(partitions, |
| + directory) |
| + for partition in partitions: |
| + if partition in ['cache', 'userdata'] and not wipe: |
| + logging.info( |
| + 'Not flashing in wipe mode. Skipping partition %s.', partition) |
| + else: |
| + logging.info( |
| + 'Flashing %s with %s', partition, flash_image_files[partition]) |
| + self.fastboot.Flash(partition, flash_image_files[partition]) |
| + if partition in self._RESTART_WHEN_FLASHING: |
| + self.Reboot(bootloader=True) |
|
jbudorick
2015/11/04 22:55:57
This will do multiple reboots in the course of fla
rnephew (Wrong account)
2015/11/04 22:58:42
Yes. After flashing those partitions, a reboot is
jbudorick
2015/11/04 22:59:08
sgtm
|
| + |
| + @contextlib.contextmanager |
| + def FastbootMode(self, timeout=None, retries=None): |
| + """Context manager that enables fastboot mode, and reboots after. |
| + |
| + Example usage: |
| + with FastbootMode(): |
| + Flash Device |
| + # Anything that runs after flashing. |
| + """ |
| + self.EnableFastbootMode() |
| + self.fastboot.SetOemOffModeCharge(False) |
| + try: |
| + yield self |
| + finally: |
| + self.fastboot.SetOemOffModeCharge(True) |
| + self.Reboot() |
| + |
| + def FlashDevice(self, directory, partitions=None, wipe=False): |
| + """Flash device with build in |directory|. |
| + |
| + Directory must contain bootloader, radio, boot, recovery, system, userdata, |
| + and cache .img files from an android build. This is a dangerous operation so |
| + use with care. |
| + |
| + Args: |
| + fastboot: A FastbootUtils instance. |
| + directory: Directory with build files. |
| + wipe: Wipes cache and userdata if set to true. |
| + partitions: List of partitions to flash. Defaults to all. |
| + """ |
| + if partitions is None: |
| + partitions = ALL_PARTITIONS |
| + with self.FastbootMode(): |
| + self._FlashPartitions(partitions, directory, wipe=wipe) |