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 218 sConcurrentFlashLock.acquireUninterruptibly(); 219 } 220 221 /** 222 * Restore a flashing permit that we acquired previously 223 * 224 * Exposed for unit testing. 225 */ 226 void returnFlashingPermit() { 227 if (sConcurrentFlashLock != null) { 228 sConcurrentFlashLock.release(); 229 } 230 } 231 232 /** 233 * {@inheritDoc} 234 */ 235 @Override 236 public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, 237 DeviceNotAvailableException, BuildError { 238 if (mDisable) { 239 CLog.i("Skipping device flashing."); 240 return; 241 } 242 CLog.i("Performing setup on %s", device.getSerialNumber()); 243 if (!(buildInfo instanceof IDeviceBuildInfo)) { 244 throw new IllegalArgumentException("Provided buildInfo is not a IDeviceBuildInfo"); 245 } 246 // don't allow interruptions during flashing operations. 247 getRunUtil().allowInterrupt(false); 248 try { 249 IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo)buildInfo; 250 device.setRecoveryMode(RecoveryMode.ONLINE); 251 IDeviceFlasher flasher = createFlasher(device); 252 flasher.setWipeTimeout(mWipeTimeout); 253 // only surround fastboot related operations with flashing permit restriction 254 try { 255 long start = System.currentTimeMillis(); 256 takeFlashingPermit(); 257 CLog.v("Flashing permit obtained after %ds", 258 TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start))); 259 260 flasher.overrideDeviceOptions(device); 261 flasher.setUserDataFlashOption(mUserDataFlashOption); 262 flasher.setForceSystemFlash(mForceSystemFlash); 263 flasher.setDataWipeSkipList(mDataWipeSkipList); 264 preEncryptDevice(device, flasher); 265 flasher.flash(device, deviceBuild); 266 } finally { 267 returnFlashingPermit(); 268 } 269 // only want logcat captured for current build, delete any accumulated log data 270 device.clearLogcat(); 271 if (mSkipPostFlashingSetup) { 272 return; 273 } 274 // Temporary re-enable interruptable since the critical flashing operation is over. 275 getRunUtil().allowInterrupt(true); 276 device.waitForDeviceOnline(); 277 // device may lose date setting if wiped, update with host side date in case anything on 278 // device side malfunction with an invalid date 279 if (device.enableAdbRoot()) { 280 device.setDate(null); 281 } 282 // Disable interrupt for encryption operation. 283 getRunUtil().allowInterrupt(false); 284 checkBuild(device, deviceBuild); 285 postEncryptDevice(device, flasher); 286 // Once critical operation is done, we re-enable interruptable 287 getRunUtil().allowInterrupt(true); 288 try { 289 device.setRecoveryMode(RecoveryMode.AVAILABLE); 290 device.waitForDeviceAvailable(mDeviceBootTime); 291 } catch (DeviceUnresponsiveException e) { 292 // assume this is a build problem 293 throw new DeviceFailedToBootError(String.format( 294 "Device %s did not become available after flashing %s", 295 device.getSerialNumber(), deviceBuild.getDeviceBuildId()), 296 device.getDeviceDescriptor()); 297 } 298 device.postBootSetup(); 299 } finally { 300 // Allow interruption at the end no matter what. 301 getRunUtil().allowInterrupt(true); 302 } 303 } 304 305 /** 306 * Verifies the expected build matches the actual build on device after flashing 307 * @throws DeviceNotAvailableException 308 */ 309 private void checkBuild(ITestDevice device, IDeviceBuildInfo deviceBuild) 310 throws DeviceNotAvailableException { 311 // Need to use deviceBuild.getDeviceBuildId instead of getBuildId because the build info 312 // could be an AppBuildInfo and return app build id. Need to be more explicit that we 313 // check for the device build here. 314 if (!mSkipPostFlashBuildIdCheck) { 315 checkBuildAttribute(deviceBuild.getDeviceBuildId(), device.getBuildId(), 316 device.getSerialNumber()); 317 } 318 if (!mSkipPostFlashFlavorCheck) { 319 checkBuildAttribute(deviceBuild.getDeviceBuildFlavor(), device.getBuildFlavor(), 320 device.getSerialNumber()); 321 } 322 // TODO: check bootloader and baseband versions too 323 } 324 325 private void checkBuildAttribute(String expectedBuildAttr, String actualBuildAttr, 326 String serial) throws DeviceNotAvailableException { 327 if (expectedBuildAttr == null || actualBuildAttr == null || 328 !expectedBuildAttr.equals(actualBuildAttr)) { 329 // throw DNAE - assume device hardware problem - we think flash was successful but 330 // device is not running right bits 331 throw new DeviceNotAvailableException( 332 String.format("Unexpected build after flashing. Expected %s, actual %s", 333 expectedBuildAttr, actualBuildAttr), serial); 334 } 335 } 336 337 /** 338 * Create {@link IDeviceFlasher} to use. Subclasses can override 339 * @throws DeviceNotAvailableException 340 */ 341 protected abstract IDeviceFlasher createFlasher(ITestDevice device) 342 throws DeviceNotAvailableException; 343 344 /** 345 * Handle encrypting of the device pre-flash. 346 * 347 * @see #postEncryptDevice(ITestDevice, IDeviceFlasher) 348 * @param device 349 * @throws DeviceNotAvailableException 350 * @throws TargetSetupError if the device could not be encrypted or unlocked. 351 */ 352 private void preEncryptDevice(ITestDevice device, IDeviceFlasher flasher) 353 throws DeviceNotAvailableException, TargetSetupError { 354 switch (mEncryptUserData) { 355 case IGNORE: 356 return; 357 case ENCRYPT: 358 if (!device.isEncryptionSupported()) { 359 throw new TargetSetupError("Encryption is not supported", 360 device.getDeviceDescriptor()); 361 } 362 if (!device.isDeviceEncrypted()) { 363 switch(flasher.getUserDataFlashOption()) { 364 case TESTS_ZIP: // Intentional fall through. 365 case WIPE_RM: 366 // a new filesystem will not be created by the flasher, but the userdata 367 // partition is expected to be cleared anyway, so we encrypt the device 368 // with wipe 369 if (!device.encryptDevice(false)) { 370 throw new TargetSetupError("Failed to encrypt device", 371 device.getDeviceDescriptor()); 372 } 373 if (!device.unlockDevice()) { 374 throw new TargetSetupError("Failed to unlock device", 375 device.getDeviceDescriptor()); 376 } 377 break; 378 case RETAIN: 379 // original filesystem must be retained, so we encrypt in place 380 if (!device.encryptDevice(true)) { 381 throw new TargetSetupError("Failed to encrypt device", 382 device.getDeviceDescriptor()); 383 } 384 if (!device.unlockDevice()) { 385 throw new TargetSetupError("Failed to unlock device", 386 device.getDeviceDescriptor()); 387 } 388 break; 389 default: 390 // Do nothing, userdata will be encrypted post-flash. 391 } 392 } 393 break; 394 default: 395 // should not get here 396 return; 397 } 398 } 399 400 /** 401 * Handle encrypting of the device post-flash. 402 * <p> 403 * This method handles encrypting the device after a flash in cases where a flash would undo any 404 * encryption pre-flash, such as when the device is flashed or wiped. 405 * </p> 406 * 407 * @see #preEncryptDevice(ITestDevice, IDeviceFlasher) 408 * @param device 409 * @throws DeviceNotAvailableException 410 * @throws TargetSetupError If the device could not be encrypted or unlocked. 411 */ 412 private void postEncryptDevice(ITestDevice device, IDeviceFlasher flasher) 413 throws DeviceNotAvailableException, TargetSetupError { 414 switch (mEncryptUserData) { 415 case IGNORE: 416 return; 417 case ENCRYPT: 418 if (!device.isEncryptionSupported()) { 419 throw new TargetSetupError("Encryption is not supported", 420 device.getDeviceDescriptor()); 421 } 422 switch(flasher.getUserDataFlashOption()) { 423 case FLASH: 424 if (!device.encryptDevice(true)) { 425 throw new TargetSetupError("Failed to encrypt device", 426 device.getDeviceDescriptor()); 427 } 428 break; 429 case WIPE: // Intentional fall through. 430 case FORCE_WIPE: 431 // since the device was just wiped, encrypt with wipe 432 if (!device.encryptDevice(false)) { 433 throw new TargetSetupError("Failed to encrypt device", 434 device.getDeviceDescriptor()); 435 } 436 break; 437 default: 438 // Do nothing, userdata was encrypted pre-flash. 439 } 440 if (!device.unlockDevice()) { 441 throw new TargetSetupError("Failed to unlock device", 442 device.getDeviceDescriptor()); 443 } 444 break; 445 default: 446 // should not get here 447 return; 448 } 449 } 450 451 @Override 452 public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) 453 throws DeviceNotAvailableException { 454 if (mDisable) { 455 CLog.i("Skipping device flashing tearDown."); 456 return; 457 } 458 if (mEncryptUserData == EncryptionOptions.ENCRYPT 459 && mUserDataFlashOption != UserDataFlashOption.RETAIN) { 460 if (e instanceof DeviceNotAvailableException) { 461 CLog.e("Device was encrypted but now unavailable. may need manual cleanup"); 462 } else if (device.isDeviceEncrypted()) { 463 if (!device.unencryptDevice()) { 464 throw new RuntimeException("Failed to unencrypt device"); 465 } 466 } 467 } 468 } 469 } 470