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