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