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