Home | History | Annotate | Download | only in targetprep
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tradefed.targetprep;
     18 
     19 import com.android.tradefed.build.IBuildInfo;
     20 import com.android.tradefed.build.IDeviceBuildInfo;
     21 import com.android.tradefed.config.GlobalConfiguration;
     22 import com.android.tradefed.config.Option;
     23 import com.android.tradefed.device.DeviceNotAvailableException;
     24 import com.android.tradefed.device.DeviceUnresponsiveException;
     25 import com.android.tradefed.device.ITestDevice;
     26 import com.android.tradefed.device.ITestDevice.RecoveryMode;
     27 import com.android.tradefed.host.IHostOptions;
     28 import com.android.tradefed.log.LogUtil.CLog;
     29 import com.android.tradefed.targetprep.IDeviceFlasher.UserDataFlashOption;
     30 import com.android.tradefed.util.IRunUtil;
     31 import com.android.tradefed.util.RunUtil;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Collection;
     35 import java.util.concurrent.Semaphore;
     36 import java.util.concurrent.TimeUnit;
     37 
     38 /**
     39  * A {@link ITargetPreparer} that flashes an image on physical Android hardware.
     40  */
     41 public abstract class DeviceFlashPreparer implements ITargetCleaner {
     42 
     43     /**
     44      * Enum of options for handling the encryption of userdata image
     45      */
     46     public static enum EncryptionOptions {ENCRYPT, IGNORE}
     47 
     48     private static final int BOOT_POLL_TIME_MS = 5 * 1000;
     49 
     50     @Option(name = "device-boot-time", description = "max time in ms to wait for device to boot.")
     51     private long mDeviceBootTime = 5 * 60 * 1000;
     52 
     53     @Option(name = "userdata-flash", description =
     54         "specify handling of userdata partition.")
     55     private UserDataFlashOption mUserDataFlashOption = UserDataFlashOption.FLASH;
     56 
     57     @Option(name = "encrypt-userdata", description = "specify if userdata partition should be "
     58             + "encrypted; defaults to IGNORE, where no actions will be taken.")
     59     private EncryptionOptions mEncryptUserData = EncryptionOptions.IGNORE;
     60 
     61     @Option(name = "force-system-flash", description =
     62         "specify if system should always be flashed even if already running desired build.")
     63     private boolean mForceSystemFlash = false;
     64 
     65     /*
     66      * A temporary workaround for special builds. Should be removed after changes from build team.
     67      * Bug: 18078421
     68      */
     69     @Option(name = "skip-post-flash-flavor-check", description =
     70             "specify if system flavor should not be checked after flash")
     71     private boolean mSkipPostFlashFlavorCheck = false;
     72 
     73     /*
     74      * Used for update testing
     75      */
     76     @Option(name = "skip-post-flash-build-id-check", description =
     77             "specify if build ID should not be checked after flash")
     78     private boolean mSkipPostFlashBuildIdCheck = false;
     79 
     80     @Option(name = "wipe-skip-list", description =
     81         "list of /data subdirectories to NOT wipe when doing UserDataFlashOption.TESTS_ZIP")
     82     private Collection<String> mDataWipeSkipList = new ArrayList<>();
     83 
     84     @Option(name = "concurrent-flasher-limit", description =
     85         "The maximum number of concurrent flashers (may be useful to avoid memory constraints)" +
     86         "This will be overriden if one is set in the host options.")
     87     private Integer mConcurrentFlasherLimit = null;
     88 
     89     @Option(name = "skip-post-flashing-setup",
     90             description = "whether or not to skip post-flashing setup steps")
     91     private boolean mSkipPostFlashingSetup = false;
     92 
     93     @Option(name = "wipe-timeout",
     94             description = "the timeout for the command of wiping user data.", isTimeVal = true)
     95     private long mWipeTimeout = 4 * 60 * 1000;
     96 
     97     @Option(name = "disable", description = "Disable the device flasher.")
     98     private boolean mDisable = false;
     99 
    100     private static Semaphore sConcurrentFlashLock = null;
    101 
    102     /**
    103      * This serves both as an indication of whether the flash lock should be used, and as an
    104      * indicator of whether or not the flash lock has been initialized -- if this is true
    105      * and {@code mConcurrentFlashLock} is {@code null}, then it has not yet been initialized.
    106      */
    107     private static Boolean sShouldCheckFlashLock = true;
    108 
    109     /**
    110      * Sets the device boot time
    111      * <p/>
    112      * Exposed for unit testing
    113      */
    114     void setDeviceBootTime(long bootTime) {
    115         mDeviceBootTime = bootTime;
    116     }
    117 
    118     /**
    119      * Gets the interval between device boot poll attempts.
    120      * <p/>
    121      * Exposed for unit testing
    122      */
    123     int getDeviceBootPollTimeMs() {
    124         return BOOT_POLL_TIME_MS;
    125     }
    126 
    127     /**
    128      * Gets the {@link IRunUtil} instance to use.
    129      * <p/>
    130      * Exposed for unit testing
    131      */
    132     IRunUtil getRunUtil() {
    133         return RunUtil.getDefault();
    134     }
    135 
    136     /**
    137      * Gets the {@link IHostOptions} instance to use.
    138      * <p/>
    139      * Exposed for unit testing
    140      */
    141     IHostOptions getHostOptions() {
    142         return GlobalConfiguration.getInstance().getHostOptions();
    143     }
    144 
    145     /**
    146      * Set the userdata-flash option
    147      *
    148      * @param flashOption
    149      */
    150     public void setUserDataFlashOption(UserDataFlashOption flashOption) {
    151         mUserDataFlashOption = flashOption;
    152     }
    153 
    154     /**
    155      * Set the state of the concurrent flash limit implementation
    156      *
    157      * Exposed for unit testing
    158      */
    159     void setConcurrentFlashSettings(Integer limit, Semaphore flashLock, boolean shouldCheck) {
    160         synchronized(sShouldCheckFlashLock) {
    161             // Make a minimal attempt to avoid having things get into an inconsistent state
    162             if (sConcurrentFlashLock != null && mConcurrentFlasherLimit != null) {
    163                 int curLimit = mConcurrentFlasherLimit;
    164                 int curAvail = sConcurrentFlashLock.availablePermits();
    165                 if (curLimit != curAvail) {
    166                     throw new IllegalStateException(String.format("setConcurrentFlashSettings may " +
    167                             "not be called while any permits are active.  The flasher limit is %d, " +
    168                             "but there are only %d permits available.", curLimit, curAvail));
    169                 }
    170             }
    171 
    172             mConcurrentFlasherLimit = limit;
    173             sConcurrentFlashLock = flashLock;
    174             sShouldCheckFlashLock = shouldCheck;
    175         }
    176     }
    177 
    178     Semaphore getConcurrentFlashLock() {
    179         return sConcurrentFlashLock;
    180     }
    181 
    182     /**
    183      * Request permission to flash.  If the number of concurrent flashers is limited, this will
    184      * wait in line in order to remain under the flash limit count.
    185      *
    186      * Exposed for unit testing.
    187      */
    188     void takeFlashingPermit() {
    189         if (!sShouldCheckFlashLock) return;
    190 
    191         // The logic below is to avoid multi-thread race conditions while initializing
    192         // mConcurrentFlashLock when we hit this condition.
    193         if (sConcurrentFlashLock == null) {
    194             // null with mShouldCheckFlashLock == true means initialization hasn't been done yet
    195             synchronized(sShouldCheckFlashLock) {
    196                 // Check all state again, since another thread might have gotten here first
    197                 if (!sShouldCheckFlashLock) return;
    198 
    199                 Integer concurrentFlasherLimit = mConcurrentFlasherLimit;
    200                 IHostOptions hostOptions = getHostOptions();
    201                 if (hostOptions.getConcurrentFlasherLimit() != null) {
    202                     CLog.i("using host-wide concurrent flasher limit %d",
    203                             hostOptions.getConcurrentFlasherLimit());
    204                     concurrentFlasherLimit = hostOptions.getConcurrentFlasherLimit();
    205                 }
    206 
    207                 if (concurrentFlasherLimit == null) {
    208                     sShouldCheckFlashLock = false;
    209                     return;
    210                 }
    211 
    212                 if (sConcurrentFlashLock == null) {
    213                     sConcurrentFlashLock = new Semaphore(concurrentFlasherLimit, true /* fair */);
    214                 }
    215             }
    216         }
    217 
    218         sConcurrentFlashLock.acquireUninterruptibly();
    219     }
    220 
    221     /**
    222      * Restore a flashing permit that we acquired previously
    223      *
    224      * Exposed for unit testing.
    225      */
    226     void returnFlashingPermit() {
    227         if (sConcurrentFlashLock != null) {
    228             sConcurrentFlashLock.release();
    229         }
    230     }
    231 
    232     /**
    233      * {@inheritDoc}
    234      */
    235     @Override
    236     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
    237             DeviceNotAvailableException, BuildError {
    238         if (mDisable) {
    239             CLog.i("Skipping device flashing.");
    240             return;
    241         }
    242         CLog.i("Performing setup on %s", device.getSerialNumber());
    243         if (!(buildInfo instanceof IDeviceBuildInfo)) {
    244             throw new IllegalArgumentException("Provided buildInfo is not a IDeviceBuildInfo");
    245         }
    246         // don't allow interruptions during flashing operations.
    247         getRunUtil().allowInterrupt(false);
    248         try {
    249             IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo)buildInfo;
    250             device.setRecoveryMode(RecoveryMode.ONLINE);
    251             IDeviceFlasher flasher = createFlasher(device);
    252             flasher.setWipeTimeout(mWipeTimeout);
    253             // only surround fastboot related operations with flashing permit restriction
    254             try {
    255                 long start = System.currentTimeMillis();
    256                 takeFlashingPermit();
    257                 CLog.v("Flashing permit obtained after %ds",
    258                         TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
    259 
    260                 flasher.overrideDeviceOptions(device);
    261                 flasher.setUserDataFlashOption(mUserDataFlashOption);
    262                 flasher.setForceSystemFlash(mForceSystemFlash);
    263                 flasher.setDataWipeSkipList(mDataWipeSkipList);
    264                 preEncryptDevice(device, flasher);
    265                 flasher.flash(device, deviceBuild);
    266             } finally {
    267                 returnFlashingPermit();
    268             }
    269             // only want logcat captured for current build, delete any accumulated log data
    270             device.clearLogcat();
    271             if (mSkipPostFlashingSetup) {
    272                 return;
    273             }
    274             // Temporary re-enable interruptable since the critical flashing operation is over.
    275             getRunUtil().allowInterrupt(true);
    276             device.waitForDeviceOnline();
    277             // device may lose date setting if wiped, update with host side date in case anything on
    278             // device side malfunction with an invalid date
    279             if (device.enableAdbRoot()) {
    280                 device.setDate(null);
    281             }
    282             // Disable interrupt for encryption operation.
    283             getRunUtil().allowInterrupt(false);
    284             checkBuild(device, deviceBuild);
    285             postEncryptDevice(device, flasher);
    286             // Once critical operation is done, we re-enable interruptable
    287             getRunUtil().allowInterrupt(true);
    288             try {
    289                 device.setRecoveryMode(RecoveryMode.AVAILABLE);
    290                 device.waitForDeviceAvailable(mDeviceBootTime);
    291             } catch (DeviceUnresponsiveException e) {
    292                 // assume this is a build problem
    293                 throw new DeviceFailedToBootError(String.format(
    294                         "Device %s did not become available after flashing %s",
    295                         device.getSerialNumber(), deviceBuild.getDeviceBuildId()),
    296                         device.getDeviceDescriptor());
    297             }
    298             device.postBootSetup();
    299         } finally {
    300             // Allow interruption at the end no matter what.
    301             getRunUtil().allowInterrupt(true);
    302         }
    303     }
    304 
    305     /**
    306      * Verifies the expected build matches the actual build on device after flashing
    307      * @throws DeviceNotAvailableException
    308      */
    309     private void checkBuild(ITestDevice device, IDeviceBuildInfo deviceBuild)
    310             throws DeviceNotAvailableException {
    311         // Need to use deviceBuild.getDeviceBuildId instead of getBuildId because the build info
    312         // could be an AppBuildInfo and return app build id. Need to be more explicit that we
    313         // check for the device build here.
    314         if (!mSkipPostFlashBuildIdCheck) {
    315             checkBuildAttribute(deviceBuild.getDeviceBuildId(), device.getBuildId(),
    316                     device.getSerialNumber());
    317         }
    318         if (!mSkipPostFlashFlavorCheck) {
    319             checkBuildAttribute(deviceBuild.getDeviceBuildFlavor(), device.getBuildFlavor(),
    320                     device.getSerialNumber());
    321         }
    322         // TODO: check bootloader and baseband versions too
    323     }
    324 
    325     private void checkBuildAttribute(String expectedBuildAttr, String actualBuildAttr,
    326             String serial) throws DeviceNotAvailableException {
    327         if (expectedBuildAttr == null || actualBuildAttr == null ||
    328                 !expectedBuildAttr.equals(actualBuildAttr)) {
    329             // throw DNAE - assume device hardware problem - we think flash was successful but
    330             // device is not running right bits
    331             throw new DeviceNotAvailableException(
    332                     String.format("Unexpected build after flashing. Expected %s, actual %s",
    333                             expectedBuildAttr, actualBuildAttr), serial);
    334         }
    335     }
    336 
    337     /**
    338      * Create {@link IDeviceFlasher} to use. Subclasses can override
    339      * @throws DeviceNotAvailableException
    340      */
    341     protected abstract IDeviceFlasher createFlasher(ITestDevice device)
    342             throws DeviceNotAvailableException;
    343 
    344     /**
    345      * Handle encrypting of the device pre-flash.
    346      *
    347      * @see #postEncryptDevice(ITestDevice, IDeviceFlasher)
    348      * @param device
    349      * @throws DeviceNotAvailableException
    350      * @throws TargetSetupError if the device could not be encrypted or unlocked.
    351      */
    352     private void preEncryptDevice(ITestDevice device, IDeviceFlasher flasher)
    353             throws DeviceNotAvailableException, TargetSetupError {
    354         switch (mEncryptUserData) {
    355             case IGNORE:
    356                 return;
    357             case ENCRYPT:
    358                 if (!device.isEncryptionSupported()) {
    359                     throw new TargetSetupError("Encryption is not supported",
    360                             device.getDeviceDescriptor());
    361                 }
    362                 if (!device.isDeviceEncrypted()) {
    363                     switch(flasher.getUserDataFlashOption()) {
    364                         case TESTS_ZIP: // Intentional fall through.
    365                         case WIPE_RM:
    366                             // a new filesystem will not be created by the flasher, but the userdata
    367                             // partition is expected to be cleared anyway, so we encrypt the device
    368                             // with wipe
    369                             if (!device.encryptDevice(false)) {
    370                                 throw new TargetSetupError("Failed to encrypt device",
    371                                         device.getDeviceDescriptor());
    372                             }
    373                             if (!device.unlockDevice()) {
    374                                 throw new TargetSetupError("Failed to unlock device",
    375                                         device.getDeviceDescriptor());
    376                             }
    377                             break;
    378                         case RETAIN:
    379                             // original filesystem must be retained, so we encrypt in place
    380                             if (!device.encryptDevice(true)) {
    381                                 throw new TargetSetupError("Failed to encrypt device",
    382                                         device.getDeviceDescriptor());
    383                             }
    384                             if (!device.unlockDevice()) {
    385                                 throw new TargetSetupError("Failed to unlock device",
    386                                         device.getDeviceDescriptor());
    387                             }
    388                             break;
    389                         default:
    390                             // Do nothing, userdata will be encrypted post-flash.
    391                     }
    392                 }
    393                 break;
    394             default:
    395                 // should not get here
    396                 return;
    397         }
    398     }
    399 
    400     /**
    401      * Handle encrypting of the device post-flash.
    402      * <p>
    403      * This method handles encrypting the device after a flash in cases where a flash would undo any
    404      * encryption pre-flash, such as when the device is flashed or wiped.
    405      * </p>
    406      *
    407      * @see #preEncryptDevice(ITestDevice, IDeviceFlasher)
    408      * @param device
    409      * @throws DeviceNotAvailableException
    410      * @throws TargetSetupError If the device could not be encrypted or unlocked.
    411      */
    412     private void postEncryptDevice(ITestDevice device, IDeviceFlasher flasher)
    413             throws DeviceNotAvailableException, TargetSetupError {
    414         switch (mEncryptUserData) {
    415             case IGNORE:
    416                 return;
    417             case ENCRYPT:
    418                 if (!device.isEncryptionSupported()) {
    419                     throw new TargetSetupError("Encryption is not supported",
    420                             device.getDeviceDescriptor());
    421                 }
    422                 switch(flasher.getUserDataFlashOption()) {
    423                     case FLASH:
    424                         if (!device.encryptDevice(true)) {
    425                             throw new TargetSetupError("Failed to encrypt device",
    426                                     device.getDeviceDescriptor());
    427                         }
    428                         break;
    429                     case WIPE: // Intentional fall through.
    430                     case FORCE_WIPE:
    431                         // since the device was just wiped, encrypt with wipe
    432                         if (!device.encryptDevice(false)) {
    433                             throw new TargetSetupError("Failed to encrypt device",
    434                                     device.getDeviceDescriptor());
    435                         }
    436                         break;
    437                     default:
    438                         // Do nothing, userdata was encrypted pre-flash.
    439                 }
    440                 if (!device.unlockDevice()) {
    441                     throw new TargetSetupError("Failed to unlock device",
    442                             device.getDeviceDescriptor());
    443                 }
    444                 break;
    445             default:
    446                 // should not get here
    447                 return;
    448         }
    449     }
    450 
    451     @Override
    452     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
    453             throws DeviceNotAvailableException {
    454         if (mDisable) {
    455             CLog.i("Skipping device flashing tearDown.");
    456             return;
    457         }
    458         if (mEncryptUserData == EncryptionOptions.ENCRYPT
    459                 && mUserDataFlashOption != UserDataFlashOption.RETAIN) {
    460             if (e instanceof DeviceNotAvailableException) {
    461                 CLog.e("Device was encrypted but now unavailable. may need manual cleanup");
    462             } else if (device.isDeviceEncrypted()) {
    463                 if (!device.unencryptDevice()) {
    464                     throw new RuntimeException("Failed to unencrypt device");
    465                 }
    466             }
    467         }
    468     }
    469 }
    470