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