1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache 5 * License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.tradefed.device; 19 20 import com.android.ddmlib.AdbCommandRejectedException; 21 import com.android.ddmlib.IShellOutputReceiver; 22 import com.android.ddmlib.ShellCommandUnresponsiveException; 23 import com.android.ddmlib.TimeoutException; 24 import com.android.tradefed.log.LogUtil.CLog; 25 import com.android.tradefed.util.IRunUtil; 26 import com.android.tradefed.util.RunUtil; 27 28 import java.io.IOException; 29 import java.util.concurrent.TimeUnit; 30 31 /** 32 * Runs a command on a given device repeating as necessary until the action is canceled. 33 * <p> 34 * When the class is run, the command is run on the device in a separate thread and the output is 35 * collected in a temporary host file. 36 * </p><p> 37 * This is done so: 38 * </p><ul> 39 * <li>if device goes permanently offline during a test, the log data is retained.</li> 40 * <li>to capture more data than may fit in device's circular log.</li> 41 * </ul> 42 */ 43 public class BackgroundDeviceAction extends Thread { 44 private static final long ONLINE_POLL_INTERVAL_MS = 10 * 1000; 45 private IShellOutputReceiver mReceiver; 46 private ITestDevice mTestDevice; 47 private String mCommand; 48 private String mDescriptor; 49 private boolean mIsCancelled; 50 private int mLogStartDelay; 51 52 /** 53 * Creates a {@link BackgroundDeviceAction} 54 * 55 * @param command the command to run 56 * @param descriptor the description of the command. For logging only. 57 * @param device the device to run the command on 58 * @param receiver the receiver for collecting the output of the command 59 * @param startDelay the delay to wait after the device becomes online 60 */ 61 public BackgroundDeviceAction(String command, String descriptor, ITestDevice device, 62 IShellOutputReceiver receiver, int startDelay) { 63 super("BackgroundDeviceAction-" + command); 64 mCommand = command; 65 mDescriptor = descriptor; 66 mTestDevice = device; 67 mReceiver = receiver; 68 mLogStartDelay = startDelay; 69 // don't keep VM open if this thread is still running 70 setDaemon(true); 71 } 72 73 /** 74 * {@inheritDoc} 75 * <p> 76 * Repeats the command until canceled. 77 * </p> 78 */ 79 @Override 80 public void run() { 81 String separator = String.format( 82 "\n========== beginning of new [%s] output ==========\n", mDescriptor); 83 while (!isCancelled()) { 84 if (mLogStartDelay > 0) { 85 CLog.d("Sleep for %d before starting %s for %s.", mLogStartDelay, mDescriptor, 86 mTestDevice.getSerialNumber()); 87 getRunUtil().sleep(mLogStartDelay); 88 } 89 blockUntilOnlineNoThrow(); 90 // check again if the operation has been cancelled after the wait for online 91 if (isCancelled()) { 92 break; 93 } 94 CLog.d("Starting %s for %s.", mDescriptor, mTestDevice.getSerialNumber()); 95 mReceiver.addOutput(separator.getBytes(), 0, separator.length()); 96 try { 97 mTestDevice.getIDevice().executeShellCommand(mCommand, mReceiver, 98 0, TimeUnit.MILLISECONDS); 99 } catch (AdbCommandRejectedException | IOException | 100 ShellCommandUnresponsiveException | TimeoutException e) { 101 waitForDeviceRecovery(e.getClass().getName()); 102 } 103 } 104 } 105 106 /** 107 * If device goes offline for any reason, the recovery will be triggered from the main 108 * so we just have to block until it recovers or invocation fails for device unavailable. 109 * @param exceptionType 110 */ 111 protected void waitForDeviceRecovery(String exceptionType) { 112 CLog.d("%s while running %s on %s. May see duplicated content in log.", exceptionType, 113 mDescriptor, mTestDevice.getSerialNumber()); 114 blockUntilOnlineNoThrow(); 115 } 116 117 /** 118 * Cancels the command. 119 */ 120 public synchronized void cancel() { 121 mIsCancelled = true; 122 interrupt(); 123 } 124 125 /** 126 * If the command is cancelled. 127 */ 128 public synchronized boolean isCancelled() { 129 return mIsCancelled; 130 } 131 132 /** 133 * Get the {@link RunUtil} instance to use. 134 * <p/> 135 * Exposed for unit testing. 136 */ 137 IRunUtil getRunUtil() { 138 return RunUtil.getDefault(); 139 } 140 141 private void blockUntilOnlineNoThrow() { 142 CLog.d("Waiting for device %s online before starting.", mTestDevice.getSerialNumber()); 143 while (!isCancelled()) { 144 // For stub device we wait no matter what to avoid flooding with logs. 145 if ((mTestDevice.getIDevice() instanceof StubDevice) 146 || !TestDeviceState.ONLINE.equals(mTestDevice.getDeviceState())) { 147 getRunUtil().sleep(ONLINE_POLL_INTERVAL_MS); 148 } else { 149 CLog.d("Device %s now online.", mTestDevice.getSerialNumber()); 150 break; 151 } 152 } 153 } 154 } 155