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