#
# Copyright (C) 2017 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Class to flash build artifacts onto devices"""
import hashlib
import logging
import os
import resource
import sys
import tempfile
import time
from host_controller import common
from vts.utils.python.common import cmd_utils
from vts.utils.python.controllers import android_device
class BuildFlasher(object):
"""Client that manages build flashing.
Attributes:
device: AndroidDevice, the device associated with the client.
"""
def __init__(self, serial="", customflasher_path=""):
"""Initialize the client.
If serial not provided, find single device connected. Error if
zero or > 1 devices connected.
Args:
serial: optional string, serial number for the device.
customflasher_path: optional string, set device to use specified
binary to flash a device
"""
if serial != "":
self.device = android_device.AndroidDevice(
serial, device_callback_port=-1)
else:
serials = android_device.list_adb_devices()
if len(serials) == 0:
serials = android_device.list_fastboot_devices()
if len(serials) == 0:
raise android_device.AndroidDeviceError(
"ADB and fastboot could not find any target devices.")
if len(serials) > 1:
logging.info("ADB or fastboot found more than one device: %s",
serials)
self.device = android_device.AndroidDevice(
serials[0], device_callback_port=-1)
if customflasher_path:
self.device.SetCustomFlasherPath(customflasher_path)
def SetSerial(self, serial):
"""Sets device serial.
Args:
serial: string, a device serial.
Returns:
True if successful; False otherwise.
"""
if not serial:
logging.error("no serial is given to BuildFlasher.SetSerial.")
return False
self.device = android_device.AndroidDevice(
serial, device_callback_port=-1)
return True
def FlashGSI(self,
system_img,
vbmeta_img=None,
skip_check=False,
skip_vbmeta=False):
"""Flash the Generic System Image to the device.
Args:
system_img: string, path to GSI
vbmeta_img: string, optional, path to vbmeta image for new devices
skip_check: boolean, set True to skip adb-based checks when
the DUT is already running its bootloader.
skip_vbmeta: bool, whether to skip flashing the vbmeta.img or not.
If the device has the vbmeta slot then flash vbmeta.img
even if the skip_vbmeta is set to True.
"""
if not os.path.exists(system_img):
raise ValueError("Couldn't find system image at %s" % system_img)
if not skip_check:
self.device.adb.wait_for_device()
if not self.device.isBootloaderMode:
self.device.log.info(self.device.adb.reboot_bootloader())
if vbmeta_img is not None:
if skip_vbmeta == False or self.device.hasVbmetaSlot:
self.device.log.info(
self.device.fastboot.flash('vbmeta', vbmeta_img))
self.device.log.info(self.device.fastboot.erase('system'))
self.device.log.info(self.device.fastboot.flash('system', system_img))
self.device.log.info(self.device.fastboot.erase('metadata'))
self.device.log.info(self.device.fastboot._w())
self.device.log.info(self.device.fastboot.reboot())
def Flashall(self, directory):
"""Flash all images in a directory to the device using flashall.
Generally the directory is the result of unzipping the .zip from AB.
Args:
directory: string, path to directory containing images
"""
# fastboot flashall looks for imgs in $ANDROID_PRODUCT_OUT
os.environ['ANDROID_PRODUCT_OUT'] = directory
self.device.adb.wait_for_device()
if not self.device.isBootloaderMode:
self.device.log.info(self.device.adb.reboot_bootloader())
self.device.log.info(self.device.fastboot.flashall())
def Flash(self, device_images, skip_vbmeta=False):
"""Flash the Generic System Image to the device.
Args:
device_images: dict, where the key is partition name and value is
image file path.
skip_vbmeta: bool, whether to skip flashing the vbmeta.img or not.
Returns:
True if succesful; False otherwise
"""
if not device_images:
logging.warn("Flash skipped because no device image is given.")
return False
if not self.device.isBootloaderMode:
self.device.adb.wait_for_device()
logging.info("rebooting to bootloader")
self.device.log.info(self.device.adb.reboot_bootloader())
logging.info("checking to flash bootloader.img and radio.img")
for partition in ["bootloader", "radio"]:
if partition in device_images:
image_path = device_images[partition]
self.device.log.info("fastboot flash %s %s", partition,
image_path)
self.device.log.info(
self.device.fastboot.flash(partition, image_path))
self.device.log.info("fastboot reboot_bootloader")
self.device.log.info(self.device.fastboot.reboot_bootloader())
logging.info("starting to flash vendor and other images...")
full_zipfile = False
if common.FULL_ZIPFILE in device_images:
logging.info("fastboot update %s --skip-reboot",
(device_images[common.FULL_ZIPFILE]))
self.device.log.info(
self.device.fastboot.update(device_images[common.FULL_ZIPFILE],
"--skip-reboot"))
full_zipfile = True
for partition, image_path in device_images.iteritems():
if partition in (common.FULL_ZIPFILE, common.FULL_ZIPFILE_DIR,
"system", "vbmeta", "bootloader", "radio",
"metadata", "userdata"):
continue
if full_zipfile and partition in ("vendor", "boot"):
logging.info("%s skipped because full zipfile was updated.",
partition)
continue
if not image_path:
self.device.log.warning("%s image is empty", partition)
continue
self.device.log.info("fastboot flash %s %s", partition, image_path)
self.device.log.info(
self.device.fastboot.flash(partition, image_path))
logging.info("starting to flash system and other images...")
if "system" in device_images and device_images["system"]:
system_img = device_images["system"]
vbmeta_img = device_images["vbmeta"] if (
"vbmeta" in device_images
and device_images["vbmeta"]) else None
self.FlashGSI(
system_img,
vbmeta_img,
skip_check=True,
skip_vbmeta=skip_vbmeta)
else:
self.device.log.info(self.device.fastboot.reboot())
return True
def FlashImage(self, device_images, image_partition=None, reboot=False):
"""Flash specified image(s) to the device.
Args:
device_images: dict, where the key is partition name and value is
image file path.
image_partition: string, set to flash only an image in a specified
partition.
reboot: boolean, true to reboot the device.
Returns:
True if successful, False otherwise
"""
if not device_images:
logging.warn("Flash skipped because no device image is given.")
return False
if not self.device.isBootloaderMode:
self.device.adb.wait_for_device()
self.device.log.info(self.device.adb.reboot_bootloader())
for partition, image_path in device_images.iteritems():
if image_partition and image_partition != partition:
continue
if partition.endswith(".img"):
partition = partition[:-4]
self.device.log.info(
self.device.fastboot.flash(partition, image_path))
if reboot:
self.device.log.info(self.device.fastboot.reboot())
return True
def WaitForDevice(self, timeout_secs=600):
"""Waits for the device to boot completely.
Args:
timeout_secs: integer, the maximum timeout value for this
operation (unit: seconds).
Returns:
True if device is booted successfully; False otherwise.
"""
return self.device.waitForBootCompletion(timeout=timeout_secs)
def FlashUsingCustomBinary(self,
device_images,
reboot_mode,
flasher_args,
timeout_secs_for_reboot=900):
"""Flash the customized image to the device.
Args:
device_images: dict, where the key is partition name and value is
image file path.
reboot_mode: string, decides which mode device will reboot into.
("bootloader"/"download").
flasher_args: list of strings, arguments that will be passed to the
flash binary.
timeout_secs_for_reboot: integer, the maximum timeout value for
reboot to flash-able mode(unit: seconds).
Returns:
True if successful; False otherwise.
"""
if not device_images:
logging.warn("Flash skipped because no device image is given.")
return False
if not flasher_args:
logging.error("No arguments.")
return False
if not self.device.isBootloaderMode:
self.device.adb.wait_for_device()
logging.info("rebooting to %s mode", reboot_mode)
self.device.log.info(self.device.adb.reboot(reboot_mode))
start = time.time()
while not self.device.customflasher._l():
if time.time() - start >= timeout_secs_for_reboot:
logging.error(
"Timeout while waiting for %s mode boot completion.",
reboot_mode)
return False
time.sleep(1)
flasher_output = self.device.customflasher.ExecCustomFlasherCmd(
flasher_args[0],
" ".join(flasher_args[1:] + [device_images["img"]]))
self.device.log.info(flasher_output)
return True
def RepackageArtifacts(self, device_images, repackage_form):
"""Repackage artifacts into a given format.
Once repackaged, device_images becomes
{"img": "path_to_repackaged_image"}
Args:
device_images: dict, where the key is partition name and value is
image file path.
repackage_form: string, format to repackage.
Returns:
True if succesful; False otherwise.
"""
if not device_images:
logging.warn("Repackage skipped because no device image is given.")
return False
if repackage_form == "tar.md5":
tmp_file_name = next(tempfile._get_candidate_names()) + ".tar"
tmp_dir_path = os.path.dirname(
device_images[device_images.keys()[0]])
for img in device_images:
if os.path.dirname(device_images[img]) != tmp_dir_path:
os.rename(device_images[img],
os.path.join(tmp_dir_path, img))
device_images[img] = os.path.join(tmp_dir_path, img)
current_dir = os.getcwd()
os.chdir(tmp_dir_path)
if sys.platform == "linux2":
tar_cmd = "tar -cf %s %s" % (tmp_file_name, ' '.join(
(device_images.keys())))
else:
logging.error("Unsupported OS for the given repackage form.")
return False
logging.info(tar_cmd)
std_out, std_err, err_code = cmd_utils.ExecuteOneShellCommand(
tar_cmd)
if err_code:
logging.error(std_err)
return False
hash_md5 = hashlib.md5()
try:
with open(tmp_file_name, "rb") as file:
data_chunk = 0
chunk_size = resource.getpagesize()
while data_chunk != b'':
data_chunk = file.read(chunk_size)
hash_md5.update(data_chunk)
hash_ret = hash_md5.hexdigest()
with open(tmp_file_name, "a") as file:
file.write("%s %s" % (hash_ret, tmp_file_name))
except IOError as e:
logging.error(e.strerror)
return False
device_images.clear()
device_images["img"] = os.path.join(tmp_dir_path, tmp_file_name)
os.chdir(current_dir)
else:
logging.error(
"Please specify correct repackage form: --repackage=%s",
repackage_form)
return False
return True