1 /*
2  * Copyright (C) 201040 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.annotations.VisibleForTesting;
19 import com.android.ddmlib.IDevice;
20 import com.android.tradefed.config.Option;
21 import com.android.tradefed.config.OptionUpdateRule;
22 import com.android.tradefed.device.DeviceManager.FastbootDevice;
23 import com.android.tradefed.device.cloud.VmRemoteDevice;
24 import com.android.tradefed.log.LogUtil.CLog;
25 
26 import com.google.common.base.Strings;
27 
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.LinkedHashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.concurrent.ExecutionException;
37 import java.util.concurrent.Future;
38 import java.util.concurrent.TimeUnit;
39 
40 /**
41  * Container for for device selection criteria.
42  */
43 public class DeviceSelectionOptions implements IDeviceSelection {
44 
45     /** The different possible types of placeholder devices supported. */
46     public enum DeviceRequestedType {
47         /** A placeholder where no device is required to be allocated. */
48         NULL_DEVICE(NullDevice.class),
49         /** Allocate an emulator running locally for the test. */
50         LOCAL_EMULATOR(StubDevice.class),
51         /** Use a placeholder for a remote device nested in a virtualized environment. */
52         GCE_DEVICE(RemoteAvdIDevice.class),
53         /** Use a placeholder for a remote device in virtualized environment. */
54         REMOTE_DEVICE(VmRemoteDevice.class),
55         /** Allocate a virtual device running on localhost. */
56         LOCAL_VIRTUAL_DEVICE(StubLocalAndroidVirtualDevice.class),
57         /** A real physical or virtual device already started, not a placeholder type. */
58         EXISTING_DEVICE(IDevice.class);
59 
60         private Class<?> mRequiredIDeviceClass;
61 
DeviceRequestedType(Class<?> requiredIDeviceClass)62         DeviceRequestedType(Class<?> requiredIDeviceClass) {
63             mRequiredIDeviceClass = requiredIDeviceClass;
64         }
65 
getRequiredClass()66         public Class<?> getRequiredClass() {
67             return mRequiredIDeviceClass;
68         }
69     }
70 
71     @Option(name = "serial", shortName = 's', description =
72         "run this test on a specific device with given serial number(s).")
73     private Collection<String> mSerials = new ArrayList<String>();
74 
75     @Option(name = "exclude-serial", description =
76         "run this test on any device except those with this serial number(s).")
77     private Collection<String> mExcludeSerials = new ArrayList<String>();
78 
79     @Option(name = "product-type", description =
80             "run this test on device with this product type(s).  May also filter by variant " +
81             "using product:variant.")
82     private Collection<String> mProductTypes = new ArrayList<String>();
83 
84     @Option(name = "property", description =
85         "run this test on device with this property value. " +
86         "Expected format --property <propertyname> <propertyvalue>.")
87     private Map<String, String> mPropertyMap = new HashMap<>();
88 
89     // ============================ DEVICE TYPE Related Options ===============================
90     @Option(name = "emulator", shortName = 'e', description = "force this test to run on emulator.")
91     private boolean mEmulatorRequested = false;
92 
93     @Option(name = "device", shortName = 'd', description =
94         "force this test to run on a physical device, not an emulator.")
95     private boolean mDeviceRequested = false;
96 
97     @Option(name = "new-emulator", description =
98         "allocate a placeholder emulator. Should be used when config intends to launch an emulator")
99     private boolean mStubEmulatorRequested = false;
100 
101     @Option(name = "null-device", shortName = 'n', description =
102         "do not allocate a device for this test.")
103     private boolean mNullDeviceRequested = false;
104 
105     @Option(
106             name = "gce-device",
107             description = "start a placeholder for a gce device that will be connected later.")
108     private boolean mGceDeviceRequested = false;
109 
110     @Option(name = "device-type", description = "The type of the device requested to be allocated.")
111     private DeviceRequestedType mRequestedType = null;
112 
113     @Option(
114             name = "base-device-type-request",
115             description =
116                     "Explicitly request a device type which will use device-type for connection.")
117     private BaseDeviceType mBaseDeviceType = null;
118     // ============================ END DEVICE TYPE Related Options ============================
119 
120     @Option(
121             name = "min-battery",
122             description =
123                     "only run this test on a device whose battery level is at least the given"
124                             + " amount. Scale: 0-100",
125             updateRule = OptionUpdateRule.GREATEST)
126     private Integer mMinBattery = null;
127 
128     @Option(
129             name = "max-battery",
130             description =
131                     "only run this test on a device whose battery level is strictly less than the "
132                             + "given amount. Scale: 0-100",
133             updateRule = OptionUpdateRule.LEAST)
134     private Integer mMaxBattery = null;
135 
136     @Option(
137         name = "max-battery-temperature",
138         description =
139                 "only run this test on a device whose battery temperature is strictly "
140                         + "less than the given amount. Scale: Degrees celsius"
141     )
142     private Integer mMaxBatteryTemperature = null;
143 
144     @Option(
145         name = "require-battery-check",
146         description =
147                 "_If_ --min-battery and/or "
148                         + "--max-battery is specified, enforce the check. If "
149                         + "require-battery-check=false, then no battery check will occur."
150     )
151     private boolean mRequireBatteryCheck = true;
152 
153     @Option(
154         name = "require-battery-temp-check",
155         description =
156                 "_If_ --max-battery-temperature is specified, enforce the battery checking. If "
157                         + "require-battery-temp-check=false, then no temperature check will occur."
158     )
159     private boolean mRequireBatteryTemperatureCheck = true;
160 
161     @Option(name = "min-sdk-level", description = "Only run this test on devices that support " +
162             "this Android SDK/API level")
163     private Integer mMinSdk = null;
164 
165     @Option(
166             name = "max-sdk-level",
167             description =
168                     "Only run this test on devices that are running "
169                             + "this or lower Android SDK/API level")
170     private Integer mMaxSdk = null;
171 
172     // If we have tried to fetch the environment variable ANDROID_SERIAL before.
173     private boolean mFetchedEnvVariable = false;
174     // Store the reason for which the device was not matched.
175     private Map<String, String> mNoMatchReason = new LinkedHashMap<>();
176     // If we fail all allocation due to serial report a special message
177     private boolean mSerialMatch = false;
178 
179     private static final String VARIANT_SEPARATOR = ":";
180 
181     /**
182      * Add a serial number to the device selection options.
183      *
184      * @param serialNumber
185      */
addSerial(String serialNumber)186     public void addSerial(String serialNumber) {
187         mSerials.add(serialNumber);
188     }
189 
190     /**
191      * {@inheritDoc}
192      */
193     @Override
setSerial(String... serialNumber)194     public void setSerial(String... serialNumber) {
195         mSerials.clear();
196         mSerials.addAll(Arrays.asList(serialNumber));
197     }
198 
199     /** {@inheritDoc} */
200     @Override
getSerials()201     public List<String> getSerials() {
202         return new ArrayList<>(mSerials);
203     }
204 
205     /** {@inheritDoc} */
206     @Override
getSerials(IDevice device)207     public Collection<String> getSerials(IDevice device) {
208         // If no serial was explicitly set, use the environment variable ANDROID_SERIAL.
209         if (mSerials.isEmpty() && !mFetchedEnvVariable) {
210             String env_serial = fetchEnvironmentVariable("ANDROID_SERIAL");
211             if (env_serial != null
212                     && (!(device instanceof StubDevice) || (device instanceof FastbootDevice))) {
213                 mSerials.add(env_serial);
214             }
215             mFetchedEnvVariable = true;
216         }
217         return copyCollection(mSerials);
218     }
219 
220     /**
221      * Add a serial number to exclusion list.
222      *
223      * @param serialNumber
224      */
addExcludeSerial(String serialNumber)225     public void addExcludeSerial(String serialNumber) {
226         mExcludeSerials.add(serialNumber);
227     }
228 
229     /**
230      * Add a product type to the device selection options.
231      *
232      * @param productType
233      */
addProductType(String productType)234     public void addProductType(String productType) {
235         mProductTypes.add(productType);
236     }
237 
238     /**
239      * Add a property criteria to the device selection options
240      */
addProperty(String propertyKey, String propValue)241     public void addProperty(String propertyKey, String propValue) {
242         mPropertyMap.put(propertyKey, propValue);
243     }
244 
245     /**
246      * {@inheritDoc}
247      */
248     @Override
getExcludeSerials()249     public Collection<String> getExcludeSerials() {
250         return copyCollection(mExcludeSerials);
251     }
252 
253     /**
254      * {@inheritDoc}
255      */
256     @Override
getProductTypes()257     public Collection<String> getProductTypes() {
258         return copyCollection(mProductTypes);
259     }
260 
261     /**
262      * {@inheritDoc}
263      */
264     @Override
deviceRequested()265     public boolean deviceRequested() {
266         return mDeviceRequested;
267     }
268 
269     /**
270      * {@inheritDoc}
271      */
272     @Override
emulatorRequested()273     public boolean emulatorRequested() {
274         if (mRequestedType != null) {
275             return mRequestedType.equals(DeviceRequestedType.LOCAL_EMULATOR);
276         }
277         return mEmulatorRequested;
278     }
279 
280     /**
281      * {@inheritDoc}
282      */
283     @Override
stubEmulatorRequested()284     public boolean stubEmulatorRequested() {
285         if (mRequestedType != null) {
286             return mRequestedType.equals(DeviceRequestedType.LOCAL_EMULATOR);
287         }
288         return mStubEmulatorRequested;
289     }
290 
291     /**
292      * {@inheritDoc}
293      */
294     @Override
nullDeviceRequested()295     public boolean nullDeviceRequested() {
296         if (mRequestedType != null) {
297             return mRequestedType.equals(DeviceRequestedType.NULL_DEVICE);
298         }
299         return mNullDeviceRequested;
300     }
301 
302     /** {@inheritDoc} */
303     @Override
gceDeviceRequested()304     public boolean gceDeviceRequested() {
305         if (mRequestedType != null) {
306             return mRequestedType.equals(DeviceRequestedType.GCE_DEVICE);
307         }
308         return mGceDeviceRequested;
309     }
310 
setGceDeviceRequested(boolean gceDeviceRequested)311     public void setGceDeviceRequested(boolean gceDeviceRequested) {
312         mGceDeviceRequested = gceDeviceRequested;
313     }
314 
remoteDeviceRequested()315     public boolean remoteDeviceRequested() {
316         return DeviceRequestedType.REMOTE_DEVICE.equals(mRequestedType);
317     }
318 
localVirtualDeviceRequested()319     public boolean localVirtualDeviceRequested() {
320         return DeviceRequestedType.LOCAL_VIRTUAL_DEVICE.equals(mRequestedType);
321     }
322 
323     /**
324      * Sets the emulator requested flag
325      */
setEmulatorRequested(boolean emulatorRequested)326     public void setEmulatorRequested(boolean emulatorRequested) {
327         mEmulatorRequested = emulatorRequested;
328     }
329 
330     /**
331      * Sets the stub emulator requested flag
332      */
setStubEmulatorRequested(boolean stubEmulatorRequested)333     public void setStubEmulatorRequested(boolean stubEmulatorRequested) {
334         mStubEmulatorRequested = stubEmulatorRequested;
335     }
336 
337     /**
338      * Sets the emulator requested flag
339      */
setDeviceRequested(boolean deviceRequested)340     public void setDeviceRequested(boolean deviceRequested) {
341         mDeviceRequested = deviceRequested;
342     }
343 
344     /**
345      * Sets the null device requested flag
346      */
setNullDeviceRequested(boolean nullDeviceRequested)347     public void setNullDeviceRequested(boolean nullDeviceRequested) {
348         mNullDeviceRequested = nullDeviceRequested;
349     }
350 
setDeviceTypeRequested(DeviceRequestedType requestedType)351     public void setDeviceTypeRequested(DeviceRequestedType requestedType) {
352         mRequestedType = requestedType;
353     }
354 
getDeviceTypeRequested()355     public DeviceRequestedType getDeviceTypeRequested() {
356         return mRequestedType;
357     }
358 
359     @Override
getBaseDeviceTypeRequested()360     public BaseDeviceType getBaseDeviceTypeRequested() {
361         return mBaseDeviceType;
362     }
363 
364     @Override
setBaseDeviceTypeRequested(BaseDeviceType type)365     public void setBaseDeviceTypeRequested(BaseDeviceType type) {
366         mBaseDeviceType = type;
367     }
368 
369     /**
370      * Sets the minimum battery level
371      */
setMinBatteryLevel(Integer minBattery)372     public void setMinBatteryLevel(Integer minBattery) {
373         mMinBattery = minBattery;
374     }
375 
376     /**
377      * Gets the requested minimum battery level
378      */
getMinBatteryLevel()379     public Integer getMinBatteryLevel() {
380         return mMinBattery;
381     }
382 
383     /**
384      * Sets the maximum battery level
385      */
setMaxBatteryLevel(Integer maxBattery)386     public void setMaxBatteryLevel(Integer maxBattery) {
387         mMaxBattery = maxBattery;
388     }
389 
390     /**
391      * Gets the requested maximum battery level
392      */
getMaxBatteryLevel()393     public Integer getMaxBatteryLevel() {
394         return mMaxBattery;
395     }
396 
397     /** Sets the maximum battery level */
setMaxBatteryTemperature(Integer maxBatteryTemperature)398     public void setMaxBatteryTemperature(Integer maxBatteryTemperature) {
399         mMaxBatteryTemperature = maxBatteryTemperature;
400     }
401 
402     /** Gets the requested maximum battery level */
getMaxBatteryTemperature()403     public Integer getMaxBatteryTemperature() {
404         return mMaxBatteryTemperature;
405     }
406 
407     /** Sets whether battery check is required for devices with unknown battery level */
408     @Override
setRequireBatteryCheck(boolean requireCheck)409     public void setRequireBatteryCheck(boolean requireCheck) {
410         mRequireBatteryCheck = requireCheck;
411     }
412 
413     /**
414      * Gets whether battery check is required for devices with unknown battery level
415      */
getRequireBatteryCheck()416     public boolean getRequireBatteryCheck() {
417         return mRequireBatteryCheck;
418     }
419 
420     /** Sets whether battery temp check is required for devices with unknown battery temperature */
setRequireBatteryTemperatureCheck(boolean requireCheckTemprature)421     public void setRequireBatteryTemperatureCheck(boolean requireCheckTemprature) {
422         mRequireBatteryTemperatureCheck = requireCheckTemprature;
423     }
424 
425     /** Gets whether battery temp check is required for devices with unknown battery temperature */
getRequireBatteryTemperatureCheck()426     public boolean getRequireBatteryTemperatureCheck() {
427         return mRequireBatteryTemperatureCheck;
428     }
429 
430     /**
431      * {@inheritDoc}
432      */
433     @Override
getProperties()434     public Map<String, String> getProperties() {
435         return mPropertyMap;
436     }
437 
copyCollection(Collection<String> original)438     private Collection<String> copyCollection(Collection<String> original) {
439         Collection<String> listCopy = new ArrayList<String>(original.size());
440         listCopy.addAll(original);
441         return listCopy;
442     }
443 
444     /**
445      * Helper function used to fetch environment variable. It is essentially a wrapper around {@link
446      * System#getenv(String)} This is done for unit testing purposes.
447      *
448      * @param name the environment variable to fetch.
449      * @return a {@link String} value of the environment variable or null if not available.
450      */
451     @VisibleForTesting
fetchEnvironmentVariable(String name)452     public String fetchEnvironmentVariable(String name) {
453         return System.getenv(name);
454     }
455 
456     /**
457      * @return <code>true</code> if the given {@link IDevice} is a match for the provided options.
458      * <code>false</code> otherwise
459      */
460     @Override
matches(IDevice device)461     public boolean matches(IDevice device) {
462         String deviceSerial = device.getSerialNumber();
463         Collection<String> serials = getSerials(device);
464         Collection<String> excludeSerials = getExcludeSerials();
465         Map<String, Collection<String>> productVariants = splitOnVariant(getProductTypes());
466         Collection<String> productTypes = productVariants.keySet();
467         Map<String, String> properties = getProperties();
468 
469         if (!serials.isEmpty() &&
470                 !serials.contains(device.getSerialNumber())) {
471             // Don't add a reason here, if the serial doesn't even match it's just verbose
472             return false;
473         }
474         mSerialMatch = true;
475         if (excludeSerials.contains(device.getSerialNumber())) {
476             addNoMatchReason(
477                     deviceSerial,
478                     String.format(
479                             "device serial was part of excluded serials(%s)", excludeSerials));
480             return false;
481         }
482         if (!productTypes.isEmpty()) {
483             String productType = getDeviceProductType(device);
484             if (productTypes.contains(productType)) {
485                 // check variant
486                 String productVariant = getDeviceProductVariant(device);
487                 Collection<String> variants = productVariants.get(productType);
488                 if (variants != null && !variants.contains(productVariant)) {
489                     addNoMatchReason(
490                             deviceSerial,
491                             String.format(
492                                     "device variant (%s) does not match requested variants(%s)",
493                                     productVariant, variants));
494                     return false;
495                 }
496             } else {
497                 // no product type matches; bye-bye
498                 addNoMatchReason(
499                         deviceSerial,
500                         String.format(
501                                 "device product type (%s) does not match requested product"
502                                         + " types(%s)",
503                                 productType, productTypes));
504                 return false;
505             }
506         }
507         for (Map.Entry<String, String> propEntry : properties.entrySet()) {
508             String deviceProperty = device.getProperty(propEntry.getKey());
509             if (!propEntry.getValue().equals(deviceProperty)) {
510                 addNoMatchReason(
511                         deviceSerial,
512                         String.format(
513                                 "device property (%s) value(%s) does not match requested value(%s)",
514                                 propEntry.getKey(), deviceProperty, propEntry.getValue()));
515                 return false;
516             }
517         }
518         // Check if the device match the requested type
519         if (!checkDeviceTypeRequested(device)) {
520             return false;
521         }
522 
523         if ((mMinSdk != null) || (mMaxSdk != null)) {
524             int deviceSdkLevel = getDeviceSdkLevel(device);
525             if (deviceSdkLevel < 0) {
526                 addNoMatchReason(
527                         deviceSerial,
528                         String.format("device returned unexpected sdk level (%s)", deviceSdkLevel));
529                 return false;
530             }
531             if (mMinSdk != null && deviceSdkLevel < mMinSdk) {
532                 addNoMatchReason(
533                         deviceSerial,
534                         String.format(
535                                 "device sdk (%s) is below the requested min sdk (%s)",
536                                 deviceSdkLevel, mMinSdk));
537                 return false;
538             }
539             if (mMaxSdk != null && mMaxSdk < deviceSdkLevel) {
540                 addNoMatchReason(
541                         deviceSerial,
542                         String.format(
543                                 "device sdk (%s) is above the requested max sdk (%s)",
544                                 deviceSdkLevel, mMaxSdk));
545                 return false;
546             }
547         }
548         // If battery check is required and we have a min/max battery requested
549         if (mRequireBatteryCheck) {
550             if ((mMinBattery != null || mMaxBattery != null)) {
551                 // Only check battery on physical device. (FastbootDevice placeholder is always for
552                 // a physical device
553                 if (device instanceof StubDevice || device instanceof FastbootDevice) {
554                     // Reading battery of fastboot and StubDevice device does not work and could
555                     // lead to weird log.
556                     addNoMatchReason(
557                             deviceSerial,
558                             String.format(
559                                     "device type is (%s) which cannot have a battery required.",
560                                     device.getClass()));
561                     return false;
562                 }
563                 Integer deviceBattery = getBatteryLevel(device);
564                 if (deviceBattery == null) {
565                     // Couldn't determine battery level when that check is required; reject device
566                     addNoMatchReason(deviceSerial, "device failed to return a battery reading.");
567                     return false;
568                 }
569                 if (isLessAndNotNull(deviceBattery, mMinBattery)) {
570                     // deviceBattery < mMinBattery
571                     addNoMatchReason(
572                             deviceSerial,
573                             String.format(
574                                     "device battery (%s) is below the requested min battery (%s)",
575                                     deviceBattery, mMinBattery));
576                     return false;
577                 }
578                 if (isLessEqAndNotNull(mMaxBattery, deviceBattery)) {
579                     // mMaxBattery <= deviceBattery
580                     addNoMatchReason(
581                             deviceSerial,
582                             String.format(
583                                     "device battery (%s) is above the requested max battery (%s)",
584                                     deviceBattery, mMaxBattery));
585                     return false;
586                 }
587             }
588         }
589         // If temperature check is required and we have a max temperature requested.
590         if (mRequireBatteryTemperatureCheck) {
591             if (mMaxBatteryTemperature != null
592                     && (!(device instanceof StubDevice) || (device instanceof FastbootDevice))) {
593                 // Only check battery temp on physical device. (FastbootDevice placeholder is
594                 // always for a physical device
595 
596                 if (device instanceof FastbootDevice) {
597                     // Cannot get battery temperature
598                     return false;
599                 }
600 
601                 // Extract the temperature from the file
602                 BatteryTemperature temp = new BatteryTemperature();
603                 Integer deviceBatteryTemp = temp.getBatteryTemperature(device);
604 
605                 if (deviceBatteryTemp <= 0) {
606                     // Couldn't determine battery temp when that check is required; reject device
607                     return false;
608                 }
609 
610                 if (isLessEqAndNotNull(mMaxBatteryTemperature, deviceBatteryTemp)) {
611                     // mMaxBatteryTemperature <= deviceBatteryTemp
612                     return false;
613                 }
614             }
615         }
616 
617         return true;
618     }
619 
620     /** Determine whether a device match the requested type or not. */
checkDeviceTypeRequested(IDevice device)621     private boolean checkDeviceTypeRequested(IDevice device) {
622         if ((emulatorRequested() || stubEmulatorRequested()) && !device.isEmulator()) {
623             return false;
624         }
625         String deviceSerial = device.getSerialNumber();
626         // If physical device is requested but device is emulator or remote ip device, skip
627         if (deviceRequested()
628                 && (device.isEmulator()
629                         || RemoteAndroidDevice.checkSerialFormatValid(device.getSerialNumber()))) {
630             addNoMatchReason(deviceSerial, "device is not a physical device");
631             return false;
632         }
633 
634         if (mRequestedType != null) {
635             Class<?> classNeeded = mRequestedType.getRequiredClass();
636             // Don't match IDevice for real device
637             if (!DeviceRequestedType.EXISTING_DEVICE.equals(mRequestedType)) {
638                 if (!device.getClass().equals(classNeeded)) {
639                     addNoMatchReason(
640                             deviceSerial,
641                             String.format(
642                                     "device is type (%s) while requested type was (%s)",
643                                     device.getClass(), classNeeded));
644                     return false;
645                 }
646             }
647         } else {
648             if (device.isEmulator() && (device instanceof StubDevice) && !stubEmulatorRequested()) {
649                 // only allocate the stub emulator if requested
650                 addNoMatchReason(deviceSerial, "device is emulator while requested type was not");
651                 return false;
652             }
653             if (nullDeviceRequested() != (device instanceof NullDevice)) {
654                 addNoMatchReason(
655                         deviceSerial, "device is null-device while requested type was not");
656                 return false;
657             }
658             if (gceDeviceRequested() != RemoteAvdIDevice.class.equals(device.getClass())) {
659                 // We only match an exact RemoteAvdIDevice here, no child class.
660                 addNoMatchReason(deviceSerial, "device is gce-device while requested type was not");
661                 return false;
662             }
663             if (remoteDeviceRequested() != VmRemoteDevice.class.equals(device.getClass())) {
664                 addNoMatchReason(
665                         deviceSerial, "device is remote-device while requested type was not");
666                 return false;
667             }
668             if (localVirtualDeviceRequested()
669                     != StubLocalAndroidVirtualDevice.class.equals(device.getClass())) {
670                 addNoMatchReason(
671                         deviceSerial, "device is local-virtual while requested type was not");
672                 return false;
673             }
674         }
675         return true;
676     }
677 
678     /** Determine if x is less-than y, given that both are non-Null */
isLessAndNotNull(Integer x, Integer y)679     private static boolean isLessAndNotNull(Integer x, Integer y) {
680         if ((x == null) || (y == null)) {
681             return false;
682         }
683         return x < y;
684     }
685 
686     /** Determine if x is less-than y, given that both are non-Null */
isLessEqAndNotNull(Integer x, Integer y)687     private static boolean isLessEqAndNotNull(Integer x, Integer y) {
688         if ((x == null) || (y == null)) {
689             return false;
690         }
691         return x <= y;
692     }
693 
splitOnVariant(Collection<String> products)694     private Map<String, Collection<String>> splitOnVariant(Collection<String> products) {
695         // FIXME: we should validate all provided device selection options once, on the first
696         // FIXME: call to #matches
697         Map<String, Collection<String>> splitProducts =
698                 new HashMap<String, Collection<String>>(products.size());
699         // FIXME: cache this
700         for (String prod : products) {
701             String[] parts = prod.split(VARIANT_SEPARATOR);
702             if (parts.length == 1) {
703                 splitProducts.put(parts[0], null);
704             } else if (parts.length == 2) {
705                 // A variant was specified as product:variant
706                 Collection<String> variants = splitProducts.get(parts[0]);
707                 if (variants == null) {
708                     variants = new HashSet<String>();
709                     splitProducts.put(parts[0], variants);
710                 }
711                 variants.add(parts[1]);
712             } else {
713                 throw new IllegalArgumentException(String.format("The product type filter \"%s\" " +
714                         "is invalid.  It must contain 0 or 1 '%s' characters, not %d.",
715                         prod, VARIANT_SEPARATOR, parts.length));
716             }
717         }
718 
719         return splitProducts;
720     }
721 
722     @Override
getDeviceProductType(IDevice device)723     public String getDeviceProductType(IDevice device) {
724         String prop = getProperty(device, DeviceProperties.BOARD);
725         // fallback to ro.hardware for legacy devices
726         if (Strings.isNullOrEmpty(prop)) {
727             prop = getProperty(device, DeviceProperties.HARDWARE);
728         }
729         if (prop != null) {
730             prop = prop.toLowerCase();
731         }
732         return prop;
733     }
734 
getProperty(IDevice device, String propName)735     private String getProperty(IDevice device, String propName) {
736         return device.getProperty(propName);
737     }
738 
739     @Override
getDeviceProductVariant(IDevice device)740     public String getDeviceProductVariant(IDevice device) {
741         String prop = getProperty(device, DeviceProperties.VARIANT);
742         if (prop == null) {
743             prop = getProperty(device, DeviceProperties.VARIANT_LEGACY_O_MR1);
744         }
745         if (prop == null) {
746             prop = getProperty(device, DeviceProperties.VARIANT_LEGACY_LESS_EQUAL_O);
747         }
748         if (prop != null) {
749             prop = prop.toLowerCase();
750         }
751         return prop;
752     }
753 
754     @Override
getBatteryLevel(IDevice device)755     public Integer getBatteryLevel(IDevice device) {
756         try {
757             // use default 5 minutes freshness
758             Future<Integer> batteryFuture = device.getBattery();
759             // get cached value or wait up to 500ms for battery level query
760             return batteryFuture.get(500, TimeUnit.MILLISECONDS);
761         } catch (InterruptedException | ExecutionException |
762                 java.util.concurrent.TimeoutException e) {
763             CLog.w("Failed to query battery level for %s: %s", device.getSerialNumber(),
764                     e.toString());
765         }
766         return null;
767     }
768 
769     /**
770      * Get the device's supported API level or -1 if it cannot be retrieved
771      * @param device
772      * @return the device's supported API level.
773      */
getDeviceSdkLevel(IDevice device)774     private int getDeviceSdkLevel(IDevice device) {
775         int apiLevel = -1;
776         String prop = getProperty(device, DeviceProperties.SDK_VERSION);
777         try {
778             apiLevel = Integer.parseInt(prop);
779         } catch (NumberFormatException nfe) {
780             CLog.w("Failed to parse sdk level %s for device %s", prop, device.getSerialNumber());
781         }
782         return apiLevel;
783     }
784 
addNoMatchReason(String device, String reason)785     private void addNoMatchReason(String device, String reason) {
786         mNoMatchReason.put(device, reason);
787     }
788 
789     @Override
getNoMatchReason()790     public Map<String, String> getNoMatchReason() {
791         if (!mSerialMatch) {
792             mNoMatchReason.put(
793                     "no_match",
794                     String.format("Need serial (%s) but couldn't match it.", getSerials()));
795         }
796         mSerialMatch = false;
797         return mNoMatchReason;
798     }
799 
800     /**
801      * Helper factory method to create a {@link IDeviceSelection} that will only match device
802      * with given serial
803      */
createForSerial(String serial)804     public static IDeviceSelection createForSerial(String serial) {
805         DeviceSelectionOptions o = new DeviceSelectionOptions();
806         o.setSerial(serial);
807         return o;
808     }
809 }
810