blob: c91d2ea16714170fd1c403780edfc9936ca78dad [file] [log] [blame]
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache
* License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.device;
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* Runs a command on a given device repeating as necessary until the action is canceled.
* <p>
* When the class is run, the command is run on the device in a separate thread and the output is
* collected in a temporary host file.
* </p><p>
* This is done so:
* </p><ul>
* <li>if device goes permanently offline during a test, the log data is retained.</li>
* <li>to capture more data than may fit in device's circular log.</li>
* </ul>
*/
public class BackgroundDeviceAction extends Thread {
private static final long ONLINE_POLL_INTERVAL_MS = 10 * 1000;
private IShellOutputReceiver mReceiver;
private ITestDevice mTestDevice;
private String mCommand;
private String mSerialNumber;
private String mDescriptor;
private boolean mIsCancelled;
private int mLogStartDelay;
/**
* Creates a {@link BackgroundDeviceAction}
*
* @param command the command to run
* @param descriptor the description of the command. For logging only.
* @param device the device to run the command on
* @param receiver the receiver for collecting the output of the command
* @param startDelay the delay to wait after the device becomes online
*/
public BackgroundDeviceAction(String command, String descriptor, ITestDevice device,
IShellOutputReceiver receiver, int startDelay) {
mCommand = command;
mDescriptor = descriptor;
mSerialNumber = device.getSerialNumber();
mTestDevice = device;
mReceiver = receiver;
mLogStartDelay = startDelay;
// don't keep VM open if this thread is still running
setDaemon(true);
}
/**
* {@inheritDoc}
* <p>
* Repeats the command until canceled.
* </p>
*/
@Override
public void run() {
String separator = String.format(
"========== beginning of new [%s] output ==========\n", mDescriptor);
while (!isCancelled()) {
if (mLogStartDelay > 0) {
CLog.d("Sleep for %d before starting %s for %s.", mLogStartDelay, mDescriptor,
mSerialNumber);
getRunUtil().sleep(mLogStartDelay);
}
blockUntilOnlineNoThrow();
// check again if the operation has been cancelled after the wait for online
if (isCancelled()) {
break;
}
CLog.d("Starting %s for %s.", mDescriptor, mSerialNumber);
mReceiver.addOutput(separator.getBytes(), 0, separator.length());
try {
mTestDevice.getIDevice().executeShellCommand(mCommand, mReceiver,
0, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
recoverDevice(e.getClass().getName());
} catch (AdbCommandRejectedException e) {
recoverDevice(e.getClass().getName());
} catch (ShellCommandUnresponsiveException e) {
recoverDevice(e.getClass().getName());
} catch (IOException e) {
recoverDevice(e.getClass().getName());
}
}
}
private void recoverDevice(String exceptionType) {
CLog.d("%s while running %s on %s. May see duplicated content in log.", exceptionType,
mDescriptor, mSerialNumber);
// Make sure we haven't been cancelled before we sleep for a long time
if (isCancelled()) {
return;
}
// sleep a small amount for device to settle
getRunUtil().sleep(5 * 1000);
// wait a long time for device to be online
try {
mTestDevice.waitForDeviceOnline(10 * 60 * 1000);
} catch (DeviceNotAvailableException e) {
CLog.w("Device %s not online", mSerialNumber);
}
}
/**
* Cancels the command.
*/
public synchronized void cancel() {
mIsCancelled = true;
interrupt();
}
/**
* If the command is cancelled.
*/
public synchronized boolean isCancelled() {
return mIsCancelled;
}
/**
* Get the {@link RunUtil} instance to use.
* <p/>
* Exposed for unit testing.
*/
IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
private void blockUntilOnlineNoThrow() {
CLog.d("Waiting for device %s online before starting.", mSerialNumber);
while (!isCancelled()) {
if (!TestDeviceState.ONLINE.equals(mTestDevice.getDeviceState())) {
getRunUtil().sleep(ONLINE_POLL_INTERVAL_MS);
} else {
CLog.d("Device %s now online.", mSerialNumber);
break;
}
}
}
}