summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrnephew <rnephew@chromium.org>2015-11-04 16:13:45 -0800
committerCommit bot <commit-bot@chromium.org>2015-11-05 00:14:41 +0000
commit536629676d896885ca26b05ba69f2fb8c4fad702 (patch)
treeebe5cd9f0f2d5cf4d7fe79f87b35cfaa6b66e107
parent6e86a1acac5d494c6e2df6bbd044dfe4539428d1 (diff)
downloadchromium_src-536629676d896885ca26b05ba69f2fb8c4fad702.zip
chromium_src-536629676d896885ca26b05ba69f2fb8c4fad702.tar.gz
chromium_src-536629676d896885ca26b05ba69f2fb8c4fad702.tar.bz2
[Android] Add functionality to flash devices.
This requires adding fastboot_utils to put a device into fastboot mode and interact with it while in fastboot mode. BUG=543257 Review URL: https://codereview.chromium.org/1417373002 Cr-Commit-Position: refs/heads/master@{#357939}
-rw-r--r--build/android/PRESUBMIT.py1
-rw-r--r--build/android/devil/android/device_errors.py25
-rw-r--r--build/android/devil/android/device_utils.py5
-rw-r--r--build/android/devil/android/fastboot_utils.py245
-rwxr-xr-xbuild/android/devil/android/fastboot_utils_test.py281
-rw-r--r--build/android/devil/android/sdk/fastboot.py98
6 files changed, 652 insertions, 3 deletions
diff --git a/build/android/PRESUBMIT.py b/build/android/PRESUBMIT.py
index c97194f..27a04f9 100644
--- a/build/android/PRESUBMIT.py
+++ b/build/android/PRESUBMIT.py
@@ -48,6 +48,7 @@ def CommonChecks(input_api, output_api):
output_api,
unit_tests=[
J('.', 'emma_coverage_stats_test.py'),
+ J('devil', 'android', 'fastboot_utils_test.py'),
J('devil', 'android', 'battery_utils_test.py'),
J('devil', 'android', 'device_utils_test.py'),
J('devil', 'android', 'md5sum_test.py'),
diff --git a/build/android/devil/android/device_errors.py b/build/android/devil/android/device_errors.py
index fde7d1a..ef6e3a9 100644
--- a/build/android/devil/android/device_errors.py
+++ b/build/android/devil/android/device_errors.py
@@ -20,8 +20,8 @@ class CommandFailedError(base_error.BaseError):
super(CommandFailedError, self).__init__(message)
-class AdbCommandFailedError(CommandFailedError):
- """Exception for adb command failures."""
+class _BaseCommandFailedError(CommandFailedError):
+ """Base Exception for adb and fastboot command failures."""
def __init__(self, args, output, status=None, device_serial=None,
message=None):
@@ -39,7 +39,26 @@ class AdbCommandFailedError(CommandFailedError):
else:
message.append('and no output.')
message = ''.join(message)
- super(AdbCommandFailedError, self).__init__(message, device_serial)
+ super(_BaseCommandFailedError, self).__init__(message, device_serial)
+
+class AdbCommandFailedError(_BaseCommandFailedError):
+ """Exception for adb command failures."""
+
+ def __init__(self, args, output, status=None, device_serial=None,
+ message=None):
+ super(AdbCommandFailedError, self).__init__(
+ args, output, status=status, message=message,
+ device_serial=device_serial)
+
+
+class FastbootCommandFailedError(_BaseCommandFailedError):
+ """Exception for fastboot command failures."""
+
+ def __init__(self, args, output, status=None, device_serial=None,
+ message=None):
+ super(FastbootCommandFailedError, self).__init__(
+ args, output, status=status, message=message,
+ device_serial=device_serial)
class DeviceVersionError(CommandFailedError):
diff --git a/build/android/devil/android/device_utils.py b/build/android/devil/android/device_utils.py
index 66485c5..00b520d 100644
--- a/build/android/devil/android/device_utils.py
+++ b/build/android/devil/android/device_utils.py
@@ -1699,6 +1699,11 @@ class DeviceUtils(object):
"""Returns the product name of the device (e.g. 'nakasi')."""
return self.GetProp('ro.product.name', cache=True)
+ @property
+ def product_board(self):
+ """Returns the product board name of the device (e.g. 'shamu')."""
+ return self.GetProp('ro.product.board', cache=True)
+
def GetProp(self, property_name, cache=False, timeout=DEFAULT,
retries=DEFAULT):
"""Gets a property from the device.
diff --git a/build/android/devil/android/fastboot_utils.py b/build/android/devil/android/fastboot_utils.py
new file mode 100644
index 0000000..587f42f
--- /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)
+
+ @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)
diff --git a/build/android/devil/android/fastboot_utils_test.py b/build/android/devil/android/fastboot_utils_test.py
new file mode 100755
index 0000000..ff2c501
--- /dev/null
+++ b/build/android/devil/android/fastboot_utils_test.py
@@ -0,0 +1,281 @@
+#!/usr/bin/env python
+# 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.
+
+"""
+Unit tests for the contents of fastboot_utils.py
+"""
+
+# pylint: disable=protected-access,unused-argument
+
+import io
+import logging
+import os
+import sys
+import unittest
+
+from devil.android import device_errors
+from devil.android import device_utils
+from devil.android import fastboot_utils
+from devil.android.sdk import fastboot
+from devil.utils import mock_calls
+from pylib import constants
+
+sys.path.append(os.path.join(
+ constants.DIR_SOURCE_ROOT, 'third_party', 'pymock'))
+import mock # pylint: disable=import-error
+
+_BOARD = 'board_type'
+_SERIAL = '0123456789abcdef'
+_PARTITIONS = ['cache', 'userdata', 'system', 'bootloader', 'radio']
+_IMAGES = {
+ 'cache': 'cache.img',
+ 'userdata': 'userdata.img',
+ 'system': 'system.img',
+ 'bootloader': 'bootloader.img',
+ 'radio': 'radio.img',
+}
+_VALID_FILES = [_BOARD + '.zip', 'android-info.txt']
+_INVALID_FILES = ['test.zip', 'android-info.txt']
+
+
+class MockFile(object):
+
+ def __init__(self, name='/tmp/some/file'):
+ self.file = mock.MagicMock(spec=file)
+ self.file.name = name
+
+ def __enter__(self):
+ return self.file
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ pass
+
+ @property
+ def name(self):
+ return self.file.name
+
+
+def _FastbootWrapperMock(test_serial):
+ fastbooter = mock.Mock(spec=fastboot.Fastboot)
+ fastbooter.__str__ = mock.Mock(return_value=test_serial)
+ fastbooter.Devices.return_value = [test_serial]
+ return fastbooter
+
+def _DeviceUtilsMock(test_serial):
+ device = mock.Mock(spec=device_utils.DeviceUtils)
+ device.__str__ = mock.Mock(return_value=test_serial)
+ device.product_board = mock.Mock(return_value=_BOARD)
+ device.adb = mock.Mock()
+ return device
+
+
+class FastbootUtilsTest(mock_calls.TestCase):
+
+ def setUp(self):
+ self.device_utils_mock = _DeviceUtilsMock(_SERIAL)
+ self.fastboot_wrapper = _FastbootWrapperMock(_SERIAL)
+ self.fastboot = fastboot_utils.FastbootUtils(
+ self.device_utils_mock, fastbooter=self.fastboot_wrapper,
+ default_timeout=2, default_retries=0)
+ self.fastboot._board = _BOARD
+
+
+class FastbootUtilsInitTest(FastbootUtilsTest):
+
+ def testInitWithDeviceUtil(self):
+ f = fastboot_utils.FastbootUtils(self.device_utils_mock)
+ self.assertEqual(str(self.device_utils_mock), str(f._device))
+
+ def testInitWithMissing_fails(self):
+ with self.assertRaises(AttributeError):
+ fastboot_utils.FastbootUtils(None)
+ with self.assertRaises(AttributeError):
+ fastboot_utils.FastbootUtils('')
+
+
+class FastbootUtilsWaitForFastbootMode(FastbootUtilsTest):
+
+ # If this test fails by timing out after 1 second.
+ @mock.patch('time.sleep', mock.Mock())
+ def testWaitForFastbootMode(self):
+ self.fastboot.WaitForFastbootMode()
+
+
+class FastbootUtilsEnableFastbootMode(FastbootUtilsTest):
+
+ def testEnableFastbootMode(self):
+ with self.assertCalls(
+ self.call.fastboot._device.EnableRoot(),
+ self.call.fastboot._device.adb.Reboot(to_bootloader=True),
+ self.call.fastboot.WaitForFastbootMode()):
+ self.fastboot.EnableFastbootMode()
+
+
+class FastbootUtilsReboot(FastbootUtilsTest):
+
+ def testReboot_bootloader(self):
+ with self.assertCalls(
+ self.call.fastboot.fastboot.RebootBootloader(),
+ self.call.fastboot.WaitForFastbootMode()):
+ self.fastboot.Reboot(bootloader=True)
+
+ def testReboot_normal(self):
+ with self.assertCalls(
+ self.call.fastboot.fastboot.Reboot(),
+ self.call.fastboot._device.WaitUntilFullyBooted(timeout=mock.ANY)):
+ self.fastboot.Reboot()
+
+
+class FastbootUtilsFlashPartitions(FastbootUtilsTest):
+
+ def testFlashPartitions_wipe(self):
+ with self.assertCalls(
+ (self.call.fastboot._VerifyBoard('test'), True),
+ (self.call.fastboot._FindAndVerifyPartitionsAndImages(
+ _PARTITIONS, 'test'), _IMAGES),
+ (self.call.fastboot.fastboot.Flash('cache', 'cache.img')),
+ (self.call.fastboot.fastboot.Flash('userdata', 'userdata.img')),
+ (self.call.fastboot.fastboot.Flash('system', 'system.img')),
+ (self.call.fastboot.fastboot.Flash('bootloader', 'bootloader.img')),
+ (self.call.fastboot.Reboot(bootloader=True)),
+ (self.call.fastboot.fastboot.Flash('radio', 'radio.img')),
+ (self.call.fastboot.Reboot(bootloader=True))):
+ self.fastboot._FlashPartitions(_PARTITIONS, 'test', wipe=True)
+
+ def testFlashPartitions_noWipe(self):
+ with self.assertCalls(
+ (self.call.fastboot._VerifyBoard('test'), True),
+ (self.call.fastboot._FindAndVerifyPartitionsAndImages(
+ _PARTITIONS, 'test'), _IMAGES),
+ (self.call.fastboot.fastboot.Flash('system', 'system.img')),
+ (self.call.fastboot.fastboot.Flash('bootloader', 'bootloader.img')),
+ (self.call.fastboot.Reboot(bootloader=True)),
+ (self.call.fastboot.fastboot.Flash('radio', 'radio.img')),
+ (self.call.fastboot.Reboot(bootloader=True))):
+ self.fastboot._FlashPartitions(_PARTITIONS, 'test')
+
+
+class FastbootUtilsFastbootMode(FastbootUtilsTest):
+
+ def testFastbootMode_good(self):
+ with self.assertCalls(
+ self.call.fastboot.EnableFastbootMode(),
+ self.call.fastboot.fastboot.SetOemOffModeCharge(False),
+ self.call.fastboot.fastboot.SetOemOffModeCharge(True),
+ self.call.fastboot.Reboot()):
+ with self.fastboot.FastbootMode() as fbm:
+ self.assertEqual(self.fastboot, fbm)
+
+ def testFastbootMode_exception(self):
+ with self.assertCalls(
+ self.call.fastboot.EnableFastbootMode(),
+ self.call.fastboot.fastboot.SetOemOffModeCharge(False),
+ self.call.fastboot.fastboot.SetOemOffModeCharge(True),
+ self.call.fastboot.Reboot()):
+ with self.assertRaises(NotImplementedError):
+ with self.fastboot.FastbootMode() as fbm:
+ self.assertEqual(self.fastboot, fbm)
+ raise NotImplementedError
+
+ def testFastbootMode_exceptionInEnableFastboot(self):
+ self.fastboot.EnableFastbootMode = mock.Mock()
+ self.fastboot.EnableFastbootMode.side_effect = NotImplementedError
+ with self.assertRaises(NotImplementedError):
+ with self.fastboot.FastbootMode():
+ pass
+
+
+class FastbootUtilsVerifyBoard(FastbootUtilsTest):
+
+ def testVerifyBoard_bothValid(self):
+ mock_file = io.StringIO(u'require board=%s\n' % _BOARD)
+ with mock.patch('__builtin__.open', return_value=mock_file, create=True):
+ with mock.patch('os.listdir', return_value=_VALID_FILES):
+ self.assertTrue(self.fastboot._VerifyBoard('test'))
+
+ def testVerifyBoard_BothNotValid(self):
+ mock_file = io.StringIO(u'abc')
+ with mock.patch('__builtin__.open', return_value=mock_file, create=True):
+ with mock.patch('os.listdir', return_value=_INVALID_FILES):
+ self.assertFalse(self.assertFalse(self.fastboot._VerifyBoard('test')))
+
+ def testVerifyBoard_FileNotFoundZipValid(self):
+ with mock.patch('os.listdir', return_value=[_BOARD + '.zip']):
+ self.assertTrue(self.fastboot._VerifyBoard('test'))
+
+ def testVerifyBoard_ZipNotFoundFileValid(self):
+ mock_file = io.StringIO(u'require board=%s\n' % _BOARD)
+ with mock.patch('__builtin__.open', return_value=mock_file, create=True):
+ with mock.patch('os.listdir', return_value=['android-info.txt']):
+ self.assertTrue(self.fastboot._VerifyBoard('test'))
+
+ def testVerifyBoard_zipNotValidFileIs(self):
+ mock_file = io.StringIO(u'require board=%s\n' % _BOARD)
+ with mock.patch('__builtin__.open', return_value=mock_file, create=True):
+ with mock.patch('os.listdir', return_value=_INVALID_FILES):
+ self.assertTrue(self.fastboot._VerifyBoard('test'))
+
+ def testVerifyBoard_fileNotValidZipIs(self):
+ mock_file = io.StringIO(u'require board=WrongBoard')
+ with mock.patch('__builtin__.open', return_value=mock_file, create=True):
+ with mock.patch('os.listdir', return_value=_VALID_FILES):
+ self.assertFalse(self.fastboot._VerifyBoard('test'))
+
+ def testVerifyBoard_noBoardInFileValidZip(self):
+ mock_file = io.StringIO(u'Regex wont match')
+ with mock.patch('__builtin__.open', return_value=mock_file, create=True):
+ with mock.patch('os.listdir', return_value=_VALID_FILES):
+ self.assertTrue(self.fastboot._VerifyBoard('test'))
+
+ def testVerifyBoard_noBoardInFileInvalidZip(self):
+ mock_file = io.StringIO(u'Regex wont match')
+ with mock.patch('__builtin__.open', return_value=mock_file, create=True):
+ with mock.patch('os.listdir', return_value=_INVALID_FILES):
+ self.assertFalse(self.fastboot._VerifyBoard('test'))
+
+class FastbootUtilsFindAndVerifyPartitionsAndImages(FastbootUtilsTest):
+
+ def testFindAndVerifyPartitionsAndImages_valid(self):
+ PARTITIONS = [
+ 'bootloader', 'radio', 'boot', 'recovery', 'system', 'userdata', 'cache'
+ ]
+ files = [
+ 'bootloader-test-.img',
+ 'radio123.img',
+ 'boot.img',
+ 'recovery.img',
+ 'system.img',
+ 'userdata.img',
+ 'cache.img'
+ ]
+ return_check = {
+ 'bootloader': 'test/bootloader-test-.img',
+ 'radio': 'test/radio123.img',
+ 'boot': 'test/boot.img',
+ 'recovery': 'test/recovery.img',
+ 'system': 'test/system.img',
+ 'userdata': 'test/userdata.img',
+ 'cache': 'test/cache.img',
+ }
+
+ with mock.patch('os.listdir', return_value=files):
+ return_value = self.fastboot._FindAndVerifyPartitionsAndImages(
+ PARTITIONS, 'test')
+ self.assertDictEqual(return_value, return_check)
+
+ def testFindAndVerifyPartitionsAndImages_badPartition(self):
+ with mock.patch('os.listdir', return_value=['test']):
+ with self.assertRaises(KeyError):
+ self.fastboot._FindAndVerifyPartitionsAndImages(['test'], 'test')
+
+ def testFindAndVerifyPartitionsAndImages_noFile(self):
+ with mock.patch('os.listdir', return_value=['test']):
+ with self.assertRaises(device_errors.FastbootCommandFailedError):
+ self.fastboot._FindAndVerifyPartitionsAndImages(['cache'], 'test')
+
+
+if __name__ == '__main__':
+ logging.getLogger().setLevel(logging.DEBUG)
+ unittest.main(verbosity=2)
diff --git a/build/android/devil/android/sdk/fastboot.py b/build/android/devil/android/sdk/fastboot.py
new file mode 100644
index 0000000..cfc566a
--- /dev/null
+++ b/build/android/devil/android/sdk/fastboot.py
@@ -0,0 +1,98 @@
+# 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.
+
+"""This module wraps Android's fastboot tool.
+
+This is a thin wrapper around the fastboot interface. Any additional complexity
+should be delegated to a higher level (ex. FastbootUtils).
+"""
+# pylint: disable=unused-argument
+
+import os
+
+from devil.android import decorators
+from devil.android import device_errors
+from devil.utils import cmd_helper
+from pylib import constants
+
+_FASTBOOT_CMD = os.path.join(
+ constants.ANDROID_SDK_ROOT, 'platform-tools', 'fastboot')
+_DEFAULT_TIMEOUT = 30
+_DEFAULT_RETRIES = 3
+_FLASH_TIMEOUT = _DEFAULT_TIMEOUT * 10
+
+class Fastboot(object):
+
+ def __init__(self, device_serial, default_timeout=_DEFAULT_TIMEOUT,
+ default_retries=_DEFAULT_RETRIES):
+ """Initializes the FastbootWrapper.
+
+ Args:
+ device_serial: The device serial number as a string.
+ """
+ if not device_serial:
+ raise ValueError('A device serial must be specified')
+ self._device_serial = str(device_serial)
+ self._default_timeout = default_timeout
+ self._default_retries = default_retries
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def _RunFastbootCommand(self, cmd, timeout=None, retries=None):
+ """Run a command line command using the fastboot android tool.
+
+ Args:
+ cmd: Command to run. Must be list of args, the first one being the command
+
+ Returns:
+ output of command.
+
+ Raises:
+ TypeError: If cmd is not of type list.
+ """
+ if type(cmd) == list:
+ cmd = [_FASTBOOT_CMD, '-s', self._device_serial] + cmd
+ else:
+ raise TypeError(
+ 'Command for _RunFastbootCommand must be a list.')
+ status, output = cmd_helper.GetCmdStatusAndOutput(cmd)
+ if int(status) != 0:
+ raise device_errors.FastbootCommandFailedError(
+ cmd, output, status, self._device_serial)
+ return output
+
+ @decorators.WithTimeoutAndRetriesDefaults(_FLASH_TIMEOUT, 0)
+ def Flash(self, partition, image, timeout=None, retries=None):
+ """Flash partition with img.
+
+ Args:
+ partition: Partition to be flashed.
+ image: location of image to flash with.
+ """
+ self._RunFastbootCommand(['flash', partition, image])
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def Devices(self, timeout=None, retries=None):
+ """Outputs list of devices in fastboot mode."""
+ output = self._RunFastbootCommand(['devices'])
+ return [line.split()[0] for line in output.splitlines()]
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def RebootBootloader(self, timeout=None, retries=None):
+ """Reboot from fastboot, into fastboot."""
+ self._RunFastbootCommand(['reboot-bootloader'])
+
+ @decorators.WithTimeoutAndRetriesDefaults(_FLASH_TIMEOUT, 0)
+ def Reboot(self, timeout=None, retries=None):
+ """Reboot from fastboot to normal usage"""
+ self._RunFastbootCommand(['reboot'])
+
+ @decorators.WithTimeoutAndRetriesFromInstance()
+ def SetOemOffModeCharge(self, value, timeout=None, retries=None):
+ """Sets off mode charging
+
+ Args:
+ value: boolean value to set off-mode-charging on or off.
+ """
+ self._RunFastbootCommand(
+ ['oem', 'off-mode-charge', str(int(value))])