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