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