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