Home | History | Annotate | Download | only in device
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.tradefed.device;
     17 
     18 import com.android.ddmlib.IDevice;
     19 import com.android.tradefed.config.Option;
     20 import com.android.tradefed.device.DeviceManager.FastbootDevice;
     21 import com.android.tradefed.log.LogUtil.CLog;
     22 
     23 import java.util.ArrayList;
     24 import java.util.Arrays;
     25 import java.util.Collection;
     26 import java.util.HashMap;
     27 import java.util.HashSet;
     28 import java.util.Map;
     29 import java.util.concurrent.ExecutionException;
     30 import java.util.concurrent.Future;
     31 import java.util.concurrent.TimeUnit;
     32 
     33 /**
     34  * Container for for device selection criteria.
     35  */
     36 public class DeviceSelectionOptions implements IDeviceSelection {
     37 
     38     @Option(name = "serial", shortName = 's', description =
     39         "run this test on a specific device with given serial number(s).")
     40     private Collection<String> mSerials = new ArrayList<String>();
     41 
     42     @Option(name = "exclude-serial", description =
     43         "run this test on any device except those with this serial number(s).")
     44     private Collection<String> mExcludeSerials = new ArrayList<String>();
     45 
     46     @Option(name = "product-type", description =
     47             "run this test on device with this product type(s).  May also filter by variant " +
     48             "using product:variant.")
     49     private Collection<String> mProductTypes = new ArrayList<String>();
     50 
     51     @Option(name = "property", description =
     52         "run this test on device with this property value. " +
     53         "Expected format --property <propertyname> <propertyvalue>.")
     54     private Map<String, String> mPropertyMap = new HashMap<>();
     55 
     56     @Option(name = "emulator", shortName = 'e', description =
     57         "force this test to run on emulator.")
     58     private boolean mEmulatorRequested = false;
     59 
     60     @Option(name = "device", shortName = 'd', description =
     61         "force this test to run on a physical device, not an emulator.")
     62     private boolean mDeviceRequested = false;
     63 
     64     @Option(name = "new-emulator", description =
     65         "allocate a placeholder emulator. Should be used when config intends to launch an emulator")
     66     private boolean mStubEmulatorRequested = false;
     67 
     68     @Option(name = "null-device", shortName = 'n', description =
     69         "do not allocate a device for this test.")
     70     private boolean mNullDeviceRequested = false;
     71 
     72     @Option(name = "tcp-device", description =
     73             "start a placeholder for a tcp device that will be connected later.")
     74     private boolean mTcpDeviceRequested = false;
     75 
     76     @Option(name = "min-battery", description =
     77         "only run this test on a device whose battery level is at least the given amount. " +
     78         "Scale: 0-100")
     79     private Integer mMinBattery = null;
     80 
     81     @Option(name = "max-battery", description =
     82         "only run this test on a device whose battery level is strictly less than the given " +
     83         "amount. Scale: 0-100")
     84     private Integer mMaxBattery = null;
     85 
     86     @Option(
     87         name = "max-battery-temperature",
     88         description =
     89                 "only run this test on a device whose battery temperature is strictly "
     90                         + "less than the given amount. Scale: Degrees celsius"
     91     )
     92     private Integer mMaxBatteryTemperature = null;
     93 
     94     @Option(
     95         name = "require-battery-check",
     96         description =
     97                 "_If_ --min-battery and/or "
     98                         + "--max-battery is specified, enforce the check. If "
     99                         + "require-battery-check=false, then no battery check will occur."
    100     )
    101     private boolean mRequireBatteryCheck = true;
    102 
    103     @Option(
    104         name = "require-battery-temp-check",
    105         description =
    106                 "_If_ --max-battery-temperature is specified, enforce the battery checking. If "
    107                         + "require-battery-temp-check=false, then no temperature check will occur."
    108     )
    109     private boolean mRequireBatteryTemperatureCheck = true;
    110 
    111     @Option(name = "min-sdk-level", description = "Only run this test on devices that support " +
    112             "this Android SDK/API level")
    113     private Integer mMinSdk = null;
    114 
    115     @Option(name = "max-sdk-level", description = "Only run this test on devices that are running " +
    116         "this or lower Android SDK/API level")
    117     private Integer mMaxSdk = null;
    118 
    119     // If we have tried to fetch the environment variable ANDROID_SERIAL before.
    120     private boolean mFetchedEnvVariable = false;
    121 
    122     private static final String VARIANT_SEPARATOR = ":";
    123 
    124     /**
    125      * Add a serial number to the device selection options.
    126      *
    127      * @param serialNumber
    128      */
    129     public void addSerial(String serialNumber) {
    130         mSerials.add(serialNumber);
    131     }
    132 
    133     /**
    134      * {@inheritDoc}
    135      */
    136     @Override
    137     public void setSerial(String... serialNumber) {
    138         mSerials.clear();
    139         mSerials.addAll(Arrays.asList(serialNumber));
    140     }
    141 
    142     /**
    143      * Add a serial number to exclusion list.
    144      *
    145      * @param serialNumber
    146      */
    147     public void addExcludeSerial(String serialNumber) {
    148         mExcludeSerials.add(serialNumber);
    149     }
    150 
    151     /**
    152      * Add a product type to the device selection options.
    153      *
    154      * @param productType
    155      */
    156     public void addProductType(String productType) {
    157         mProductTypes.add(productType);
    158     }
    159 
    160     /**
    161      * Add a property criteria to the device selection options
    162      */
    163     public void addProperty(String propertyKey, String propValue) {
    164         mPropertyMap.put(propertyKey, propValue);
    165     }
    166 
    167     /** {@inheritDoc} */
    168     @Override
    169     public Collection<String> getSerials(IDevice device) {
    170         // If no serial was explicitly set, use the environment variable ANDROID_SERIAL.
    171         if (mSerials.isEmpty() && !mFetchedEnvVariable) {
    172             String env_serial = fetchEnvironmentVariable("ANDROID_SERIAL");
    173             if (env_serial != null && !(device instanceof StubDevice)) {
    174                 mSerials.add(env_serial);
    175             }
    176             mFetchedEnvVariable = true;
    177         }
    178         return copyCollection(mSerials);
    179     }
    180 
    181     /**
    182      * {@inheritDoc}
    183      */
    184     @Override
    185     public Collection<String> getExcludeSerials() {
    186         return copyCollection(mExcludeSerials);
    187     }
    188 
    189     /**
    190      * {@inheritDoc}
    191      */
    192     @Override
    193     public Collection<String> getProductTypes() {
    194         return copyCollection(mProductTypes);
    195     }
    196 
    197     /**
    198      * {@inheritDoc}
    199      */
    200     @Override
    201     public boolean deviceRequested() {
    202         return mDeviceRequested;
    203     }
    204 
    205     /**
    206      * {@inheritDoc}
    207      */
    208     @Override
    209     public boolean emulatorRequested() {
    210         return mEmulatorRequested;
    211     }
    212 
    213     /**
    214      * {@inheritDoc}
    215      */
    216     @Override
    217     public boolean stubEmulatorRequested() {
    218         return mStubEmulatorRequested;
    219     }
    220 
    221     /**
    222      * {@inheritDoc}
    223      */
    224     @Override
    225     public boolean nullDeviceRequested() {
    226         return mNullDeviceRequested;
    227     }
    228 
    229     public boolean tcpDeviceRequested() {
    230         return mTcpDeviceRequested;
    231     }
    232 
    233     /**
    234      * Sets the emulator requested flag
    235      */
    236     public void setEmulatorRequested(boolean emulatorRequested) {
    237         mEmulatorRequested = emulatorRequested;
    238     }
    239 
    240     /**
    241      * Sets the stub emulator requested flag
    242      */
    243     public void setStubEmulatorRequested(boolean stubEmulatorRequested) {
    244         mStubEmulatorRequested = stubEmulatorRequested;
    245     }
    246 
    247     /**
    248      * Sets the emulator requested flag
    249      */
    250     public void setDeviceRequested(boolean deviceRequested) {
    251         mDeviceRequested = deviceRequested;
    252     }
    253 
    254     /**
    255      * Sets the null device requested flag
    256      */
    257     public void setNullDeviceRequested(boolean nullDeviceRequested) {
    258         mNullDeviceRequested = nullDeviceRequested;
    259     }
    260 
    261     /**
    262      * Sets the tcp device requested flag
    263      */
    264     public void setTcpDeviceRequested(boolean tcpDeviceRequested) {
    265         mTcpDeviceRequested = tcpDeviceRequested;
    266     }
    267 
    268     /**
    269      * Sets the minimum battery level
    270      */
    271     public void setMinBatteryLevel(Integer minBattery) {
    272         mMinBattery = minBattery;
    273     }
    274 
    275     /**
    276      * Gets the requested minimum battery level
    277      */
    278     public Integer getMinBatteryLevel() {
    279         return mMinBattery;
    280     }
    281 
    282     /**
    283      * Sets the maximum battery level
    284      */
    285     public void setMaxBatteryLevel(Integer maxBattery) {
    286         mMaxBattery = maxBattery;
    287     }
    288 
    289     /**
    290      * Gets the requested maximum battery level
    291      */
    292     public Integer getMaxBatteryLevel() {
    293         return mMaxBattery;
    294     }
    295 
    296     /** Sets the maximum battery level */
    297     public void setMaxBatteryTemperature(Integer maxBatteryTemperature) {
    298         mMaxBatteryTemperature = maxBatteryTemperature;
    299     }
    300 
    301     /** Gets the requested maximum battery level */
    302     public Integer getMaxBatteryTemperature() {
    303         return mMaxBatteryTemperature;
    304     }
    305 
    306     /**
    307      * Sets whether battery check is required for devices with unknown battery level
    308      */
    309     public void setRequireBatteryCheck(boolean requireCheck) {
    310         mRequireBatteryCheck = requireCheck;
    311     }
    312 
    313     /**
    314      * Gets whether battery check is required for devices with unknown battery level
    315      */
    316     public boolean getRequireBatteryCheck() {
    317         return mRequireBatteryCheck;
    318     }
    319 
    320     /** Sets whether battery temp check is required for devices with unknown battery temperature */
    321     public void setRequireBatteryTemperatureCheck(boolean requireCheckTemprature) {
    322         mRequireBatteryTemperatureCheck = requireCheckTemprature;
    323     }
    324 
    325     /** Gets whether battery temp check is required for devices with unknown battery temperature */
    326     public boolean getRequireBatteryTemperatureCheck() {
    327         return mRequireBatteryTemperatureCheck;
    328     }
    329 
    330     /**
    331      * {@inheritDoc}
    332      */
    333     @Override
    334     public Map<String, String> getProperties() {
    335         return mPropertyMap;
    336     }
    337 
    338     private Collection<String> copyCollection(Collection<String> original) {
    339         Collection<String> listCopy = new ArrayList<String>(original.size());
    340         listCopy.addAll(original);
    341         return listCopy;
    342     }
    343 
    344     /**
    345      * Helper function used to fetch environment variable. It is essentially a wrapper around
    346      * {@link System#getenv(String)} This is done for unit testing purposes.
    347      *
    348      * @param name the environment variable to fetch.
    349      * @return a {@link String} value of the environment variable or null if not available.
    350      */
    351     String fetchEnvironmentVariable(String name) {
    352         return System.getenv(name);
    353     }
    354 
    355     /**
    356      * @return <code>true</code> if the given {@link IDevice} is a match for the provided options.
    357      * <code>false</code> otherwise
    358      */
    359     @Override
    360     public boolean matches(IDevice device) {
    361         Collection<String> serials = getSerials(device);
    362         Collection<String> excludeSerials = getExcludeSerials();
    363         Map<String, Collection<String>> productVariants = splitOnVariant(getProductTypes());
    364         Collection<String> productTypes = productVariants.keySet();
    365         Map<String, String> properties = getProperties();
    366 
    367         if (!serials.isEmpty() &&
    368                 !serials.contains(device.getSerialNumber())) {
    369             return false;
    370         }
    371         if (excludeSerials.contains(device.getSerialNumber())) {
    372             return false;
    373         }
    374         if (!productTypes.isEmpty()) {
    375             String productType = getDeviceProductType(device);
    376             if (productTypes.contains(productType)) {
    377                 // check variant
    378                 String productVariant = getDeviceProductVariant(device);
    379                 Collection<String> variants = productVariants.get(productType);
    380                 if (variants != null && !variants.contains(productVariant)) {
    381                     return false;
    382                 }
    383             } else {
    384                 // no product type matches; bye-bye
    385                 return false;
    386             }
    387         }
    388         for (Map.Entry<String, String> propEntry : properties.entrySet()) {
    389             if (!propEntry.getValue().equals(device.getProperty(propEntry.getKey()))) {
    390                 return false;
    391             }
    392         }
    393         if ((emulatorRequested() || stubEmulatorRequested()) && !device.isEmulator()) {
    394             return false;
    395         }
    396         if (deviceRequested() && device.isEmulator()) {
    397             return false;
    398         }
    399         if (device.isEmulator() && (device instanceof StubDevice) && !stubEmulatorRequested()) {
    400             // only allocate the stub emulator if requested
    401             return false;
    402         }
    403         if (nullDeviceRequested() != (device instanceof NullDevice)) {
    404             return false;
    405         }
    406         if (tcpDeviceRequested() != (TcpDevice.class.equals(device.getClass()))) {
    407             // We only match an exact TcpDevice here, no child class.
    408             return false;
    409         }
    410         if ((mMinSdk != null) || (mMaxSdk != null)) {
    411             int deviceSdkLevel = getDeviceSdkLevel(device);
    412             if (deviceSdkLevel < 0) {
    413                 return false;
    414             }
    415             if (mMinSdk != null && deviceSdkLevel < mMinSdk) {
    416                 return false;
    417             }
    418             if (mMaxSdk != null && mMaxSdk < deviceSdkLevel) {
    419                 return false;
    420             }
    421         }
    422         // If battery check is required and we have a min/max battery requested
    423         if (mRequireBatteryCheck) {
    424             if (((mMinBattery != null) || (mMaxBattery != null))
    425                     && (!(device instanceof StubDevice) || (device instanceof FastbootDevice))) {
    426                 // Only check battery on physical device. (FastbootDevice placeholder is always for
    427                 // a physical device
    428                 if (device instanceof FastbootDevice) {
    429                     // Ready battery of fastboot device does not work and could lead to weird log.
    430                     return false;
    431                 }
    432                 Integer deviceBattery = getBatteryLevel(device);
    433                 if (deviceBattery == null) {
    434                     // Couldn't determine battery level when that check is required; reject device
    435                     return false;
    436                 }
    437                 if (isLessAndNotNull(deviceBattery, mMinBattery)) {
    438                     // deviceBattery < mMinBattery
    439                     return false;
    440                 }
    441                 if (isLessEqAndNotNull(mMaxBattery, deviceBattery)) {
    442                     // mMaxBattery <= deviceBattery
    443                     return false;
    444                 }
    445             }
    446         }
    447         // If temperature check is required and we have a max temperature requested.
    448         if (mRequireBatteryTemperatureCheck) {
    449             if (mMaxBatteryTemperature != null
    450                     && (!(device instanceof StubDevice) || (device instanceof FastbootDevice))) {
    451                 // Only check battery temp on physical device. (FastbootDevice placeholder is
    452                 // always for a physical device
    453 
    454                 if (device instanceof FastbootDevice) {
    455                     // Cannot get battery temperature
    456                     return false;
    457                 }
    458 
    459                 // Extract the temperature from the file
    460                 IBatteryTemperature temp = new BatteryTemperature();
    461                 Integer deviceBatteryTemp = temp.getBatteryTemperature(device);
    462 
    463                 if (deviceBatteryTemp <= 0) {
    464                     // Couldn't determine battery temp when that check is required; reject device
    465                     return false;
    466                 }
    467 
    468                 if (isLessEqAndNotNull(mMaxBatteryTemperature, deviceBatteryTemp)) {
    469                     // mMaxBatteryTemperature <= deviceBatteryTemp
    470                     return false;
    471                 }
    472             }
    473         }
    474 
    475         return extraMatching(device);
    476     }
    477 
    478     /** Extra validation step that maybe overridden if it does not make sense. */
    479     protected boolean extraMatching(IDevice device) {
    480         // Any device that extends TcpDevice and is not a TcpDevice will be rejected.
    481         if (device instanceof TcpDevice && !device.getClass().isAssignableFrom(TcpDevice.class)) {
    482             return false;
    483         }
    484         return true;
    485     }
    486 
    487     /** Determine if x is less-than y, given that both are non-Null */
    488     private static boolean isLessAndNotNull(Integer x, Integer y) {
    489         if ((x == null) || (y == null)) {
    490             return false;
    491         }
    492         return x < y;
    493     }
    494 
    495     /** Determine if x is less-than y, given that both are non-Null */
    496     private static boolean isLessEqAndNotNull(Integer x, Integer y) {
    497         if ((x == null) || (y == null)) {
    498             return false;
    499         }
    500         return x <= y;
    501     }
    502 
    503     private Map<String, Collection<String>> splitOnVariant(Collection<String> products) {
    504         // FIXME: we should validate all provided device selection options once, on the first
    505         // FIXME: call to #matches
    506         Map<String, Collection<String>> splitProducts =
    507                 new HashMap<String, Collection<String>>(products.size());
    508         // FIXME: cache this
    509         for (String prod : products) {
    510             String[] parts = prod.split(VARIANT_SEPARATOR);
    511             if (parts.length == 1) {
    512                 splitProducts.put(parts[0], null);
    513             } else if (parts.length == 2) {
    514                 // A variant was specified as product:variant
    515                 Collection<String> variants = splitProducts.get(parts[0]);
    516                 if (variants == null) {
    517                     variants = new HashSet<String>();
    518                     splitProducts.put(parts[0], variants);
    519                 }
    520                 variants.add(parts[1]);
    521             } else {
    522                 throw new IllegalArgumentException(String.format("The product type filter \"%s\" " +
    523                         "is invalid.  It must contain 0 or 1 '%s' characters, not %d.",
    524                         prod, VARIANT_SEPARATOR, parts.length));
    525             }
    526         }
    527 
    528         return splitProducts;
    529     }
    530 
    531     @Override
    532     public String getDeviceProductType(IDevice device) {
    533         String prop = getProperty(device, DeviceProperties.BOARD);
    534         if (prop != null) {
    535             prop = prop.toLowerCase();
    536         }
    537         return prop;
    538     }
    539 
    540     private String getProperty(IDevice device, String propName) {
    541         return device.getProperty(propName);
    542     }
    543 
    544     @Override
    545     public String getDeviceProductVariant(IDevice device) {
    546         String prop = getProperty(device, DeviceProperties.VARIANT);
    547         if (prop == null) {
    548             prop = getProperty(device, DeviceProperties.VARIANT_LEGACY);
    549         }
    550         if (prop != null) {
    551             prop = prop.toLowerCase();
    552         }
    553         return prop;
    554     }
    555 
    556     @Override
    557     public Integer getBatteryLevel(IDevice device) {
    558         try {
    559             // use default 5 minutes freshness
    560             Future<Integer> batteryFuture = device.getBattery();
    561             // get cached value or wait up to 500ms for battery level query
    562             return batteryFuture.get(500, TimeUnit.MILLISECONDS);
    563         } catch (InterruptedException | ExecutionException |
    564                 java.util.concurrent.TimeoutException e) {
    565             CLog.w("Failed to query battery level for %s: %s", device.getSerialNumber(),
    566                     e.toString());
    567         }
    568         return null;
    569     }
    570 
    571     /**
    572      * Get the device's supported API level or -1 if it cannot be retrieved
    573      * @param device
    574      * @return the device's supported API level.
    575      */
    576     private int getDeviceSdkLevel(IDevice device) {
    577         int apiLevel = -1;
    578         String prop = getProperty(device, DeviceProperties.SDK_VERSION);
    579         try {
    580             apiLevel = Integer.parseInt(prop);
    581         } catch (NumberFormatException nfe) {
    582             CLog.w("Failed to parse sdk level %s for device %s", prop, device.getSerialNumber());
    583         }
    584         return apiLevel;
    585     }
    586 
    587     /**
    588      * Helper factory method to create a {@link IDeviceSelection} that will only match device
    589      * with given serial
    590      */
    591     public static IDeviceSelection createForSerial(String serial) {
    592         DeviceSelectionOptions o = new DeviceSelectionOptions();
    593         o.setSerial(serial);
    594         return o;
    595     }
    596 }
    597