Home | History | Annotate | Download | only in device
      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