text: # 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
  
  logger = logging.getLogger(__name__)
  
  _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:
                logger.warning('No board type found in %s.',
                               self._BOARD_VERIFICATION_FILE)
      else:
        logger.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:
          logger.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:
          logger.info(
              'Not flashing in wipe mode. Skipping partition %s.', partition)
        else:
          logger.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)
  