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      */
addSerial(String serialNumber)129     public void addSerial(String serialNumber) {
130         mSerials.add(serialNumber);
131     }
132 
133     /**
134      * {@inheritDoc}
135      */
136     @Override
setSerial(String... serialNumber)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      */
addExcludeSerial(String serialNumber)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      */
addProductType(String productType)156     public void addProductType(String productType) {
157         mProductTypes.add(productType);
158     }
159 
160     /**
161      * Add a property criteria to the device selection options
162      */
addProperty(String propertyKey, String propValue)163     public void addProperty(String propertyKey, String propValue) {
164         mPropertyMap.put(propertyKey, propValue);
165     }
166 
167     /** {@inheritDoc} */
168     @Override
getSerials(IDevice device)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
getExcludeSerials()185     public Collection<String> getExcludeSerials() {
186         return copyCollection(mExcludeSerials);
187     }
188 
189     /**
190      * {@inheritDoc}
191      */
192     @Override
getProductTypes()193     public Collection<String> getProductTypes() {
194         return copyCollection(mProductTypes);
195     }
196 
197     /**
198      * {@inheritDoc}
199      */
200     @Override
deviceRequested()201     public boolean deviceRequested() {
202         return mDeviceRequested;
203     }
204 
205     /**
206      * {@inheritDoc}
207      */
208     @Override
emulatorRequested()209     public boolean emulatorRequested() {
210         return mEmulatorRequested;
211     }
212 
213     /**
214      * {@inheritDoc}
215      */
216     @Override
stubEmulatorRequested()217     public boolean stubEmulatorRequested() {
218         return mStubEmulatorRequested;
219     }
220 
221     /**
222      * {@inheritDoc}
223      */
224     @Override
nullDeviceRequested()225     public boolean nullDeviceRequested() {
226         return mNullDeviceRequested;
227     }
228 
tcpDeviceRequested()229     public boolean tcpDeviceRequested() {
230         return mTcpDeviceRequested;
231     }
232 
233     /**
234      * Sets the emulator requested flag
235      */
setEmulatorRequested(boolean emulatorRequested)236     public void setEmulatorRequested(boolean emulatorRequested) {
237         mEmulatorRequested = emulatorRequested;
238     }
239 
240     /**
241      * Sets the stub emulator requested flag
242      */
setStubEmulatorRequested(boolean stubEmulatorRequested)243     public void setStubEmulatorRequested(boolean stubEmulatorRequested) {
244         mStubEmulatorRequested = stubEmulatorRequested;
245     }
246 
247     /**
248      * Sets the emulator requested flag
249      */
setDeviceRequested(boolean deviceRequested)250     public void setDeviceRequested(boolean deviceRequested) {
251         mDeviceRequested = deviceRequested;
252     }
253 
254     /**
255      * Sets the null device requested flag
256      */
setNullDeviceRequested(boolean nullDeviceRequested)257     public void setNullDeviceRequested(boolean nullDeviceRequested) {
258         mNullDeviceRequested = nullDeviceRequested;
259     }
260 
261     /**
262      * Sets the tcp device requested flag
263      */
setTcpDeviceRequested(boolean tcpDeviceRequested)264     public void setTcpDeviceRequested(boolean tcpDeviceRequested) {
265         mTcpDeviceRequested = tcpDeviceRequested;
266     }
267 
268     /**
269      * Sets the minimum battery level
270      */
setMinBatteryLevel(Integer minBattery)271     public void setMinBatteryLevel(Integer minBattery) {
272         mMinBattery = minBattery;
273     }
274 
275     /**
276      * Gets the requested minimum battery level
277      */
getMinBatteryLevel()278     public Integer getMinBatteryLevel() {
279         return mMinBattery;
280     }
281 
282     /**
283      * Sets the maximum battery level
284      */
setMaxBatteryLevel(Integer maxBattery)285     public void setMaxBatteryLevel(Integer maxBattery) {
286         mMaxBattery = maxBattery;
287     }
288 
289     /**
290      * Gets the requested maximum battery level
291      */
getMaxBatteryLevel()292     public Integer getMaxBatteryLevel() {
293         return mMaxBattery;
294     }
295 
296     /** Sets the maximum battery level */
setMaxBatteryTemperature(Integer maxBatteryTemperature)297     public void setMaxBatteryTemperature(Integer maxBatteryTemperature) {
298         mMaxBatteryTemperature = maxBatteryTemperature;
299     }
300 
301     /** Gets the requested maximum battery level */
getMaxBatteryTemperature()302     public Integer getMaxBatteryTemperature() {
303         return mMaxBatteryTemperature;
304     }
305 
306     /**
307      * Sets whether battery check is required for devices with unknown battery level
308      */
setRequireBatteryCheck(boolean requireCheck)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      */
getRequireBatteryCheck()316     public boolean getRequireBatteryCheck() {
317         return mRequireBatteryCheck;
318     }
319 
320     /** Sets whether battery temp check is required for devices with unknown battery temperature */
setRequireBatteryTemperatureCheck(boolean requireCheckTemprature)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 */
getRequireBatteryTemperatureCheck()326     public boolean getRequireBatteryTemperatureCheck() {
327         return mRequireBatteryTemperatureCheck;
328     }
329 
330     /**
331      * {@inheritDoc}
332      */
333     @Override
getProperties()334     public Map<String, String> getProperties() {
335         return mPropertyMap;
336     }
337 
copyCollection(Collection<String> original)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      */
fetchEnvironmentVariable(String name)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
matches(IDevice device)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. */
extraMatching(IDevice device)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 */
isLessAndNotNull(Integer x, Integer y)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 */
isLessEqAndNotNull(Integer x, Integer y)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 
splitOnVariant(Collection<String> products)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
getDeviceProductType(IDevice device)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 
getProperty(IDevice device, String propName)540     private String getProperty(IDevice device, String propName) {
541         return device.getProperty(propName);
542     }
543 
544     @Override
getDeviceProductVariant(IDevice device)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
getBatteryLevel(IDevice device)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      */
getDeviceSdkLevel(IDevice device)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      */
createForSerial(String serial)591     public static IDeviceSelection createForSerial(String serial) {
592         DeviceSelectionOptions o = new DeviceSelectionOptions();
593         o.setSerial(serial);
594         return o;
595     }
596 }
597