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.IDeviceBuildInfo; 20 import com.android.tradefed.command.remote.DeviceDescriptor; 21 import com.android.tradefed.device.DeviceNotAvailableException; 22 import com.android.tradefed.device.IManagedTestDevice; 23 import com.android.tradefed.device.ITestDevice; 24 import com.android.tradefed.device.TestDeviceState; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.util.CommandResult; 27 import com.android.tradefed.util.CommandStatus; 28 import com.android.tradefed.util.FileUtil; 29 import com.android.tradefed.util.IRunUtil; 30 import com.android.tradefed.util.RunUtil; 31 import com.android.tradefed.util.ZipUtil2; 32 33 import org.apache.commons.compress.archivers.zip.ZipFile; 34 35 import java.io.File; 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collection; 40 import java.util.List; 41 import java.util.Random; 42 import java.util.regex.Matcher; 43 import java.util.regex.Pattern; 44 import java.util.stream.Collectors; 45 46 /** 47 * A class that relies on fastboot to flash an image on physical Android hardware. 48 */ 49 public class FastbootDeviceFlasher implements IDeviceFlasher { 50 public static final String BASEBAND_IMAGE_NAME = "radio"; 51 52 private static final String FASTBOOT_VERSION = "fastboot_version"; 53 private static final int MAX_RETRY_ATTEMPTS = 3; 54 private static final int RETRY_SLEEP = 2 * 1000; // 2s sleep between retries 55 56 private static final String SLOT_PROP = "ro.boot.slot_suffix"; 57 private static final String SLOT_VAR = "current-slot"; 58 59 private long mWipeTimeout = 4 * 60 * 1000; 60 61 private UserDataFlashOption mUserDataFlashOption = UserDataFlashOption.FLASH; 62 63 private IFlashingResourcesRetriever mResourceRetriever; 64 65 private ITestsZipInstaller mTestsZipInstaller = null; 66 67 private Collection<String> mFlashOptions = new ArrayList<>(); 68 69 private Collection<String> mDataWipeSkipList = null; 70 71 private boolean mForceSystemFlash; 72 73 private CommandStatus mFbCmdStatus; 74 75 private CommandStatus mSystemFlashStatus; 76 77 /** 78 * {@inheritDoc} 79 */ 80 @Override setFlashingResourcesRetriever(IFlashingResourcesRetriever retriever)81 public void setFlashingResourcesRetriever(IFlashingResourcesRetriever retriever) { 82 mResourceRetriever = retriever; 83 } 84 getFlashingResourcesRetriever()85 protected IFlashingResourcesRetriever getFlashingResourcesRetriever() { 86 return mResourceRetriever; 87 } 88 89 /** 90 * {@inheritDoc} 91 */ 92 @Override setUserDataFlashOption(UserDataFlashOption flashOption)93 public void setUserDataFlashOption(UserDataFlashOption flashOption) { 94 mUserDataFlashOption = flashOption; 95 } 96 97 /** 98 * {@inheritDoc} 99 */ 100 @Override getUserDataFlashOption()101 public UserDataFlashOption getUserDataFlashOption() { 102 return mUserDataFlashOption; 103 } 104 setTestsZipInstaller(ITestsZipInstaller testsZipInstaller)105 void setTestsZipInstaller(ITestsZipInstaller testsZipInstaller) { 106 mTestsZipInstaller = testsZipInstaller; 107 } 108 getTestsZipInstaller()109 ITestsZipInstaller getTestsZipInstaller() { 110 // Lazily initialize the TestZipInstaller. 111 if (mTestsZipInstaller == null) { 112 if (mDataWipeSkipList == null) { 113 mDataWipeSkipList = new ArrayList<String> (); 114 } 115 if (mDataWipeSkipList.isEmpty()) { 116 // To maintain backwards compatibility. Keep media by default. 117 // TODO: deprecate and remove this. 118 mDataWipeSkipList.add("media"); 119 } 120 mTestsZipInstaller = new DefaultTestsZipInstaller(mDataWipeSkipList); 121 } 122 return mTestsZipInstaller; 123 } 124 125 /** 126 * Sets a list of options to pass with flash/update commands. 127 * 128 * @param flashOptions 129 */ setFlashOptions(Collection<String> flashOptions)130 public void setFlashOptions(Collection<String> flashOptions) { 131 // HACK: To workaround TF's command line parsing, options starting with a dash 132 // needs to be prepended with a whitespace and trimmed before they are used. 133 mFlashOptions = flashOptions.stream().map(String::trim).collect(Collectors.toList()); 134 } 135 136 /** 137 * {@inheritDoc} 138 */ 139 @Override flash(ITestDevice device, IDeviceBuildInfo deviceBuild)140 public void flash(ITestDevice device, IDeviceBuildInfo deviceBuild) throws TargetSetupError, 141 DeviceNotAvailableException { 142 143 CLog.i("Flashing device %s with build %s", device.getSerialNumber(), 144 deviceBuild.getDeviceBuildId()); 145 146 // get system build id and build flavor before booting into fastboot 147 String systemBuildId = device.getBuildId(); 148 String systemBuildFlavor = device.getBuildFlavor(); 149 150 device.rebootIntoBootloader(); 151 152 downloadFlashingResources(device, deviceBuild); 153 preFlashSetup(device, deviceBuild); 154 if (device instanceof IManagedTestDevice) { 155 String fastbootVersion = ((IManagedTestDevice) device).getFastbootVersion(); 156 if (fastbootVersion != null) { 157 deviceBuild.addBuildAttribute(FASTBOOT_VERSION, fastbootVersion); 158 } 159 } 160 handleUserDataFlashing(device, deviceBuild); 161 checkAndFlashBootloader(device, deviceBuild); 162 checkAndFlashBaseband(device, deviceBuild); 163 flashExtraImages(device, deviceBuild); 164 checkAndFlashSystem(device, systemBuildId, systemBuildFlavor, deviceBuild); 165 } 166 buildFastbootCommand(String action, String... args)167 private String[] buildFastbootCommand(String action, String... args) { 168 List<String> cmdArgs = new ArrayList<>(); 169 if ("flash".equals(action) || "update".equals(action)) { 170 cmdArgs.addAll(mFlashOptions); 171 } 172 cmdArgs.add(action); 173 cmdArgs.addAll(Arrays.asList(args)); 174 return cmdArgs.toArray(new String[cmdArgs.size()]); 175 } 176 177 /** 178 * Perform any additional pre-flashing setup required. No-op unless overridden. 179 * 180 * @param device the {@link ITestDevice} to prepare 181 * @param deviceBuild the {@link IDeviceBuildInfo} containing the build files 182 * @throws DeviceNotAvailableException 183 * @throws TargetSetupError 184 */ preFlashSetup(ITestDevice device, IDeviceBuildInfo deviceBuild)185 protected void preFlashSetup(ITestDevice device, IDeviceBuildInfo deviceBuild) 186 throws DeviceNotAvailableException, TargetSetupError {} 187 188 /** 189 * Handle flashing of userdata/cache partition 190 * 191 * @param device the {@link ITestDevice} to flash 192 * @param deviceBuild the {@link IDeviceBuildInfo} that contains the files to flash 193 * @throws DeviceNotAvailableException 194 * @throws TargetSetupError 195 */ handleUserDataFlashing(ITestDevice device, IDeviceBuildInfo deviceBuild)196 protected void handleUserDataFlashing(ITestDevice device, IDeviceBuildInfo deviceBuild) 197 throws DeviceNotAvailableException, TargetSetupError { 198 if (UserDataFlashOption.FORCE_WIPE.equals(mUserDataFlashOption) || 199 UserDataFlashOption.WIPE.equals(mUserDataFlashOption)) { 200 CommandResult result = device.executeFastbootCommand(mWipeTimeout, "-w"); 201 handleFastbootResult(device, result, "-w"); 202 } else { 203 flashUserData(device, deviceBuild); 204 wipeCache(device); 205 } 206 } 207 208 /** 209 * Flash an individual partition of a device 210 * 211 * @param device the {@link ITestDevice} to flash 212 * @param imgFile a {@link File} pointing to the image to be flashed 213 * @param partition the name of the partition to be flashed 214 */ flashPartition(ITestDevice device, File imgFile, String partition)215 protected void flashPartition(ITestDevice device, File imgFile, String partition) 216 throws DeviceNotAvailableException, TargetSetupError { 217 CLog.d("fastboot flash %s %s", partition, imgFile.getAbsolutePath()); 218 executeLongFastbootCmd( 219 device, buildFastbootCommand("flash", partition, imgFile.getAbsolutePath())); 220 } 221 222 /** 223 * Wipe the specified partition with `fastboot erase <name>` 224 * 225 * @param device the {@link ITestDevice} to operate on 226 * @param partition the name of the partition to be wiped 227 */ wipePartition(ITestDevice device, String partition)228 protected void wipePartition(ITestDevice device, String partition) 229 throws DeviceNotAvailableException, TargetSetupError { 230 String wipeMethod = device.getUseFastbootErase() ? "erase" : "format"; 231 CLog.d("fastboot %s %s", wipeMethod, partition); 232 CommandResult result = device.fastbootWipePartition(partition); 233 handleFastbootResult(device, result, wipeMethod, partition); 234 } 235 236 /** 237 * Checks with the bootloader if the specified partition exists or not 238 * 239 * @param device the {@link ITestDevice} to operate on 240 * @param partition the name of the partition to be checked 241 */ hasPartition(ITestDevice device, String partition)242 protected boolean hasPartition(ITestDevice device, String partition) 243 throws DeviceNotAvailableException { 244 String partitionType = String.format("partition-type:%s", partition); 245 CommandResult result = device.executeFastbootCommand("getvar", partitionType); 246 if (!CommandStatus.SUCCESS.equals(result.getStatus()) 247 || result.getStderr().contains("FAILED")) { 248 return false; 249 } 250 Pattern regex = Pattern.compile(String.format("^%s:\\s*\\S+$", partitionType), 251 Pattern.MULTILINE); 252 return regex.matcher(result.getStderr()).find(); 253 } 254 255 /** 256 * Downloads extra flashing image files needed 257 * 258 * @param device the {@link ITestDevice} to download resources for 259 * @param localBuild the {@link IDeviceBuildInfo} to populate. Assumes device image file is 260 * already set 261 * 262 * @throws DeviceNotAvailableException if device is not available 263 * @throws TargetSetupError if failed to retrieve resources 264 */ downloadFlashingResources(ITestDevice device, IDeviceBuildInfo localBuild)265 protected void downloadFlashingResources(ITestDevice device, IDeviceBuildInfo localBuild) 266 throws TargetSetupError, DeviceNotAvailableException { 267 IFlashingResourcesParser resourceParser = createFlashingResourcesParser(localBuild, 268 device.getDeviceDescriptor()); 269 270 if (resourceParser.getRequiredBoards() == null) { 271 throw new TargetSetupError(String.format("Build %s is missing required board info.", 272 localBuild.getDeviceBuildId()), device.getDeviceDescriptor()); 273 } 274 String deviceProductType = device.getProductType(); 275 if (deviceProductType == null) { 276 // treat this as a fatal device error 277 throw new DeviceNotAvailableException(String.format( 278 "Could not determine product type for device %s", device.getSerialNumber()), 279 device.getSerialNumber()); 280 } 281 verifyRequiredBoards(device, resourceParser, deviceProductType); 282 283 String bootloaderVersion = resourceParser.getRequiredBootloaderVersion(); 284 // only set bootloader image if this build doesn't have one already 285 // TODO: move this logic to the BuildProvider step 286 if (bootloaderVersion != null && localBuild.getBootloaderImageFile() == null) { 287 localBuild.setBootloaderImageFile(getFlashingResourcesRetriever().retrieveFile( 288 getBootloaderFilePrefix(device), bootloaderVersion), bootloaderVersion); 289 } 290 String basebandVersion = resourceParser.getRequiredBasebandVersion(); 291 // only set baseband image if this build doesn't have one already 292 if (basebandVersion != null && localBuild.getBasebandImageFile() == null) { 293 localBuild.setBasebandImage(getFlashingResourcesRetriever().retrieveFile( 294 BASEBAND_IMAGE_NAME, basebandVersion), basebandVersion); 295 } 296 downloadExtraImageFiles(resourceParser, getFlashingResourcesRetriever(), localBuild); 297 } 298 299 /** 300 * Verify that the device's product type supports the build-to-be-flashed. 301 * <p/> 302 * The base implementation will verify that the deviceProductType is included in the 303 * {@link IFlashingResourcesParser#getRequiredBoards()} collection. Subclasses may override 304 * as desired. 305 * 306 * @param device the {@link ITestDevice} to be flashed 307 * @param resourceParser the {@link IFlashingResourcesParser} 308 * @param deviceProductType the <var>device</var>'s product type 309 * @throws TargetSetupError if the build's required board info did not match the device 310 */ verifyRequiredBoards(ITestDevice device, IFlashingResourcesParser resourceParser, String deviceProductType)311 protected void verifyRequiredBoards(ITestDevice device, IFlashingResourcesParser resourceParser, 312 String deviceProductType) throws TargetSetupError { 313 if (!containsIgnoreCase(resourceParser.getRequiredBoards(), deviceProductType)) { 314 throw new TargetSetupError(String.format("Device %s is %s. Expected %s", 315 device.getSerialNumber(), deviceProductType, 316 resourceParser.getRequiredBoards()), device.getDeviceDescriptor()); 317 } 318 } 319 containsIgnoreCase(Collection<String> stringList, String anotherString)320 private static boolean containsIgnoreCase(Collection<String> stringList, String anotherString) { 321 for (String aString : stringList) { 322 if (aString != null && aString.equalsIgnoreCase(anotherString)) { 323 return true; 324 } 325 } 326 return false; 327 } 328 329 /** 330 * Hook to allow subclasses to download extra custom image files if needed. 331 * 332 * @param resourceParser the {@link IFlashingResourcesParser} 333 * @param retriever the {@link IFlashingResourcesRetriever} 334 * @param localBuild the {@link IDeviceBuildInfo} 335 * @throws TargetSetupError 336 */ downloadExtraImageFiles(IFlashingResourcesParser resourceParser, IFlashingResourcesRetriever retriever, IDeviceBuildInfo localBuild)337 protected void downloadExtraImageFiles(IFlashingResourcesParser resourceParser, 338 IFlashingResourcesRetriever retriever, IDeviceBuildInfo localBuild) 339 throws TargetSetupError { 340 } 341 342 /** 343 * Factory method for creating a {@link IFlashingResourcesParser}. 344 * <p/> 345 * Exposed for unit testing. 346 * 347 * @param localBuild the {@link IDeviceBuildInfo} to parse 348 * @param descriptor the descriptor of the device being flashed. 349 * @return a {@link IFlashingResourcesParser} created by the factory method. 350 * @throws TargetSetupError 351 */ createFlashingResourcesParser(IDeviceBuildInfo localBuild, DeviceDescriptor descriptor)352 protected IFlashingResourcesParser createFlashingResourcesParser(IDeviceBuildInfo localBuild, 353 DeviceDescriptor descriptor) throws TargetSetupError { 354 try { 355 return new FlashingResourcesParser(localBuild.getDeviceImageFile()); 356 } catch (TargetSetupError e) { 357 // Rethrow with descriptor since FlashingResourceParser doesn't have it. 358 throw new TargetSetupError(e.getMessage(), e, descriptor); 359 } 360 } 361 362 /** 363 * If needed, flash the bootloader image on device. 364 * <p/> 365 * Will only flash bootloader if current version on device != required version. 366 * 367 * @param device the {@link ITestDevice} to flash 368 * @param deviceBuild the {@link IDeviceBuildInfo} that contains the bootloader image to flash 369 * @return <code>true</code> if bootloader was flashed, <code>false</code> if it was skipped 370 * @throws DeviceNotAvailableException if device is not available 371 * @throws TargetSetupError if failed to flash bootloader 372 */ checkAndFlashBootloader(ITestDevice device, IDeviceBuildInfo deviceBuild)373 protected boolean checkAndFlashBootloader(ITestDevice device, IDeviceBuildInfo deviceBuild) 374 throws DeviceNotAvailableException, TargetSetupError { 375 String currentBootloaderVersion = getImageVersion(device, "bootloader"); 376 if (deviceBuild.getBootloaderVersion() != null && 377 !deviceBuild.getBootloaderVersion().equals(currentBootloaderVersion)) { 378 CLog.i("Flashing bootloader %s", deviceBuild.getBootloaderVersion()); 379 flashBootloader(device, deviceBuild.getBootloaderImageFile()); 380 return true; 381 } else { 382 CLog.i("Bootloader is already version %s, skipping flashing", currentBootloaderVersion); 383 return false; 384 } 385 } 386 387 /** 388 * Flashes the given bootloader image and reboots back into bootloader 389 * 390 * @param device the {@link ITestDevice} to flash 391 * @param bootloaderImageFile the bootloader image {@link File} 392 * @throws DeviceNotAvailableException if device is not available 393 * @throws TargetSetupError if failed to flash 394 */ flashBootloader(ITestDevice device, File bootloaderImageFile)395 protected void flashBootloader(ITestDevice device, File bootloaderImageFile) 396 throws DeviceNotAvailableException, TargetSetupError { 397 // bootloader images are small, and flash quickly. so use the 'normal' timeout 398 executeFastbootCmd( 399 device, 400 buildFastbootCommand( 401 "flash", getBootPartitionName(), bootloaderImageFile.getAbsolutePath())); 402 device.rebootIntoBootloader(); 403 } 404 405 /** 406 * Get the boot partition name for this device flasher. 407 * <p/> 408 * Defaults to 'hboot'. Subclasses should override if necessary. 409 */ getBootPartitionName()410 protected String getBootPartitionName() { 411 return "hboot"; 412 } 413 414 /** 415 * Get the bootloader file prefix. 416 * <p/> 417 * Defaults to {@link #getBootPartitionName()}. Subclasses should override if necessary. 418 * 419 * @param device the {@link ITestDevice} to flash 420 * @throws DeviceNotAvailableException if device is not available 421 * @throws TargetSetupError if failed to get prefix 422 */ getBootloaderFilePrefix(ITestDevice device)423 protected String getBootloaderFilePrefix(ITestDevice device) throws TargetSetupError, 424 DeviceNotAvailableException { 425 return getBootPartitionName(); 426 } 427 428 /** 429 * If needed, flash the baseband image on device. Will only flash baseband if current version 430 * on device != required version 431 * 432 * @param device the {@link ITestDevice} to flash 433 * @param deviceBuild the {@link IDeviceBuildInfo} that contains the baseband image to flash 434 * @throws DeviceNotAvailableException if device is not available 435 * @throws TargetSetupError if failed to flash baseband 436 */ checkAndFlashBaseband(ITestDevice device, IDeviceBuildInfo deviceBuild)437 protected void checkAndFlashBaseband(ITestDevice device, IDeviceBuildInfo deviceBuild) 438 throws DeviceNotAvailableException, TargetSetupError { 439 String currentBasebandVersion = getImageVersion(device, "baseband"); 440 if (checkShouldFlashBaseband(device, deviceBuild)) { 441 CLog.i("Flashing baseband %s", deviceBuild.getBasebandVersion()); 442 flashBaseband(device, deviceBuild.getBasebandImageFile()); 443 } else { 444 CLog.i("Baseband is already version %s, skipping flashing", currentBasebandVersion); 445 } 446 } 447 448 /** 449 * Check if the baseband on the provided device needs to be flashed. 450 * 451 * @param device the {@link ITestDevice} to check 452 * @param deviceBuild the {@link IDeviceBuildInfo} that contains the baseband image to check 453 * @throws DeviceNotAvailableException if device is not available 454 * @throws TargetSetupError if failed to flash baseband 455 */ checkShouldFlashBaseband(ITestDevice device, IDeviceBuildInfo deviceBuild)456 protected boolean checkShouldFlashBaseband(ITestDevice device, IDeviceBuildInfo deviceBuild) 457 throws DeviceNotAvailableException, TargetSetupError { 458 String currentBasebandVersion = getImageVersion(device, "baseband"); 459 return (deviceBuild.getBasebandVersion() != null && 460 !deviceBuild.getBasebandVersion().equals(currentBasebandVersion)); 461 } 462 463 /** 464 * Flashes the given baseband image and reboot back into bootloader 465 * 466 * @param device the {@link ITestDevice} to flash 467 * @param basebandImageFile the baseband image {@link File} 468 * @throws DeviceNotAvailableException if device is not available 469 * @throws TargetSetupError if failed to flash baseband 470 */ flashBaseband(ITestDevice device, File basebandImageFile)471 protected void flashBaseband(ITestDevice device, File basebandImageFile) 472 throws DeviceNotAvailableException, TargetSetupError { 473 flashPartition(device, basebandImageFile, BASEBAND_IMAGE_NAME); 474 device.rebootIntoBootloader(); 475 } 476 477 /** 478 * Wipe the cache partition on device. 479 * 480 * @param device the {@link ITestDevice} to flash 481 * @throws DeviceNotAvailableException if device is not available 482 * @throws TargetSetupError if failed to flash cache 483 */ wipeCache(ITestDevice device)484 protected void wipeCache(ITestDevice device) throws DeviceNotAvailableException, 485 TargetSetupError { 486 // only wipe cache if user data is being wiped 487 if (!mUserDataFlashOption.equals(UserDataFlashOption.RETAIN)) { 488 CLog.i("Wiping cache on %s", device.getSerialNumber()); 489 String partition = "cache"; 490 if (hasPartition(device, partition)) { 491 wipePartition(device, partition); 492 } 493 } else { 494 CLog.d("Skipping cache wipe on %s", device.getSerialNumber()); 495 } 496 } 497 498 /** 499 * Flash userdata partition on device. 500 * 501 * @param device the {@link ITestDevice} to flash 502 * @param deviceBuild the {@link IDeviceBuildInfo} that contains the files to flash 503 * @throws DeviceNotAvailableException if device is not available 504 * @throws TargetSetupError if failed to flash user data 505 */ flashUserData(ITestDevice device, IDeviceBuildInfo deviceBuild)506 protected void flashUserData(ITestDevice device, IDeviceBuildInfo deviceBuild) 507 throws DeviceNotAvailableException, TargetSetupError { 508 switch (mUserDataFlashOption) { 509 case FLASH: 510 CLog.i("Flashing %s with userdata %s", device.getSerialNumber(), 511 deviceBuild.getUserDataImageFile().getAbsolutePath()); 512 flashPartition(device, deviceBuild.getUserDataImageFile(), "userdata"); 513 break; 514 case FLASH_IMG_ZIP: 515 flashUserDataFromDeviceImageFile(device, deviceBuild); 516 break; 517 case FORCE_WIPE: // intentional fallthrough 518 case WIPE: 519 CLog.i("Wiping userdata %s", device.getSerialNumber()); 520 wipePartition(device, "userdata"); 521 break; 522 523 case TESTS_ZIP: 524 device.rebootUntilOnline(); // required to install tests 525 if (device.isEncryptionSupported() && device.isDeviceEncrypted()) { 526 device.unlockDevice(); 527 } 528 getTestsZipInstaller().pushTestsZipOntoData(device, deviceBuild); 529 // Reboot into bootloader to continue the flashing process 530 device.rebootIntoBootloader(); 531 break; 532 533 case WIPE_RM: 534 device.rebootUntilOnline(); // required to install tests 535 getTestsZipInstaller().deleteData(device); 536 // Reboot into bootloader to continue the flashing process 537 device.rebootIntoBootloader(); 538 break; 539 540 default: 541 CLog.d("Skipping userdata flash for %s", device.getSerialNumber()); 542 } 543 } 544 545 /** 546 * Extracts the userdata.img from device image file and flashes it onto device 547 * @param device the {@link ITestDevice} to flash 548 * @param deviceBuild the {@link IDeviceBuildInfo} that contains the files to flash 549 * @throws DeviceNotAvailableException if device is not available 550 * @throws TargetSetupError if failed to extract or flash user data 551 */ flashUserDataFromDeviceImageFile(ITestDevice device, IDeviceBuildInfo deviceBuild)552 protected void flashUserDataFromDeviceImageFile(ITestDevice device, 553 IDeviceBuildInfo deviceBuild) throws DeviceNotAvailableException, TargetSetupError { 554 File userdataImg = null; 555 try { 556 try (ZipFile zip = new ZipFile(deviceBuild.getDeviceImageFile())) { 557 userdataImg = ZipUtil2.extractFileFromZip(zip, "userdata.img"); 558 } catch (IOException ioe) { 559 throw new TargetSetupError("failed to extract userdata.img from image file", ioe, 560 device.getDeviceDescriptor()); 561 } 562 CLog.i("Flashing %s with userdata %s", device.getSerialNumber(), userdataImg); 563 flashPartition(device, userdataImg, "userdata"); 564 } finally { 565 FileUtil.deleteFile(userdataImg); 566 } 567 } 568 569 /** 570 * Flash any device specific partitions before flashing system and rebooting. No-op unless 571 * overridden. 572 * 573 * @param device the {@link ITestDevice} to flash 574 * @param deviceBuild the {@link IDeviceBuildInfo} containing the build files 575 * @throws DeviceNotAvailableException 576 * @throws TargetSetupError 577 */ flashExtraImages(ITestDevice device, IDeviceBuildInfo deviceBuild)578 protected void flashExtraImages(ITestDevice device, IDeviceBuildInfo deviceBuild) 579 throws DeviceNotAvailableException, TargetSetupError {} 580 581 /** 582 * If needed, flash the system image on device. 583 * 584 * <p>Please look at {@link #shouldFlashSystem(String, String, IDeviceBuildInfo)} 585 * 586 * <p>Regardless of path chosen, after method execution device should be booting into userspace. 587 * 588 * @param device the {@link ITestDevice} to flash 589 * @param systemBuildId the current build id running on the device 590 * @param systemBuildFlavor the current build flavor running on the device 591 * @param deviceBuild the {@link IDeviceBuildInfo} that contains the system image to flash 592 * @return <code>true</code> if system was flashed, <code>false</code> if it was skipped 593 * @throws DeviceNotAvailableException if device is not available 594 * @throws TargetSetupError if failed to flash bootloader 595 */ checkAndFlashSystem( ITestDevice device, String systemBuildId, String systemBuildFlavor, IDeviceBuildInfo deviceBuild)596 protected boolean checkAndFlashSystem( 597 ITestDevice device, 598 String systemBuildId, 599 String systemBuildFlavor, 600 IDeviceBuildInfo deviceBuild) 601 throws DeviceNotAvailableException, TargetSetupError { 602 if (shouldFlashSystem(systemBuildId, systemBuildFlavor, deviceBuild)) { 603 CLog.i("Flashing system %s", deviceBuild.getDeviceBuildId()); 604 flashSystem(device, deviceBuild); 605 return true; 606 } 607 CLog.i("System is already version %s and build flavor %s, skipping flashing", 608 systemBuildId, systemBuildFlavor); 609 // reboot 610 device.rebootUntilOnline(); 611 return false; 612 } 613 614 /** 615 * Helper method used to determine if we need to flash the system image. 616 * 617 * @param systemBuildId the current build id running on the device 618 * @param systemBuildFlavor the current build flavor running on the device 619 * @param deviceBuild the {@link IDeviceBuildInfo} that contains the system image to flash 620 * @return <code>true</code> if we should flash the system, <code>false</code> otherwise. 621 */ shouldFlashSystem(String systemBuildId, String systemBuildFlavor, IDeviceBuildInfo deviceBuild)622 boolean shouldFlashSystem(String systemBuildId, String systemBuildFlavor, 623 IDeviceBuildInfo deviceBuild) { 624 if (mForceSystemFlash) { 625 // Flag overrides all logic. 626 return true; 627 } 628 // Err on the side of caution, if we failed to get the build id or build flavor, force a 629 // flash of the system. 630 if (systemBuildFlavor == null || systemBuildId == null) { 631 return true; 632 } 633 // If we have the same build id and build flavor we don't need to flash it. 634 if (systemBuildId.equals(deviceBuild.getDeviceBuildId()) && 635 systemBuildFlavor.equalsIgnoreCase(deviceBuild.getBuildFlavor())) { 636 return false; 637 } 638 return true; 639 } 640 641 /** 642 * Flash the system image on device. 643 * 644 * @param device the {@link ITestDevice} to flash 645 * @param deviceBuild the {@link IDeviceBuildInfo} to flash 646 * @throws DeviceNotAvailableException if device is not available 647 * @throws TargetSetupError if fastboot command fails 648 */ flashSystem(ITestDevice device, IDeviceBuildInfo deviceBuild)649 protected void flashSystem(ITestDevice device, IDeviceBuildInfo deviceBuild) 650 throws DeviceNotAvailableException, TargetSetupError { 651 CLog.i("Flashing %s with update %s", device.getSerialNumber(), 652 deviceBuild.getDeviceImageFile().getAbsolutePath()); 653 // give extra time to the update cmd 654 try { 655 executeLongFastbootCmd( 656 device, 657 buildFastbootCommand( 658 "update", deviceBuild.getDeviceImageFile().getAbsolutePath())); 659 // only transfer last fastboot command status over to system flash status after having 660 // flashing the system partitions 661 mSystemFlashStatus = mFbCmdStatus; 662 } finally { 663 // if system flash status is still null here, an exception has happened 664 if (mSystemFlashStatus == null) { 665 mSystemFlashStatus = CommandStatus.EXCEPTION; 666 } 667 } 668 } 669 670 /** 671 * Helper method to get the current image version on device. 672 * 673 * @param device the {@link ITestDevice} to execute command on 674 * @param imageName the name of image to get. 675 * @return String the stdout output from command 676 * @throws DeviceNotAvailableException if device is not available 677 * @throws TargetSetupError if fastboot command fails or version could not be determined 678 */ getImageVersion(ITestDevice device, String imageName)679 protected String getImageVersion(ITestDevice device, String imageName) 680 throws DeviceNotAvailableException, TargetSetupError { 681 int attempts = 0; 682 String versionQuery = String.format("version-%s", imageName); 683 String patternString = String.format("%s:\\s(.*)\\s", versionQuery); 684 Pattern versionOutputPattern = Pattern.compile(patternString); 685 686 while (attempts < MAX_RETRY_ATTEMPTS) { 687 String queryOutput = executeFastbootCmd(device, "getvar", versionQuery); 688 Matcher matcher = versionOutputPattern.matcher(queryOutput); 689 if (matcher.find()) { 690 return matcher.group(1); 691 } else { 692 attempts++; 693 CLog.w("Could not find version for '%s'. Output '%s', retrying.", 694 imageName, queryOutput); 695 getRunUtil().sleep(RETRY_SLEEP * (attempts - 1) 696 + new Random(System.currentTimeMillis()).nextInt(RETRY_SLEEP)); 697 continue; 698 } 699 } 700 throw new TargetSetupError(String.format( 701 "Could not find version for '%s' after %d retry attempts", imageName, attempts), 702 device.getDeviceDescriptor()); 703 } 704 705 /** 706 * Helper method to retrieve the current slot (for A/B capable devices). 707 * 708 * @param device the {@link ITestDevice} to execute command on. 709 * @return "a", "b" or null (if device is not A/B capable) 710 * @throws DeviceNotAvailableException 711 * @throws TargetSetupError 712 */ getCurrentSlot(ITestDevice device)713 protected String getCurrentSlot(ITestDevice device) 714 throws DeviceNotAvailableException, TargetSetupError { 715 Matcher matcher; 716 if (device.getDeviceState().equals(TestDeviceState.FASTBOOT)) { 717 String queryOutput = executeFastbootCmd(device, "getvar", SLOT_VAR); 718 Pattern outputPattern = Pattern.compile(String.format("^%s: _?([ab])", SLOT_VAR)); 719 matcher = outputPattern.matcher(queryOutput); 720 } else { 721 String queryOutput = device.executeShellCommand(String.format("getprop %s", SLOT_PROP)); 722 Pattern outputPattern = 723 Pattern.compile(String.format("^\\[%s\\]: \\[_?([ab])\\]", SLOT_PROP)); 724 matcher = outputPattern.matcher(queryOutput); 725 } 726 if (matcher.find()) { 727 return matcher.group(1); 728 } else { 729 return null; 730 } 731 } 732 733 /** Exposed for testing. */ getRunUtil()734 protected IRunUtil getRunUtil() { 735 return RunUtil.getDefault(); 736 } 737 738 /** 739 * Helper method to execute fastboot command. 740 * 741 * @param device the {@link ITestDevice} to execute command on 742 * @param cmdArgs the arguments to provide to fastboot 743 * @return String the stderr output from command if non-empty. Otherwise returns the stdout 744 * Some fastboot commands are weird in that they dump output to stderr on success case 745 * 746 * @throws DeviceNotAvailableException if device is not available 747 * @throws TargetSetupError if fastboot command fails 748 */ executeFastbootCmd(ITestDevice device, String... cmdArgs)749 protected String executeFastbootCmd(ITestDevice device, String... cmdArgs) 750 throws DeviceNotAvailableException, TargetSetupError { 751 CLog.v("Executing short fastboot command %s", java.util.Arrays.toString(cmdArgs)); 752 CommandResult result = device.executeFastbootCommand(cmdArgs); 753 return handleFastbootResult(device, result, cmdArgs); 754 } 755 756 /** 757 * Helper method to execute a long-running fastboot command. 758 * <p/> 759 * Note: Most fastboot commands normally execute within the timeout allowed by 760 * {@link ITestDevice#executeFastbootCommand(String...)}. However, when multiple devices are 761 * flashing devices at once, fastboot commands can take much longer than normal. 762 * 763 * @param device the {@link ITestDevice} to execute command on 764 * @param cmdArgs the arguments to provide to fastboot 765 * @return String the stderr output from command if non-empty. Otherwise returns the stdout 766 * Some fastboot commands are weird in that they dump output to stderr on success case 767 * 768 * @throws DeviceNotAvailableException if device is not available 769 * @throws TargetSetupError if fastboot command fails 770 */ executeLongFastbootCmd(ITestDevice device, String... cmdArgs)771 protected String executeLongFastbootCmd(ITestDevice device, String... cmdArgs) 772 throws DeviceNotAvailableException, TargetSetupError { 773 CommandResult result = device.executeLongFastbootCommand(cmdArgs); 774 return handleFastbootResult(device, result, cmdArgs); 775 } 776 777 /** 778 * Interpret the result of a fastboot command 779 * 780 * @param device 781 * @param result 782 * @param cmdArgs 783 * @return the stderr output from command if non-empty. Otherwise returns the stdout 784 * @throws TargetSetupError 785 */ handleFastbootResult(ITestDevice device, CommandResult result, String... cmdArgs)786 private String handleFastbootResult(ITestDevice device, CommandResult result, String... cmdArgs) 787 throws TargetSetupError { 788 CLog.v("fastboot stdout: " + result.getStdout()); 789 CLog.v("fastboot stderr: " + result.getStderr()); 790 mFbCmdStatus = result.getStatus(); 791 if (result.getStderr().contains("FAILED")) { 792 // if output contains "FAILED", just override to failure 793 mFbCmdStatus = CommandStatus.FAILED; 794 } 795 if (mFbCmdStatus != CommandStatus.SUCCESS) { 796 throw new TargetSetupError(String.format( 797 "fastboot command %s failed in device %s. stdout: %s, stderr: %s", cmdArgs[0], 798 device.getSerialNumber(), result.getStdout(), result.getStderr()), 799 device.getDeviceDescriptor()); 800 } 801 if (result.getStderr().length() > 0) { 802 return result.getStderr(); 803 } else { 804 return result.getStdout(); 805 } 806 } 807 808 /** 809 * {@inheritDoc} 810 */ 811 @Override overrideDeviceOptions(ITestDevice device)812 public void overrideDeviceOptions(ITestDevice device) { 813 // ignore 814 } 815 816 /** 817 * {@inheritDoc} 818 */ 819 @Override setForceSystemFlash(boolean forceSystemFlash)820 public void setForceSystemFlash(boolean forceSystemFlash) { 821 mForceSystemFlash = forceSystemFlash; 822 } 823 824 /** 825 * {@inheritDoc} 826 */ 827 @Override setDataWipeSkipList(Collection<String> dataWipeSkipList)828 public void setDataWipeSkipList(Collection<String> dataWipeSkipList) { 829 if (dataWipeSkipList == null) { 830 dataWipeSkipList = new ArrayList<String> (); 831 } 832 if(dataWipeSkipList.isEmpty()) { 833 // To maintain backwards compatibility. 834 // TODO: deprecate and remove. 835 dataWipeSkipList.add("media"); 836 } 837 mDataWipeSkipList = dataWipeSkipList; 838 } 839 840 /** 841 * {@inheritDoc} 842 */ 843 @Override setWipeTimeout(long timeout)844 public void setWipeTimeout(long timeout) { 845 mWipeTimeout = timeout; 846 } 847 848 /** 849 * {@inheritDoc} 850 */ 851 @Override getSystemFlashingStatus()852 public CommandStatus getSystemFlashingStatus() { 853 return mSystemFlashStatus; 854 } 855 } 856