Home | History | Annotate | Download | only in device
      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 package com.android.tradefed.device;
     17 
     18 import com.android.ddmlib.AdbCommandRejectedException;
     19 import com.android.ddmlib.IDevice;
     20 import com.android.ddmlib.Log;
     21 import com.android.ddmlib.TimeoutException;
     22 import com.android.tradefed.config.Option;
     23 import com.android.tradefed.log.LogUtil.CLog;
     24 import com.android.tradefed.util.CommandResult;
     25 import com.android.tradefed.util.CommandStatus;
     26 import com.android.tradefed.util.IRunUtil;
     27 import com.android.tradefed.util.RunUtil;
     28 
     29 import java.io.IOException;
     30 import java.util.concurrent.ExecutionException;
     31 
     32 /**
     33  * A simple implementation of a {@link IDeviceRecovery} that waits for device to be online and
     34  * respond to simple commands.
     35  */
     36 public class WaitDeviceRecovery implements IDeviceRecovery {
     37 
     38     private static final String LOG_TAG = "WaitDeviceRecovery";
     39 
     40     /** the time in ms to wait before beginning recovery attempts */
     41     protected static final long INITIAL_PAUSE_TIME = 5 * 1000;
     42 
     43     /**
     44      * The number of attempts to check if device is in bootloader.
     45      * <p/>
     46      * Exposed for unit testing
     47      */
     48     public static final int BOOTLOADER_POLL_ATTEMPTS = 3;
     49 
     50     // TODO: add a separate configurable timeout per operation
     51     @Option(name="online-wait-time",
     52             description="maximum time in ms to wait for device to come online.")
     53     protected long mOnlineWaitTime = 60 * 1000;
     54     @Option(name="device-wait-time",
     55             description="maximum time in ms to wait for a single device recovery command.")
     56     protected long mWaitTime = 4 * 60 * 1000;
     57 
     58     @Option(name="bootloader-wait-time",
     59             description="maximum time in ms to wait for device to be in fastboot.")
     60     protected long mBootloaderWaitTime = 30 * 1000;
     61 
     62     @Option(name="shell-wait-time",
     63             description="maximum time in ms to wait for device shell to be responsive.")
     64     protected long mShellWaitTime = 30 * 1000;
     65 
     66     @Option(name="fastboot-wait-time",
     67             description="maximum time in ms to wait for a fastboot command result.")
     68     protected long mFastbootWaitTime = 30 * 1000;
     69 
     70     @Option(name = "min-battery-after-recovery",
     71             description = "require a min battery level after successful recovery, " +
     72                           "default to 0 for ignoring.")
     73     protected int mRequiredMinBattery = 0;
     74 
     75     @Option(name = "disable-unresponsive-reboot",
     76             description = "If this is set, we will not attempt to reboot an unresponsive device" +
     77             "that is in userspace.  Note that this will have no effect if the device is in " +
     78             "fastboot or is expected to be in fastboot.")
     79     protected boolean mDisableUnresponsiveReboot = false;
     80 
     81     private String mFastbootPath = "fastboot";
     82 
     83     /**
     84      * Get the {@link RunUtil} instance to use.
     85      * <p/>
     86      * Exposed for unit testing.
     87      */
     88     protected IRunUtil getRunUtil() {
     89         return RunUtil.getDefault();
     90     }
     91 
     92     /**
     93      * Sets the maximum time in ms to wait for a single device recovery command.
     94      */
     95     void setWaitTime(long waitTime) {
     96         mWaitTime = waitTime;
     97     }
     98 
     99     /**
    100      * {@inheritDoc}
    101      */
    102     @Override
    103     public void setFastbootPath(String fastbootPath) {
    104         mFastbootPath = fastbootPath;
    105     }
    106 
    107     /**
    108      * {@inheritDoc}
    109      */
    110     @Override
    111     public void recoverDevice(IDeviceStateMonitor monitor, boolean recoverUntilOnline)
    112             throws DeviceNotAvailableException {
    113         // device may have just gone offline
    114         // sleep a small amount to give ddms state a chance to settle
    115         // TODO - see if there is better way to handle this
    116         Log.i(LOG_TAG, String.format("Pausing for %d for %s to recover",
    117                 INITIAL_PAUSE_TIME, monitor.getSerialNumber()));
    118         getRunUtil().sleep(INITIAL_PAUSE_TIME);
    119 
    120         // ensure bootloader state is updated
    121         monitor.waitForDeviceBootloaderStateUpdate();
    122 
    123         if (monitor.getDeviceState().equals(TestDeviceState.FASTBOOT)) {
    124             Log.i(LOG_TAG, String.format(
    125                     "Found device %s in fastboot but expected online. Rebooting...",
    126                     monitor.getSerialNumber()));
    127             // TODO: retry if failed
    128             getRunUtil().runTimedCmd(mFastbootWaitTime, mFastbootPath, "-s",
    129                     monitor.getSerialNumber(), "reboot");
    130         }
    131 
    132         // wait for device online
    133         IDevice device = monitor.waitForDeviceOnline(mOnlineWaitTime);
    134         if (device == null) {
    135             handleDeviceNotAvailable(monitor, recoverUntilOnline);
    136             // function returning implies that recovery is successful, check battery level here
    137             checkMinBatteryLevel(getDeviceAfterRecovery(monitor));
    138             return;
    139         }
    140         // occasionally device is erroneously reported as online - double check that we can shell
    141         // into device
    142         if (!monitor.waitForDeviceShell(mShellWaitTime)) {
    143             // treat this as a not available device
    144             handleDeviceNotAvailable(monitor, recoverUntilOnline);
    145             checkMinBatteryLevel(getDeviceAfterRecovery(monitor));
    146             return;
    147         }
    148 
    149         if (!recoverUntilOnline) {
    150             if (monitor.waitForDeviceAvailable(mWaitTime) == null) {
    151                 // device is online but not responsive
    152                 handleDeviceUnresponsive(device, monitor);
    153             }
    154         }
    155         // do a final check here when all previous if blocks are skipped or the last
    156         // handleDeviceUnresponsive was successful
    157         checkMinBatteryLevel(getDeviceAfterRecovery(monitor));
    158     }
    159 
    160     private IDevice getDeviceAfterRecovery(IDeviceStateMonitor monitor)
    161             throws DeviceNotAvailableException {
    162         IDevice device = monitor.waitForDeviceOnline(mOnlineWaitTime);
    163         if (device == null) {
    164             throw new DeviceNotAvailableException(
    165                     "Device still not online after successful recovery", monitor.getSerialNumber());
    166         }
    167         return device;
    168     }
    169 
    170     /**
    171      * Checks if device battery level meets min requirement
    172      * @param device
    173      * @throws DeviceNotAvailableException if battery level cannot be read or lower than min
    174      */
    175     protected void checkMinBatteryLevel(IDevice device) throws DeviceNotAvailableException {
    176         if (mRequiredMinBattery <= 0) {
    177             // don't do anything if check is not required
    178             return;
    179         }
    180         try {
    181             Integer level = device.getBattery().get();
    182             if (level == null) {
    183                 // can't read battery level but we are requiring a min, reject
    184                 // device
    185                 throw new DeviceNotAvailableException(
    186                         "Cannot read battery level but a min is required",
    187                         device.getSerialNumber());
    188             } else if (level < mRequiredMinBattery) {
    189                 throw new DeviceNotAvailableException(String.format(
    190                         "After recovery, device battery level %d is lower than required minimum %d",
    191                         level, mRequiredMinBattery), device.getSerialNumber());
    192             }
    193             return;
    194         } catch (InterruptedException | ExecutionException e) {
    195             throw new DeviceNotAvailableException("exception while reading battery level", e,
    196                     device.getSerialNumber());
    197         }
    198     }
    199 
    200     /**
    201      * Handle situation where device is online but unresponsive.
    202      * @param monitor
    203      * @throws DeviceNotAvailableException
    204      */
    205     protected void handleDeviceUnresponsive(IDevice device, IDeviceStateMonitor monitor)
    206             throws DeviceNotAvailableException {
    207         if (!mDisableUnresponsiveReboot) {
    208             Log.i(LOG_TAG, String.format(
    209                     "Device %s unresponsive. Rebooting...", monitor.getSerialNumber()));
    210             rebootDevice(device);
    211             IDevice newdevice = monitor.waitForDeviceOnline(mOnlineWaitTime);
    212             if (newdevice == null) {
    213                 handleDeviceNotAvailable(monitor, false);
    214                 return;
    215             }
    216             if (monitor.waitForDeviceAvailable(mWaitTime) != null) {
    217                 return;
    218             }
    219         }
    220         // If no reboot was done, waitForDeviceAvailable has already been checked.
    221         throw new DeviceUnresponsiveException(String.format(
    222                 "Device %s is online but unresponsive", monitor.getSerialNumber()),
    223                 monitor.getSerialNumber());
    224     }
    225 
    226     /**
    227      * Handle situation where device is not available.
    228      *
    229      * @param monitor the {@link IDeviceStateMonitor}
    230      * @param recoverTillOnline if true this method should return if device is online, and not
    231      * check for responsiveness
    232      * @throws DeviceNotAvailableException
    233      */
    234     protected void handleDeviceNotAvailable(IDeviceStateMonitor monitor, boolean recoverTillOnline)
    235             throws DeviceNotAvailableException {
    236         throw new DeviceNotAvailableException(String.format("Could not find device %s",
    237                 monitor.getSerialNumber()), monitor.getSerialNumber());
    238     }
    239 
    240     /**
    241      * {@inheritDoc}
    242      */
    243     @Override
    244     public void recoverDeviceBootloader(final IDeviceStateMonitor monitor)
    245             throws DeviceNotAvailableException {
    246         // device may have just gone offline
    247         // wait a small amount to give device state a chance to settle
    248         // TODO - see if there is better way to handle this
    249         Log.i(LOG_TAG, String.format("Pausing for %d for %s to recover",
    250                 INITIAL_PAUSE_TIME, monitor.getSerialNumber()));
    251         getRunUtil().sleep(INITIAL_PAUSE_TIME);
    252 
    253         // poll and wait for device to return to valid state
    254         long pollTime = mBootloaderWaitTime / BOOTLOADER_POLL_ATTEMPTS;
    255         for (int i=0; i < BOOTLOADER_POLL_ATTEMPTS; i++) {
    256             if (monitor.waitForDeviceBootloader(pollTime)) {
    257                 handleDeviceBootloaderUnresponsive(monitor);
    258                 // passed above check, abort
    259                 return;
    260             } else if (monitor.getDeviceState() == TestDeviceState.ONLINE) {
    261                 handleDeviceOnlineExpectedBootloader(monitor);
    262                 return;
    263             }
    264         }
    265         handleDeviceBootloaderNotAvailable(monitor);
    266     }
    267 
    268     /**
    269      * Handle condition where device is online, but should be in bootloader state.
    270      * <p/>
    271      * If this method
    272      * @param monitor
    273      * @throws DeviceNotAvailableException
    274      */
    275     protected void handleDeviceOnlineExpectedBootloader(final IDeviceStateMonitor monitor)
    276             throws DeviceNotAvailableException {
    277         Log.i(LOG_TAG, String.format("Found device %s online but expected fastboot.",
    278             monitor.getSerialNumber()));
    279         // call waitForDeviceOnline to get handle to IDevice
    280         IDevice device = monitor.waitForDeviceOnline(mOnlineWaitTime);
    281         if (device == null) {
    282             handleDeviceBootloaderNotAvailable(monitor);
    283             return;
    284         }
    285         rebootDeviceIntoBootloader(device);
    286         if (!monitor.waitForDeviceBootloader(mBootloaderWaitTime)) {
    287             throw new DeviceNotAvailableException(String.format(
    288                     "Device %s not in bootloader after reboot", monitor.getSerialNumber()),
    289                     monitor.getSerialNumber());
    290         }
    291     }
    292 
    293     /**
    294      * @param monitor
    295      * @throws DeviceNotAvailableException
    296      */
    297     protected void handleDeviceBootloaderUnresponsive(IDeviceStateMonitor monitor)
    298             throws DeviceNotAvailableException {
    299         CLog.i("Found device %s in fastboot but potentially unresponsive.",
    300                 monitor.getSerialNumber());
    301         // TODO: retry reboot
    302         getRunUtil().runTimedCmd(mFastbootWaitTime, mFastbootPath, "-s", monitor.getSerialNumber(),
    303                 "reboot-bootloader");
    304         // wait for device to reboot
    305         monitor.waitForDeviceNotAvailable(20*1000);
    306         if (!monitor.waitForDeviceBootloader(mBootloaderWaitTime)) {
    307             throw new DeviceNotAvailableException(String.format(
    308                     "Device %s not in bootloader after reboot", monitor.getSerialNumber()),
    309                     monitor.getSerialNumber());
    310         }
    311         // running a meaningless command just to see whether the device is responsive.
    312         CommandResult result = getRunUtil().runTimedCmd(mFastbootWaitTime, mFastbootPath, "-s",
    313                 monitor.getSerialNumber(), "getvar", "product");
    314         if (result.getStatus().equals(CommandStatus.TIMED_OUT)) {
    315             throw new DeviceNotAvailableException(String.format(
    316                     "Device %s is in fastboot but unresponsive", monitor.getSerialNumber()),
    317                     monitor.getSerialNumber());
    318         }
    319     }
    320 
    321     /**
    322      * Reboot device into bootloader.
    323      *
    324      * @param device the {@link IDevice} to reboot.
    325      */
    326     protected void rebootDeviceIntoBootloader(IDevice device) {
    327         try {
    328             device.reboot("bootloader");
    329         } catch (IOException e) {
    330             Log.w(LOG_TAG, String.format("failed to reboot %s: %s", device.getSerialNumber(),
    331                     e.getMessage()));
    332         } catch (TimeoutException e) {
    333             Log.w(LOG_TAG, String.format("failed to reboot %s: timeout", device.getSerialNumber()));
    334         } catch (AdbCommandRejectedException e) {
    335             Log.w(LOG_TAG, String.format("failed to reboot %s: %s", device.getSerialNumber(),
    336                     e.getMessage()));
    337         }
    338     }
    339 
    340     /**
    341      * Reboot device into bootloader.
    342      *
    343      * @param device the {@link IDevice} to reboot.
    344      */
    345     protected void rebootDevice(IDevice device) {
    346         try {
    347             device.reboot(null);
    348         } catch (IOException e) {
    349             Log.w(LOG_TAG, String.format("failed to reboot %s: %s", device.getSerialNumber(),
    350                     e.getMessage()));
    351         } catch (TimeoutException e) {
    352             Log.w(LOG_TAG, String.format("failed to reboot %s: timeout", device.getSerialNumber()));
    353         } catch (AdbCommandRejectedException e) {
    354             Log.w(LOG_TAG, String.format("failed to reboot %s: %s", device.getSerialNumber(),
    355                     e.getMessage()));
    356         }
    357     }
    358 
    359     /**
    360      * Handle situation where device is not available when expected to be in bootloader.
    361      *
    362      * @param monitor the {@link IDeviceStateMonitor}
    363      * @throws DeviceNotAvailableException
    364      */
    365     protected void handleDeviceBootloaderNotAvailable(final IDeviceStateMonitor monitor)
    366             throws DeviceNotAvailableException {
    367         throw new DeviceNotAvailableException(String.format(
    368                 "Could not find device %s in bootloader", monitor.getSerialNumber()),
    369                 monitor.getSerialNumber());
    370     }
    371 
    372     /**
    373      * {@inheritDoc}
    374      */
    375     @Override
    376     public void recoverDeviceRecovery(IDeviceStateMonitor monitor)
    377             throws DeviceNotAvailableException {
    378         throw new DeviceNotAvailableException("device recovery not implemented",
    379                 monitor.getSerialNumber());
    380     }
    381 }
    382