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 17 package com.android.tradefed.targetprep; 18 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.build.IDeviceBuildInfo; 21 import com.android.tradefed.config.GlobalConfiguration; 22 import com.android.tradefed.config.Option; 23 import com.android.tradefed.device.DeviceNotAvailableException; 24 import com.android.tradefed.device.DeviceUnresponsiveException; 25 import com.android.tradefed.device.ITestDevice; 26 import com.android.tradefed.device.ITestDevice.RecoveryMode; 27 import com.android.tradefed.host.IHostOptions; 28 import com.android.tradefed.log.LogUtil.CLog; 29 import com.android.tradefed.targetprep.IDeviceFlasher.UserDataFlashOption; 30 import com.android.tradefed.util.IRunUtil; 31 import com.android.tradefed.util.RunUtil; 32 33 import java.util.ArrayList; 34 import java.util.Collection; 35 import java.util.concurrent.Semaphore; 36 import java.util.concurrent.TimeUnit; 37 38 /** 39 * A {@link ITargetPreparer} that flashes an image on physical Android hardware. 40 */ 41 public abstract class DeviceFlashPreparer implements ITargetCleaner { 42 43 /** 44 * Enum of options for handling the encryption of userdata image 45 */ 46 public static enum EncryptionOptions {ENCRYPT, IGNORE} 47 48 private static final int BOOT_POLL_TIME_MS = 5 * 1000; 49 50 @Option(name = "device-boot-time", description = "max time in ms to wait for device to boot.") 51 private long mDeviceBootTime = 5 * 60 * 1000; 52 53 @Option(name = "userdata-flash", description = 54 "specify handling of userdata partition.") 55 private UserDataFlashOption mUserDataFlashOption = UserDataFlashOption.FLASH; 56 57 @Option(name = "encrypt-userdata", description = "specify if userdata partition should be " 58 + "encrypted; defaults to IGNORE, where no actions will be taken.") 59 private EncryptionOptions mEncryptUserData = EncryptionOptions.IGNORE; 60 61 @Option(name = "force-system-flash", description = 62 "specify if system should always be flashed even if already running desired build.") 63 private boolean mForceSystemFlash = false; 64 65 /* 66 * A temporary workaround for special builds. Should be removed after changes from build team. 67 * Bug: 18078421 68 */ 69 @Option(name = "skip-post-flash-flavor-check", description = 70 "specify if system flavor should not be checked after flash") 71 private boolean mSkipPostFlashFlavorCheck = false; 72 73 /* 74 * Used for update testing 75 */ 76 @Option(name = "skip-post-flash-build-id-check", description = 77 "specify if build ID should not be checked after flash") 78 private boolean mSkipPostFlashBuildIdCheck = false; 79 80 @Option(name = "wipe-skip-list", description = 81 "list of /data subdirectories to NOT wipe when doing UserDataFlashOption.TESTS_ZIP") 82 private Collection<String> mDataWipeSkipList = new ArrayList<>(); 83 84 @Option(name = "concurrent-flasher-limit", description = 85 "The maximum number of concurrent flashers (may be useful to avoid memory constraints)" + 86 "This will be overriden if one is set in the host options.") 87 private Integer mConcurrentFlasherLimit = null; 88 89 @Option(name = "skip-post-flashing-setup", 90 description = "whether or not to skip post-flashing setup steps") 91 private boolean mSkipPostFlashingSetup = false; 92 93 @Option(name = "wipe-timeout", 94 description = "the timeout for the command of wiping user data.", isTimeVal = true) 95 private long mWipeTimeout = 4 * 60 * 1000; 96 97 @Option(name = "disable", description = "Disable the device flasher.") 98 private boolean mDisable = false; 99 100 private static Semaphore sConcurrentFlashLock = null; 101 102 /** 103 * This serves both as an indication of whether the flash lock should be used, and as an 104 * indicator of whether or not the flash lock has been initialized -- if this is true 105 * and {@code mConcurrentFlashLock} is {@code null}, then it has not yet been initialized. 106 */ 107 private static Boolean sShouldCheckFlashLock = true; 108 109 /** 110 * Sets the device boot time 111 * <p/> 112 * Exposed for unit testing 113 */ 114 void setDeviceBootTime(long bootTime) { 115 mDeviceBootTime = bootTime; 116 } 117 118 /** 119 * Gets the interval between device boot poll attempts. 120 * <p/> 121 * Exposed for unit testing 122 */ 123 int getDeviceBootPollTimeMs() { 124 return BOOT_POLL_TIME_MS; 125 } 126 127 /** 128 * Gets the {@link IRunUtil} instance to use. 129 * <p/> 130 * Exposed for unit testing 131 */ 132 IRunUtil getRunUtil() { 133 return RunUtil.getDefault(); 134 } 135 136 /** 137 * Gets the {@link IHostOptions} instance to use. 138 * <p/> 139 * Exposed for unit testing 140 */ 141 IHostOptions getHostOptions() { 142 return GlobalConfiguration.getInstance().getHostOptions(); 143 } 144 145 /** 146 * Set the userdata-flash option 147 * 148 * @param flashOption 149 */ 150 public void setUserDataFlashOption(UserDataFlashOption flashOption) { 151 mUserDataFlashOption = flashOption; 152 } 153 154 /** 155 * Set the state of the concurrent flash limit implementation 156 * 157 * Exposed for unit testing 158 */ 159 void setConcurrentFlashSettings(Integer limit, Semaphore flashLock, boolean shouldCheck) { 160 synchronized(sShouldCheckFlashLock) { 161 // Make a minimal attempt to avoid having things get into an inconsistent state 162 if (sConcurrentFlashLock != null && mConcurrentFlasherLimit != null) { 163 int curLimit = mConcurrentFlasherLimit; 164 int curAvail = sConcurrentFlashLock.availablePermits(); 165 if (curLimit != curAvail) { 166 throw new IllegalStateException(String.format("setConcurrentFlashSettings may " + 167 "not be called while any permits are active. The flasher limit is %d, " + 168 "but there are only %d permits available.", curLimit, curAvail)); 169 } 170 } 171 172 mConcurrentFlasherLimit = limit; 173 sConcurrentFlashLock = flashLock; 174 sShouldCheckFlashLock = shouldCheck; 175 } 176 } 177 178 Semaphore getConcurrentFlashLock() { 179 return sConcurrentFlashLock; 180 } 181 182 /** 183 * Request permission to flash. If the number of concurrent flashers is limited, this will 184 * wait in line in order to remain under the flash limit count. 185 * 186 * Exposed for unit testing. 187 */ 188 void takeFlashingPermit() { 189 if (!sShouldCheckFlashLock) return; 190 191 // The logic below is to avoid multi-thread race conditions while initializing 192 // mConcurrentFlashLock when we hit this condition. 193 if (sConcurrentFlashLock == null) { 194 // null with mShouldCheckFlashLock == true means initialization hasn't been done yet 195 synchronized(sShouldCheckFlashLock) { 196 // Check all state again, since another thread might have gotten here first 197 if (!sShouldCheckFlashLock) return; 198 199 Integer concurrentFlasherLimit = mConcurrentFlasherLimit; 200 IHostOptions hostOptions = getHostOptions(); 201 if (hostOptions.getConcurrentFlasherLimit() != null) { 202 CLog.i("using host-wide concurrent flasher limit %d", 203 hostOptions.getConcurrentFlasherLimit()); 204 concurrentFlasherLimit = hostOptions.getConcurrentFlasherLimit(); 205 } 206 207 if (concurrentFlasherLimit == null) { 208 sShouldCheckFlashLock = false; 209 return; 210 } 211 212 if (sConcurrentFlashLock == null) { 213 sConcurrentFlashLock = new Semaphore(concurrentFlasherLimit, true /* fair */); 214 } 215 } 216 } 217 CLog.i( 218 "Requesting a flashing permit out of the host max limit of %s. Current queue " 219 + "length: %s", 220 getHostOptions().getConcurrentFlasherLimit(), 221 sConcurrentFlashLock.getQueueLength()); 222 sConcurrentFlashLock.acquireUninterruptibly(); 223 } 224 225 /** 226 * Restore a flashing permit that we acquired previously 227 * 228 * Exposed for unit testing. 229 */ 230 void returnFlashingPermit() { 231 if (sConcurrentFlashLock != null) { 232 sConcurrentFlashLock.release(); 233 } 234 } 235 236 /** 237 * {@inheritDoc} 238 */ 239 @Override 240 public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, 241 DeviceNotAvailableException, BuildError { 242 if (mDisable) { 243 CLog.i("Skipping device flashing."); 244 return; 245 } 246 CLog.i("Performing setup on %s", device.getSerialNumber()); 247 if (!(buildInfo instanceof IDeviceBuildInfo)) { 248 throw new IllegalArgumentException("Provided buildInfo is not a IDeviceBuildInfo"); 249 } 250 // don't allow interruptions during flashing operations. 251 getRunUtil().allowInterrupt(false); 252 try { 253 IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo)buildInfo; 254 checkDeviceProductType(device, deviceBuild); 255 device.setRecoveryMode(RecoveryMode.ONLINE); 256 IDeviceFlasher flasher = createFlasher(device); 257 flasher.setWipeTimeout(mWipeTimeout); 258 // only surround fastboot related operations with flashing permit restriction 259 try { 260 long start = System.currentTimeMillis(); 261 takeFlashingPermit(); 262 CLog.v("Flashing permit obtained after %ds", 263 TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start))); 264 265 flasher.overrideDeviceOptions(device); 266 flasher.setUserDataFlashOption(mUserDataFlashOption); 267 flasher.setForceSystemFlash(mForceSystemFlash); 268 flasher.setDataWipeSkipList(mDataWipeSkipList); 269 preEncryptDevice(device, flasher); 270 flasher.flash(device, deviceBuild); 271 } finally { 272 returnFlashingPermit(); 273 } 274 // only want logcat captured for current build, delete any accumulated log data 275 device.clearLogcat(); 276 if (mSkipPostFlashingSetup) { 277 return; 278 } 279 // Temporary re-enable interruptable since the critical flashing operation is over. 280 getRunUtil().allowInterrupt(true); 281 device.waitForDeviceOnline(); 282 // device may lose date setting if wiped, update with host side date in case anything on 283 // device side malfunction with an invalid date 284 if (device.enableAdbRoot()) { 285 device.setDate(null); 286 } 287 // Disable interrupt for encryption operation. 288 getRunUtil().allowInterrupt(false); 289 checkBuild(device, deviceBuild); 290 postEncryptDevice(device, flasher); 291 // Once critical operation is done, we re-enable interruptable 292 getRunUtil().allowInterrupt(true); 293 try { 294 device.setRecoveryMode(RecoveryMode.AVAILABLE); 295 device.waitForDeviceAvailable(mDeviceBootTime); 296 } catch (DeviceUnresponsiveException e) { 297 // assume this is a build problem 298 throw new DeviceFailedToBootError(String.format( 299 "Device %s did not become available after flashing %s", 300 device.getSerialNumber(), deviceBuild.getDeviceBuildId()), 301 device.getDeviceDescriptor()); 302 } 303 device.postBootSetup(); 304 } finally { 305 // Allow interruption at the end no matter what. 306 getRunUtil().allowInterrupt(true); 307 } 308 } 309 310 /** 311 * Possible check before flashing to ensure the device is as expected compare to the build info. 312 * 313 * @param device the {@link ITestDevice} to flash. 314 * @param deviceBuild the {@link IDeviceBuildInfo} used to flash. 315 * @throws BuildError 316 * @throws DeviceNotAvailableException 317 */ 318 protected void checkDeviceProductType(ITestDevice device, IDeviceBuildInfo deviceBuild) 319 throws BuildError, DeviceNotAvailableException { 320 // empty of purpose 321 } 322 323 /** 324 * Verifies the expected build matches the actual build on device after flashing 325 * @throws DeviceNotAvailableException 326 */ 327 private void checkBuild(ITestDevice device, IDeviceBuildInfo deviceBuild) 328 throws DeviceNotAvailableException { 329 // Need to use deviceBuild.getDeviceBuildId instead of getBuildId because the build info 330 // could be an AppBuildInfo and return app build id. Need to be more explicit that we 331 // check for the device build here. 332 if (!mSkipPostFlashBuildIdCheck) { 333 checkBuildAttribute(deviceBuild.getDeviceBuildId(), device.getBuildId(), 334 device.getSerialNumber()); 335 } 336 if (!mSkipPostFlashFlavorCheck) { 337 checkBuildAttribute(deviceBuild.getDeviceBuildFlavor(), device.getBuildFlavor(), 338 device.getSerialNumber()); 339 } 340 // TODO: check bootloader and baseband versions too 341 } 342 343 private void checkBuildAttribute(String expectedBuildAttr, String actualBuildAttr, 344 String serial) throws DeviceNotAvailableException { 345 if (expectedBuildAttr == null || actualBuildAttr == null || 346 !expectedBuildAttr.equals(actualBuildAttr)) { 347 // throw DNAE - assume device hardware problem - we think flash was successful but 348 // device is not running right bits 349 throw new DeviceNotAvailableException( 350 String.format("Unexpected build after flashing. Expected %s, actual %s", 351 expectedBuildAttr, actualBuildAttr), serial); 352 } 353 } 354 355 /** 356 * Create {@link IDeviceFlasher} to use. Subclasses can override 357 * @throws DeviceNotAvailableException 358 */ 359 protected abstract IDeviceFlasher createFlasher(ITestDevice device) 360 throws DeviceNotAvailableException; 361 362 /** 363 * Handle encrypting of the device pre-flash. 364 * 365 * @see #postEncryptDevice(ITestDevice, IDeviceFlasher) 366 * @param device 367 * @throws DeviceNotAvailableException 368 * @throws TargetSetupError if the device could not be encrypted or unlocked. 369 */ 370 private void preEncryptDevice(ITestDevice device, IDeviceFlasher flasher) 371 throws DeviceNotAvailableException, TargetSetupError { 372 switch (mEncryptUserData) { 373 case IGNORE: 374 return; 375 case ENCRYPT: 376 if (!device.isEncryptionSupported()) { 377 throw new TargetSetupError("Encryption is not supported", 378 device.getDeviceDescriptor()); 379 } 380 if (!device.isDeviceEncrypted()) { 381 switch(flasher.getUserDataFlashOption()) { 382 case TESTS_ZIP: // Intentional fall through. 383 case WIPE_RM: 384 // a new filesystem will not be created by the flasher, but the userdata 385 // partition is expected to be cleared anyway, so we encrypt the device 386 // with wipe 387 if (!device.encryptDevice(false)) { 388 throw new TargetSetupError("Failed to encrypt device", 389 device.getDeviceDescriptor()); 390 } 391 if (!device.unlockDevice()) { 392 throw new TargetSetupError("Failed to unlock device", 393 device.getDeviceDescriptor()); 394 } 395 break; 396 case RETAIN: 397 // original filesystem must be retained, so we encrypt in place 398 if (!device.encryptDevice(true)) { 399 throw new TargetSetupError("Failed to encrypt device", 400 device.getDeviceDescriptor()); 401 } 402 if (!device.unlockDevice()) { 403 throw new TargetSetupError("Failed to unlock device", 404 device.getDeviceDescriptor()); 405 } 406 break; 407 default: 408 // Do nothing, userdata will be encrypted post-flash. 409 } 410 } 411 break; 412 default: 413 // should not get here 414 return; 415 } 416 } 417 418 /** 419 * Handle encrypting of the device post-flash. 420 * <p> 421 * This method handles encrypting the device after a flash in cases where a flash would undo any 422 * encryption pre-flash, such as when the device is flashed or wiped. 423 * </p> 424 * 425 * @see #preEncryptDevice(ITestDevice, IDeviceFlasher) 426 * @param device 427 * @throws DeviceNotAvailableException 428 * @throws TargetSetupError If the device could not be encrypted or unlocked. 429 */ 430 private void postEncryptDevice(ITestDevice device, IDeviceFlasher flasher) 431 throws DeviceNotAvailableException, TargetSetupError { 432 switch (mEncryptUserData) { 433 case IGNORE: 434 return; 435 case ENCRYPT: 436 if (!device.isEncryptionSupported()) { 437 throw new TargetSetupError("Encryption is not supported", 438 device.getDeviceDescriptor()); 439 } 440 switch(flasher.getUserDataFlashOption()) { 441 case FLASH: 442 if (!device.encryptDevice(true)) { 443 throw new TargetSetupError("Failed to encrypt device", 444 device.getDeviceDescriptor()); 445 } 446 break; 447 case WIPE: // Intentional fall through. 448 case FORCE_WIPE: 449 // since the device was just wiped, encrypt with wipe 450 if (!device.encryptDevice(false)) { 451 throw new TargetSetupError("Failed to encrypt device", 452 device.getDeviceDescriptor()); 453 } 454 break; 455 default: 456 // Do nothing, userdata was encrypted pre-flash. 457 } 458 if (!device.unlockDevice()) { 459 throw new TargetSetupError("Failed to unlock device", 460 device.getDeviceDescriptor()); 461 } 462 break; 463 default: 464 // should not get here 465 return; 466 } 467 } 468 469 @Override 470 public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) 471 throws DeviceNotAvailableException { 472 if (mDisable) { 473 CLog.i("Skipping device flashing tearDown."); 474 return; 475 } 476 if (mEncryptUserData == EncryptionOptions.ENCRYPT 477 && mUserDataFlashOption != UserDataFlashOption.RETAIN) { 478 if (e instanceof DeviceNotAvailableException) { 479 CLog.e("Device was encrypted but now unavailable. may need manual cleanup"); 480 } else if (device.isDeviceEncrypted()) { 481 if (!device.unencryptDevice()) { 482 throw new RuntimeException("Failed to unencrypt device"); 483 } 484 } 485 } 486 } 487 } 488