Home | History | Annotate | Download | only in device
      1 /*
      2  * Copyright (C) 2016 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.CollectingOutputReceiver;
     20 import com.android.ddmlib.IDevice;
     21 import com.android.ddmlib.ShellCommandUnresponsiveException;
     22 import com.android.ddmlib.TimeoutException;
     23 import com.android.tradefed.device.IDeviceManager.IFastbootListener;
     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.ArrayList;
     30 import java.util.Collection;
     31 import java.util.List;
     32 import java.util.concurrent.ExecutionException;
     33 import java.util.concurrent.TimeUnit;
     34 
     35 /**
     36  * Helper class for monitoring the state of a {@link IDevice} with no framework support.
     37  */
     38 public class NativeDeviceStateMonitor implements IDeviceStateMonitor {
     39 
     40     static final String BOOTCOMPLETE_PROP = "dev.bootcomplete";
     41 
     42     private IDevice mDevice;
     43     private TestDeviceState mDeviceState;
     44 
     45     /** the time in ms to wait between 'poll for responsiveness' attempts */
     46     private static final long CHECK_POLL_TIME = 3 * 1000;
     47     protected static final long MAX_CHECK_POLL_TIME = 30 * 1000;
     48     /** the maximum operation time in ms for a 'poll for responsiveness' command */
     49     protected static final int MAX_OP_TIME = 10 * 1000;
     50 
     51     /** The  time in ms to wait for a device to be online. */
     52     private long mDefaultOnlineTimeout = 1 * 60 * 1000;
     53 
     54     /** The  time in ms to wait for a device to available. */
     55     private long mDefaultAvailableTimeout = 6 * 60 * 1000;
     56 
     57     private List<DeviceStateListener> mStateListeners;
     58     private IDeviceManager mMgr;
     59     private final boolean mFastbootEnabled;
     60 
     61     protected static final String PERM_DENIED_ERROR_PATTERN = "Permission denied";
     62 
     63     public NativeDeviceStateMonitor(IDeviceManager mgr, IDevice device,
     64             boolean fastbootEnabled) {
     65         mMgr = mgr;
     66         mDevice = device;
     67         mStateListeners = new ArrayList<DeviceStateListener>();
     68         mDeviceState = TestDeviceState.getStateByDdms(device.getState());
     69         mFastbootEnabled = fastbootEnabled;
     70     }
     71 
     72     /**
     73      * Get the {@link RunUtil} instance to use.
     74      * <p/>
     75      * Exposed for unit testing.
     76      */
     77     IRunUtil getRunUtil() {
     78         return RunUtil.getDefault();
     79     }
     80 
     81     /**
     82      * Set the time in ms to wait for a device to be online in {@link #waitForDeviceOnline()}.
     83      */
     84     @Override
     85     public void setDefaultOnlineTimeout(long timeoutMs) {
     86         mDefaultOnlineTimeout = timeoutMs;
     87     }
     88 
     89     /**
     90      * Set the time in ms to wait for a device to be available in {@link #waitForDeviceAvailable()}.
     91      */
     92     @Override
     93     public void setDefaultAvailableTimeout(long timeoutMs) {
     94         mDefaultAvailableTimeout = timeoutMs;
     95     }
     96 
     97     /**
     98      * {@inheritDoc}
     99      */
    100     @Override
    101     public IDevice waitForDeviceOnline(long waitTime) {
    102         if (waitForDeviceState(TestDeviceState.ONLINE, waitTime)) {
    103             return getIDevice();
    104         }
    105         return null;
    106     }
    107 
    108     /**
    109      * @return {@link IDevice} associate with the state monitor
    110      */
    111     protected IDevice getIDevice() {
    112         synchronized (mDevice) {
    113             return mDevice;
    114         }
    115     }
    116 
    117     /**
    118      * {@inheritDoc}
    119      */
    120     @Override
    121     public String getSerialNumber() {
    122         return getIDevice().getSerialNumber();
    123     }
    124 
    125     /**
    126      * {@inheritDoc}
    127      */
    128     @Override
    129     public IDevice waitForDeviceOnline() {
    130         return waitForDeviceOnline(mDefaultOnlineTimeout);
    131     }
    132 
    133     /**
    134      * {@inheritDoc}
    135      */
    136     @Override
    137     public boolean waitForDeviceNotAvailable(long waitTime) {
    138         IFastbootListener listener = new StubFastbootListener();
    139         if (mFastbootEnabled) {
    140             mMgr.addFastbootListener(listener);
    141         }
    142         boolean result = waitForDeviceState(TestDeviceState.NOT_AVAILABLE, waitTime);
    143         if (mFastbootEnabled) {
    144             mMgr.removeFastbootListener(listener);
    145         }
    146         return result;
    147     }
    148 
    149     /**
    150      * {@inheritDoc}
    151      */
    152     @Override
    153     public boolean waitForDeviceInRecovery(long waitTime) {
    154         return waitForDeviceState(TestDeviceState.RECOVERY, waitTime);
    155     }
    156 
    157     /**
    158      * {@inheritDoc}
    159      */
    160     @Override
    161     public boolean waitForDeviceShell(final long waitTime) {
    162         CLog.i("Waiting %d ms for device %s shell to be responsive", waitTime,
    163                 getSerialNumber());
    164         long startTime = System.currentTimeMillis();
    165         int counter = 1;
    166         while (System.currentTimeMillis() - startTime < waitTime) {
    167             final CollectingOutputReceiver receiver = createOutputReceiver();
    168             final String cmd = "ls /system/bin/adb";
    169             try {
    170                 getIDevice().executeShellCommand(cmd, receiver, MAX_OP_TIME, TimeUnit.MILLISECONDS);
    171                 String output = receiver.getOutput();
    172                 if (output.contains("/system/bin/adb")) {
    173                     return true;
    174                 }
    175             } catch (IOException | AdbCommandRejectedException |
    176                     ShellCommandUnresponsiveException e) {
    177                 CLog.i("%s failed:", cmd);
    178                 CLog.e(e);
    179             } catch (TimeoutException e) {
    180                 CLog.i("%s failed: timeout", cmd);
    181                 CLog.e(e);
    182             }
    183             getRunUtil().sleep(Math.min(getCheckPollTime() * counter, MAX_CHECK_POLL_TIME));
    184             counter++;
    185         }
    186         CLog.w("Device %s shell is unresponsive", getSerialNumber());
    187         return false;
    188     }
    189 
    190     /**
    191      * {@inheritDoc}
    192      */
    193     @Override
    194     public IDevice waitForDeviceAvailable(final long waitTime) {
    195         // A device is currently considered "available" if and only if four events are true:
    196         // 1. Device is online aka visible via DDMS/adb
    197         // 2. Device has dev.bootcomplete flag set
    198         // 3. Device's package manager is responsive (may be inop)
    199         // 4. Device's external storage is mounted
    200         //
    201         // The current implementation waits for each event to occur in sequence.
    202         //
    203         // it will track the currently elapsed time and fail if it is
    204         // greater than waitTime
    205 
    206         long startTime = System.currentTimeMillis();
    207         IDevice device = waitForDeviceOnline(waitTime);
    208         if (device == null) {
    209             return null;
    210         }
    211         long elapsedTime = System.currentTimeMillis() - startTime;
    212         if (!waitForBootComplete(waitTime - elapsedTime)) {
    213             return null;
    214         }
    215         elapsedTime = System.currentTimeMillis() - startTime;
    216         if (!postOnlineCheck(waitTime - elapsedTime)) {
    217             return null;
    218         }
    219         return device;
    220     }
    221 
    222     /**
    223      * {@inheritDoc}
    224      */
    225     @Override
    226     public IDevice waitForDeviceAvailable() {
    227         return waitForDeviceAvailable(mDefaultAvailableTimeout);
    228     }
    229 
    230     /**
    231      * {@inheritDoc}
    232      */
    233     @Override
    234     public boolean waitForBootComplete(final long waitTime) {
    235         CLog.i("Waiting %d ms for device %s boot complete", waitTime, getSerialNumber());
    236         int counter = 1;
    237         long startTime = System.currentTimeMillis();
    238         final String cmd = "getprop " + BOOTCOMPLETE_PROP;
    239         while ((System.currentTimeMillis() - startTime) < waitTime) {
    240             try {
    241                 String bootFlag = getIDevice().getSystemProperty("dev.bootcomplete").get();
    242                 if ("1".equals(bootFlag)) {
    243                     return true;
    244                 }
    245             } catch (InterruptedException e) {
    246                 CLog.i("%s on device %s failed: %s", cmd, getSerialNumber(), e.getMessage());
    247                 // exit the loop for InterruptedException
    248                 break;
    249             } catch (ExecutionException e) {
    250                 CLog.i("%s on device %s failed: %s", cmd, getSerialNumber(), e.getMessage());
    251             }
    252             getRunUtil().sleep(Math.min(getCheckPollTime() * counter, MAX_CHECK_POLL_TIME));
    253             counter++;
    254         }
    255         CLog.w("Device %s did not boot after %d ms", getSerialNumber(), waitTime);
    256         return false;
    257     }
    258 
    259     /**
    260      * Additional checks to be done on an Online device
    261      *
    262      * @param waitTime time in ms to wait before giving up
    263      * @return <code>true</code> if checks are successful before waitTime expires.
    264      * <code>false</code> otherwise
    265      */
    266     protected boolean postOnlineCheck(final long waitTime) {
    267         return waitForStoreMount(waitTime);
    268     }
    269 
    270     /**
    271      * Waits for the device's external store to be mounted.
    272      *
    273      * @param waitTime time in ms to wait before giving up
    274      * @return <code>true</code> if external store is mounted before waitTime expires.
    275      * <code>false</code> otherwise
    276      */
    277     protected boolean waitForStoreMount(final long waitTime) {
    278         CLog.i("Waiting %d ms for device %s external store", waitTime, getSerialNumber());
    279         long startTime = System.currentTimeMillis();
    280         int counter = 1;
    281         while (System.currentTimeMillis() - startTime < waitTime) {
    282             final CollectingOutputReceiver receiver = createOutputReceiver();
    283             final CollectingOutputReceiver bitBucket = new CollectingOutputReceiver();
    284             final long number = getCurrentTime();
    285             final String externalStore = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
    286 
    287             final String testFile = String.format("'%s/%d'", externalStore, number);
    288             final String testString = String.format("number %d one", number);
    289             final String writeCmd = String.format("echo '%s' > %s", testString, testFile);
    290             final String checkCmd = String.format("cat %s", testFile);
    291             final String cleanupCmd = String.format("rm %s", testFile);
    292             String cmd = null;
    293             if (externalStore != null) {
    294                 try {
    295                     cmd = writeCmd;
    296                     getIDevice().executeShellCommand(writeCmd, bitBucket,
    297                             MAX_OP_TIME, TimeUnit.MILLISECONDS);
    298                     cmd = checkCmd;
    299                     getIDevice().executeShellCommand(checkCmd, receiver,
    300                             MAX_OP_TIME, TimeUnit.MILLISECONDS);
    301                     cmd = cleanupCmd;
    302                     getIDevice().executeShellCommand(cleanupCmd, bitBucket,
    303                             MAX_OP_TIME, TimeUnit.MILLISECONDS);
    304 
    305                     String output = receiver.getOutput();
    306                     CLog.v("%s returned %s", checkCmd, output);
    307                     if (output.contains(testString)) {
    308                         return true;
    309                     } else if (output.contains(PERM_DENIED_ERROR_PATTERN)) {
    310                         CLog.w("Device %s mount check returned Permision Denied, "
    311                                 + "issue with mounting.", getSerialNumber());
    312                         return false;
    313                     }
    314                 } catch (IOException | AdbCommandRejectedException |
    315                         ShellCommandUnresponsiveException e) {
    316                     CLog.i("%s on device %s failed:", cmd, getSerialNumber());
    317                     CLog.e(e);
    318                 } catch (TimeoutException e) {
    319                     CLog.i("%s on device %s failed: timeout", cmd, getSerialNumber());
    320                     CLog.e(e);
    321                 }
    322             } else {
    323                 CLog.w("Failed to get external store mount point for %s", getSerialNumber());
    324             }
    325             getRunUtil().sleep(Math.min(getCheckPollTime() * counter, MAX_CHECK_POLL_TIME));
    326             counter++;
    327         }
    328         CLog.w("Device %s external storage is not mounted after %d ms",
    329                 getSerialNumber(), waitTime);
    330         return false;
    331     }
    332 
    333     /**
    334      * {@inheritDoc}
    335      */
    336     @Override
    337     public String getMountPoint(String mountName) {
    338         String mountPoint = getIDevice().getMountPoint(mountName);
    339         if (mountPoint != null) {
    340             return mountPoint;
    341         }
    342         // cached mount point is null - try querying directly
    343         CollectingOutputReceiver receiver = createOutputReceiver();
    344         try {
    345             getIDevice().executeShellCommand("echo $" + mountName, receiver);
    346             return receiver.getOutput().trim();
    347         } catch (IOException e) {
    348             return null;
    349         } catch (TimeoutException e) {
    350             return null;
    351         } catch (AdbCommandRejectedException e) {
    352             return null;
    353         } catch (ShellCommandUnresponsiveException e) {
    354             return null;
    355         }
    356     }
    357 
    358     /**
    359      * {@inheritDoc}
    360      */
    361     @Override
    362     public TestDeviceState getDeviceState() {
    363         return mDeviceState;
    364     }
    365 
    366     /**
    367      * {@inheritDoc}
    368      */
    369     @Override
    370     public boolean waitForDeviceBootloader(long time) {
    371         if (!mFastbootEnabled) {
    372             return false;
    373         }
    374         long startTime = System.currentTimeMillis();
    375         // ensure fastboot state is updated at least once
    376         waitForDeviceBootloaderStateUpdate();
    377         long elapsedTime = System.currentTimeMillis() - startTime;
    378         IFastbootListener listener = new StubFastbootListener();
    379         mMgr.addFastbootListener(listener);
    380         long waitTime = time - elapsedTime;
    381         if (waitTime < 0) {
    382             // wait at least 200ms
    383             waitTime = 200;
    384         }
    385         boolean result =  waitForDeviceState(TestDeviceState.FASTBOOT, waitTime);
    386         mMgr.removeFastbootListener(listener);
    387         return result;
    388     }
    389 
    390     @Override
    391     public void waitForDeviceBootloaderStateUpdate() {
    392         if (!mFastbootEnabled) {
    393             return;
    394         }
    395         IFastbootListener listener = new NotifyFastbootListener();
    396         synchronized (listener) {
    397             mMgr.addFastbootListener(listener);
    398             try {
    399                 listener.wait();
    400             } catch (InterruptedException e) {
    401                 CLog.w("wait for device bootloader state update interrupted");
    402                 throw new RuntimeException(e);
    403             } finally {
    404                 mMgr.removeFastbootListener(listener);
    405             }
    406         }
    407     }
    408 
    409     private boolean waitForDeviceState(TestDeviceState state, long time) {
    410         String deviceSerial = getSerialNumber();
    411         if (getDeviceState() == state) {
    412             CLog.i("Device %s is already %s", deviceSerial, state);
    413             return true;
    414         }
    415         CLog.i("Waiting for device %s to be %s; it is currently %s...", deviceSerial,
    416                 state, getDeviceState());
    417         DeviceStateListener listener = new DeviceStateListener(state);
    418         addDeviceStateListener(listener);
    419         synchronized (listener) {
    420             try {
    421                 listener.wait(time);
    422             } catch (InterruptedException e) {
    423                 CLog.w("wait for device state interrupted");
    424                 throw new RuntimeException(e);
    425             } finally {
    426                 removeDeviceStateListener(listener);
    427             }
    428         }
    429         return getDeviceState().equals(state);
    430     }
    431 
    432     /**
    433      * @param listener
    434      */
    435     private void removeDeviceStateListener(DeviceStateListener listener) {
    436         synchronized (mStateListeners) {
    437             mStateListeners.remove(listener);
    438         }
    439     }
    440 
    441     /**
    442      * @param listener
    443      */
    444     private void addDeviceStateListener(DeviceStateListener listener) {
    445         synchronized (mStateListeners) {
    446             mStateListeners.add(listener);
    447         }
    448     }
    449 
    450     /**
    451      * {@inheritDoc}
    452      */
    453     @Override
    454     public void setState(TestDeviceState deviceState) {
    455         mDeviceState = deviceState;
    456         // create a copy of listeners to prevent holding mStateListeners lock when notifying
    457         // and to protect from list modification when iterating
    458         Collection<DeviceStateListener> listenerCopy = new ArrayList<DeviceStateListener>(
    459                 mStateListeners.size());
    460         synchronized (mStateListeners) {
    461             listenerCopy.addAll(mStateListeners);
    462         }
    463         for (DeviceStateListener listener: listenerCopy) {
    464             listener.stateChanged(deviceState);
    465         }
    466     }
    467 
    468     @Override
    469     public void setIDevice(IDevice newDevice) {
    470         IDevice currentDevice = mDevice;
    471         if (!getIDevice().equals(newDevice)) {
    472             synchronized (currentDevice) {
    473                 mDevice = newDevice;
    474             }
    475         }
    476     }
    477 
    478     private static class DeviceStateListener {
    479         private final TestDeviceState mExpectedState;
    480 
    481         public DeviceStateListener(TestDeviceState expectedState) {
    482             mExpectedState = expectedState;
    483         }
    484 
    485         public void stateChanged(TestDeviceState newState) {
    486             if (mExpectedState.equals(newState)) {
    487                 synchronized (this) {
    488                     notify();
    489                 }
    490             }
    491         }
    492     }
    493 
    494     /**
    495      * An empty implementation of {@link IFastbootListener}
    496      */
    497     private static class StubFastbootListener implements IFastbootListener {
    498         @Override
    499         public void stateUpdated() {
    500             // ignore
    501         }
    502     }
    503 
    504     /**
    505      * A {@link IFastbootListener} that notifies when a status update has been received.
    506      */
    507     private static class NotifyFastbootListener implements IFastbootListener {
    508         @Override
    509         public void stateUpdated() {
    510             synchronized (this) {
    511                 notify();
    512             }
    513         }
    514     }
    515 
    516     /**
    517      * {@inheritDoc}
    518      */
    519     @Override
    520     public boolean isAdbTcp() {
    521         return mDevice.getSerialNumber().contains(":");
    522     }
    523 
    524     /**
    525      * Exposed for testing
    526      * @return {@link CollectingOutputReceiver}
    527      */
    528     protected CollectingOutputReceiver createOutputReceiver() {
    529         return new CollectingOutputReceiver();
    530     }
    531 
    532     /**
    533      * Exposed for testing
    534      */
    535     protected long getCheckPollTime() {
    536         return CHECK_POLL_TIME;
    537     }
    538 
    539     /**
    540      * Exposed for testing
    541      */
    542     protected long getCurrentTime() {
    543         return System.currentTimeMillis();
    544     }
    545 }
    546