blob: 1de8e04b53496b0cd366cbb3d091c664c857667c [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.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.regex.Pattern;
/**
* A {@link ITargetPreparer} that configures a device for testing based on provided {@link Option}s.
* <p/>
* Requires a device where 'adb root' is possible, typically a userdebug build type.
* <p/>
* Should be performed *after* a new build is flashed.
*/
@OptionClass(alias = "device-setup")
public class DeviceSetup implements ITargetPreparer, ITargetCleaner {
private static final String LOG_TAG = "DeviceSetup";
private static final Pattern RELEASE_BUILD_NAME_PATTERN =
Pattern.compile("[A-Z]{3}\\d{2}[A-Z]?");
private static final String PERSIST_PREFIX = "persist.";
@Option(name="wifi-network", description="the name of wifi network to connect to.")
private String mWifiNetwork = null;
@Option(name="wifi-psk", description="WPA-PSK passphrase of wifi network to connect to.")
private String mWifiPsk = null;
@Option(name = "disconnect-wifi-after-test", description =
"disconnect from wifi network after test completes.")
private boolean mDisconnectWifiAfterTest = true;
@Option(name="min-external-store-space", description="the minimum amount of free space in KB" +
" that must be present on device's external storage.")
// require 500K by default. Values <=0 mean external storage is not required
private long mMinExternalStoreSpace = 500;
@Option(name = "local-data-path",
description = "optional local file path of test data to sync to device's external " +
"storage. Use --remote-data-path to set remote location.")
private File mLocalDataFile = null;
@Option(name = "remote-data-path",
description = "optional file path on device's external storage to sync test data. " +
"Must be used with --local-data-path.")
private String mRemoteDataPath = null;
@Option(name = "force-skip-system-props", description =
"force setup to not modify any device system properties. " +
"All other system property options will be ignored.")
private boolean mForceNoSystemProps = false;
@Option(name="disable-dialing", description="set disable dialing property on boot.")
private boolean mDisableDialing = true;
@Option(name="set-test-harness", description="set the read-only test harness flag on boot. " +
"Requires adb root.")
private boolean mSetTestHarness = true;
@Option(name="audio-silent", description="set ro.audio.silent on boot.")
private boolean mSetAudioSilent = true;
@Option(name="disable-dalvik-verifier", description="disable the dalvik verifier on device. "
+ "Allows package-private framework tests to run.")
private boolean mDisableDalvikVerifier = false;
@Option(name="setprop", description="set the specified property on boot. " +
"Format: --setprop key=value. May be repeated.")
private Collection<String> mSetProps = new ArrayList<String>();
/**
* Sets the local data path to use
* <p/>
* Exposed for unit testing
*/
void setLocalDataPath(File localPath) {
mLocalDataFile = localPath;
}
/**
* Sets the remote data path to use
* <p/>
* Exposed for unit testing
*/
void setRemoteDataPath(String remotePath) {
mRemoteDataPath = remotePath;
}
/**
* Sets the wifi network ssid to setup.
* <p/>
* Exposed for unit testing
*/
void setWifiNetwork(String network) {
mWifiNetwork = network;
}
/**
* Sets the minimum external store space
* <p/>
* Exposed for unit testing
*/
void setMinExternalStoreSpace(int minKBytes) {
mMinExternalStoreSpace = minKBytes;
}
/**
* Adds a property to the list of properties to set
* <p/>
* Exposed for unit testing
*/
void addSetProperty(String prop) {
mSetProps.add(prop);
}
/**
* {@inheritDoc}
*/
@Override
public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
DeviceNotAvailableException, BuildError {
Log.i(LOG_TAG, String.format("Performing setup on %s", device.getSerialNumber()));
if (!device.enableAdbRoot()) {
throw new TargetSetupError(String.format("failed to enable adb root on %s",
device.getSerialNumber()));
}
configureSystemProperties(device);
changeSettings(device);
keepScreenOn(device);
connectToWifi(device);
syncTestData(device);
checkExternalStoreSpace(device);
device.clearErrorDialogs();
}
/**
* Configures device system properties.
* <p/>
* Device will be rebooted if any property is changed.
*
* @param device
* @throws TargetSetupError
* @throws DeviceNotAvailableException
*/
private void configureSystemProperties(ITestDevice device) throws TargetSetupError,
DeviceNotAvailableException {
if (mForceNoSystemProps) {
return;
}
// build the local.prop file contents with properties to change
StringBuilder propertyBuilder = new StringBuilder();
if (mDisableDialing) {
propertyBuilder.append("ro.telephony.disable-call=true\n");
}
if (mSetTestHarness) {
// set both ro.monkey and ro.test_harness, for compatibility with older platforms
propertyBuilder.append("ro.monkey=1\n");
propertyBuilder.append("ro.test_harness=1\n");
}
if (mSetAudioSilent) {
propertyBuilder.append("ro.audio.silent=1\n");
}
if (mDisableDalvikVerifier) {
propertyBuilder.append("dalvik.vm.dexopt-flags = v=n\n");
}
for (String prop : mSetProps) {
if (prop.startsWith(PERSIST_PREFIX)) {
prop = prop.replace('=', ' ');
device.executeShellCommand("setprop " + prop);
} else {
propertyBuilder.append(prop);
propertyBuilder.append("\n");
}
}
if (propertyBuilder.length() > 0) {
// create a local.prop file, and push it to /data/local.prop
boolean result = device.pushString(propertyBuilder.toString(), "/data/local.prop");
if (!result) {
throw new TargetSetupError(String.format("Failed to push file to %s",
device.getSerialNumber()));
}
// Set reasonable permissions for /data/local.prop
device.executeShellCommand("chmod 644 /data/local.prop");
Log.i(LOG_TAG, String.format(
"Setup requires system property change. Reboot of %s required",
device.getSerialNumber()));
device.reboot();
}
}
/**
* Change additional settings for the device. This is intended to be overridden by subclass for
* additional change of settings.
*
* @param device
* @throws DeviceNotAvailableException
* @throws TargetSetupError
*/
protected void changeSettings(ITestDevice device) throws DeviceNotAvailableException,
TargetSetupError {
// ignore
}
/**
* @param device
* @throws DeviceNotAvailableException
*/
private void keepScreenOn(ITestDevice device) throws DeviceNotAvailableException {
device.executeShellCommand("svc power stayon true");
}
/**
* Check that device external store has the required space
*
* @param device
* @throws DeviceNotAvailableException if device does not have required space
*/
private void checkExternalStoreSpace(ITestDevice device) throws DeviceNotAvailableException {
if (mMinExternalStoreSpace > 0) {
long freeSpace = device.getExternalStoreFreeSpace();
if (freeSpace < mMinExternalStoreSpace) {
throw new DeviceNotAvailableException(String.format(
"External store free space %dK is less than required %dK for device %s",
freeSpace , mMinExternalStoreSpace, device.getSerialNumber()));
}
}
}
/**
* Connect to wifi network if specified
*
* @param device
* @throws DeviceNotAvailableException
* @throws TargetSetupError if failed to connect to wifi
*/
private void connectToWifi(ITestDevice device) throws DeviceNotAvailableException,
TargetSetupError {
if (mWifiNetwork != null) {
if (!device.connectToWifiNetwork(mWifiNetwork, mWifiPsk)) {
throw new TargetSetupError(String.format(
"Failed to connect to wifi network %s on %s", mWifiNetwork,
device.getSerialNumber()));
}
}
}
/**
* Syncs a set of test data files, specified via local-data-path, to devices external storage.
*
* @param device the {@link ITestDevice} to sync data to
* @throws TargetSetupError if data fails to sync
*/
void syncTestData(ITestDevice device) throws TargetSetupError, DeviceNotAvailableException {
if (mLocalDataFile != null) {
if (!mLocalDataFile.exists() || !mLocalDataFile.isDirectory()) {
throw new TargetSetupError(String.format("local-data-path %s is not a directory",
mLocalDataFile.getAbsolutePath()));
}
String fullRemotePath = device.getIDevice().getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
if (fullRemotePath == null) {
throw new TargetSetupError(String.format(
"failed to get external storage path on device %s",
device.getSerialNumber()));
}
if (mRemoteDataPath != null) {
fullRemotePath = String.format("%s/%s", fullRemotePath, mRemoteDataPath);
}
boolean result = device.syncFiles(mLocalDataFile, fullRemotePath);
if (!result) {
// TODO: get exact error code and respond accordingly
throw new TargetSetupError(String.format(
"failed to sync test data from local-data-path %s to %s on device %s",
mLocalDataFile.getAbsolutePath(), fullRemotePath,
device.getSerialNumber()));
}
}
}
protected boolean isReleaseBuildName(String name) {
return RELEASE_BUILD_NAME_PATTERN.matcher(name).matches();
}
/**
* {@inheritDoc}
*/
@Override
public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
throws DeviceNotAvailableException {
Log.i(LOG_TAG, String.format("Performing teardown on %s", device.getSerialNumber()));
if (mWifiNetwork != null && mDisconnectWifiAfterTest) {
disconnectFromWifi(device);
}
}
private void disconnectFromWifi(ITestDevice device) throws DeviceNotAvailableException {
if (device.isWifiEnabled()) {
if (!device.disconnectFromWifi()) {
CLog.w("Failed to disconnect from wifi network on %s", device.getSerialNumber());
return;
}
CLog.i("Successfully disconnected from wifi network on %s", device.getSerialNumber());
}
}
}