# 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 collections 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 _KNOWN_PARTITIONS = collections.OrderedDict([ ('bootloader', {'image': 'bootloader*.img', 'restart': True}), ('radio', {'image': 'radio*.img', 'restart': True}), ('boot', {'image': 'boot.img'}), ('recovery', {'image': 'recovery.img'}), ('system', {'image': 'system.img'}), ('userdata', {'image': 'userdata.img', 'wipe_only': True}), ('cache', {'image': 'cache.img', 'wipe_only': True}), ('vendor', {'image': 'vendor*.img', 'optional': True}), ]) ALL_PARTITIONS = _KNOWN_PARTITIONS.keys() def _FindAndVerifyPartitionsAndImages(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) return_dict = collections.OrderedDict() def find_file(pattern): for filename in files: if fnmatch.fnmatch(filename, pattern): return os.path.join(directory, filename) return None for partition in partitions: partition_info = _KNOWN_PARTITIONS[partition] image_file = find_file(partition_info['image']) if image_file: return_dict[partition] = image_file elif not partition_info.get('optional'): raise device_errors.FastbootCommandFailedError( 'Failed to flash device. Could not find image for %s.', partition_info['image']) return return_dict class FastbootUtils(object): _FASTBOOT_WAIT_TIME = 1 _BOARD_VERIFICATION_FILE = 'android-info.txt' 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, wait_for_reboot=True, 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() if wait_for_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 _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 = _FindAndVerifyPartitionsAndImages(partitions, directory) partitions = flash_image_files.keys() for partition in partitions: if _KNOWN_PARTITIONS[partition].get('wipe_only') 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 _KNOWN_PARTITIONS[partition].get('restart', False): self.Reboot(bootloader=True) @contextlib.contextmanager def FastbootMode(self, wait_for_reboot=True, 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(wait_for_reboot=wait_for_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 # If a device is wiped, then it will no longer have adb keys so it cannot be # communicated with to verify that it is rebooted. It is up to the user of # this script to ensure that the adb keys are set on the device after using # this to wipe a device. with self.FastbootMode(wait_for_reboot=not wipe): self._FlashPartitions(partitions, directory, wipe=wipe)