From 536629676d896885ca26b05ba69f2fb8c4fad702 Mon Sep 17 00:00:00 2001 From: rnephew Date: Wed, 4 Nov 2015 16:13:45 -0800 Subject: [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} --- build/android/PRESUBMIT.py | 1 + build/android/devil/android/device_errors.py | 25 +- build/android/devil/android/device_utils.py | 5 + build/android/devil/android/fastboot_utils.py | 245 ++++++++++++++++++ build/android/devil/android/fastboot_utils_test.py | 281 +++++++++++++++++++++ build/android/devil/android/sdk/fastboot.py | 98 +++++++ 6 files changed, 652 insertions(+), 3 deletions(-) create mode 100644 build/android/devil/android/fastboot_utils.py create mode 100755 build/android/devil/android/fastboot_utils_test.py create mode 100644 build/android/devil/android/sdk/fastboot.py 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))]) -- cgit v1.1