blob: 98aaf569cba19b73937cf20c9210ddfbd26a2a83 [file] [log] [blame]
/*
* Copyright (C) 2010 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.
*/
package com.android.tradefed.targetprep;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.IDeviceManager;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.host.IHostOptions;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.targetprep.IDeviceFlasher.UserDataFlashOption;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.google.common.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
/** A {@link ITargetPreparer} that flashes an image on physical Android hardware. */
public abstract class DeviceFlashPreparer extends BaseTargetPreparer {
/**
* Enum of options for handling the encryption of userdata image
*/
public static enum EncryptionOptions {ENCRYPT, IGNORE}
private static final int BOOT_POLL_TIME_MS = 5 * 1000;
@Option(
name = "device-boot-time",
description = "max time to wait for device to boot.",
isTimeVal = true
)
private long mDeviceBootTime = 5 * 60 * 1000;
@Option(name = "userdata-flash", description =
"specify handling of userdata partition.")
private UserDataFlashOption mUserDataFlashOption = UserDataFlashOption.FLASH;
@Option(name = "encrypt-userdata", description = "specify if userdata partition should be "
+ "encrypted; defaults to IGNORE, where no actions will be taken.")
private EncryptionOptions mEncryptUserData = EncryptionOptions.IGNORE;
@Option(name = "force-system-flash", description =
"specify if system should always be flashed even if already running desired build.")
private boolean mForceSystemFlash = false;
/*
* A temporary workaround for special builds. Should be removed after changes from build team.
* Bug: 18078421
*/
@Option(name = "skip-post-flash-flavor-check", description =
"specify if system flavor should not be checked after flash")
private boolean mSkipPostFlashFlavorCheck = false;
/*
* Used for update testing
*/
@Option(name = "skip-post-flash-build-id-check", description =
"specify if build ID should not be checked after flash")
private boolean mSkipPostFlashBuildIdCheck = false;
@Option(name = "wipe-skip-list", description =
"list of /data subdirectories to NOT wipe when doing UserDataFlashOption.TESTS_ZIP")
private Collection<String> mDataWipeSkipList = new ArrayList<>();
/**
* @deprecated use host-options:concurrent-flasher-limit.
*/
@Deprecated
@Option(name = "concurrent-flasher-limit", description =
"No-op, do not use. Left for backwards compatibility.")
private Integer mConcurrentFlasherLimit = null;
@Option(name = "skip-post-flashing-setup",
description = "whether or not to skip post-flashing setup steps")
private boolean mSkipPostFlashingSetup = false;
@Option(name = "wipe-timeout",
description = "the timeout for the command of wiping user data.", isTimeVal = true)
private long mWipeTimeout = 4 * 60 * 1000;
@Option(
name = "fastboot-flash-option",
description = "additional options to pass with fastboot flash/update command."
)
private Collection<String> mFastbootFlashOptions = new ArrayList<>();
@Option(
name = "flash-ramdisk",
description =
"flashes ramdisk (boot partition) in addition " + "to regular system image")
private boolean mShouldFlashRamdisk = false;
/**
* Sets the device boot time
* <p/>
* Exposed for unit testing
*/
void setDeviceBootTime(long bootTime) {
mDeviceBootTime = bootTime;
}
/** Gets the device boot wait time */
protected long getDeviceBootWaitTime() {
return mDeviceBootTime;
}
/**
* Gets the interval between device boot poll attempts.
* <p/>
* Exposed for unit testing
*/
int getDeviceBootPollTimeMs() {
return BOOT_POLL_TIME_MS;
}
/**
* Gets the {@link IRunUtil} instance to use.
* <p/>
* Exposed for unit testing
*/
IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
/**
* Getg a reference to the {@link IDeviceManager}
*
* Exposed for unit testing
*
* @return the {@link IDeviceManager} to use
*/
IDeviceManager getDeviceManager() {
return GlobalConfiguration.getDeviceManagerInstance();
}
/**
* Gets the {@link IHostOptions} instance to use.
* <p/>
* Exposed for unit testing
*/
protected IHostOptions getHostOptions() {
return GlobalConfiguration.getInstance().getHostOptions();
}
/**
* Set the userdata-flash option
*
* @param flashOption
*/
public void setUserDataFlashOption(UserDataFlashOption flashOption) {
mUserDataFlashOption = flashOption;
}
/** {@inheritDoc} */
@Override
public void setUp(TestInformation testInfo)
throws TargetSetupError, DeviceNotAvailableException, BuildError {
if (isDisabled()) {
CLog.i("Skipping device flashing.");
return;
}
ITestDevice device = testInfo.getDevice();
IBuildInfo buildInfo = testInfo.getBuildInfo();
CLog.i("Performing setup on %s", device.getSerialNumber());
if (!(buildInfo instanceof IDeviceBuildInfo)) {
throw new IllegalArgumentException("Provided buildInfo is not a IDeviceBuildInfo");
}
IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) buildInfo;
if (mShouldFlashRamdisk && deviceBuild.getRamdiskFile() == null) {
throw new IllegalArgumentException(
"ramdisk flashing enabled but no ramdisk file was found in build info");
}
// don't allow interruptions during flashing operations.
getRunUtil().allowInterrupt(false);
IDeviceManager deviceManager = getDeviceManager();
long queueTime = -1;
long flashingTime = -1;
long start = -1;
try {
checkDeviceProductType(device, deviceBuild);
device.setRecoveryMode(RecoveryMode.ONLINE);
IDeviceFlasher flasher = createFlasher(device);
flasher.setWipeTimeout(mWipeTimeout);
// only surround fastboot related operations with flashing permit restriction
try {
start = System.currentTimeMillis();
deviceManager.takeFlashingPermit();
queueTime = System.currentTimeMillis() - start;
CLog.v(
"Flashing permit obtained after %ds",
TimeUnit.MILLISECONDS.toSeconds(queueTime));
flasher.overrideDeviceOptions(device);
flasher.setUserDataFlashOption(mUserDataFlashOption);
flasher.setForceSystemFlash(mForceSystemFlash);
flasher.setDataWipeSkipList(mDataWipeSkipList);
flasher.setShouldFlashRamdisk(mShouldFlashRamdisk);
if (flasher instanceof FastbootDeviceFlasher) {
((FastbootDeviceFlasher) flasher).setFlashOptions(mFastbootFlashOptions);
}
preEncryptDevice(device, flasher);
start = System.currentTimeMillis();
flasher.flash(device, deviceBuild);
} finally {
flashingTime = System.currentTimeMillis() - start;
deviceManager.returnFlashingPermit();
// report flashing status
CommandStatus status = flasher.getSystemFlashingStatus();
if (status == null) {
CLog.i("Skipped reporting metrics because system partitions were not flashed.");
} else {
reportFlashMetrics(buildInfo.getBuildBranch(), buildInfo.getBuildFlavor(),
buildInfo.getBuildId(), device.getSerialNumber(), queueTime,
flashingTime, status);
}
}
// only want logcat captured for current build, delete any accumulated log data
device.clearLogcat();
if (mSkipPostFlashingSetup) {
return;
}
// Temporary re-enable interruptable since the critical flashing operation is over.
getRunUtil().allowInterrupt(true);
device.waitForDeviceOnline();
// device may lose date setting if wiped, update with host side date in case anything on
// device side malfunction with an invalid date
if (device.enableAdbRoot()) {
device.setDate(null);
}
// Disable interrupt for encryption operation.
getRunUtil().allowInterrupt(false);
checkBuild(device, deviceBuild);
postEncryptDevice(device, flasher);
// Once critical operation is done, we re-enable interruptable
getRunUtil().allowInterrupt(true);
try {
device.setRecoveryMode(RecoveryMode.AVAILABLE);
device.waitForDeviceAvailable(mDeviceBootTime);
} catch (DeviceUnresponsiveException e) {
// assume this is a build problem
throw new DeviceFailedToBootError(String.format(
"Device %s did not become available after flashing %s",
device.getSerialNumber(), deviceBuild.getDeviceBuildId()),
device.getDeviceDescriptor());
}
device.postBootSetup();
} finally {
// Allow interruption at the end no matter what.
getRunUtil().allowInterrupt(true);
}
}
/**
* Possible check before flashing to ensure the device is as expected compare to the build info.
*
* @param device the {@link ITestDevice} to flash.
* @param deviceBuild the {@link IDeviceBuildInfo} used to flash.
* @throws BuildError
* @throws DeviceNotAvailableException
*/
protected void checkDeviceProductType(ITestDevice device, IDeviceBuildInfo deviceBuild)
throws BuildError, DeviceNotAvailableException {
// empty of purpose
}
/**
* Verifies the expected build matches the actual build on device after flashing
* @throws DeviceNotAvailableException
*/
private void checkBuild(ITestDevice device, IDeviceBuildInfo deviceBuild)
throws DeviceNotAvailableException {
// Need to use deviceBuild.getDeviceBuildId instead of getBuildId because the build info
// could be an AppBuildInfo and return app build id. Need to be more explicit that we
// check for the device build here.
if (!mSkipPostFlashBuildIdCheck) {
checkBuildAttribute(deviceBuild.getDeviceBuildId(), device.getBuildId(),
device.getSerialNumber());
}
if (!mSkipPostFlashFlavorCheck) {
checkBuildAttribute(deviceBuild.getDeviceBuildFlavor(), device.getBuildFlavor(),
device.getSerialNumber());
}
// TODO: check bootloader and baseband versions too
}
private void checkBuildAttribute(String expectedBuildAttr, String actualBuildAttr,
String serial) throws DeviceNotAvailableException {
if (expectedBuildAttr == null || actualBuildAttr == null ||
!expectedBuildAttr.equals(actualBuildAttr)) {
// throw DNAE - assume device hardware problem - we think flash was successful but
// device is not running right bits
throw new DeviceNotAvailableException(
String.format("Unexpected build after flashing. Expected %s, actual %s",
expectedBuildAttr, actualBuildAttr), serial);
}
}
/**
* Create {@link IDeviceFlasher} to use. Subclasses can override
* @throws DeviceNotAvailableException
*/
protected abstract IDeviceFlasher createFlasher(ITestDevice device)
throws DeviceNotAvailableException;
/**
* Handle encrypting of the device pre-flash.
*
* @see #postEncryptDevice(ITestDevice, IDeviceFlasher)
* @param device
* @throws DeviceNotAvailableException
* @throws TargetSetupError if the device could not be encrypted or unlocked.
*/
private void preEncryptDevice(ITestDevice device, IDeviceFlasher flasher)
throws DeviceNotAvailableException, TargetSetupError {
switch (mEncryptUserData) {
case IGNORE:
return;
case ENCRYPT:
if (!device.isEncryptionSupported()) {
throw new TargetSetupError("Encryption is not supported",
device.getDeviceDescriptor());
}
if (!device.isDeviceEncrypted()) {
switch(flasher.getUserDataFlashOption()) {
case TESTS_ZIP: // Intentional fall through.
case WIPE_RM:
// a new filesystem will not be created by the flasher, but the userdata
// partition is expected to be cleared anyway, so we encrypt the device
// with wipe
if (!device.encryptDevice(false)) {
throw new TargetSetupError("Failed to encrypt device",
device.getDeviceDescriptor());
}
if (!device.unlockDevice()) {
throw new TargetSetupError("Failed to unlock device",
device.getDeviceDescriptor());
}
break;
case RETAIN:
// original filesystem must be retained, so we encrypt in place
if (!device.encryptDevice(true)) {
throw new TargetSetupError("Failed to encrypt device",
device.getDeviceDescriptor());
}
if (!device.unlockDevice()) {
throw new TargetSetupError("Failed to unlock device",
device.getDeviceDescriptor());
}
break;
default:
// Do nothing, userdata will be encrypted post-flash.
}
}
break;
default:
// should not get here
return;
}
}
/**
* Handle encrypting of the device post-flash.
* <p>
* This method handles encrypting the device after a flash in cases where a flash would undo any
* encryption pre-flash, such as when the device is flashed or wiped.
* </p>
*
* @see #preEncryptDevice(ITestDevice, IDeviceFlasher)
* @param device
* @throws DeviceNotAvailableException
* @throws TargetSetupError If the device could not be encrypted or unlocked.
*/
private void postEncryptDevice(ITestDevice device, IDeviceFlasher flasher)
throws DeviceNotAvailableException, TargetSetupError {
switch (mEncryptUserData) {
case IGNORE:
return;
case ENCRYPT:
if (!device.isEncryptionSupported()) {
throw new TargetSetupError("Encryption is not supported",
device.getDeviceDescriptor());
}
switch(flasher.getUserDataFlashOption()) {
case FLASH:
if (!device.encryptDevice(true)) {
throw new TargetSetupError("Failed to encrypt device",
device.getDeviceDescriptor());
}
break;
case WIPE: // Intentional fall through.
case FORCE_WIPE:
// since the device was just wiped, encrypt with wipe
if (!device.encryptDevice(false)) {
throw new TargetSetupError("Failed to encrypt device",
device.getDeviceDescriptor());
}
break;
default:
// Do nothing, userdata was encrypted pre-flash.
}
if (!device.unlockDevice()) {
throw new TargetSetupError("Failed to unlock device",
device.getDeviceDescriptor());
}
break;
default:
// should not get here
return;
}
}
@Override
public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
if (isDisabled()) {
CLog.i("Skipping device flashing tearDown.");
return;
}
ITestDevice device = testInfo.getDevice();
if (mEncryptUserData == EncryptionOptions.ENCRYPT
&& mUserDataFlashOption != UserDataFlashOption.RETAIN) {
if (e instanceof DeviceNotAvailableException) {
CLog.e("Device was encrypted but now unavailable. may need manual cleanup");
} else if (device.isDeviceEncrypted()) {
if (!device.unencryptDevice()) {
throw new RuntimeException("Failed to unencrypt device");
}
}
}
}
/**
* Reports device flashing timing data to metrics backend
* @param branch the branch where the device build originated from
* @param buildFlavor the build flavor of the device build
* @param buildId the build number of the device build
* @param serial the serial number of device
* @param queueTime the time spent waiting for a flashing limit to become available
* @param flashingTime the time spent in flashing device image zip
* @param flashingStatus the execution status of flashing command
*/
protected void reportFlashMetrics(String branch, String buildFlavor, String buildId,
String serial, long queueTime, long flashingTime, CommandStatus flashingStatus) {
// no-op as default implementation
}
/**
* Sets the option for whether ramdisk should be flashed
*
* @param shouldFlashRamdisk
*/
@VisibleForTesting
void setShouldFlashRamdisk(boolean shouldFlashRamdisk) {
mShouldFlashRamdisk = shouldFlashRamdisk;
}
protected void setSkipPostFlashFlavorCheck(boolean skipPostFlashFlavorCheck) {
mSkipPostFlashFlavorCheck = skipPostFlashFlavorCheck;
}
protected void setSkipPostFlashBuildIdCheck(boolean skipPostFlashBuildIdCheck) {
mSkipPostFlashBuildIdCheck = skipPostFlashBuildIdCheck;
}
}