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 */ getRunUtil()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 */ setWaitTime(long waitTime)95 void setWaitTime(long waitTime) { 96 mWaitTime = waitTime; 97 } 98 99 /** 100 * {@inheritDoc} 101 */ 102 @Override setFastbootPath(String fastbootPath)103 public void setFastbootPath(String fastbootPath) { 104 mFastbootPath = fastbootPath; 105 } 106 107 /** 108 * {@inheritDoc} 109 */ 110 @Override recoverDevice(IDeviceStateMonitor monitor, boolean recoverUntilOnline)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 getDeviceAfterRecovery(IDeviceStateMonitor monitor)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 */ checkMinBatteryLevel(IDevice device)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 */ handleDeviceUnresponsive(IDevice device, IDeviceStateMonitor monitor)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 */ handleDeviceNotAvailable(IDeviceStateMonitor monitor, boolean recoverTillOnline)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 recoverDeviceBootloader(final IDeviceStateMonitor monitor)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 */ handleDeviceOnlineExpectedBootloader(final IDeviceStateMonitor monitor)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 */ handleDeviceBootloaderUnresponsive(IDeviceStateMonitor monitor)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 */ rebootDeviceIntoBootloader(IDevice device)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 */ rebootDevice(IDevice device)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 */ handleDeviceBootloaderNotAvailable(final IDeviceStateMonitor monitor)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 recoverDeviceRecovery(IDeviceStateMonitor monitor)376 public void recoverDeviceRecovery(IDeviceStateMonitor monitor) 377 throws DeviceNotAvailableException { 378 throw new DeviceNotAvailableException("device recovery not implemented", 379 monitor.getSerialNumber()); 380 } 381 } 382