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         CLog.i(
    218                 "Requesting a flashing permit out of the host max limit of %s. Current queue "
    219                         + "length: %s",
    220                 getHostOptions().getConcurrentFlasherLimit(),
    221                 sConcurrentFlashLock.getQueueLength());
    222         sConcurrentFlashLock.acquireUninterruptibly();
    223     }
    224 
    225     /**
    226      * Restore a flashing permit that we acquired previously
    227      *
    228      * Exposed for unit testing.
    229      */
    230     void returnFlashingPermit() {
    231         if (sConcurrentFlashLock != null) {
    232             sConcurrentFlashLock.release();
    233         }
    234     }
    235 
    236     /**
    237      * {@inheritDoc}
    238      */
    239     @Override
    240     public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError,
    241             DeviceNotAvailableException, BuildError {
    242         if (mDisable) {
    243             CLog.i("Skipping device flashing.");
    244             return;
    245         }
    246         CLog.i("Performing setup on %s", device.getSerialNumber());
    247         if (!(buildInfo instanceof IDeviceBuildInfo)) {
    248             throw new IllegalArgumentException("Provided buildInfo is not a IDeviceBuildInfo");
    249         }
    250         // don't allow interruptions during flashing operations.
    251         getRunUtil().allowInterrupt(false);
    252         try {
    253             IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo)buildInfo;
    254             checkDeviceProductType(device, deviceBuild);
    255             device.setRecoveryMode(RecoveryMode.ONLINE);
    256             IDeviceFlasher flasher = createFlasher(device);
    257             flasher.setWipeTimeout(mWipeTimeout);
    258             // only surround fastboot related operations with flashing permit restriction
    259             try {
    260                 long start = System.currentTimeMillis();
    261                 takeFlashingPermit();
    262                 CLog.v("Flashing permit obtained after %ds",
    263                         TimeUnit.MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));
    264 
    265                 flasher.overrideDeviceOptions(device);
    266                 flasher.setUserDataFlashOption(mUserDataFlashOption);
    267                 flasher.setForceSystemFlash(mForceSystemFlash);
    268                 flasher.setDataWipeSkipList(mDataWipeSkipList);
    269                 preEncryptDevice(device, flasher);
    270                 flasher.flash(device, deviceBuild);
    271             } finally {
    272                 returnFlashingPermit();
    273             }
    274             // only want logcat captured for current build, delete any accumulated log data
    275             device.clearLogcat();
    276             if (mSkipPostFlashingSetup) {
    277                 return;
    278             }
    279             // Temporary re-enable interruptable since the critical flashing operation is over.
    280             getRunUtil().allowInterrupt(true);
    281             device.waitForDeviceOnline();
    282             // device may lose date setting if wiped, update with host side date in case anything on
    283             // device side malfunction with an invalid date
    284             if (device.enableAdbRoot()) {
    285                 device.setDate(null);
    286             }
    287             // Disable interrupt for encryption operation.
    288             getRunUtil().allowInterrupt(false);
    289             checkBuild(device, deviceBuild);
    290             postEncryptDevice(device, flasher);
    291             // Once critical operation is done, we re-enable interruptable
    292             getRunUtil().allowInterrupt(true);
    293             try {
    294                 device.setRecoveryMode(RecoveryMode.AVAILABLE);
    295                 device.waitForDeviceAvailable(mDeviceBootTime);
    296             } catch (DeviceUnresponsiveException e) {
    297                 // assume this is a build problem
    298                 throw new DeviceFailedToBootError(String.format(
    299                         "Device %s did not become available after flashing %s",
    300                         device.getSerialNumber(), deviceBuild.getDeviceBuildId()),
    301                         device.getDeviceDescriptor());
    302             }
    303             device.postBootSetup();
    304         } finally {
    305             // Allow interruption at the end no matter what.
    306             getRunUtil().allowInterrupt(true);
    307         }
    308     }
    309 
    310     /**
    311      * Possible check before flashing to ensure the device is as expected compare to the build info.
    312      *
    313      * @param device the {@link ITestDevice} to flash.
    314      * @param deviceBuild the {@link IDeviceBuildInfo} used to flash.
    315      * @throws BuildError
    316      * @throws DeviceNotAvailableException
    317      */
    318     protected void checkDeviceProductType(ITestDevice device, IDeviceBuildInfo deviceBuild)
    319             throws BuildError, DeviceNotAvailableException {
    320         // empty of purpose
    321     }
    322 
    323     /**
    324      * Verifies the expected build matches the actual build on device after flashing
    325      * @throws DeviceNotAvailableException
    326      */
    327     private void checkBuild(ITestDevice device, IDeviceBuildInfo deviceBuild)
    328             throws DeviceNotAvailableException {
    329         // Need to use deviceBuild.getDeviceBuildId instead of getBuildId because the build info
    330         // could be an AppBuildInfo and return app build id. Need to be more explicit that we
    331         // check for the device build here.
    332         if (!mSkipPostFlashBuildIdCheck) {
    333             checkBuildAttribute(deviceBuild.getDeviceBuildId(), device.getBuildId(),
    334                     device.getSerialNumber());
    335         }
    336         if (!mSkipPostFlashFlavorCheck) {
    337             checkBuildAttribute(deviceBuild.getDeviceBuildFlavor(), device.getBuildFlavor(),
    338                     device.getSerialNumber());
    339         }
    340         // TODO: check bootloader and baseband versions too
    341     }
    342 
    343     private void checkBuildAttribute(String expectedBuildAttr, String actualBuildAttr,
    344             String serial) throws DeviceNotAvailableException {
    345         if (expectedBuildAttr == null || actualBuildAttr == null ||
    346                 !expectedBuildAttr.equals(actualBuildAttr)) {
    347             // throw DNAE - assume device hardware problem - we think flash was successful but
    348             // device is not running right bits
    349             throw new DeviceNotAvailableException(
    350                     String.format("Unexpected build after flashing. Expected %s, actual %s",
    351                             expectedBuildAttr, actualBuildAttr), serial);
    352         }
    353     }
    354 
    355     /**
    356      * Create {@link IDeviceFlasher} to use. Subclasses can override
    357      * @throws DeviceNotAvailableException
    358      */
    359     protected abstract IDeviceFlasher createFlasher(ITestDevice device)
    360             throws DeviceNotAvailableException;
    361 
    362     /**
    363      * Handle encrypting of the device pre-flash.
    364      *
    365      * @see #postEncryptDevice(ITestDevice, IDeviceFlasher)
    366      * @param device
    367      * @throws DeviceNotAvailableException
    368      * @throws TargetSetupError if the device could not be encrypted or unlocked.
    369      */
    370     private void preEncryptDevice(ITestDevice device, IDeviceFlasher flasher)
    371             throws DeviceNotAvailableException, TargetSetupError {
    372         switch (mEncryptUserData) {
    373             case IGNORE:
    374                 return;
    375             case ENCRYPT:
    376                 if (!device.isEncryptionSupported()) {
    377                     throw new TargetSetupError("Encryption is not supported",
    378                             device.getDeviceDescriptor());
    379                 }
    380                 if (!device.isDeviceEncrypted()) {
    381                     switch(flasher.getUserDataFlashOption()) {
    382                         case TESTS_ZIP: // Intentional fall through.
    383                         case WIPE_RM:
    384                             // a new filesystem will not be created by the flasher, but the userdata
    385                             // partition is expected to be cleared anyway, so we encrypt the device
    386                             // with wipe
    387                             if (!device.encryptDevice(false)) {
    388                                 throw new TargetSetupError("Failed to encrypt device",
    389                                         device.getDeviceDescriptor());
    390                             }
    391                             if (!device.unlockDevice()) {
    392                                 throw new TargetSetupError("Failed to unlock device",
    393                                         device.getDeviceDescriptor());
    394                             }
    395                             break;
    396                         case RETAIN:
    397                             // original filesystem must be retained, so we encrypt in place
    398                             if (!device.encryptDevice(true)) {
    399                                 throw new TargetSetupError("Failed to encrypt device",
    400                                         device.getDeviceDescriptor());
    401                             }
    402                             if (!device.unlockDevice()) {
    403                                 throw new TargetSetupError("Failed to unlock device",
    404                                         device.getDeviceDescriptor());
    405                             }
    406                             break;
    407                         default:
    408                             // Do nothing, userdata will be encrypted post-flash.
    409                     }
    410                 }
    411                 break;
    412             default:
    413                 // should not get here
    414                 return;
    415         }
    416     }
    417 
    418     /**
    419      * Handle encrypting of the device post-flash.
    420      * <p>
    421      * This method handles encrypting the device after a flash in cases where a flash would undo any
    422      * encryption pre-flash, such as when the device is flashed or wiped.
    423      * </p>
    424      *
    425      * @see #preEncryptDevice(ITestDevice, IDeviceFlasher)
    426      * @param device
    427      * @throws DeviceNotAvailableException
    428      * @throws TargetSetupError If the device could not be encrypted or unlocked.
    429      */
    430     private void postEncryptDevice(ITestDevice device, IDeviceFlasher flasher)
    431             throws DeviceNotAvailableException, TargetSetupError {
    432         switch (mEncryptUserData) {
    433             case IGNORE:
    434                 return;
    435             case ENCRYPT:
    436                 if (!device.isEncryptionSupported()) {
    437                     throw new TargetSetupError("Encryption is not supported",
    438                             device.getDeviceDescriptor());
    439                 }
    440                 switch(flasher.getUserDataFlashOption()) {
    441                     case FLASH:
    442                         if (!device.encryptDevice(true)) {
    443                             throw new TargetSetupError("Failed to encrypt device",
    444                                     device.getDeviceDescriptor());
    445                         }
    446                         break;
    447                     case WIPE: // Intentional fall through.
    448                     case FORCE_WIPE:
    449                         // since the device was just wiped, encrypt with wipe
    450                         if (!device.encryptDevice(false)) {
    451                             throw new TargetSetupError("Failed to encrypt device",
    452                                     device.getDeviceDescriptor());
    453                         }
    454                         break;
    455                     default:
    456                         // Do nothing, userdata was encrypted pre-flash.
    457                 }
    458                 if (!device.unlockDevice()) {
    459                     throw new TargetSetupError("Failed to unlock device",
    460                             device.getDeviceDescriptor());
    461                 }
    462                 break;
    463             default:
    464                 // should not get here
    465                 return;
    466         }
    467     }
    468 
    469     @Override
    470     public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
    471             throws DeviceNotAvailableException {
    472         if (mDisable) {
    473             CLog.i("Skipping device flashing tearDown.");
    474             return;
    475         }
    476         if (mEncryptUserData == EncryptionOptions.ENCRYPT
    477                 && mUserDataFlashOption != UserDataFlashOption.RETAIN) {
    478             if (e instanceof DeviceNotAvailableException) {
    479                 CLog.e("Device was encrypted but now unavailable. may need manual cleanup");
    480             } else if (device.isDeviceEncrypted()) {
    481                 if (!device.unencryptDevice()) {
    482                     throw new RuntimeException("Failed to unencrypt device");
    483                 }
    484             }
    485         }
    486     }
    487 }
    488