blob: 15f1a81121198a091dbae054d789a170c27a05b5 [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.device;
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
import com.android.tradefed.config.Option;
import com.android.tradefed.log.LogUtil.CLog;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
* Container for for device selection criteria.
*/
public class DeviceSelectionOptions implements IDeviceSelection {
private static final String LOG_TAG = "DeviceSelectionOptions";
@Option(name = "serial", shortName = 's', description =
"run this test on a specific device with given serial number(s).")
private Collection<String> mSerials = new ArrayList<String>();
@Option(name = "exclude-serial", description =
"run this test on any device except those with this serial number(s).")
private Collection<String> mExcludeSerials = new ArrayList<String>();
@Option(name = "product-type", description =
"run this test on device with this product type(s). May also filter by variant " +
"using product:variant.")
private Collection<String> mProductTypes = new ArrayList<String>();
@Option(name = "property", description =
"run this test on device with this property value. " +
"Expected format <propertyname>=<propertyvalue>.")
private Collection<String> mPropertyStrings = new ArrayList<String>();
@Option(name = "emulator", shortName = 'e', description =
"run this test on emulator.")
private boolean mEmulatorRequested = false;
@Option(name = "null-device", shortName = 'n', description =
"do not allocate a device for this test.")
private boolean mNullDeviceRequested = false;
@Option(name = "min-battery", description =
"only run this test on a device whose battery level is greater than given amount. " +
"Scale: 0-100")
private Integer mMinBattery = null;
// If we have tried to fetch the environment variable ANDROID_SERIAL before.
private boolean mFetchedEnvVariable = false;
private static final String VARIANT_SEPARATOR = ":";
/**
* Add a serial number to the device selection options.
*
* @param serialNumber
*/
public void addSerial(String serialNumber) {
mSerials.add(serialNumber);
}
/**
* Add a serial number to exclusion list.
*
* @param serialNumber
*/
public void addExcludeSerial(String serialNumber) {
mExcludeSerials.add(serialNumber);
}
/**
* Add a product type to the device selection options.
*
* @param serialNumber
*/
public void addProductType(String productType) {
mProductTypes.add(productType);
}
/**
* Add a property criteria to the device selection options
*
* @param propertyKeyValue a property to match. Expected format propertykey=propertyvalue
*/
public void addProperty(String propertyKeyValue) {
mPropertyStrings.add(propertyKeyValue);
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getSerials() {
// If no serial was explicitly set, use the environment variable ANDROID_SERIAL.
if (mSerials.isEmpty() && !mFetchedEnvVariable) {
String env_serial = fetchEnvironmentVariable("ANDROID_SERIAL");
if (env_serial != null) {
mSerials.add(env_serial);
}
mFetchedEnvVariable = true;
}
return copyCollection(mSerials);
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getExcludeSerials() {
return copyCollection(mExcludeSerials);
}
/**
* {@inheritDoc}
*/
@Override
public Collection<String> getProductTypes() {
return copyCollection(mProductTypes);
}
/**
* {@inheritDoc}
*/
@Override
public boolean emulatorRequested() {
return mEmulatorRequested;
}
/**
* {@inheritDoc}
*/
@Override
public boolean nullDeviceRequested() {
return mNullDeviceRequested;
}
/**
* Sets the emulator requested flag
*/
public void setEmulatorRequested(boolean emulatorRequested) {
mEmulatorRequested = emulatorRequested;
}
/**
* Sets the null device requested flag
*/
public void setNullDeviceRequested(boolean nullDeviceRequested) {
mNullDeviceRequested = nullDeviceRequested;
}
/**
* Sets the minimum battery level
*/
public void setMinBatteryLevel(Integer minBattery) {
mMinBattery = minBattery;
}
/**
* Gets the requested minimum battery level
*/
public Integer getMinBatteryLevel() {
return mMinBattery;
}
/**
* {@inheritDoc}
*/
@Override
public Map<String, String> getProperties() {
Map<String, String> propertyMap = new HashMap<String, String>(mPropertyStrings.size());
for (String propertyKeyValue : mPropertyStrings) {
String[] keyValuePair = propertyKeyValue.split("=");
if (keyValuePair.length == 2) {
propertyMap.put(keyValuePair[0], keyValuePair[1]);
} else {
Log.e(LOG_TAG, String.format("Unrecognized property key value pair: '%s'",
propertyKeyValue));
}
}
return propertyMap;
}
private Collection<String> copyCollection(Collection<String> original) {
Collection<String> listCopy = new ArrayList<String>(original.size());
listCopy.addAll(original);
return listCopy;
}
/**
* Helper function used to fetch environment variable. It is essentially a wrapper around
* {@link System#getenv(String)} This is done for unit testing purposes.
*
* @param name the environment variable to fetch.
* @return a {@link String} value of the environment variable or null if not available.
*/
String fetchEnvironmentVariable(String name) {
return System.getenv(name);
}
/**
* @return <code>true</code> if the given {@link IDevice} is a match for the provided options.
* <code>false</code> otherwise
*/
@Override
public boolean matches(IDevice device) {
Collection<String> serials = getSerials();
Collection<String> excludeSerials = getExcludeSerials();
Map<String, Collection<String>> productVariants = splitOnVariant(getProductTypes());
Collection<String> productTypes = productVariants.keySet();
Map<String, String> properties = getProperties();
if (!serials.isEmpty() &&
!serials.contains(device.getSerialNumber())) {
return false;
}
if (excludeSerials.contains(device.getSerialNumber())) {
return false;
}
if (!productTypes.isEmpty()) {
String productType = getDeviceProductType(device);
if (productTypes.contains(productType)) {
// check variant
String productVariant = getDeviceProductVariant(device);
Collection<String> variants = productVariants.get(productType);
if (variants != null && !variants.contains(productVariant)) {
return false;
}
} else {
// no product type matches; bye-bye
return false;
}
}
for (Map.Entry<String, String> propEntry : properties.entrySet()) {
if (!propEntry.getValue().equals(device.getProperty(propEntry.getKey()))) {
return false;
}
}
if (emulatorRequested() != device.isEmulator()) {
// only match with emulator if explicitly requested
return false;
}
if (nullDeviceRequested() != (device instanceof NullDevice)) {
return false;
}
if (mMinBattery != null) {
Integer deviceBattery = getBatteryLevel(device);
if (deviceBattery == null || deviceBattery < mMinBattery) {
return false;
}
}
return true;
}
private Map<String, Collection<String>> splitOnVariant(Collection<String> products) {
// FIXME: we should validate all provided device selection options once, on the first
// FIXME: call to #matches
Map<String, Collection<String>> splitProducts =
new HashMap<String, Collection<String>>(products.size());
// FIXME: cache this
for (String prod : products) {
String[] parts = prod.split(VARIANT_SEPARATOR);
if (parts.length == 1) {
splitProducts.put(parts[0], null);
} else if (parts.length == 2) {
// A variant was specified as product:variant
Collection<String> variants = splitProducts.get(parts[0]);
if (variants == null) {
variants = new HashSet<String>();
splitProducts.put(parts[0], variants);
}
variants.add(parts[1]);
} else {
throw new IllegalArgumentException(String.format("The product type filter \"%s\" " +
"is invalid. It must contain 0 or 1 '%s' characters, not %d.",
prod, VARIANT_SEPARATOR, parts.length));
}
}
return splitProducts;
}
@Override
public String getDeviceProductType(IDevice device) {
// FIXME: merge this into the getProperties match
return device.getProperty("ro.hardware");
}
@Override
public String getDeviceProductVariant(IDevice device) {
return device.getProperty("ro.product.device");
}
@Override
public Integer getBatteryLevel(IDevice device) {
try {
return device.getBatteryLevel();
} catch (TimeoutException e) {
handleBatteryException(device, e);
} catch (AdbCommandRejectedException e) {
handleBatteryException(device, e);
} catch (IOException e) {
handleBatteryException(device, e);
} catch (ShellCommandUnresponsiveException e) {
handleBatteryException(device, e);
}
return null;
}
private void handleBatteryException(IDevice device, Exception e) {
CLog.w("Failed to query battery level for %s: %s", device.getSerialNumber(), e.toString());
}
}