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.FileListingService;
     20 import com.android.ddmlib.FileListingService.FileEntry;
     21 import com.android.ddmlib.IDevice;
     22 import com.android.ddmlib.IDevice.DeviceState;
     23 import com.android.ddmlib.IShellOutputReceiver;
     24 import com.android.ddmlib.InstallException;
     25 import com.android.ddmlib.NullOutputReceiver;
     26 import com.android.ddmlib.ShellCommandUnresponsiveException;
     27 import com.android.ddmlib.SyncException;
     28 import com.android.ddmlib.SyncException.SyncError;
     29 import com.android.ddmlib.SyncService;
     30 import com.android.ddmlib.TimeoutException;
     31 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
     32 import com.android.ddmlib.testrunner.ITestRunListener;
     33 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
     34 import com.android.tradefed.build.IBuildInfo;
     35 import com.android.tradefed.command.remote.DeviceDescriptor;
     36 import com.android.tradefed.log.ITestLogger;
     37 import com.android.tradefed.log.LogUtil.CLog;
     38 import com.android.tradefed.result.ByteArrayInputStreamSource;
     39 import com.android.tradefed.result.FileInputStreamSource;
     40 import com.android.tradefed.result.InputStreamSource;
     41 import com.android.tradefed.result.LogDataType;
     42 import com.android.tradefed.result.SnapshotInputStreamSource;
     43 import com.android.tradefed.result.StubTestRunListener;
     44 import com.android.tradefed.targetprep.TargetSetupError;
     45 import com.android.tradefed.util.ArrayUtil;
     46 import com.android.tradefed.util.Bugreport;
     47 import com.android.tradefed.util.CommandResult;
     48 import com.android.tradefed.util.CommandStatus;
     49 import com.android.tradefed.util.FileUtil;
     50 import com.android.tradefed.util.IRunUtil;
     51 import com.android.tradefed.util.KeyguardControllerState;
     52 import com.android.tradefed.util.ProcessInfo;
     53 import com.android.tradefed.util.PsParser;
     54 import com.android.tradefed.util.RunUtil;
     55 import com.android.tradefed.util.SizeLimitedOutputStream;
     56 import com.android.tradefed.util.StreamUtil;
     57 import com.android.tradefed.util.ZipUtil2;
     58 
     59 import org.apache.commons.compress.archivers.zip.ZipFile;
     60 
     61 import java.io.ByteArrayInputStream;
     62 import java.io.File;
     63 import java.io.FilenameFilter;
     64 import java.io.IOException;
     65 import java.text.ParseException;
     66 import java.text.SimpleDateFormat;
     67 import java.util.ArrayList;
     68 import java.util.Arrays;
     69 import java.util.Collection;
     70 import java.util.Date;
     71 import java.util.List;
     72 import java.util.Map;
     73 import java.util.Random;
     74 import java.util.Set;
     75 import java.util.TimeZone;
     76 import java.util.concurrent.ExecutionException;
     77 import java.util.concurrent.TimeUnit;
     78 import java.util.concurrent.locks.ReentrantLock;
     79 import java.util.regex.Matcher;
     80 import java.util.regex.Pattern;
     81 
     82 import javax.annotation.concurrent.GuardedBy;
     83 
     84 /**
     85  * Default implementation of a {@link ITestDevice}
     86  * Non-full stack android devices.
     87  */
     88 public class NativeDevice implements IManagedTestDevice {
     89 
     90     /**
     91      * Allow pauses of up to 2 minutes while receiving bugreport.
     92      * <p/>
     93      * Note that dumpsys may pause up to a minute while waiting for unresponsive components.
     94      * It still should bail after that minute, if it will ever terminate on its own.
     95      */
     96     private static final int BUGREPORT_TIMEOUT = 2 * 60 * 1000;
     97     /**
     98      * Allow a little more time for bugreportz because there are extra steps.
     99      */
    100     private static final int BUGREPORTZ_TIMEOUT = 5 * 60 * 1000;
    101     private static final String BUGREPORT_CMD = "bugreport";
    102     private static final String BUGREPORTZ_CMD = "bugreportz";
    103     private static final String BUGREPORTZ_TMP_PATH = "/bugreports/";
    104 
    105     /**
    106      * Allow up to 2 minutes to receives the full logcat dump.
    107      */
    108     private static final int LOGCAT_DUMP_TIMEOUT = 2 * 60 * 1000;
    109 
    110     /** the default number of command retry attempts to perform */
    111     protected static final int MAX_RETRY_ATTEMPTS = 2;
    112 
    113     /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value **/
    114     protected static final int INVALID_USER_ID = -10000;
    115 
    116     /** regex to match input dispatch readiness line **/
    117     static final Pattern INPUT_DISPATCH_STATE_REGEX =
    118             Pattern.compile("DispatchEnabled:\\s?([01])");
    119     /** regex to match build signing key type */
    120     private static final Pattern KEYS_PATTERN = Pattern.compile("^.*-keys$");
    121     private static final Pattern DF_PATTERN = Pattern.compile(
    122             //Fs 1K-blks Used    Available Use%      Mounted on
    123             "^/\\S+\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+\\d+%\\s+/\\S*$", Pattern.MULTILINE);
    124     private static final Pattern BUGREPORTZ_RESPONSE_PATTERN = Pattern.compile("(OK:)(.*)");
    125 
    126     protected static final long MAX_HOST_DEVICE_TIME_OFFSET = 5 * 1000;
    127 
    128     /** The password for encrypting and decrypting the device. */
    129     private static final String ENCRYPTION_PASSWORD = "android";
    130     /** Encrypting with inplace can take up to 2 hours. */
    131     private static final int ENCRYPTION_INPLACE_TIMEOUT_MIN = 2 * 60;
    132     /** Encrypting with wipe can take up to 20 minutes. */
    133     private static final long ENCRYPTION_WIPE_TIMEOUT_MIN = 20;
    134     /** Beginning of the string returned by vdc for "vdc cryptfs enablecrypto". */
    135     private static final String ENCRYPTION_SUPPORTED_CODE = "500";
    136     /** Message in the string returned by vdc for "vdc cryptfs enablecrypto". */
    137     private static final String ENCRYPTION_SUPPORTED_USAGE = "Usage: ";
    138 
    139     /** The time in ms to wait before starting logcat for a device */
    140     private int mLogStartDelay = 5*1000;
    141 
    142     /** The time in ms to wait for a device to become unavailable. Should usually be short */
    143     private static final int DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000;
    144     /** The time in ms to wait for a recovery that we skip because of the NONE mode */
    145     static final int NONE_RECOVERY_MODE_DELAY = 1000;
    146 
    147     static final String BUILD_ID_PROP = "ro.build.version.incremental";
    148     private static final String PRODUCT_NAME_PROP = "ro.product.name";
    149     private static final String BUILD_TYPE_PROP = "ro.build.type";
    150     private static final String BUILD_ALIAS_PROP = "ro.build.id";
    151     private static final String BUILD_FLAVOR = "ro.build.flavor";
    152     private static final String HEADLESS_PROP = "ro.build.headless";
    153     static final String BUILD_CODENAME_PROP = "ro.build.version.codename";
    154     static final String BUILD_TAGS = "ro.build.tags";
    155     private static final String PS_COMMAND = "ps -A || ps";
    156 
    157     private static final String SIM_STATE_PROP = "gsm.sim.state";
    158     private static final String SIM_OPERATOR_PROP = "gsm.operator.alpha";
    159 
    160     static final String MAC_ADDRESS_PATTERN = "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}";
    161     static final String MAC_ADDRESS_COMMAND = "cat /sys/class/net/wlan0/address";
    162 
    163 
    164     /** The network monitoring interval in ms. */
    165     private static final int NETWORK_MONITOR_INTERVAL = 10 * 1000;
    166 
    167     /** Wifi reconnect check interval in ms. */
    168     private static final int WIFI_RECONNECT_CHECK_INTERVAL = 1 * 1000;
    169 
    170     /** Wifi reconnect timeout in ms. */
    171     private static final int WIFI_RECONNECT_TIMEOUT = 60 * 1000;
    172 
    173     /** The time in ms to wait for a command to complete. */
    174     private int mCmdTimeout = 2 * 60 * 1000;
    175     /** The time in ms to wait for a 'long' command to complete. */
    176     private long mLongCmdTimeout = 25 * 60 * 1000;
    177 
    178     private IDevice mIDevice;
    179     private IDeviceRecovery mRecovery = new WaitDeviceRecovery();
    180     protected final IDeviceStateMonitor mStateMonitor;
    181     private TestDeviceState mState = TestDeviceState.ONLINE;
    182     private final ReentrantLock mFastbootLock = new ReentrantLock();
    183     private LogcatReceiver mLogcatReceiver;
    184     private boolean mFastbootEnabled = true;
    185     private String mFastbootPath = "fastboot";
    186 
    187     protected TestDeviceOptions mOptions = new TestDeviceOptions();
    188     private Process mEmulatorProcess;
    189     private SizeLimitedOutputStream mEmulatorOutput;
    190 
    191     private RecoveryMode mRecoveryMode = RecoveryMode.AVAILABLE;
    192 
    193     private Boolean mIsEncryptionSupported = null;
    194     private ReentrantLock mAllocationStateLock = new ReentrantLock();
    195     @GuardedBy("mAllocationStateLock")
    196     private DeviceAllocationState mAllocationState = DeviceAllocationState.Unknown;
    197     private IDeviceMonitor mAllocationMonitor = null;
    198 
    199     private String mLastConnectedWifiSsid = null;
    200     private String mLastConnectedWifiPsk = null;
    201     private boolean mNetworkMonitorEnabled = false;
    202 
    203     /**
    204      * Interface for a generic device communication attempt.
    205      */
    206     abstract interface DeviceAction {
    207 
    208         /**
    209          * Execute the device operation.
    210          *
    211          * @return <code>true</code> if operation is performed successfully, <code>false</code>
    212          *         otherwise
    213          * @throws IOException, TimeoutException, AdbCommandRejectedException,
    214          *         ShellCommandUnresponsiveException, InstallException,
    215          *         SyncException if operation terminated abnormally
    216          */
    217         public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
    218                 ShellCommandUnresponsiveException, InstallException, SyncException;
    219     }
    220 
    221     /**
    222      * A {@link DeviceAction} for running a OS 'adb ....' command.
    223      */
    224     protected class AdbAction implements DeviceAction {
    225         /** the output from the command */
    226         String mOutput = null;
    227         private String[] mCmd;
    228 
    229         AdbAction(String[] cmd) {
    230             mCmd = cmd;
    231         }
    232 
    233         @Override
    234         public boolean run() throws TimeoutException, IOException {
    235             CommandResult result = getRunUtil().runTimedCmd(getCommandTimeout(), mCmd);
    236             // TODO: how to determine device not present with command failing for other reasons
    237             if (result.getStatus() == CommandStatus.EXCEPTION) {
    238                 throw new IOException();
    239             } else if (result.getStatus() == CommandStatus.TIMED_OUT) {
    240                 throw new TimeoutException();
    241             } else if (result.getStatus() == CommandStatus.FAILED) {
    242                 // interpret as communication failure
    243                 throw new IOException();
    244             }
    245             mOutput = result.getStdout();
    246             return true;
    247         }
    248     }
    249 
    250     /**
    251      * Creates a {@link TestDevice}.
    252      *
    253      * @param device the associated {@link IDevice}
    254      * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use
    255      * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes.
    256      *            Can be null
    257      */
    258     public NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor,
    259             IDeviceMonitor allocationMonitor) {
    260         throwIfNull(device);
    261         throwIfNull(stateMonitor);
    262         mIDevice = device;
    263         mStateMonitor = stateMonitor;
    264         mAllocationMonitor = allocationMonitor;
    265     }
    266 
    267     /**
    268      * Get the {@link RunUtil} instance to use.
    269      * <p/>
    270      * Exposed for unit testing.
    271      */
    272     protected IRunUtil getRunUtil() {
    273         return RunUtil.getDefault();
    274     }
    275 
    276     /**
    277      * {@inheritDoc}
    278      */
    279     @Override
    280     public void setOptions(TestDeviceOptions options) {
    281         throwIfNull(options);
    282         mOptions = options;
    283         mStateMonitor.setDefaultOnlineTimeout(options.getOnlineTimeout());
    284         mStateMonitor.setDefaultAvailableTimeout(options.getAvailableTimeout());
    285     }
    286 
    287     /**
    288      * Sets the max size of a tmp logcat file.
    289      *
    290      * @param size max byte size of tmp file
    291      */
    292     void setTmpLogcatSize(long size) {
    293         mOptions.setMaxLogcatDataSize(size);
    294     }
    295 
    296     /**
    297      * Sets the time in ms to wait before starting logcat capture for a online device.
    298      *
    299      * @param delay the delay in ms
    300      */
    301     protected void setLogStartDelay(int delay) {
    302         mLogStartDelay = delay;
    303     }
    304 
    305     /**
    306      * {@inheritDoc}
    307      */
    308     @Override
    309     public IDevice getIDevice() {
    310         synchronized (mIDevice) {
    311             return mIDevice;
    312         }
    313     }
    314 
    315     /**
    316      * {@inheritDoc}
    317      */
    318     @Override
    319     public void setIDevice(IDevice newDevice) {
    320         throwIfNull(newDevice);
    321         IDevice currentDevice = mIDevice;
    322         if (!getIDevice().equals(newDevice)) {
    323             synchronized (currentDevice) {
    324                 mIDevice = newDevice;
    325             }
    326             mStateMonitor.setIDevice(mIDevice);
    327         }
    328     }
    329 
    330     /**
    331      * {@inheritDoc}
    332      */
    333     @Override
    334     public String getSerialNumber() {
    335         return getIDevice().getSerialNumber();
    336     }
    337 
    338     private boolean nullOrEmpty(String string) {
    339         return string == null || string.isEmpty();
    340     }
    341 
    342     /**
    343      * Fetch a device property, from the ddmlib cache by default, and falling back to either
    344      * `adb shell getprop` or `fastboot getvar` depending on whether the device is in Fastboot or
    345      * not.
    346      *
    347      * @param propName The name of the device property as returned by `adb shell getprop`
    348      * @param fastbootVar The name of the equivalent fastboot variable to query. if {@code null},
    349      * fastboot query will not be attempted
    350      * @param description A simple description of the variable.  First letter should be capitalized.
    351      * @return A string, possibly {@code null} or empty, containing the value of the given property
    352      */
    353     private String internalGetProperty(String propName, String fastbootVar, String description)
    354             throws DeviceNotAvailableException, UnsupportedOperationException {
    355         String propValue = getIDevice().getProperty(propName);
    356         if (propValue != null) {
    357             return propValue;
    358         } else if (TestDeviceState.FASTBOOT.equals(getDeviceState()) &&
    359                 fastbootVar != null) {
    360             CLog.i("%s for device %s is null, re-querying in fastboot", description,
    361                     getSerialNumber());
    362             return getFastbootVariable(fastbootVar);
    363         } else {
    364             CLog.d("property collection for device %s is null, re-querying for prop %s",
    365                     getSerialNumber(), description);
    366             return getProperty(propName);
    367         }
    368     }
    369 
    370     /**
    371      * {@inheritDoc}
    372      */
    373     @Override
    374     public String getProperty(final String name) throws DeviceNotAvailableException {
    375         if (!DeviceState.ONLINE.equals(getIDevice().getState())) {
    376             CLog.d("Device %s is not online cannot get property %s.", getSerialNumber(), name);
    377             return null;
    378         }
    379         final String[] result = new String[1];
    380         DeviceAction propAction = new DeviceAction() {
    381 
    382             @Override
    383             public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
    384                     ShellCommandUnresponsiveException, InstallException, SyncException {
    385                 try {
    386                     result[0] = getIDevice().getSystemProperty(name).get();
    387                 } catch (InterruptedException | ExecutionException e) {
    388                     // getProperty will stash the original exception inside
    389                     // ExecutionException.getCause
    390                     // throw the specific original exception if available in case TF ever does
    391                     // specific handling for different exceptions
    392                     if (e.getCause() instanceof IOException) {
    393                         throw (IOException)e.getCause();
    394                     } else if (e.getCause() instanceof TimeoutException) {
    395                         throw (TimeoutException)e.getCause();
    396                     } else if (e.getCause() instanceof AdbCommandRejectedException) {
    397                         throw (AdbCommandRejectedException)e.getCause();
    398                     } else if (e.getCause() instanceof ShellCommandUnresponsiveException) {
    399                         throw (ShellCommandUnresponsiveException)e.getCause();
    400                     }
    401                     else {
    402                         throw new IOException(e);
    403                     }
    404                 }
    405                 return true;
    406             }
    407 
    408         };
    409         performDeviceAction("getprop", propAction, MAX_RETRY_ATTEMPTS);
    410         return result[0];
    411     }
    412 
    413     /**
    414      * {@inheritDoc}
    415      */
    416     @Override
    417     public String getBootloaderVersion() throws UnsupportedOperationException,
    418             DeviceNotAvailableException {
    419         return internalGetProperty("ro.bootloader", "version-bootloader", "Bootloader");
    420     }
    421 
    422     @Override
    423     public String getBasebandVersion() throws DeviceNotAvailableException {
    424         return internalGetProperty("gsm.version.baseband", "version-baseband", "Baseband");
    425     }
    426 
    427     /**
    428      * {@inheritDoc}
    429      */
    430     @Override
    431     public String getProductType() throws DeviceNotAvailableException {
    432         return internalGetProductType(MAX_RETRY_ATTEMPTS);
    433     }
    434 
    435     /**
    436      * {@link #getProductType()}
    437      *
    438      * @param retryAttempts The number of times to try calling {@link #recoverDevice()} if the
    439      *        device's product type cannot be found.
    440      */
    441     private String internalGetProductType(int retryAttempts) throws DeviceNotAvailableException {
    442         String productType = internalGetProperty("ro.hardware", "product", "Product type");
    443 
    444         // Things will likely break if we don't have a valid product type.  Try recovery (in case
    445         // the device is only partially booted for some reason), and if that doesn't help, bail.
    446         if (nullOrEmpty(productType)) {
    447             if (retryAttempts > 0) {
    448                 recoverDevice();
    449                 productType = internalGetProductType(retryAttempts - 1);
    450             }
    451 
    452             if (nullOrEmpty(productType)) {
    453                 throw new DeviceNotAvailableException(String.format(
    454                         "Could not determine product type for device %s.", getSerialNumber()),
    455                         getSerialNumber());
    456             }
    457         }
    458 
    459         return productType;
    460     }
    461 
    462     /**
    463      * {@inheritDoc}
    464      */
    465     @Override
    466     public String getFastbootProductType()
    467             throws DeviceNotAvailableException, UnsupportedOperationException {
    468         return getFastbootVariable("product");
    469     }
    470 
    471     /**
    472      * {@inheritDoc}
    473      */
    474     @Override
    475     public String getProductVariant() throws DeviceNotAvailableException {
    476         return internalGetProperty("ro.product.device", "variant", "Product variant");
    477     }
    478 
    479     /**
    480      * {@inheritDoc}
    481      */
    482     @Override
    483     public String getFastbootProductVariant()
    484             throws DeviceNotAvailableException, UnsupportedOperationException {
    485         return getFastbootVariable("variant");
    486     }
    487 
    488     private String getFastbootVariable(String variableName)
    489             throws DeviceNotAvailableException, UnsupportedOperationException {
    490         CommandResult result = executeFastbootCommand("getvar", variableName);
    491         if (result.getStatus() == CommandStatus.SUCCESS) {
    492             Pattern fastbootProductPattern = Pattern.compile(variableName + ":\\s(.*)\\s");
    493             // fastboot is weird, and may dump the output on stderr instead of stdout
    494             String resultText = result.getStdout();
    495             if (resultText == null || resultText.length() < 1) {
    496                 resultText = result.getStderr();
    497             }
    498             Matcher matcher = fastbootProductPattern.matcher(resultText);
    499             if (matcher.find()) {
    500                 return matcher.group(1);
    501             }
    502         }
    503         return null;
    504     }
    505 
    506     /**
    507      * {@inheritDoc}
    508      */
    509     @Override
    510     public String getBuildAlias() throws DeviceNotAvailableException {
    511         String alias = getProperty(BUILD_ALIAS_PROP);
    512         if (alias == null || alias.isEmpty()) {
    513             return getBuildId();
    514         }
    515         return alias;
    516     }
    517 
    518     /**
    519      * {@inheritDoc}
    520      */
    521     @Override
    522     public String getBuildId() throws DeviceNotAvailableException {
    523         String bid = getProperty(BUILD_ID_PROP);
    524         if (bid == null) {
    525             CLog.w("Could not get device %s build id.", getSerialNumber());
    526             return IBuildInfo.UNKNOWN_BUILD_ID;
    527         }
    528         return bid;
    529     }
    530 
    531     /**
    532      * {@inheritDoc}
    533      */
    534     @Override
    535     public String getBuildFlavor() throws DeviceNotAvailableException {
    536         String buildFlavor = getProperty(BUILD_FLAVOR);
    537         if (buildFlavor != null && !buildFlavor.isEmpty()) {
    538             return buildFlavor;
    539         }
    540         String productName = getProperty(PRODUCT_NAME_PROP);
    541         String buildType = getProperty(BUILD_TYPE_PROP);
    542         if (productName == null || buildType == null) {
    543             CLog.w("Could not get device %s build flavor.", getSerialNumber());
    544             return null;
    545         }
    546         return String.format("%s-%s", productName, buildType);
    547     }
    548 
    549     /**
    550      * {@inheritDoc}
    551      */
    552     @Override
    553     public void executeShellCommand(final String command, final IShellOutputReceiver receiver)
    554             throws DeviceNotAvailableException {
    555         DeviceAction action = new DeviceAction() {
    556             @Override
    557             public boolean run() throws TimeoutException, IOException,
    558                     AdbCommandRejectedException, ShellCommandUnresponsiveException {
    559                 getIDevice().executeShellCommand(command, receiver,
    560                         mCmdTimeout, TimeUnit.MILLISECONDS);
    561                 return true;
    562             }
    563         };
    564         performDeviceAction(String.format("shell %s", command), action, MAX_RETRY_ATTEMPTS);
    565     }
    566 
    567     /**
    568      * {@inheritDoc}
    569      */
    570     @Override
    571     public void executeShellCommand(final String command, final IShellOutputReceiver receiver,
    572             final long maxTimeToOutputShellResponse, final TimeUnit timeUnit,
    573             final int retryAttempts) throws DeviceNotAvailableException {
    574         DeviceAction action = new DeviceAction() {
    575             @Override
    576             public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
    577                     ShellCommandUnresponsiveException {
    578                 getIDevice().executeShellCommand(command, receiver,
    579                         maxTimeToOutputShellResponse, timeUnit);
    580                 return true;
    581             }
    582         };
    583         performDeviceAction(String.format("shell %s", command), action, retryAttempts);
    584     }
    585 
    586     /**
    587      * {@inheritDoc}
    588      */
    589     @Override
    590     public String executeShellCommand(String command) throws DeviceNotAvailableException {
    591         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
    592         executeShellCommand(command, receiver);
    593         String output = receiver.getOutput();
    594         CLog.v("%s on %s returned %s", command, getSerialNumber(), output);
    595         return output;
    596     }
    597 
    598     /**
    599      * {@inheritDoc}
    600      */
    601     @Override
    602     public boolean runInstrumentationTests(final IRemoteAndroidTestRunner runner,
    603             final Collection<ITestRunListener> listeners) throws DeviceNotAvailableException {
    604         RunFailureListener failureListener = new RunFailureListener();
    605         listeners.add(failureListener);
    606         DeviceAction runTestsAction = new DeviceAction() {
    607             @Override
    608             public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
    609                     ShellCommandUnresponsiveException, InstallException, SyncException {
    610                 runner.run(listeners);
    611                 return true;
    612             }
    613 
    614         };
    615         boolean result = performDeviceAction(String.format("run %s instrumentation tests",
    616                 runner.getPackageName()), runTestsAction, 0);
    617         if (failureListener.isRunFailure()) {
    618             // run failed, might be system crash. Ensure device is up
    619             if (mStateMonitor.waitForDeviceAvailable(5 * 1000) == null) {
    620                 // device isn't up, recover
    621                 recoverDevice();
    622             }
    623         }
    624         return result;
    625     }
    626 
    627     /**
    628      * {@inheritDoc}
    629      */
    630     @Override
    631     public boolean runInstrumentationTestsAsUser(final IRemoteAndroidTestRunner runner,
    632             int userId, final Collection<ITestRunListener> listeners)
    633                     throws DeviceNotAvailableException {
    634         String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
    635         boolean result = runInstrumentationTests(runner, listeners);
    636         resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
    637         return result;
    638     }
    639 
    640     /**
    641      * Helper method to add user run time option to {@link RemoteAndroidTestRunner}
    642      *
    643      * @param runner {@link IRemoteAndroidTestRunner}
    644      * @param userId the integer of the user id to run as.
    645      * @return original run time options.
    646      */
    647     private String appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId) {
    648         if (runner instanceof RemoteAndroidTestRunner) {
    649             String original = ((RemoteAndroidTestRunner) runner).getRunOptions();
    650             String userRunTimeOption = String.format("--user %s", Integer.toString(userId));
    651             ((RemoteAndroidTestRunner) runner).setRunOptions(userRunTimeOption);
    652             return original;
    653         } else {
    654             throw new IllegalStateException(String.format("%s runner does not support multi-user",
    655                     runner.getClass().getName()));
    656         }
    657     }
    658 
    659     /**
    660      * Helper method to reset the run time options to {@link RemoteAndroidTestRunner}
    661      *
    662      * @param runner {@link IRemoteAndroidTestRunner}
    663      * @param oldRunTimeOptions
    664      */
    665     private void resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner,
    666             String oldRunTimeOptions) {
    667         if (runner instanceof RemoteAndroidTestRunner) {
    668             if (oldRunTimeOptions != null) {
    669                 ((RemoteAndroidTestRunner) runner).setRunOptions(oldRunTimeOptions);
    670             }
    671         } else {
    672             throw new IllegalStateException(String.format("%s runner does not support multi-user",
    673                     runner.getClass().getName()));
    674         }
    675     }
    676 
    677     private static class RunFailureListener extends StubTestRunListener {
    678         private boolean mIsRunFailure = false;
    679 
    680         @Override
    681         public void testRunFailed(String message) {
    682             mIsRunFailure = true;
    683         }
    684 
    685         public boolean isRunFailure() {
    686             return mIsRunFailure;
    687         }
    688     }
    689 
    690     /**
    691      * {@inheritDoc}
    692      */
    693     @Override
    694     public boolean runInstrumentationTests(IRemoteAndroidTestRunner runner,
    695             ITestRunListener... listeners) throws DeviceNotAvailableException {
    696         List<ITestRunListener> listenerList = new ArrayList<>();
    697         listenerList.addAll(Arrays.asList(listeners));
    698         return runInstrumentationTests(runner, listenerList);
    699     }
    700 
    701     /**
    702      * {@inheritDoc}
    703      */
    704     @Override
    705     public boolean runInstrumentationTestsAsUser(IRemoteAndroidTestRunner runner, int userId,
    706             ITestRunListener... listeners) throws DeviceNotAvailableException {
    707         String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
    708         boolean result = runInstrumentationTests(runner, listeners);
    709         resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
    710         return result;
    711     }
    712 
    713     /**
    714      * {@inheritDoc}
    715      */
    716     @Override
    717     public boolean isRuntimePermissionSupported() throws DeviceNotAvailableException {
    718         return getApiLevel() > 22;
    719     }
    720 
    721     /**
    722      * helper method to throw exception if runtime permission isn't supported
    723      * @throws DeviceNotAvailableException
    724      */
    725     protected void ensureRuntimePermissionSupported() throws DeviceNotAvailableException {
    726         boolean runtimePermissionSupported = isRuntimePermissionSupported();
    727         if (!runtimePermissionSupported) {
    728             throw new UnsupportedOperationException(
    729                     "platform on device does not support runtime permission granting!");
    730         }
    731     }
    732 
    733     /**
    734      * {@inheritDoc}
    735      */
    736     @Override
    737     public String installPackage(final File packageFile, final boolean reinstall,
    738             final String... extraArgs) throws DeviceNotAvailableException {
    739         throw new UnsupportedOperationException("No support for Package Manager's features");
    740     }
    741 
    742     /**
    743      * {@inheritDoc}
    744      */
    745     @Override
    746     public String installPackage(File packageFile, boolean reinstall, boolean grantPermissions,
    747             String... extraArgs) throws DeviceNotAvailableException {
    748         throw new UnsupportedOperationException("No support for Package Manager's features");
    749     }
    750 
    751     /**
    752      * {@inheritDoc}
    753      */
    754     @Override
    755     public String installPackageForUser(File packageFile, boolean reinstall, int userId,
    756             String... extraArgs) throws DeviceNotAvailableException {
    757         throw new UnsupportedOperationException("No support for Package Manager's features");
    758     }
    759 
    760     /**
    761      * {@inheritDoc}
    762      */
    763     @Override
    764     public String installPackageForUser(File packageFile, boolean reinstall,
    765             boolean grantPermissions, int userId, String... extraArgs)
    766                     throws DeviceNotAvailableException {
    767         throw new UnsupportedOperationException("No support for Package Manager's features");
    768     }
    769 
    770     /**
    771      * {@inheritDoc}
    772      */
    773     @Override
    774     public String uninstallPackage(final String packageName) throws DeviceNotAvailableException {
    775         throw new UnsupportedOperationException("No support for Package Manager's features");
    776     }
    777 
    778     /**
    779      * {@inheritDoc}
    780      */
    781     @Override
    782     public boolean pullFile(final String remoteFilePath, final File localFile)
    783             throws DeviceNotAvailableException {
    784 
    785         DeviceAction pullAction = new DeviceAction() {
    786             @Override
    787             public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
    788                     SyncException {
    789                 SyncService syncService = null;
    790                 boolean status = false;
    791                 try {
    792                     syncService = getIDevice().getSyncService();
    793                     syncService.pullFile(interpolatePathVariables(remoteFilePath),
    794                             localFile.getAbsolutePath(), SyncService.getNullProgressMonitor());
    795                     status = true;
    796                 } catch (SyncException e) {
    797                     CLog.w("Failed to pull %s from %s to %s. Message %s", remoteFilePath,
    798                             getSerialNumber(), localFile.getAbsolutePath(), e.getMessage());
    799                     throw e;
    800                 } finally {
    801                     if (syncService != null) {
    802                         syncService.close();
    803                     }
    804                 }
    805                 return status;
    806             }
    807         };
    808         return performDeviceAction(String.format("pull %s to %s", remoteFilePath,
    809                 localFile.getAbsolutePath()), pullAction, MAX_RETRY_ATTEMPTS);
    810     }
    811 
    812     /**
    813      * {@inheritDoc}
    814      */
    815     @Override
    816     public File pullFile(String remoteFilePath) throws DeviceNotAvailableException {
    817         File localFile = null;
    818         boolean success = false;
    819         try {
    820             localFile = FileUtil.createTempFileForRemote(remoteFilePath, null);
    821             if (pullFile(remoteFilePath, localFile)) {
    822                 success = true;
    823                 return localFile;
    824             }
    825         } catch (IOException e) {
    826             CLog.w("Encountered IOException while trying to pull '%s':", remoteFilePath);
    827             CLog.e(e);
    828         } finally {
    829             if (!success) {
    830                 FileUtil.deleteFile(localFile);
    831             }
    832         }
    833         return null;
    834     }
    835 
    836     /**
    837      * {@inheritDoc}
    838      */
    839     @Override
    840     public File pullFileFromExternal(String remoteFilePath) throws DeviceNotAvailableException {
    841         String externalPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
    842         String fullPath = (new File(externalPath, remoteFilePath)).getPath();
    843         return pullFile(fullPath);
    844     }
    845 
    846     /**
    847      * Helper function that watches for the string "${EXTERNAL_STORAGE}" and replaces it with the
    848      * pathname of the EXTERNAL_STORAGE mountpoint.  Specifically intended to be used for pathnames
    849      * that are being passed to SyncService, which does not support variables inside of filenames.
    850      */
    851     String interpolatePathVariables(String path) {
    852         final String esString = "${EXTERNAL_STORAGE}";
    853         if (path.contains(esString)) {
    854             final String esPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
    855             path = path.replace(esString, esPath);
    856         }
    857         return path;
    858     }
    859 
    860     /**
    861      * {@inheritDoc}
    862      */
    863     @Override
    864     public boolean pushFile(final File localFile, final String remoteFilePath)
    865             throws DeviceNotAvailableException {
    866         DeviceAction pushAction =
    867                 new DeviceAction() {
    868                     @Override
    869                     public boolean run()
    870                             throws TimeoutException, IOException, AdbCommandRejectedException,
    871                                     SyncException {
    872                         SyncService syncService = null;
    873                         boolean status = false;
    874                         try {
    875                             syncService = getIDevice().getSyncService();
    876                             if (syncService == null) {
    877                                 throw new IOException("SyncService returned null.");
    878                             }
    879                             syncService.pushFile(
    880                                     localFile.getAbsolutePath(),
    881                                     interpolatePathVariables(remoteFilePath),
    882                                     SyncService.getNullProgressMonitor());
    883                             status = true;
    884                         } catch (SyncException e) {
    885                             CLog.w(
    886                                     "Failed to push %s to %s on device %s. Message %s",
    887                                     localFile.getAbsolutePath(),
    888                                     remoteFilePath,
    889                                     getSerialNumber(),
    890                                     e.getMessage());
    891                             throw e;
    892                         } finally {
    893                             if (syncService != null) {
    894                                 syncService.close();
    895                             }
    896                         }
    897                         return status;
    898                     }
    899                 };
    900         return performDeviceAction(String.format("push %s to %s", localFile.getAbsolutePath(),
    901                 remoteFilePath), pushAction, MAX_RETRY_ATTEMPTS);
    902     }
    903 
    904     /**
    905      * {@inheritDoc}
    906      */
    907     @Override
    908     public boolean pushString(final String contents, final String remoteFilePath)
    909             throws DeviceNotAvailableException {
    910         File tmpFile = null;
    911         try {
    912             tmpFile = FileUtil.createTempFile("temp", ".txt");
    913             FileUtil.writeToFile(contents, tmpFile);
    914             return pushFile(tmpFile, remoteFilePath);
    915         } catch (IOException e) {
    916             CLog.e(e);
    917             return false;
    918         } finally {
    919             FileUtil.deleteFile(tmpFile);
    920         }
    921     }
    922 
    923     /**
    924      * {@inheritDoc}
    925      */
    926     @Override
    927     public boolean doesFileExist(String destPath) throws DeviceNotAvailableException {
    928         String lsGrep = executeShellCommand(String.format("ls \"%s\"", destPath));
    929         return !lsGrep.contains("No such file or directory");
    930     }
    931 
    932     /**
    933      * {@inheritDoc}
    934      */
    935     @Override
    936     public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
    937         CLog.i("Checking free space for %s", getSerialNumber());
    938         String externalStorePath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
    939         String output = getDfOutput(externalStorePath);
    940         // Try coreutils/toybox style output first.
    941         Long available = parseFreeSpaceFromModernOutput(output);
    942         if (available != null) {
    943             return available;
    944         }
    945         // Then the two legacy toolbox formats.
    946         available = parseFreeSpaceFromAvailable(output);
    947         if (available != null) {
    948             return available;
    949         }
    950         available = parseFreeSpaceFromFree(externalStorePath, output);
    951         if (available != null) {
    952             return available;
    953         }
    954 
    955         CLog.e("free space command output \"%s\" did not match expected patterns", output);
    956         return 0;
    957     }
    958 
    959     /**
    960      * Run the 'df' shell command and return output, making multiple attempts if necessary.
    961      *
    962      * @param externalStorePath the path to check
    963      * @return the output from 'shell df path'
    964      * @throws DeviceNotAvailableException
    965      */
    966     private String getDfOutput(String externalStorePath) throws DeviceNotAvailableException {
    967         for (int i=0; i < MAX_RETRY_ATTEMPTS; i++) {
    968             String output = executeShellCommand(String.format("df %s", externalStorePath));
    969             if (output.trim().length() > 0) {
    970                 return output;
    971             }
    972         }
    973         throw new DeviceUnresponsiveException(String.format(
    974                 "Device %s not returning output from df command after %d attempts",
    975                 getSerialNumber(), MAX_RETRY_ATTEMPTS), getSerialNumber());
    976     }
    977 
    978     /**
    979      * Parses a partition's available space from the legacy output of a 'df' command, used
    980      * pre-gingerbread.
    981      * <p/>
    982      * Assumes output format of:
    983      * <br>/
    984      * <code>
    985      * [partition]: 15659168K total, 51584K used, 15607584K available (block size 32768)
    986      * </code>
    987      * @param dfOutput the output of df command to parse
    988      * @return the available space in kilobytes or <code>null</code> if output could not be parsed
    989      */
    990     private Long parseFreeSpaceFromAvailable(String dfOutput) {
    991         final Pattern freeSpacePattern = Pattern.compile("(\\d+)K available");
    992         Matcher patternMatcher = freeSpacePattern.matcher(dfOutput);
    993         if (patternMatcher.find()) {
    994             String freeSpaceString = patternMatcher.group(1);
    995             try {
    996                 return Long.parseLong(freeSpaceString);
    997             } catch (NumberFormatException e) {
    998                 // fall through
    999             }
   1000         }
   1001         return null;
   1002     }
   1003 
   1004     /**
   1005      * Parses a partition's available space from the 'table-formatted' output of a toolbox 'df'
   1006      * command, used from gingerbread to lollipop.
   1007      * <p/>
   1008      * Assumes output format of:
   1009      * <br/>
   1010      * <code>
   1011      * Filesystem             Size   Used   Free   Blksize
   1012      * <br/>
   1013      * [partition]:              3G   790M  2G     4096
   1014      * </code>
   1015      * @param dfOutput the output of df command to parse
   1016      * @return the available space in kilobytes or <code>null</code> if output could not be parsed
   1017      */
   1018     Long parseFreeSpaceFromFree(String externalStorePath, String dfOutput) {
   1019         Long freeSpace = null;
   1020         final Pattern freeSpaceTablePattern = Pattern.compile(String.format(
   1021                 //fs   Size         Used         Free
   1022                 "%s\\s+[\\w\\d\\.]+\\s+[\\w\\d\\.]+\\s+([\\d\\.]+)(\\w)", externalStorePath));
   1023         Matcher tablePatternMatcher = freeSpaceTablePattern.matcher(dfOutput);
   1024         if (tablePatternMatcher.find()) {
   1025             String numericValueString = tablePatternMatcher.group(1);
   1026             String unitType = tablePatternMatcher.group(2);
   1027             try {
   1028                 Float freeSpaceFloat = Float.parseFloat(numericValueString);
   1029                 if (unitType.equals("M")) {
   1030                     freeSpaceFloat = freeSpaceFloat * 1024;
   1031                 } else if (unitType.equals("G")) {
   1032                     freeSpaceFloat = freeSpaceFloat * 1024 * 1024;
   1033                 }
   1034                 freeSpace = freeSpaceFloat.longValue();
   1035             } catch (NumberFormatException e) {
   1036                 // fall through
   1037             }
   1038         }
   1039         return freeSpace;
   1040     }
   1041 
   1042     /**
   1043      * Parses a partition's available space from the modern coreutils/toybox 'df' output, used
   1044      * after lollipop.
   1045      * <p/>
   1046      * Assumes output format of:
   1047      * <br/>
   1048      * <code>
   1049      * Filesystem      1K-blocks	Used  Available Use% Mounted on
   1050      * <br/>
   1051      * /dev/fuse        11585536    1316348   10269188  12% /mnt/shell/emulated
   1052      * </code>
   1053      * @param dfOutput the output of df command to parse
   1054      * @return the available space in kilobytes or <code>null</code> if output could not be parsed
   1055      */
   1056     Long parseFreeSpaceFromModernOutput(String dfOutput) {
   1057         Matcher matcher = DF_PATTERN.matcher(dfOutput);
   1058         if (matcher.find()) {
   1059             try {
   1060                 return Long.parseLong(matcher.group(1));
   1061             } catch (NumberFormatException e) {
   1062                 // fall through
   1063             }
   1064         }
   1065         return null;
   1066     }
   1067 
   1068     /**
   1069      * {@inheritDoc}
   1070      */
   1071     @Override
   1072     public String getMountPoint(String mountName) {
   1073         return mStateMonitor.getMountPoint(mountName);
   1074     }
   1075 
   1076     /**
   1077      * {@inheritDoc}
   1078      */
   1079     @Override
   1080     public List<MountPointInfo> getMountPointInfo() throws DeviceNotAvailableException {
   1081         final String mountInfo = executeShellCommand("cat /proc/mounts");
   1082         final String[] mountInfoLines = mountInfo.split("\r?\n");
   1083         List<MountPointInfo> list = new ArrayList<>(mountInfoLines.length);
   1084 
   1085         for (String line : mountInfoLines) {
   1086             // We ignore the last two fields
   1087             // /dev/block/mtdblock4 /cache yaffs2 rw,nosuid,nodev,relatime 0 0
   1088             final String[] parts = line.split("\\s+", 5);
   1089             list.add(new MountPointInfo(parts[0], parts[1], parts[2], parts[3]));
   1090         }
   1091 
   1092         return list;
   1093     }
   1094 
   1095     /**
   1096      * {@inheritDoc}
   1097      */
   1098     @Override
   1099     public MountPointInfo getMountPointInfo(String mountpoint) throws DeviceNotAvailableException {
   1100         // The overhead of parsing all of the lines should be minimal
   1101         List<MountPointInfo> mountpoints = getMountPointInfo();
   1102         for (MountPointInfo info : mountpoints) {
   1103             if (mountpoint.equals(info.mountpoint)) return info;
   1104         }
   1105         return null;
   1106     }
   1107 
   1108     /**
   1109      * {@inheritDoc}
   1110      */
   1111     @Override
   1112     public IFileEntry getFileEntry(String path) throws DeviceNotAvailableException {
   1113         path = interpolatePathVariables(path);
   1114         String[] pathComponents = path.split(FileListingService.FILE_SEPARATOR);
   1115         FileListingService service = getFileListingService();
   1116         IFileEntry rootFile = new FileEntryWrapper(this, service.getRoot());
   1117         return FileEntryWrapper.getDescendant(rootFile, Arrays.asList(pathComponents));
   1118     }
   1119 
   1120     /**
   1121      * {@inheritDoc}
   1122      */
   1123     @Override
   1124     public boolean isDirectory(String path) throws DeviceNotAvailableException {
   1125         return executeShellCommand(String.format("ls -ld %s", path)).charAt(0) == 'd';
   1126     }
   1127 
   1128     /**
   1129      * {@inheritDoc}
   1130      */
   1131     @Override
   1132     public String[] getChildren(String path) throws DeviceNotAvailableException {
   1133         String lsOutput = executeShellCommand(String.format("ls -A1 %s", path));
   1134         if (lsOutput.trim().isEmpty()) {
   1135             return new String[0];
   1136         }
   1137         return lsOutput.split("\r?\n");
   1138     }
   1139 
   1140     /**
   1141      * Retrieve the {@link FileListingService} for the {@link IDevice}, making multiple attempts
   1142      * and recovery operations if necessary.
   1143      * <p/>
   1144      * This is necessary because {@link IDevice#getFileListingService()} can return
   1145      * <code>null</code> if device is in fastboot.  The symptom of this condition is that the
   1146      * current {@link #getIDevice()} is a {@link StubDevice}.
   1147      *
   1148      * @return the {@link FileListingService}
   1149      * @throws DeviceNotAvailableException if device communication is lost.
   1150      */
   1151     private FileListingService getFileListingService() throws DeviceNotAvailableException  {
   1152         final FileListingService[] service = new FileListingService[1];
   1153         DeviceAction serviceAction = new DeviceAction() {
   1154             @Override
   1155             public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
   1156                     ShellCommandUnresponsiveException, InstallException, SyncException {
   1157                 service[0] = getIDevice().getFileListingService();
   1158                 if (service[0] == null) {
   1159                     // could not get file listing service - must be a stub device - enter recovery
   1160                     throw new IOException("Could not get file listing service");
   1161                 }
   1162                 return true;
   1163             }
   1164         };
   1165         performDeviceAction("getFileListingService", serviceAction, MAX_RETRY_ATTEMPTS);
   1166         return service[0];
   1167     }
   1168 
   1169     /**
   1170      * {@inheritDoc}
   1171      */
   1172     @Override
   1173     public boolean pushDir(File localFileDir, String deviceFilePath)
   1174             throws DeviceNotAvailableException {
   1175         if (!localFileDir.isDirectory()) {
   1176             CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
   1177             return false;
   1178         }
   1179         File[] childFiles = localFileDir.listFiles();
   1180         if (childFiles == null) {
   1181             CLog.e("Could not read files in %s", localFileDir.getAbsolutePath());
   1182             return false;
   1183         }
   1184         for (File childFile : childFiles) {
   1185             String remotePath = String.format("%s/%s", deviceFilePath, childFile.getName());
   1186             if (childFile.isDirectory()) {
   1187                 executeShellCommand(String.format("mkdir -p \"%s\"", remotePath));
   1188                 if (!pushDir(childFile, remotePath)) {
   1189                     return false;
   1190                 }
   1191             } else if (childFile.isFile()) {
   1192                 if (!pushFile(childFile, remotePath)) {
   1193                     return false;
   1194                 }
   1195             }
   1196         }
   1197         return true;
   1198     }
   1199 
   1200     /**
   1201      * {@inheritDoc}
   1202      */
   1203     @Override
   1204     public boolean pullDir(String deviceFilePath, File localDir)
   1205             throws DeviceNotAvailableException {
   1206         if (!localDir.isDirectory()) {
   1207             CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
   1208             return false;
   1209         }
   1210         if (!isDirectory(deviceFilePath)) {
   1211             CLog.e("Device path %s is not a directory", deviceFilePath);
   1212             return false;
   1213         }
   1214         String lsOutput = executeShellCommand(String.format("ls -Ap1 %s", deviceFilePath));
   1215         if (lsOutput.trim().isEmpty()) {
   1216             CLog.i("Device path is empty, nothing to do.");
   1217             return true;
   1218         }
   1219         String[] items = lsOutput.split("\r?\n");
   1220         for (String item : items) {
   1221             if (item.isEmpty()) {
   1222                 // skip empty entries
   1223                 continue;
   1224             }
   1225             if (item.endsWith("/")) {
   1226                 // handle sub dir
   1227                 // prepare local path first
   1228                 item = item.substring(0, item.length() - 1);
   1229                 File subDir = new File(localDir, item);
   1230                 if (!subDir.mkdir()) {
   1231                     CLog.w("Failed to create sub directory %s, aborting.",
   1232                             subDir.getAbsolutePath());
   1233                     return false;
   1234                 }
   1235                 String deviceSubDir = String.format("%s/%s", deviceFilePath, item);
   1236                 if (!pullDir(deviceSubDir, subDir)) {
   1237                     CLog.w("Failed to pull sub directory %s from device, aborting", deviceSubDir);
   1238                     return false;
   1239                 }
   1240             } else {
   1241                 // handle regular file
   1242                 String deviceFile = String.format("%s/%s", deviceFilePath, item);
   1243                 File localFile = new File(localDir, item);
   1244                 if (!pullFile(deviceFile, localFile)) {
   1245                     CLog.w("Failed to pull file %s from device, aborting", deviceFile);
   1246                     return false;
   1247                 }
   1248             }
   1249         }
   1250         return true;
   1251     }
   1252 
   1253     /**
   1254      * {@inheritDoc}
   1255      */
   1256     @Override
   1257     public boolean syncFiles(File localFileDir, String deviceFilePath)
   1258             throws DeviceNotAvailableException {
   1259         if (localFileDir == null || deviceFilePath == null) {
   1260             throw new IllegalArgumentException("syncFiles does not take null arguments");
   1261         }
   1262         CLog.i("Syncing %s to %s on device %s",
   1263                 localFileDir.getAbsolutePath(), deviceFilePath, getSerialNumber());
   1264         if (!localFileDir.isDirectory()) {
   1265             CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
   1266             return false;
   1267         }
   1268         // get the real destination path. This is done because underlying syncService.push
   1269         // implementation will add localFileDir.getName() to destination path
   1270         deviceFilePath = String.format("%s/%s", interpolatePathVariables(deviceFilePath),
   1271                 localFileDir.getName());
   1272         if (!doesFileExist(deviceFilePath)) {
   1273             executeShellCommand(String.format("mkdir -p \"%s\"", deviceFilePath));
   1274         }
   1275         IFileEntry remoteFileEntry = getFileEntry(deviceFilePath);
   1276         if (remoteFileEntry == null) {
   1277             CLog.e("Could not find remote file entry %s ", deviceFilePath);
   1278             return false;
   1279         }
   1280 
   1281         return syncFiles(localFileDir, remoteFileEntry);
   1282     }
   1283 
   1284     /**
   1285      * Recursively sync newer files.
   1286      *
   1287      * @param localFileDir the local {@link File} directory to sync
   1288      * @param remoteFileEntry the remote destination {@link IFileEntry}
   1289      * @return <code>true</code> if files were synced successfully
   1290      * @throws DeviceNotAvailableException
   1291      */
   1292     private boolean syncFiles(File localFileDir, final IFileEntry remoteFileEntry)
   1293             throws DeviceNotAvailableException {
   1294         CLog.d("Syncing %s to %s on %s", localFileDir.getAbsolutePath(),
   1295                 remoteFileEntry.getFullPath(), getSerialNumber());
   1296         // find newer files to sync
   1297         File[] localFiles = localFileDir.listFiles(new NoHiddenFilesFilter());
   1298         ArrayList<String> filePathsToSync = new ArrayList<>();
   1299         for (File localFile : localFiles) {
   1300             IFileEntry entry = remoteFileEntry.findChild(localFile.getName());
   1301             if (entry == null) {
   1302                 CLog.d("Detected missing file path %s", localFile.getAbsolutePath());
   1303                 filePathsToSync.add(localFile.getAbsolutePath());
   1304             } else if (localFile.isDirectory()) {
   1305                 // This directory exists remotely. recursively sync it to sync only its newer files
   1306                 // contents
   1307                 if (!syncFiles(localFile, entry)) {
   1308                     return false;
   1309                 }
   1310             } else if (isNewer(localFile, entry)) {
   1311                 CLog.d("Detected newer file %s", localFile.getAbsolutePath());
   1312                 filePathsToSync.add(localFile.getAbsolutePath());
   1313             }
   1314         }
   1315 
   1316         if (filePathsToSync.size() == 0) {
   1317             CLog.d("No files to sync");
   1318             return true;
   1319         }
   1320         final String files[] = filePathsToSync.toArray(new String[filePathsToSync.size()]);
   1321         DeviceAction syncAction = new DeviceAction() {
   1322             @Override
   1323             public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
   1324                     SyncException {
   1325                 SyncService syncService = null;
   1326                 boolean status = false;
   1327                 try {
   1328                     syncService = getIDevice().getSyncService();
   1329                     syncService.push(files, remoteFileEntry.getFileEntry(),
   1330                             SyncService.getNullProgressMonitor());
   1331                     status = true;
   1332                 } catch (SyncException e) {
   1333                     CLog.w("Failed to sync files to %s on device %s. Message %s",
   1334                             remoteFileEntry.getFullPath(), getSerialNumber(), e.getMessage());
   1335                     throw e;
   1336                 } finally {
   1337                     if (syncService != null) {
   1338                         syncService.close();
   1339                     }
   1340                 }
   1341                 return status;
   1342             }
   1343         };
   1344         return performDeviceAction(String.format("sync files %s", remoteFileEntry.getFullPath()),
   1345                 syncAction, MAX_RETRY_ATTEMPTS);
   1346     }
   1347 
   1348     /**
   1349      * Queries the file listing service for a given directory
   1350      *
   1351      * @param remoteFileEntry
   1352      * @throws DeviceNotAvailableException
   1353      */
   1354     FileEntry[] getFileChildren(final FileEntry remoteFileEntry)
   1355             throws DeviceNotAvailableException {
   1356         // time this operation because its known to hang
   1357         FileQueryAction action = new FileQueryAction(remoteFileEntry,
   1358                 getIDevice().getFileListingService());
   1359         performDeviceAction("buildFileCache", action, MAX_RETRY_ATTEMPTS);
   1360         return action.mFileContents;
   1361     }
   1362 
   1363     private class FileQueryAction implements DeviceAction {
   1364 
   1365         FileEntry[] mFileContents = null;
   1366         private final FileEntry mRemoteFileEntry;
   1367         private final FileListingService mService;
   1368 
   1369         FileQueryAction(FileEntry remoteFileEntry, FileListingService service) {
   1370             throwIfNull(remoteFileEntry);
   1371             throwIfNull(service);
   1372             mRemoteFileEntry = remoteFileEntry;
   1373             mService = service;
   1374         }
   1375 
   1376         @Override
   1377         public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
   1378                 ShellCommandUnresponsiveException {
   1379             mFileContents = mService.getChildrenSync(mRemoteFileEntry);
   1380             return true;
   1381         }
   1382     }
   1383 
   1384     /**
   1385      * A {@link FilenameFilter} that rejects hidden (ie starts with ".") files.
   1386      */
   1387     private static class NoHiddenFilesFilter implements FilenameFilter {
   1388         /**
   1389          * {@inheritDoc}
   1390          */
   1391         @Override
   1392         public boolean accept(File dir, String name) {
   1393             return !name.startsWith(".");
   1394         }
   1395     }
   1396 
   1397     /**
   1398      * helper to get the timezone from the device. Example: "Europe/London"
   1399      */
   1400     private String getDeviceTimezone() {
   1401         try {
   1402             // This may not be set at first, default to GMT in this case.
   1403             String timezone = getProperty("persist.sys.timezone");
   1404             if (timezone != null) {
   1405                 return timezone.trim();
   1406             }
   1407         } catch (DeviceNotAvailableException e) {
   1408             // Fall through on purpose
   1409         }
   1410         return "GMT";
   1411     }
   1412 
   1413     /**
   1414      * Return <code>true</code> if local file is newer than remote file. {@link IFileEntry} being
   1415      * accurate to the minute, in case of equal times, the file will be considered newer.
   1416      * Exposed for testing.
   1417      */
   1418     protected boolean isNewer(File localFile, IFileEntry entry) {
   1419         final String entryTimeString = String.format("%s %s", entry.getDate(), entry.getTime());
   1420         try {
   1421             String timezone = getDeviceTimezone();
   1422             // expected format of a FileEntry's date and time
   1423             SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
   1424             format.setTimeZone(TimeZone.getTimeZone(timezone));
   1425             Date remoteDate = format.parse(entryTimeString);
   1426 
   1427             long offset = 0;
   1428             try {
   1429                 offset = getDeviceTimeOffset(null);
   1430             } catch (DeviceNotAvailableException e) {
   1431                 offset = 0;
   1432             }
   1433             CLog.i("Device offset time: %s", offset);
   1434 
   1435             // localFile.lastModified has granularity of ms, but remoteDate.getTime only has
   1436             // granularity of minutes. Shift remoteDate.getTime() backward by one minute so newly
   1437             // modified files get synced
   1438             return localFile.lastModified() > (remoteDate.getTime() - 60 * 1000 + offset);
   1439         } catch (ParseException e) {
   1440             CLog.e("Error converting remote time stamp %s for %s on device %s", entryTimeString,
   1441                     entry.getFullPath(), getSerialNumber());
   1442         }
   1443         // sync file by default
   1444         return true;
   1445     }
   1446 
   1447     /**
   1448      * {@inheritDoc}
   1449      */
   1450     @Override
   1451     public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
   1452         final String[] fullCmd = buildAdbCommand(cmdArgs);
   1453         AdbAction adbAction = new AdbAction(fullCmd);
   1454         performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
   1455         return adbAction.mOutput;
   1456     }
   1457 
   1458     /**
   1459      * {@inheritDoc}
   1460      */
   1461     @Override
   1462     public CommandResult executeFastbootCommand(String... cmdArgs)
   1463             throws DeviceNotAvailableException, UnsupportedOperationException {
   1464         return doFastbootCommand(getCommandTimeout(), cmdArgs);
   1465     }
   1466 
   1467     /**
   1468      * {@inheritDoc}
   1469      */
   1470     @Override
   1471     public CommandResult executeFastbootCommand(long timeout, String... cmdArgs)
   1472             throws DeviceNotAvailableException, UnsupportedOperationException {
   1473         return doFastbootCommand(timeout, cmdArgs);
   1474     }
   1475 
   1476     /**
   1477      * {@inheritDoc}
   1478      */
   1479     @Override
   1480     public CommandResult executeLongFastbootCommand(String... cmdArgs)
   1481             throws DeviceNotAvailableException, UnsupportedOperationException {
   1482         return doFastbootCommand(getLongCommandTimeout(), cmdArgs);
   1483     }
   1484 
   1485     /**
   1486      * @param cmdArgs
   1487      * @throws DeviceNotAvailableException
   1488      */
   1489     private CommandResult doFastbootCommand(final long timeout, String... cmdArgs)
   1490             throws DeviceNotAvailableException, UnsupportedOperationException {
   1491         if (!mFastbootEnabled) {
   1492             throw new UnsupportedOperationException(String.format(
   1493                     "Attempted to fastboot on device %s , but fastboot is not available. Aborting.",
   1494                     getSerialNumber()));
   1495         }
   1496         final String[] fullCmd = buildFastbootCommand(cmdArgs);
   1497         for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
   1498             CommandResult result = new CommandResult(CommandStatus.EXCEPTION);
   1499             // block state changes while executing a fastboot command, since
   1500             // device will disappear from fastboot devices while command is being executed
   1501             mFastbootLock.lock();
   1502             try {
   1503                 result = getRunUtil().runTimedCmd(timeout, fullCmd);
   1504             } finally {
   1505                 mFastbootLock.unlock();
   1506             }
   1507             if (!isRecoveryNeeded(result)) {
   1508                 return result;
   1509             }
   1510             CLog.w("Recovery needed after executing fastboot command");
   1511             if (result != null) {
   1512                 CLog.v("fastboot command output:\nstdout: %s\nstderr:%s",
   1513                         result.getStdout(), result.getStderr());
   1514             }
   1515             recoverDeviceFromBootloader();
   1516         }
   1517         throw new DeviceUnresponsiveException(String.format("Attempted fastboot %s multiple "
   1518                 + "times on device %s without communication success. Aborting.", cmdArgs[0],
   1519                 getSerialNumber()), getSerialNumber());
   1520     }
   1521 
   1522     /**
   1523      * {@inheritDoc}
   1524      */
   1525     @Override
   1526     public boolean getUseFastbootErase() {
   1527         return mOptions.getUseFastbootErase();
   1528     }
   1529 
   1530     /**
   1531      * {@inheritDoc}
   1532      */
   1533     @Override
   1534     public void setUseFastbootErase(boolean useFastbootErase) {
   1535         mOptions.setUseFastbootErase(useFastbootErase);
   1536     }
   1537 
   1538     /**
   1539      * {@inheritDoc}
   1540      */
   1541     @Override
   1542     public CommandResult fastbootWipePartition(String partition)
   1543             throws DeviceNotAvailableException {
   1544         if (mOptions.getUseFastbootErase()) {
   1545             return executeLongFastbootCommand("erase", partition);
   1546         } else {
   1547             return executeLongFastbootCommand("format", partition);
   1548         }
   1549     }
   1550 
   1551     /**
   1552      * Evaluate the given fastboot result to determine if recovery mode needs to be entered
   1553      *
   1554      * @param fastbootResult the {@link CommandResult} from a fastboot command
   1555      * @return <code>true</code> if recovery mode should be entered, <code>false</code> otherwise.
   1556      */
   1557     private boolean isRecoveryNeeded(CommandResult fastbootResult) {
   1558         if (fastbootResult.getStatus().equals(CommandStatus.TIMED_OUT)) {
   1559             // fastboot commands always time out if devices is not present
   1560             return true;
   1561         } else {
   1562             // check for specific error messages in result that indicate bad device communication
   1563             // and recovery mode is needed
   1564             if (fastbootResult.getStderr() == null ||
   1565                 fastbootResult.getStderr().contains("data transfer failure (Protocol error)") ||
   1566                 fastbootResult.getStderr().contains("status read failed (No such device)")) {
   1567                 CLog.w("Bad fastboot response from device %s. stderr: %s. Entering recovery",
   1568                         getSerialNumber(), fastbootResult.getStderr());
   1569                 return true;
   1570             }
   1571         }
   1572         return false;
   1573     }
   1574 
   1575     /**
   1576      * Get the max time allowed in ms for commands.
   1577      */
   1578     int getCommandTimeout() {
   1579         return mCmdTimeout;
   1580     }
   1581 
   1582     /**
   1583      * Set the max time allowed in ms for commands.
   1584      */
   1585     void setLongCommandTimeout(long timeout) {
   1586         mLongCmdTimeout = timeout;
   1587     }
   1588 
   1589     /**
   1590      * Get the max time allowed in ms for commands.
   1591      */
   1592     long getLongCommandTimeout() {
   1593         return mLongCmdTimeout;
   1594     }
   1595 
   1596     /**
   1597      * Set the max time allowed in ms for commands.
   1598      */
   1599     void setCommandTimeout(int timeout) {
   1600         mCmdTimeout = timeout;
   1601     }
   1602 
   1603     /**
   1604      * Builds the OS command for the given adb command and args
   1605      */
   1606     private String[] buildAdbCommand(String... commandArgs) {
   1607         return ArrayUtil.buildArray(new String[] {"adb", "-s", getSerialNumber()},
   1608                 commandArgs);
   1609     }
   1610 
   1611     /**
   1612      * Builds the OS command for the given fastboot command and args
   1613      */
   1614     private String[] buildFastbootCommand(String... commandArgs) {
   1615         return ArrayUtil.buildArray(new String[] {getFastbootPath(), "-s", getSerialNumber()},
   1616                 commandArgs);
   1617     }
   1618 
   1619     /**
   1620      * Performs an action on this device. Attempts to recover device and optionally retry command
   1621      * if action fails.
   1622      *
   1623      * @param actionDescription a short description of action to be performed. Used for logging
   1624      *            purposes only.
   1625      * @param action the action to be performed
   1626      * @param retryAttempts the retry attempts to make for action if it fails but
   1627      *            recovery succeeds
   1628      * @return <code>true</code> if action was performed successfully
   1629      * @throws DeviceNotAvailableException if recovery attempt fails or max attempts done without
   1630      *             success
   1631      */
   1632     protected boolean performDeviceAction(String actionDescription, final DeviceAction action,
   1633             int retryAttempts) throws DeviceNotAvailableException {
   1634 
   1635         for (int i = 0; i < retryAttempts + 1; i++) {
   1636             try {
   1637                 return action.run();
   1638             } catch (TimeoutException e) {
   1639                 logDeviceActionException(actionDescription, e);
   1640             } catch (IOException e) {
   1641                 logDeviceActionException(actionDescription, e);
   1642             } catch (InstallException e) {
   1643                 logDeviceActionException(actionDescription, e);
   1644             } catch (SyncException e) {
   1645                 logDeviceActionException(actionDescription, e);
   1646                 // a SyncException is not necessarily a device communication problem
   1647                 // do additional diagnosis
   1648                 if (!e.getErrorCode().equals(SyncError.BUFFER_OVERRUN) &&
   1649                         !e.getErrorCode().equals(SyncError.TRANSFER_PROTOCOL_ERROR)) {
   1650                     // this is a logic problem, doesn't need recovery or to be retried
   1651                     return false;
   1652                 }
   1653             } catch (AdbCommandRejectedException e) {
   1654                 logDeviceActionException(actionDescription, e);
   1655             } catch (ShellCommandUnresponsiveException e) {
   1656                 CLog.w("Device %s stopped responding when attempting %s", getSerialNumber(),
   1657                         actionDescription);
   1658             }
   1659             // TODO: currently treat all exceptions the same. In future consider different recovery
   1660             // mechanisms for time out's vs IOExceptions
   1661             recoverDevice();
   1662         }
   1663         if (retryAttempts > 0) {
   1664             throw new DeviceUnresponsiveException(String.format("Attempted %s multiple times "
   1665                     + "on device %s without communication success. Aborting.", actionDescription,
   1666                     getSerialNumber()), getSerialNumber());
   1667         }
   1668         return false;
   1669     }
   1670 
   1671     /**
   1672      * Log an entry for given exception
   1673      *
   1674      * @param actionDescription the action's description
   1675      * @param e the exception
   1676      */
   1677     private void logDeviceActionException(String actionDescription, Exception e) {
   1678         CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(),
   1679                 getExceptionMessage(e), actionDescription, getSerialNumber());
   1680     }
   1681 
   1682     /**
   1683      * Make a best effort attempt to retrieve a meaningful short descriptive message for given
   1684      * {@link Exception}
   1685      *
   1686      * @param e the {@link Exception}
   1687      * @return a short message
   1688      */
   1689     private String getExceptionMessage(Exception e) {
   1690         StringBuilder msgBuilder = new StringBuilder();
   1691         if (e.getMessage() != null) {
   1692             msgBuilder.append(e.getMessage());
   1693         }
   1694         if (e.getCause() != null) {
   1695             msgBuilder.append(" cause: ");
   1696             msgBuilder.append(e.getCause().getClass().getSimpleName());
   1697             if (e.getCause().getMessage() != null) {
   1698                 msgBuilder.append(" (");
   1699                 msgBuilder.append(e.getCause().getMessage());
   1700                 msgBuilder.append(")");
   1701             }
   1702         }
   1703         return msgBuilder.toString();
   1704     }
   1705 
   1706     /**
   1707      * Attempts to recover device communication.
   1708      *
   1709      * @throws DeviceNotAvailableException if device is not longer available
   1710      */
   1711     @Override
   1712     public void recoverDevice() throws DeviceNotAvailableException {
   1713         if (mRecoveryMode.equals(RecoveryMode.NONE)) {
   1714             CLog.i("Skipping recovery on %s", getSerialNumber());
   1715             getRunUtil().sleep(NONE_RECOVERY_MODE_DELAY);
   1716             return;
   1717         }
   1718         CLog.i("Attempting recovery on %s", getSerialNumber());
   1719         try {
   1720             mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE));
   1721         } catch (DeviceUnresponsiveException due) {
   1722             RecoveryMode previousRecoveryMode = mRecoveryMode;
   1723             mRecoveryMode = RecoveryMode.NONE;
   1724             boolean enabled = enableAdbRoot();
   1725             CLog.d("Device Unresponsive during recovery, is root still enabled: %s", enabled);
   1726             mRecoveryMode = previousRecoveryMode;
   1727             throw due;
   1728         }
   1729         if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) {
   1730             // turn off recovery mode to prevent reentrant recovery
   1731             // TODO: look for a better way to handle this, such as doing postBootUp steps in
   1732             // recovery itself
   1733             mRecoveryMode = RecoveryMode.NONE;
   1734             // this might be a runtime reset - still need to run post boot setup steps
   1735             if (isEncryptionSupported() && isDeviceEncrypted()) {
   1736                 unlockDevice();
   1737             }
   1738             postBootSetup();
   1739             mRecoveryMode = RecoveryMode.AVAILABLE;
   1740         } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) {
   1741             // turn off recovery mode to prevent reentrant recovery
   1742             // TODO: look for a better way to handle this, such as doing postBootUp steps in
   1743             // recovery itself
   1744             mRecoveryMode = RecoveryMode.NONE;
   1745             enableAdbRoot();
   1746             mRecoveryMode = RecoveryMode.ONLINE;
   1747         }
   1748         CLog.i("Recovery successful for %s", getSerialNumber());
   1749     }
   1750 
   1751     /**
   1752      * Attempts to recover device fastboot communication.
   1753      *
   1754      * @throws DeviceNotAvailableException if device is not longer available
   1755      */
   1756     private void recoverDeviceFromBootloader() throws DeviceNotAvailableException {
   1757         CLog.i("Attempting recovery on %s in bootloader", getSerialNumber());
   1758         mRecovery.recoverDeviceBootloader(mStateMonitor);
   1759         CLog.i("Bootloader recovery successful for %s", getSerialNumber());
   1760     }
   1761 
   1762     private void recoverDeviceInRecovery() throws DeviceNotAvailableException {
   1763         CLog.i("Attempting recovery on %s in recovery", getSerialNumber());
   1764         mRecovery.recoverDeviceRecovery(mStateMonitor);
   1765         CLog.i("Recovery mode recovery successful for %s", getSerialNumber());
   1766     }
   1767 
   1768     /**
   1769      * {@inheritDoc}
   1770      */
   1771     @Override
   1772     public void startLogcat() {
   1773         if (mLogcatReceiver != null) {
   1774             CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber());
   1775             return;
   1776         }
   1777         mLogcatReceiver = createLogcatReceiver();
   1778         mLogcatReceiver.start();
   1779     }
   1780 
   1781     /**
   1782      * {@inheritDoc}
   1783      */
   1784     @Override
   1785     public void clearLogcat() {
   1786         if (mLogcatReceiver != null) {
   1787             mLogcatReceiver.clear();
   1788         }
   1789     }
   1790 
   1791     /**
   1792      * {@inheritDoc}
   1793      */
   1794     @Override
   1795     public InputStreamSource getLogcat() {
   1796         if (mLogcatReceiver == null) {
   1797             CLog.w("Not capturing logcat for %s in background, returning a logcat dump",
   1798                     getSerialNumber());
   1799             return getLogcatDump();
   1800         } else {
   1801             return mLogcatReceiver.getLogcatData();
   1802         }
   1803     }
   1804 
   1805     /**
   1806      * {@inheritDoc}
   1807      */
   1808     @Override
   1809     public InputStreamSource getLogcat(int maxBytes) {
   1810         if (mLogcatReceiver == null) {
   1811             CLog.w("Not capturing logcat for %s in background, returning a logcat dump "
   1812                     + "ignoring size", getSerialNumber());
   1813             return getLogcatDump();
   1814         } else {
   1815             return mLogcatReceiver.getLogcatData(maxBytes);
   1816         }
   1817     }
   1818 
   1819     /**
   1820      * {@inheritDoc}
   1821      */
   1822     @Override
   1823     public InputStreamSource getLogcatSince(long date) {
   1824         try {
   1825             if (getApiLevel() <= 22) {
   1826                 CLog.i("Api level too low to use logcat -t 'time' reverting to dump");
   1827                 return getLogcatDump();
   1828             }
   1829         } catch (DeviceNotAvailableException e) {
   1830             // For convenience of interface, we catch the DNAE here.
   1831             CLog.e(e);
   1832             return getLogcatDump();
   1833         }
   1834 
   1835         byte[] output = new byte[0];
   1836         try {
   1837             // use IDevice directly because we don't want callers to handle
   1838             // DeviceNotAvailableException for this method
   1839             CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
   1840             String command = String.format("%s -t '%s'", LogcatReceiver.LOGCAT_CMD, date);
   1841             getIDevice().executeShellCommand(command, receiver);
   1842             output = receiver.getOutput();
   1843         } catch (IOException|AdbCommandRejectedException|
   1844                 ShellCommandUnresponsiveException|TimeoutException e) {
   1845             CLog.w("Failed to get logcat dump from %s: %s", getSerialNumber(), e.getMessage());
   1846             CLog.e(e);
   1847         }
   1848         return new ByteArrayInputStreamSource(output);
   1849     }
   1850 
   1851     /**
   1852      * {@inheritDoc}
   1853      */
   1854     @Override
   1855     public InputStreamSource getLogcatDump() {
   1856         byte[] output = new byte[0];
   1857         try {
   1858             // use IDevice directly because we don't want callers to handle
   1859             // DeviceNotAvailableException for this method
   1860             CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
   1861             // add -d parameter to make this a non blocking call
   1862             getIDevice().executeShellCommand(LogcatReceiver.LOGCAT_CMD + " -d", receiver,
   1863                     LOGCAT_DUMP_TIMEOUT, TimeUnit.MILLISECONDS);
   1864             output = receiver.getOutput();
   1865         } catch (IOException e) {
   1866             CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
   1867         } catch (TimeoutException e) {
   1868             CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber());
   1869         } catch (AdbCommandRejectedException e) {
   1870             CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
   1871         } catch (ShellCommandUnresponsiveException e) {
   1872             CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
   1873         }
   1874         return new ByteArrayInputStreamSource(output);
   1875     }
   1876 
   1877     /**
   1878      * {@inheritDoc}
   1879      */
   1880     @Override
   1881     public void stopLogcat() {
   1882         if (mLogcatReceiver != null) {
   1883             mLogcatReceiver.stop();
   1884             mLogcatReceiver = null;
   1885         } else {
   1886             CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber());
   1887         }
   1888     }
   1889 
   1890     /**
   1891      * Factory method to create a {@link LogcatReceiver}.
   1892      * <p/>
   1893      * Exposed for unit testing.
   1894      */
   1895     LogcatReceiver createLogcatReceiver() {
   1896         String logcatOptions = mOptions.getLogcatOptions();
   1897         if (logcatOptions == null) {
   1898             return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay);
   1899         } else {
   1900             return new LogcatReceiver(this,
   1901                     String.format("%s %s", LogcatReceiver.LOGCAT_CMD, logcatOptions),
   1902                     mOptions.getMaxLogcatDataSize(), mLogStartDelay);
   1903         }
   1904     }
   1905 
   1906     /**
   1907      * {@inheritDoc}
   1908      */
   1909     @Override
   1910     public InputStreamSource getBugreport() {
   1911         int apiLevel;
   1912         try {
   1913             apiLevel = getApiLevel();
   1914         } catch (DeviceNotAvailableException e) {
   1915             CLog.e("Device became unavailable while checking API level.");
   1916             CLog.e(e);
   1917             return null;
   1918         }
   1919 
   1920         if (apiLevel < 24) {
   1921             CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
   1922             try {
   1923                 executeShellCommand(BUGREPORT_CMD, receiver,
   1924                         BUGREPORT_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
   1925             } catch (DeviceNotAvailableException e) {
   1926                 // Log, but don't throw, so the caller can get the bugreport contents even
   1927                 // if the device goes away
   1928                 CLog.e("Device %s became unresponsive while retrieving bugreport",
   1929                         getSerialNumber());
   1930             }
   1931             return new ByteArrayInputStreamSource(receiver.getOutput());
   1932         } else {
   1933             CLog.d("Api level above 24, using bugreportz instead.");
   1934             File mainEntry = null;
   1935             File bugreportzFile = null;
   1936             try {
   1937                 bugreportzFile = getBugreportzInternal();
   1938                 if (bugreportzFile == null) {
   1939                     CLog.w("Fail to collect the bugreportz.");
   1940                     return bugreportzFallback();
   1941                 }
   1942                 try (ZipFile zip = new ZipFile(bugreportzFile)) {
   1943                     // We get the main_entry.txt that contains the bugreport name.
   1944                     mainEntry = ZipUtil2.extractFileFromZip(zip, "main_entry.txt");
   1945                     String bugreportName = FileUtil.readStringFromFile(mainEntry).trim();
   1946                     CLog.d("bugreport name: '%s'", bugreportName);
   1947                     File bugreport = ZipUtil2.extractFileFromZip(zip, bugreportName);
   1948                     return new FileInputStreamSource(bugreport, true);
   1949                 }
   1950             } catch (IOException e) {
   1951                 CLog.e("Error while unzipping bugreportz");
   1952                 CLog.e(e);
   1953                 return bugreportzFallback();
   1954             } finally {
   1955                 FileUtil.deleteFile(bugreportzFile);
   1956                 FileUtil.deleteFile(mainEntry);
   1957             }
   1958         }
   1959     }
   1960 
   1961     /**
   1962      * If first bugreportz collection was interrupted for any reasons, the temporary file where
   1963      * the dumpstate is redirected could exists if it started. We attempt to get it to have some
   1964      * partial data.
   1965      */
   1966     private InputStreamSource bugreportzFallback() {
   1967         try {
   1968             IFileEntry entries = getFileEntry(BUGREPORTZ_TMP_PATH);
   1969             if (entries != null) {
   1970                 for (IFileEntry f : entries.getChildren(false)) {
   1971                     String name = f.getName();
   1972                     CLog.d("bugreport entry: %s", name);
   1973                     if (name.endsWith(".tmp") || name.endsWith(".zip")) {
   1974                         File tmpBugreport = pullFile(BUGREPORTZ_TMP_PATH + name);
   1975                         if (tmpBugreport != null) {
   1976                             return new FileInputStreamSource(tmpBugreport, true);
   1977                         }
   1978                     }
   1979                 }
   1980                 CLog.w("Could not find a tmp bugreport file in the directory.");
   1981             } else {
   1982                 CLog.w("Could not find the file entry: '%s' on the device.", BUGREPORTZ_TMP_PATH);
   1983             }
   1984         } catch (DeviceNotAvailableException e) {
   1985             CLog.e(e);
   1986         }
   1987         return new ByteArrayInputStreamSource(new byte[] {});
   1988     }
   1989 
   1990     /**
   1991      * {@inheritDoc}
   1992      */
   1993     @Override
   1994     public boolean logBugreport(String dataName, ITestLogger listener) {
   1995         InputStreamSource bugreport = getBugreportz();
   1996         LogDataType type = LogDataType.BUGREPORTZ;
   1997         try {
   1998             if (bugreport == null) {
   1999                 bugreport = getBugreport();
   2000                 type = LogDataType.BUGREPORT;
   2001             }
   2002             if (bugreport != null) {
   2003                 listener.testLog(dataName, type, bugreport);
   2004                 return true;
   2005             }
   2006         } finally {
   2007             StreamUtil.cancel(bugreport);
   2008         }
   2009         CLog.d("takeBugreport() was not successful in collecting and logging the bugreport "
   2010                 + "for device %s", getSerialNumber());
   2011         return false;
   2012     }
   2013 
   2014     /**
   2015      * {@inheritDoc}
   2016      */
   2017     @Override
   2018     public Bugreport takeBugreport() {
   2019         int apiLevel;
   2020         try {
   2021             apiLevel = getApiLevel();
   2022         } catch (DeviceNotAvailableException e) {
   2023             CLog.e("Device became unavailable while checking API level.");
   2024             CLog.e(e);
   2025             return null;
   2026         }
   2027         File bugreportFile = null;
   2028         if (apiLevel < 24) {
   2029             CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
   2030             try {
   2031                 executeShellCommand(BUGREPORT_CMD, receiver,
   2032                         BUGREPORT_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
   2033                 bugreportFile = FileUtil.createTempFile("bugreport", ".txt");
   2034                 FileUtil.writeToFile(new ByteArrayInputStream(receiver.getOutput()), bugreportFile);
   2035                 return new Bugreport(bugreportFile, false);
   2036             } catch (DeviceNotAvailableException e) {
   2037                 // Log, but don't throw, so the caller can get the bugreport contents even
   2038                 // if the device goes away
   2039                 CLog.e("Device %s became unresponsive while retrieving bugreport",
   2040                         getSerialNumber());
   2041             } catch (IOException e) {
   2042                 CLog.e("Error when writing the bugreport file");
   2043                 CLog.e(e);
   2044             }
   2045             return null;
   2046         } else {
   2047             CLog.d("Api level above 24, using bugreportz instead.");
   2048             bugreportFile = getBugreportzInternal();
   2049             if (bugreportFile != null) {
   2050                 return new Bugreport(bugreportFile, true);
   2051             } else {
   2052                 CLog.w("Error when collecting the bugreportz.");
   2053                 return null;
   2054             }
   2055         }
   2056     }
   2057 
   2058     /**
   2059      * {@inheritDoc}
   2060      */
   2061     @Override
   2062     public InputStreamSource getBugreportz() {
   2063         try {
   2064             checkApiLevelAgainst("getBugreportz", 24);
   2065             File bugreportZip = getBugreportzInternal();
   2066             if (bugreportZip != null) {
   2067                 return new FileInputStreamSource(bugreportZip, true);
   2068             }
   2069         } catch (IllegalArgumentException e) {
   2070             CLog.e("API level error when checking bugreportz support.");
   2071             CLog.e(e);
   2072         }
   2073         return null;
   2074     }
   2075 
   2076     /**
   2077      * Internal Helper method to get the bugreportz zip file as a {@link File}.
   2078      * Exposed for testing.
   2079      */
   2080     protected File getBugreportzInternal() {
   2081         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
   2082         // Does not rely on {@link ITestDevice#executeAdbCommand(String...)} because it does not
   2083         // provide a timeout.
   2084         try {
   2085             executeShellCommand(BUGREPORTZ_CMD, receiver,
   2086                     BUGREPORTZ_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
   2087             String output = receiver.getOutput().trim();
   2088             Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
   2089             if (!match.find()) {
   2090                 CLog.e("Something went went wrong during bugreportz collection: '%s'", output);
   2091                 return null;
   2092             } else {
   2093                 String remoteFilePath = match.group(2);
   2094                 File zipFile = null;
   2095                 try {
   2096                     if (!doesFileExist(remoteFilePath)) {
   2097                         CLog.e("Did not find bugreportz at: %s", remoteFilePath);
   2098                         return null;
   2099                     }
   2100                     // Create a placeholder to replace the file
   2101                     zipFile = FileUtil.createTempFile("bugreportz", ".zip");
   2102                     pullFile(remoteFilePath, zipFile);
   2103                     String bugreportDir =
   2104                             remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
   2105                     if (!bugreportDir.isEmpty()) {
   2106                         // clean bugreport files directory on device
   2107                         executeShellCommand(String.format("rm %s/*", bugreportDir));
   2108                     }
   2109 
   2110                     return zipFile;
   2111                 } catch (IOException e) {
   2112                     CLog.e("Failed to create the temporary file.");
   2113                     return null;
   2114                 }
   2115             }
   2116         } catch (DeviceNotAvailableException e) {
   2117             CLog.e("Device %s became unresponsive while retrieving bugreportz", getSerialNumber());
   2118             CLog.e(e);
   2119         }
   2120         return null;
   2121     }
   2122 
   2123     /**
   2124      * {@inheritDoc}
   2125      */
   2126     @Override
   2127     public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
   2128         throw new UnsupportedOperationException("No support for Screenshot");
   2129     }
   2130 
   2131     /**
   2132      * {@inheritDoc}
   2133      */
   2134     @Override
   2135     public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
   2136         throw new UnsupportedOperationException("No support for Screenshot");
   2137     }
   2138 
   2139     /** {@inheritDoc} */
   2140     @Override
   2141     public InputStreamSource getScreenshot(String format, boolean rescale)
   2142             throws DeviceNotAvailableException {
   2143         throw new UnsupportedOperationException("No support for Screenshot");
   2144     }
   2145 
   2146     /** {@inheritDoc} */
   2147     @Override
   2148     public void clearLastConnectedWifiNetwork() {
   2149         mLastConnectedWifiSsid = null;
   2150         mLastConnectedWifiPsk = null;
   2151     }
   2152 
   2153     /**
   2154      * {@inheritDoc}
   2155      */
   2156     @Override
   2157     public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk)
   2158             throws DeviceNotAvailableException {
   2159         return connectToWifiNetwork(wifiSsid, wifiPsk, false);
   2160     }
   2161 
   2162     /**
   2163      * {@inheritDoc}
   2164      */
   2165     @Override
   2166     public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)
   2167             throws DeviceNotAvailableException {
   2168         // Clears the last connected wifi network.
   2169         mLastConnectedWifiSsid = null;
   2170         mLastConnectedWifiPsk = null;
   2171 
   2172         // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()}
   2173         // times
   2174         Random rnd = new Random();
   2175         int backoffSlotCount = 2;
   2176         int waitTime = mOptions.getWifiRetryWaitTime();
   2177         IWifiHelper wifi = createWifiHelper();
   2178         try {
   2179             for (int i = 1; i <= mOptions.getWifiAttempts(); i++) {
   2180                 CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber());
   2181                 boolean success =
   2182                         wifi.connectToNetwork(
   2183                                 wifiSsid, wifiPsk, mOptions.getConnCheckUrl(), scanSsid);
   2184                 final Map<String, String> wifiInfo = wifi.getWifiInfo();
   2185                 if (success) {
   2186                     CLog.i(
   2187                             "Successfully connected to wifi network %s(%s) on %s",
   2188                             wifiSsid, wifiInfo.get("bssid"), getSerialNumber());
   2189 
   2190                     mLastConnectedWifiSsid = wifiSsid;
   2191                     mLastConnectedWifiPsk = wifiPsk;
   2192 
   2193                     return true;
   2194                 } else {
   2195                     CLog.w(
   2196                             "Failed to connect to wifi network %s(%s) on %s on attempt %d of %d",
   2197                             wifiSsid,
   2198                             wifiInfo.get("bssid"),
   2199                             getSerialNumber(),
   2200                             i,
   2201                             mOptions.getWifiAttempts());
   2202                 }
   2203                 if (i < mOptions.getWifiAttempts()) {
   2204                     if (mOptions.isWifiExpoRetryEnabled()) {
   2205                         // use binary exponential back-offs when retrying.
   2206                         waitTime *= rnd.nextInt(backoffSlotCount);
   2207                         backoffSlotCount *= 2;
   2208                     }
   2209                     CLog.e("Waiting for %d ms before reconnecting to %s...", waitTime, wifiSsid);
   2210                     getRunUtil().sleep(waitTime);
   2211                 }
   2212             }
   2213         } finally {
   2214             wifi.cleanUp();
   2215         }
   2216         return false;
   2217     }
   2218 
   2219     /**
   2220      * {@inheritDoc}
   2221      */
   2222     @Override
   2223     public boolean checkConnectivity() throws DeviceNotAvailableException {
   2224         IWifiHelper wifi = createWifiHelper();
   2225         return wifi.checkConnectivity(mOptions.getConnCheckUrl());
   2226     }
   2227 
   2228     /**
   2229      * {@inheritDoc}
   2230      */
   2231     @Override
   2232     public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)
   2233             throws DeviceNotAvailableException {
   2234         return connectToWifiNetworkIfNeeded(wifiSsid, wifiPsk, false);
   2235     }
   2236 
   2237     /**
   2238      * {@inheritDoc}
   2239      */
   2240     @Override
   2241     public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)
   2242             throws DeviceNotAvailableException {
   2243         if (!checkConnectivity())  {
   2244             return connectToWifiNetwork(wifiSsid, wifiPsk, scanSsid);
   2245         }
   2246         return true;
   2247     }
   2248 
   2249     /**
   2250      * {@inheritDoc}
   2251      */
   2252     @Override
   2253     public boolean isWifiEnabled() throws DeviceNotAvailableException {
   2254         final IWifiHelper wifi = createWifiHelper();
   2255         try {
   2256             return wifi.isWifiEnabled();
   2257         } catch (RuntimeException e) {
   2258             CLog.w("Failed to create WifiHelper: %s", e.getMessage());
   2259             return false;
   2260         } finally {
   2261             wifi.cleanUp();
   2262         }
   2263     }
   2264 
   2265     /**
   2266      * Checks that the device is currently successfully connected to given wifi SSID.
   2267      *
   2268      * @param wifiSSID the wifi ssid
   2269      * @return <code>true</code> if device is currently connected to wifiSSID and has network
   2270      *         connectivity. <code>false</code> otherwise
   2271      * @throws DeviceNotAvailableException if connection with device was lost
   2272      */
   2273     boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException {
   2274         CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber());
   2275         final IWifiHelper wifi = createWifiHelper();
   2276         try {
   2277             // getSSID returns SSID as "SSID"
   2278             final String quotedSSID = String.format("\"%s\"", wifiSSID);
   2279 
   2280             boolean test = wifi.isWifiEnabled();
   2281             CLog.v("%s: wifi enabled? %b", getSerialNumber(), test);
   2282 
   2283             if (test) {
   2284                 final String actualSSID = wifi.getSSID();
   2285                 test = quotedSSID.equals(actualSSID);
   2286                 CLog.v(
   2287                         "%s: SSID match (%s, %s, %b)",
   2288                         getSerialNumber(), quotedSSID, actualSSID, test);
   2289             }
   2290             if (test) {
   2291                 test = wifi.hasValidIp();
   2292                 CLog.v("%s: validIP? %b", getSerialNumber(), test);
   2293             }
   2294             if (test) {
   2295                 test = checkConnectivity();
   2296                 CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test);
   2297             }
   2298             return test;
   2299         } finally {
   2300             wifi.cleanUp();
   2301         }
   2302     }
   2303 
   2304     /**
   2305      * {@inheritDoc}
   2306      */
   2307     @Override
   2308     public boolean disconnectFromWifi() throws DeviceNotAvailableException {
   2309         CLog.i("Disconnecting from wifi on %s", getSerialNumber());
   2310         // Clears the last connected wifi network.
   2311         mLastConnectedWifiSsid = null;
   2312         mLastConnectedWifiPsk = null;
   2313 
   2314         IWifiHelper wifi = createWifiHelper();
   2315         try {
   2316             return wifi.disconnectFromNetwork();
   2317         } finally {
   2318             wifi.cleanUp();
   2319         }
   2320     }
   2321 
   2322     /**
   2323      * {@inheritDoc}
   2324      */
   2325     @Override
   2326     public String getIpAddress() throws DeviceNotAvailableException {
   2327         IWifiHelper wifi = createWifiHelper();
   2328         try {
   2329             return wifi.getIpAddress();
   2330         } finally {
   2331             wifi.cleanUp();
   2332         }
   2333     }
   2334 
   2335     /**
   2336      * {@inheritDoc}
   2337      */
   2338     @Override
   2339     public boolean enableNetworkMonitor() throws DeviceNotAvailableException {
   2340         mNetworkMonitorEnabled = false;
   2341 
   2342         IWifiHelper wifi = createWifiHelper();
   2343         try {
   2344             wifi.stopMonitor();
   2345             if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) {
   2346                 mNetworkMonitorEnabled = true;
   2347                 return true;
   2348             }
   2349         } finally {
   2350             wifi.cleanUp();
   2351         }
   2352         return false;
   2353     }
   2354 
   2355     /**
   2356      * {@inheritDoc}
   2357      */
   2358     @Override
   2359     public boolean disableNetworkMonitor() throws DeviceNotAvailableException {
   2360         mNetworkMonitorEnabled = false;
   2361 
   2362         IWifiHelper wifi = createWifiHelper();
   2363         List<Long> samples = wifi.stopMonitor();
   2364         if (!samples.isEmpty()) {
   2365             int failures = 0;
   2366             long totalLatency = 0;
   2367             for (Long sample : samples) {
   2368                 if (sample < 0) {
   2369                     failures += 1;
   2370                 } else {
   2371                     totalLatency += sample;
   2372                 }
   2373             }
   2374             double failureRate = failures * 100.0 / samples.size();
   2375             double avgLatency = 0.0;
   2376             if (failures < samples.size()) {
   2377                 avgLatency = totalLatency / (samples.size() - failures);
   2378             }
   2379             CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f",
   2380                     mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000,
   2381                     failureRate, avgLatency);
   2382         }
   2383         return true;
   2384     }
   2385 
   2386     /**
   2387      * Create a {@link WifiHelper} to use
   2388      * <p/>
   2389      * Exposed so unit tests can mock
   2390      * @throws DeviceNotAvailableException
   2391      */
   2392     IWifiHelper createWifiHelper() throws DeviceNotAvailableException {
   2393         // current wifi helper won't work on AndroidNativeDevice
   2394         // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when
   2395         // we learn what is available.
   2396         throw new UnsupportedOperationException("Wifi helper is not supported.");
   2397     }
   2398 
   2399     /**
   2400      * {@inheritDoc}
   2401      */
   2402     @Override
   2403     public boolean clearErrorDialogs() throws DeviceNotAvailableException {
   2404         throw new UnsupportedOperationException("No support for Screen's features");
   2405     }
   2406 
   2407     /** {@inheritDoc} */
   2408     @Override
   2409     public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException {
   2410         throw new UnsupportedOperationException("No support for keyguard querying.");
   2411     }
   2412 
   2413     IDeviceStateMonitor getDeviceStateMonitor() {
   2414         return mStateMonitor;
   2415     }
   2416 
   2417     /**
   2418      * {@inheritDoc}
   2419      */
   2420     @Override
   2421     public void postBootSetup() throws DeviceNotAvailableException  {
   2422         enableAdbRoot();
   2423         prePostBootSetup();
   2424         for (String command : mOptions.getPostBootCommands()) {
   2425             executeShellCommand(command);
   2426         }
   2427     }
   2428 
   2429     /**
   2430      * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for
   2431      * specific post boot setup.
   2432      * @throws DeviceNotAvailableException
   2433      */
   2434     protected void prePostBootSetup() throws DeviceNotAvailableException {
   2435         // Empty on purpose.
   2436     }
   2437 
   2438     /**
   2439      * Ensure wifi connection is re-established after boot. This is intended to be called after TF
   2440      * initiated reboots(ones triggered by {@link #reboot()}) only.
   2441      *
   2442      * @throws DeviceNotAvailableException
   2443      */
   2444     void postBootWifiSetup() throws DeviceNotAvailableException {
   2445         if (mLastConnectedWifiSsid != null) {
   2446             reconnectToWifiNetwork();
   2447         }
   2448         if (mNetworkMonitorEnabled) {
   2449             if (!enableNetworkMonitor()) {
   2450                 CLog.w("Failed to enable network monitor on %s after reboot", getSerialNumber());
   2451             }
   2452         }
   2453     }
   2454 
   2455     void reconnectToWifiNetwork() throws DeviceNotAvailableException {
   2456         // First, wait for wifi to re-connect automatically.
   2457         long startTime = System.currentTimeMillis();
   2458         boolean isConnected = checkConnectivity();
   2459         while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) {
   2460             getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL);
   2461             isConnected = checkConnectivity();
   2462         }
   2463 
   2464         if (isConnected) {
   2465             return;
   2466         }
   2467 
   2468         // If wifi is still not connected, try to re-connect on our own.
   2469         final String wifiSsid = mLastConnectedWifiSsid;
   2470         if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) {
   2471             throw new NetworkNotAvailableException(
   2472                     String.format("Failed to connect to wifi network %s on %s after reboot",
   2473                             wifiSsid, getSerialNumber()));
   2474         }
   2475     }
   2476 
   2477     /**
   2478      * {@inheritDoc}
   2479      */
   2480     @Override
   2481     public void rebootIntoBootloader()
   2482             throws DeviceNotAvailableException, UnsupportedOperationException {
   2483         if (!mFastbootEnabled) {
   2484             throw new UnsupportedOperationException(
   2485                     "Fastboot is not available and cannot reboot into bootloader");
   2486         }
   2487         CLog.i("Rebooting device %s in state %s into bootloader", getSerialNumber(),
   2488                 getDeviceState());
   2489         if (TestDeviceState.FASTBOOT.equals(getDeviceState())) {
   2490             CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
   2491             executeFastbootCommand("reboot-bootloader");
   2492         } else {
   2493             CLog.i("Booting device %s into bootloader", getSerialNumber());
   2494             doAdbRebootBootloader();
   2495         }
   2496         if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
   2497             recoverDeviceFromBootloader();
   2498         }
   2499     }
   2500 
   2501     private void doAdbRebootBootloader() throws DeviceNotAvailableException {
   2502         doAdbReboot("bootloader");
   2503     }
   2504 
   2505     /**
   2506      * {@inheritDoc}
   2507      */
   2508     @Override
   2509     public void reboot() throws DeviceNotAvailableException {
   2510         rebootUntilOnline();
   2511 
   2512         RecoveryMode cachedRecoveryMode = getRecoveryMode();
   2513         setRecoveryMode(RecoveryMode.ONLINE);
   2514 
   2515         if (isEncryptionSupported() && isDeviceEncrypted()) {
   2516             unlockDevice();
   2517         }
   2518 
   2519         setRecoveryMode(cachedRecoveryMode);
   2520 
   2521         if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) {
   2522             postBootSetup();
   2523             postBootWifiSetup();
   2524             return;
   2525         } else {
   2526             recoverDevice();
   2527         }
   2528     }
   2529 
   2530     /**
   2531      * {@inheritDoc}
   2532      */
   2533     @Override
   2534     public void rebootUntilOnline() throws DeviceNotAvailableException {
   2535         doReboot();
   2536         RecoveryMode cachedRecoveryMode = getRecoveryMode();
   2537         setRecoveryMode(RecoveryMode.ONLINE);
   2538         if (mStateMonitor.waitForDeviceOnline() != null) {
   2539             enableAdbRoot();
   2540         } else {
   2541             recoverDevice();
   2542         }
   2543         setRecoveryMode(cachedRecoveryMode);
   2544     }
   2545 
   2546     /**
   2547      * {@inheritDoc}
   2548      */
   2549     @Override
   2550     public void rebootIntoRecovery() throws DeviceNotAvailableException {
   2551         if (TestDeviceState.FASTBOOT == getDeviceState()) {
   2552             CLog.w("device %s in fastboot when requesting boot to recovery. " +
   2553                     "Rebooting to userspace first.", getSerialNumber());
   2554             rebootUntilOnline();
   2555         }
   2556         doAdbReboot("recovery");
   2557         if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) {
   2558             recoverDeviceInRecovery();
   2559         }
   2560     }
   2561 
   2562     /**
   2563      * {@inheritDoc}
   2564      */
   2565     @Override
   2566     public void nonBlockingReboot() throws DeviceNotAvailableException {
   2567         doReboot();
   2568     }
   2569 
   2570     /**
   2571      * Exposed for unit testing.
   2572      *
   2573      * @throws DeviceNotAvailableException
   2574      */
   2575     void doReboot() throws DeviceNotAvailableException, UnsupportedOperationException {
   2576         if (TestDeviceState.FASTBOOT == getDeviceState()) {
   2577             CLog.i("device %s in fastboot. Rebooting to userspace.", getSerialNumber());
   2578             executeFastbootCommand("reboot");
   2579         } else {
   2580             if (mOptions.shouldDisableReboot()) {
   2581                 CLog.i("Device reboot disabled by options, skipped.");
   2582                 return;
   2583             }
   2584             CLog.i("Rebooting device %s", getSerialNumber());
   2585             doAdbReboot(null);
   2586             waitForDeviceNotAvailable("reboot", DEFAULT_UNAVAILABLE_TIMEOUT);
   2587         }
   2588     }
   2589 
   2590     /**
   2591      * Perform a adb reboot.
   2592      *
   2593      * @param into the bootloader name to reboot into, or <code>null</code> to just reboot the
   2594      *            device.
   2595      * @throws DeviceNotAvailableException
   2596      */
   2597     protected void doAdbReboot(final String into) throws DeviceNotAvailableException {
   2598         DeviceAction rebootAction = new DeviceAction() {
   2599             @Override
   2600             public boolean run() throws TimeoutException, IOException,
   2601                     AdbCommandRejectedException {
   2602                 getIDevice().reboot(into);
   2603                 return true;
   2604             }
   2605         };
   2606         performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS);
   2607 
   2608     }
   2609 
   2610     protected void waitForDeviceNotAvailable(String operationDesc, long time) {
   2611         // TODO: a bit of a race condition here. Would be better to start a
   2612         // before the operation
   2613         if (!mStateMonitor.waitForDeviceNotAvailable(time)) {
   2614             // above check is flaky, ignore till better solution is found
   2615             CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(),
   2616                     operationDesc);
   2617         }
   2618     }
   2619 
   2620     /**
   2621      * {@inheritDoc}
   2622      */
   2623     @Override
   2624     public boolean enableAdbRoot() throws DeviceNotAvailableException {
   2625         // adb root is a relatively intensive command, so do a brief check first to see
   2626         // if its necessary or not
   2627         if (isAdbRoot()) {
   2628             CLog.i("adb is already running as root on %s", getSerialNumber());
   2629             return true;
   2630         }
   2631         // Don't enable root if user requested no root
   2632         if (!isEnableAdbRoot()) {
   2633             CLog.i("\"enable-root\" set to false; ignoring 'adb root' request");
   2634             return false;
   2635         }
   2636         CLog.i("adb root on device %s", getSerialNumber());
   2637         int attempts = MAX_RETRY_ATTEMPTS + 1;
   2638         for (int i=1; i <= attempts; i++) {
   2639             String output = executeAdbCommand("root");
   2640             // wait for device to disappear from adb
   2641             waitForDeviceNotAvailable("root", 20 * 1000);
   2642 
   2643             postAdbRootAction();
   2644 
   2645             // wait for device to be back online
   2646             waitForDeviceOnline();
   2647 
   2648             if (isAdbRoot()) {
   2649                 return true;
   2650             }
   2651             CLog.w("'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'",
   2652                     getSerialNumber(), i, attempts, output);
   2653         }
   2654         return false;
   2655     }
   2656 
   2657     /**
   2658      * {@inheritDoc}
   2659      */
   2660     @Override
   2661     public boolean disableAdbRoot() throws DeviceNotAvailableException {
   2662         if (!isAdbRoot()) {
   2663             CLog.i("adb is already unroot on %s", getSerialNumber());
   2664             return true;
   2665         }
   2666 
   2667         CLog.i("adb unroot on device %s", getSerialNumber());
   2668         int attempts = MAX_RETRY_ATTEMPTS + 1;
   2669         for (int i=1; i <= attempts; i++) {
   2670             String output = executeAdbCommand("unroot");
   2671             // wait for device to disappear from adb
   2672             waitForDeviceNotAvailable("unroot", 5 * 1000);
   2673 
   2674             postAdbUnrootAction();
   2675 
   2676             // wait for device to be back online
   2677             waitForDeviceOnline();
   2678 
   2679             if (!isAdbRoot()) {
   2680                 return true;
   2681             }
   2682             CLog.w("'adb unroot' on %s unsuccessful on attempt %d of %d. Output: '%s'",
   2683                     getSerialNumber(), i, attempts, output);
   2684         }
   2685         return false;
   2686     }
   2687 
   2688     /**
   2689      * Override if the device needs some specific actions to be taken after adb root and before the
   2690      * device is back online.
   2691      * Default implementation doesn't include any addition actions.
   2692      * adb root is not guaranteed to be enabled at this stage.
   2693      * @throws DeviceNotAvailableException
   2694      */
   2695     public void postAdbRootAction() throws DeviceNotAvailableException {
   2696         // Empty on purpose.
   2697     }
   2698 
   2699     /**
   2700      * Override if the device needs some specific actions to be taken after adb unroot and before
   2701      * the device is back online.
   2702      * Default implementation doesn't include any additional actions.
   2703      * adb root is not guaranteed to be disabled at this stage.
   2704      * @throws DeviceNotAvailableException
   2705      */
   2706     public void postAdbUnrootAction() throws DeviceNotAvailableException {
   2707         // Empty on purpose.
   2708     }
   2709 
   2710     /**
   2711      * {@inheritDoc}
   2712      */
   2713     @Override
   2714     public boolean isAdbRoot() throws DeviceNotAvailableException {
   2715         String output = executeShellCommand("id");
   2716         return output.contains("uid=0(root)");
   2717     }
   2718 
   2719     /**
   2720      * {@inheritDoc}
   2721      */
   2722     @Override
   2723     public boolean encryptDevice(boolean inplace) throws DeviceNotAvailableException,
   2724             UnsupportedOperationException {
   2725         if (!isEncryptionSupported()) {
   2726             throw new UnsupportedOperationException(String.format("Can't encrypt device %s: "
   2727                     + "encryption not supported", getSerialNumber()));
   2728         }
   2729 
   2730         if (isDeviceEncrypted()) {
   2731             CLog.d("Device %s is already encrypted, skipping", getSerialNumber());
   2732             return true;
   2733         }
   2734 
   2735         enableAdbRoot();
   2736 
   2737         String encryptMethod;
   2738         long timeout;
   2739         if (inplace) {
   2740             encryptMethod = "inplace";
   2741             timeout = ENCRYPTION_INPLACE_TIMEOUT_MIN;
   2742         } else {
   2743             encryptMethod = "wipe";
   2744             timeout = ENCRYPTION_WIPE_TIMEOUT_MIN;
   2745         }
   2746 
   2747         CLog.i("Encrypting device %s via %s", getSerialNumber(), encryptMethod);
   2748 
   2749         // enable crypto takes one of the following formats:
   2750         // cryptfs enablecrypto <wipe|inplace> <passwd>
   2751         // cryptfs enablecrypto <wipe|inplace> default|password|pin|pattern [passwd]
   2752         // Try the first one first, if it outputs "500 0 Usage: ...", try the second.
   2753         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
   2754         String command = String.format("vdc cryptfs enablecrypto %s \"%s\"", encryptMethod,
   2755                 ENCRYPTION_PASSWORD);
   2756         executeShellCommand(command, receiver, timeout, TimeUnit.MINUTES, 1);
   2757         if (receiver.getOutput().split(":")[0].matches("500 \\d+ Usage")) {
   2758             command = String.format("vdc cryptfs enablecrypto %s default", encryptMethod);
   2759             executeShellCommand(command, new NullOutputReceiver(), timeout, TimeUnit.MINUTES, 1);
   2760         }
   2761 
   2762         waitForDeviceNotAvailable("reboot", getCommandTimeout());
   2763         waitForDeviceOnline();  // Device will not become available until the user data is unlocked.
   2764 
   2765         return isDeviceEncrypted();
   2766     }
   2767 
   2768     /**
   2769      * {@inheritDoc}
   2770      */
   2771     @Override
   2772     public boolean unencryptDevice() throws DeviceNotAvailableException,
   2773             UnsupportedOperationException {
   2774         if (!isEncryptionSupported()) {
   2775             throw new UnsupportedOperationException(String.format("Can't unencrypt device %s: "
   2776                     + "encryption not supported", getSerialNumber()));
   2777         }
   2778 
   2779         if (!isDeviceEncrypted()) {
   2780             CLog.d("Device %s is already unencrypted, skipping", getSerialNumber());
   2781             return true;
   2782         }
   2783 
   2784         CLog.i("Unencrypting device %s", getSerialNumber());
   2785 
   2786         // If the device supports fastboot format, then we're done.
   2787         if (!mOptions.getUseFastbootErase()) {
   2788             rebootIntoBootloader();
   2789             fastbootWipePartition("userdata");
   2790             rebootUntilOnline();
   2791             waitForDeviceAvailable(ENCRYPTION_WIPE_TIMEOUT_MIN * 60 * 1000);
   2792             return true;
   2793         }
   2794 
   2795         // Determine if we need to format partition instead of wipe.
   2796         boolean format = false;
   2797         String output = executeShellCommand("vdc volume list");
   2798         String[] splitOutput;
   2799         if (output != null) {
   2800             splitOutput = output.split("\r?\n");
   2801             for (String line : splitOutput) {
   2802                 if (line.startsWith("110 ") && line.contains("sdcard /mnt/sdcard") &&
   2803                         !line.endsWith("0")) {
   2804                     format = true;
   2805                 }
   2806             }
   2807         }
   2808 
   2809         rebootIntoBootloader();
   2810         fastbootWipePartition("userdata");
   2811 
   2812         // If the device requires time to format the filesystem after fastboot erase userdata, wait
   2813         // for the device to reboot a second time.
   2814         if (mOptions.getUnencryptRebootTimeout() > 0) {
   2815             rebootUntilOnline();
   2816             if (waitForDeviceNotAvailable(mOptions.getUnencryptRebootTimeout())) {
   2817                 waitForDeviceOnline();
   2818             }
   2819         }
   2820 
   2821         if (format) {
   2822             CLog.d("Need to format sdcard for device %s", getSerialNumber());
   2823 
   2824             RecoveryMode cachedRecoveryMode = getRecoveryMode();
   2825             setRecoveryMode(RecoveryMode.ONLINE);
   2826 
   2827             output = executeShellCommand("vdc volume format sdcard");
   2828             if (output == null) {
   2829                 CLog.e("Command vdc volume format sdcard failed will no output for device %s:\n%s",
   2830                         getSerialNumber());
   2831                 setRecoveryMode(cachedRecoveryMode);
   2832                 return false;
   2833             }
   2834             splitOutput = output.split("\r?\n");
   2835             if (!splitOutput[splitOutput.length - 1].startsWith("200 ")) {
   2836                 CLog.e("Command vdc volume format sdcard failed for device %s:\n%s",
   2837                         getSerialNumber(), output);
   2838                 setRecoveryMode(cachedRecoveryMode);
   2839                 return false;
   2840             }
   2841 
   2842             setRecoveryMode(cachedRecoveryMode);
   2843         }
   2844 
   2845         reboot();
   2846 
   2847         return true;
   2848     }
   2849 
   2850     /**
   2851      * {@inheritDoc}
   2852      */
   2853     @Override
   2854     public boolean unlockDevice() throws DeviceNotAvailableException,
   2855             UnsupportedOperationException {
   2856         if (!isEncryptionSupported()) {
   2857             throw new UnsupportedOperationException(String.format("Can't unlock device %s: "
   2858                     + "encryption not supported", getSerialNumber()));
   2859         }
   2860 
   2861         if (!isDeviceEncrypted()) {
   2862             CLog.d("Device %s is not encrypted, skipping", getSerialNumber());
   2863             return true;
   2864         }
   2865 
   2866         CLog.i("Unlocking device %s", getSerialNumber());
   2867 
   2868         enableAdbRoot();
   2869 
   2870         // FIXME: currently, vcd checkpw can return an empty string when it never should.  Try 3
   2871         // times.
   2872         String output;
   2873         int i = 0;
   2874         do {
   2875             // Enter the password. Output will be:
   2876             // "200 [X] -1" if the password has already been entered correctly,
   2877             // "200 [X] 0" if the password is entered correctly,
   2878             // "200 [X] N" where N is any positive number if the password is incorrect,
   2879             // any other string if there is an error.
   2880             output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"",
   2881                     ENCRYPTION_PASSWORD)).trim();
   2882 
   2883             if (output.startsWith("200 ") && output.endsWith(" -1")) {
   2884                 return true;
   2885             }
   2886 
   2887             if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) {
   2888                 CLog.e("checkpw gave output '%s' while trying to unlock device %s",
   2889                         output, getSerialNumber());
   2890                 return false;
   2891             }
   2892 
   2893             getRunUtil().sleep(500);
   2894         } while (output.isEmpty() && ++i < 3);
   2895 
   2896         if (output.isEmpty()) {
   2897             CLog.e("checkpw gave no output while trying to unlock device %s");
   2898         }
   2899 
   2900         // Restart the framework. Output will be:
   2901         // "200 [X] 0" if the user data partition can be mounted,
   2902         // "200 [X] -1" if the user data partition can not be mounted (no correct password given),
   2903         // any other string if there is an error.
   2904         output = executeShellCommand("vdc cryptfs restart").trim();
   2905 
   2906         if (!(output.startsWith("200 ") &&  output.endsWith(" 0"))) {
   2907             CLog.e("restart gave output '%s' while trying to unlock device %s", output,
   2908                     getSerialNumber());
   2909             return false;
   2910         }
   2911 
   2912         waitForDeviceAvailable();
   2913 
   2914         return true;
   2915     }
   2916 
   2917     /**
   2918      * {@inheritDoc}
   2919      */
   2920     @Override
   2921     public boolean isDeviceEncrypted() throws DeviceNotAvailableException {
   2922         String output = getProperty("ro.crypto.state");
   2923 
   2924         if (output == null && isEncryptionSupported()) {
   2925             CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber());
   2926         }
   2927 
   2928         return "encrypted".equals(output);
   2929     }
   2930 
   2931     /**
   2932      * {@inheritDoc}
   2933      */
   2934     @Override
   2935     public boolean isEncryptionSupported() throws DeviceNotAvailableException {
   2936         if (!isEnableAdbRoot()) {
   2937             CLog.i("root is required for encryption");
   2938             mIsEncryptionSupported = false;
   2939             return mIsEncryptionSupported;
   2940         }
   2941         if (mIsEncryptionSupported != null) {
   2942             return mIsEncryptionSupported.booleanValue();
   2943         }
   2944         enableAdbRoot();
   2945         String output = executeShellCommand("vdc cryptfs enablecrypto").trim();
   2946         mIsEncryptionSupported = (output != null && output.startsWith(ENCRYPTION_SUPPORTED_CODE) &&
   2947                 output.contains(ENCRYPTION_SUPPORTED_USAGE));
   2948         return mIsEncryptionSupported;
   2949     }
   2950 
   2951     /**
   2952      * {@inheritDoc}
   2953      */
   2954     @Override
   2955     public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException {
   2956         if (mStateMonitor.waitForDeviceOnline(waitTime) == null) {
   2957             recoverDevice();
   2958         }
   2959     }
   2960 
   2961     /**
   2962      * {@inheritDoc}
   2963      */
   2964     @Override
   2965     public void waitForDeviceOnline() throws DeviceNotAvailableException {
   2966         if (mStateMonitor.waitForDeviceOnline() == null) {
   2967             recoverDevice();
   2968         }
   2969     }
   2970 
   2971     /**
   2972      * {@inheritDoc}
   2973      */
   2974     @Override
   2975     public void waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException {
   2976         if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) {
   2977             recoverDevice();
   2978         }
   2979     }
   2980 
   2981     /**
   2982      * {@inheritDoc}
   2983      */
   2984     @Override
   2985     public void waitForDeviceAvailable() throws DeviceNotAvailableException {
   2986         if (mStateMonitor.waitForDeviceAvailable() == null) {
   2987             recoverDevice();
   2988         }
   2989     }
   2990 
   2991     /**
   2992      * {@inheritDoc}
   2993      */
   2994     @Override
   2995     public boolean waitForDeviceNotAvailable(long waitTime) {
   2996         return mStateMonitor.waitForDeviceNotAvailable(waitTime);
   2997     }
   2998 
   2999     /**
   3000      * {@inheritDoc}
   3001      */
   3002     @Override
   3003     public boolean waitForDeviceInRecovery(long waitTime) {
   3004         return mStateMonitor.waitForDeviceInRecovery(waitTime);
   3005     }
   3006 
   3007     /**
   3008      * Small helper function to throw an NPE if the passed arg is null.  This should be used when
   3009      * some value will be stored and used later, in which case it'll avoid hard-to-trace
   3010      * asynchronous NullPointerExceptions by throwing the exception synchronously.  This is not
   3011      * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care
   3012      * of it in that case.
   3013      */
   3014     private void throwIfNull(Object obj) {
   3015         if (obj == null) throw new NullPointerException();
   3016     }
   3017 
   3018     /**
   3019      * Retrieve this device's recovery mechanism.
   3020      * <p/>
   3021      * Exposed for unit testing.
   3022      */
   3023     IDeviceRecovery getRecovery() {
   3024         return mRecovery;
   3025     }
   3026 
   3027     /**
   3028      * {@inheritDoc}
   3029      */
   3030     @Override
   3031     public void setRecovery(IDeviceRecovery recovery) {
   3032         throwIfNull(recovery);
   3033         mRecovery = recovery;
   3034     }
   3035 
   3036     /**
   3037      * {@inheritDoc}
   3038      */
   3039     @Override
   3040     public void setRecoveryMode(RecoveryMode mode) {
   3041         throwIfNull(mRecoveryMode);
   3042         mRecoveryMode = mode;
   3043     }
   3044 
   3045     /**
   3046      * {@inheritDoc}
   3047      */
   3048     @Override
   3049     public RecoveryMode getRecoveryMode() {
   3050         return mRecoveryMode;
   3051     }
   3052 
   3053     /**
   3054      * {@inheritDoc}
   3055      */
   3056     @Override
   3057     public void setFastbootEnabled(boolean fastbootEnabled) {
   3058         mFastbootEnabled = fastbootEnabled;
   3059     }
   3060 
   3061     /**
   3062      * {@inheritDoc}
   3063      */
   3064     @Override
   3065     public boolean isFastbootEnabled() {
   3066         return mFastbootEnabled;
   3067     }
   3068 
   3069     /**
   3070      * {@inheritDoc}
   3071      */
   3072     @Override
   3073     public void setFastbootPath(String fastbootPath) {
   3074         mFastbootPath = fastbootPath;
   3075         // ensure the device and its associated recovery use the same fastboot version.
   3076         mRecovery.setFastbootPath(fastbootPath);
   3077     }
   3078 
   3079     /**
   3080      * {@inheritDoc}
   3081      */
   3082     @Override
   3083     public String getFastbootPath() {
   3084         return mFastbootPath;
   3085     }
   3086 
   3087     /**
   3088      * {@inheritDoc}
   3089      */
   3090     @Override
   3091     public void setDeviceState(final TestDeviceState deviceState) {
   3092         if (!deviceState.equals(getDeviceState())) {
   3093             // disable state changes while fastboot lock is held, because issuing fastboot command
   3094             // will disrupt state
   3095             if (getDeviceState().equals(TestDeviceState.FASTBOOT) && mFastbootLock.isLocked()) {
   3096                 return;
   3097             }
   3098             mState = deviceState;
   3099             CLog.d("Device %s state is now %s", getSerialNumber(), deviceState);
   3100             mStateMonitor.setState(deviceState);
   3101         }
   3102     }
   3103 
   3104     /**
   3105      * {@inheritDoc}
   3106      */
   3107     @Override
   3108     public TestDeviceState getDeviceState() {
   3109         return mState;
   3110     }
   3111 
   3112     @Override
   3113     public boolean isAdbTcp() {
   3114         return mStateMonitor.isAdbTcp();
   3115     }
   3116 
   3117     /**
   3118      * {@inheritDoc}
   3119      */
   3120     @Override
   3121     public String switchToAdbTcp() throws DeviceNotAvailableException {
   3122         String ipAddress = getIpAddress();
   3123         if (ipAddress == null) {
   3124             CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber());
   3125             return null;
   3126         }
   3127         String port = "5555";
   3128         executeAdbCommand("tcpip", port);
   3129         // TODO: analyze result? wait for device offline?
   3130         return String.format("%s:%s", ipAddress, port);
   3131     }
   3132 
   3133     /**
   3134      * {@inheritDoc}
   3135      */
   3136     @Override
   3137     public boolean switchToAdbUsb() throws DeviceNotAvailableException {
   3138         executeAdbCommand("usb");
   3139         // TODO: analyze result? wait for device offline?
   3140         return true;
   3141     }
   3142 
   3143     /**
   3144      * {@inheritDoc}
   3145      */
   3146     @Override
   3147     public void setEmulatorProcess(Process p) {
   3148         mEmulatorProcess = p;
   3149 
   3150     }
   3151 
   3152     /**
   3153      * For emulator set {@link SizeLimitedOutputStream} to log output
   3154      * @param output to log the output
   3155      */
   3156     public void setEmulatorOutputStream(SizeLimitedOutputStream output) {
   3157         mEmulatorOutput = output;
   3158     }
   3159 
   3160     /**
   3161      * {@inheritDoc}
   3162      */
   3163     @Override
   3164     public void stopEmulatorOutput() {
   3165         if (mEmulatorOutput != null) {
   3166             mEmulatorOutput.delete();
   3167             mEmulatorOutput = null;
   3168         }
   3169     }
   3170 
   3171     /**
   3172      * {@inheritDoc}
   3173      */
   3174     @Override
   3175     public InputStreamSource getEmulatorOutput() {
   3176         if (getIDevice().isEmulator()) {
   3177             if (mEmulatorOutput == null) {
   3178                 CLog.w("Emulator output for %s was not captured in background",
   3179                         getSerialNumber());
   3180             } else {
   3181                 try {
   3182                     return new SnapshotInputStreamSource(mEmulatorOutput.getData());
   3183                 } catch (IOException e) {
   3184                     CLog.e("Failed to get %s data.", getSerialNumber());
   3185                     CLog.e(e);
   3186                 }
   3187             }
   3188         }
   3189         return new ByteArrayInputStreamSource(new byte[0]);
   3190     }
   3191 
   3192     /**
   3193      * {@inheritDoc}
   3194      */
   3195     @Override
   3196     public Process getEmulatorProcess() {
   3197         return mEmulatorProcess;
   3198     }
   3199 
   3200     /**
   3201      * @return <code>true</code> if adb root should be enabled on device
   3202      */
   3203     public boolean isEnableAdbRoot() {
   3204         return mOptions.isEnableAdbRoot();
   3205     }
   3206 
   3207     /**
   3208      * {@inheritDoc}
   3209      */
   3210     @Override
   3211     public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException {
   3212         throw new UnsupportedOperationException("No support for Package's feature");
   3213     }
   3214 
   3215     /**
   3216      * {@inheritDoc}
   3217      */
   3218     @Override
   3219     public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException {
   3220         throw new UnsupportedOperationException("No support for Package's feature");
   3221     }
   3222 
   3223     /**
   3224      * {@inheritDoc}
   3225      */
   3226     @Override
   3227     public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException {
   3228         throw new UnsupportedOperationException("No support for Package's feature");
   3229     }
   3230 
   3231     /**
   3232      * {@inheritDoc}
   3233      */
   3234     @Override
   3235     public TestDeviceOptions getOptions() {
   3236         return mOptions;
   3237     }
   3238 
   3239     /**
   3240      * {@inheritDoc}
   3241      */
   3242     @Override
   3243     public int getApiLevel() throws DeviceNotAvailableException {
   3244         int apiLevel = UNKNOWN_API_LEVEL;
   3245         try {
   3246             String prop = getProperty("ro.build.version.sdk");
   3247             apiLevel = Integer.parseInt(prop);
   3248         } catch (NumberFormatException nfe) {
   3249             // ignore, return unknown instead
   3250         }
   3251         return apiLevel;
   3252     }
   3253 
   3254     @Override
   3255     public IDeviceStateMonitor getMonitor() {
   3256         return mStateMonitor;
   3257     }
   3258 
   3259     /**
   3260      * {@inheritDoc}
   3261      */
   3262     @Override
   3263     public boolean waitForDeviceShell(long waitTime) {
   3264         return mStateMonitor.waitForDeviceShell(waitTime);
   3265     }
   3266 
   3267     @Override
   3268     public DeviceAllocationState getAllocationState() {
   3269         return mAllocationState;
   3270     }
   3271 
   3272     /**
   3273      * {@inheritDoc}
   3274      * <p>
   3275      * Process the DeviceEvent, which may or may not transition this device to a new allocation
   3276      * state.
   3277      * </p>
   3278      */
   3279     @Override
   3280     public DeviceEventResponse handleAllocationEvent(DeviceEvent event) {
   3281 
   3282         // keep track of whether state has actually changed or not
   3283         boolean stateChanged = false;
   3284         DeviceAllocationState newState;
   3285         DeviceAllocationState oldState = mAllocationState;
   3286         mAllocationStateLock.lock();
   3287         try {
   3288             // update oldState here, just in case in changed before we got lock
   3289             oldState = mAllocationState;
   3290             newState = mAllocationState.handleDeviceEvent(event);
   3291             if (oldState != newState) {
   3292                 // state has changed! record this fact, and store the new state
   3293                 stateChanged = true;
   3294                 mAllocationState = newState;
   3295             }
   3296         } finally {
   3297             mAllocationStateLock.unlock();
   3298         }
   3299         if (stateChanged && mAllocationMonitor != null) {
   3300             // state has changed! Lets inform the allocation monitor listener
   3301             mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState);
   3302         }
   3303         return new DeviceEventResponse(newState, stateChanged);
   3304     }
   3305 
   3306     /**
   3307      * Helper to get the time difference between the device and the host. Use Epoch time.
   3308      * Exposed for testing.
   3309      */
   3310     protected long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException {
   3311         Long deviceTime = getDeviceDate();
   3312         long offset = 0;
   3313 
   3314         if (date == null) {
   3315             date = new Date();
   3316         }
   3317 
   3318         offset = date.getTime() - deviceTime * 1000;
   3319         CLog.d("Time offset = %d ms", offset);
   3320         return offset;
   3321     }
   3322 
   3323     /**
   3324      * {@inheritDoc}
   3325      */
   3326     @Override
   3327     public void setDate(Date date) throws DeviceNotAvailableException {
   3328         if (date == null) {
   3329             date = new Date();
   3330         }
   3331         long timeOffset = getDeviceTimeOffset(date);
   3332         // no need to set date
   3333         if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) {
   3334             return;
   3335         }
   3336         String dateString = null;
   3337         if (getApiLevel() < 23) {
   3338             // set date in epoch format
   3339             dateString = Long.toString(date.getTime() / 1000); //ms to s
   3340         } else {
   3341             // set date with POSIX like params
   3342             SimpleDateFormat sdf = new java.text.SimpleDateFormat(
   3343                     "MMddHHmmyyyy.ss");
   3344             sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
   3345             dateString = sdf.format(date);
   3346         }
   3347         // best effort, no verification
   3348         executeShellCommand("date -u " + dateString);
   3349     }
   3350 
   3351     /**
   3352      * {@inheritDoc}
   3353      */
   3354     @Override
   3355     public long getDeviceDate() throws DeviceNotAvailableException {
   3356         String deviceTimeString = executeShellCommand("date +%s");
   3357         Long deviceTime = null;
   3358         try {
   3359             deviceTime = Long.valueOf(deviceTimeString.trim());
   3360         } catch (NumberFormatException nfe) {
   3361             CLog.i("Invalid device time: \"%s\", ignored.", nfe);
   3362             return 0;
   3363         }
   3364         return deviceTime;
   3365     }
   3366 
   3367     /**
   3368      * {@inheritDoc}
   3369      */
   3370     @Override
   3371     public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException {
   3372         return mStateMonitor.waitForBootComplete(timeOut);
   3373     }
   3374 
   3375     /**
   3376      * {@inheritDoc}
   3377      */
   3378     @Override
   3379     public ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
   3380         throw new UnsupportedOperationException("No support for user's feature.");
   3381     }
   3382 
   3383     /**
   3384      * {@inheritDoc}
   3385      */
   3386     @Override
   3387     public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
   3388         throw new UnsupportedOperationException("No support for user's feature.");
   3389     }
   3390 
   3391     /**
   3392      * {@inheritDoc}
   3393      */
   3394     @Override
   3395     public boolean isMultiUserSupported() throws DeviceNotAvailableException {
   3396         throw new UnsupportedOperationException("No support for user's feature.");
   3397     }
   3398 
   3399     /**
   3400      * {@inheritDoc}
   3401      */
   3402     @Override
   3403     public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException {
   3404         throw new UnsupportedOperationException("No support for user's feature.");
   3405     }
   3406 
   3407     /**
   3408      * {@inheritDoc}
   3409      */
   3410     @Override
   3411     public int createUser(String name, boolean guest, boolean ephemeral)
   3412             throws DeviceNotAvailableException, IllegalStateException {
   3413         throw new UnsupportedOperationException("No support for user's feature.");
   3414     }
   3415 
   3416     /**
   3417      * {@inheritDoc}
   3418      */
   3419     @Override
   3420     public boolean removeUser(int userId) throws DeviceNotAvailableException {
   3421         throw new UnsupportedOperationException("No support for user's feature.");
   3422     }
   3423 
   3424     /**
   3425      * {@inheritDoc}
   3426      */
   3427     @Override
   3428     public boolean startUser(int userId) throws DeviceNotAvailableException {
   3429         throw new UnsupportedOperationException("No support for user's feature.");
   3430     }
   3431 
   3432     /**
   3433      * {@inheritDoc}
   3434      */
   3435     @Override
   3436     public boolean stopUser(int userId) throws DeviceNotAvailableException {
   3437         throw new UnsupportedOperationException("No support for user's feature.");
   3438     }
   3439 
   3440     /**
   3441      * {@inheritDoc}
   3442      */
   3443     @Override
   3444     public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
   3445             throws DeviceNotAvailableException {
   3446         throw new UnsupportedOperationException("No support for user's feature.");
   3447     }
   3448 
   3449     /**
   3450      * {@inheritDoc}
   3451      */
   3452     @Override
   3453     public void remountSystemWritable() throws DeviceNotAvailableException {
   3454         String verity = getProperty("partition.system.verified");
   3455         // have the property set (regardless state) implies verity is enabled, so we send adb
   3456         // command to disable verity
   3457         if (verity != null && !verity.isEmpty()) {
   3458             executeAdbCommand("disable-verity");
   3459             reboot();
   3460         }
   3461         executeAdbCommand("remount");
   3462         waitForDeviceAvailable();
   3463     }
   3464 
   3465     /**
   3466      * {@inheritDoc}
   3467      */
   3468     @Override
   3469     public Integer getPrimaryUserId() throws DeviceNotAvailableException {
   3470         throw new UnsupportedOperationException("No support for user's feature.");
   3471     }
   3472 
   3473     /**
   3474      * {@inheritDoc}
   3475      */
   3476     @Override
   3477     public int getCurrentUser() throws DeviceNotAvailableException {
   3478         throw new UnsupportedOperationException("No support for user's feature.");
   3479     }
   3480 
   3481     /**
   3482      * {@inheritDoc}
   3483      */
   3484     @Override
   3485     public int getUserFlags(int userId) throws DeviceNotAvailableException {
   3486         throw new UnsupportedOperationException("No support for user's feature.");
   3487     }
   3488 
   3489     /**
   3490      * {@inheritDoc}
   3491      */
   3492     @Override
   3493     public int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
   3494         throw new UnsupportedOperationException("No support for user's feature.");
   3495     }
   3496 
   3497     /**
   3498      * {@inheritDoc}
   3499      */
   3500     @Override
   3501     public boolean switchUser(int userId) throws DeviceNotAvailableException {
   3502         throw new UnsupportedOperationException("No support for user's feature.");
   3503     }
   3504 
   3505     /**
   3506      * {@inheritDoc}
   3507      */
   3508     @Override
   3509     public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException {
   3510         throw new UnsupportedOperationException("No support for user's feature.");
   3511     }
   3512 
   3513     /**
   3514      * {@inheritDoc}
   3515      */
   3516     @Override
   3517     public boolean isUserRunning(int userId) throws DeviceNotAvailableException {
   3518         throw new UnsupportedOperationException("No support for user's feature.");
   3519     }
   3520 
   3521     /**
   3522      * {@inheritDoc}
   3523      */
   3524     @Override
   3525     public boolean hasFeature(String feature) throws DeviceNotAvailableException {
   3526         throw new UnsupportedOperationException("No support pm's features.");
   3527     }
   3528 
   3529     /**
   3530      * {@inheritDoc}
   3531      */
   3532     @Override
   3533     public String getSetting(String namespace, String key)
   3534             throws DeviceNotAvailableException {
   3535         throw new UnsupportedOperationException("No support for setting's feature.");
   3536     }
   3537 
   3538     /**
   3539      * {@inheritDoc}
   3540      */
   3541     @Override
   3542     public String getSetting(int userId, String namespace, String key)
   3543             throws DeviceNotAvailableException {
   3544         throw new UnsupportedOperationException("No support for setting's feature.");
   3545     }
   3546 
   3547     /**
   3548      * {@inheritDoc}
   3549      */
   3550     @Override
   3551     public void setSetting(String namespace, String key, String value)
   3552             throws DeviceNotAvailableException {
   3553         throw new UnsupportedOperationException("No support for setting's feature.");
   3554     }
   3555 
   3556     /**
   3557      * {@inheritDoc}
   3558      */
   3559     @Override
   3560     public void setSetting(int userId, String namespace, String key, String value)
   3561             throws DeviceNotAvailableException {
   3562         throw new UnsupportedOperationException("No support for setting's feature.");
   3563     }
   3564 
   3565     /**
   3566      * {@inheritDoc}
   3567      */
   3568     @Override
   3569     public String getBuildSigningKeys() throws DeviceNotAvailableException {
   3570         String buildTags = getProperty(BUILD_TAGS);
   3571         if (buildTags != null) {
   3572             String[] tags = buildTags.split(",");
   3573             for (String tag : tags) {
   3574                 Matcher m = KEYS_PATTERN.matcher(tag);
   3575                 if (m.matches()) {
   3576                     return tag;
   3577                 }
   3578             }
   3579         }
   3580         return null;
   3581     }
   3582 
   3583     /**
   3584      * {@inheritDoc}
   3585      */
   3586     @Override
   3587     public String getAndroidId(int userId) throws DeviceNotAvailableException {
   3588         throw new UnsupportedOperationException("No support for user's feature.");
   3589     }
   3590 
   3591     /**
   3592      * {@inheritDoc}
   3593      */
   3594     @Override
   3595     public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
   3596         throw new UnsupportedOperationException("No support for user's feature.");
   3597     }
   3598 
   3599     /** {@inheritDoc} */
   3600     @Override
   3601     public boolean setDeviceOwner(String componentName, int userId)
   3602             throws DeviceNotAvailableException {
   3603         throw new UnsupportedOperationException("No support for user's feature.");
   3604     }
   3605 
   3606     /** {@inheritDoc} */
   3607     @Override
   3608     public boolean removeAdmin(String componentName, int userId)
   3609             throws DeviceNotAvailableException {
   3610         throw new UnsupportedOperationException("No support for user's feature.");
   3611     }
   3612 
   3613     /** {@inheritDoc} */
   3614     @Override
   3615     public void removeOwners() throws DeviceNotAvailableException {
   3616         throw new UnsupportedOperationException("No support for user's feature.");
   3617     }
   3618 
   3619     /**
   3620      * {@inheritDoc}
   3621      */
   3622     @Override
   3623     public void disableKeyguard() throws DeviceNotAvailableException {
   3624         throw new UnsupportedOperationException("No support for Window Manager's features");
   3625     }
   3626 
   3627     /** {@inheritDoc} */
   3628     @Override
   3629     public String getDeviceClass() {
   3630         IDevice device = getIDevice();
   3631         if (device == null) {
   3632             CLog.w("No IDevice instance, cannot determine device class.");
   3633             return "";
   3634         }
   3635         return device.getClass().getSimpleName();
   3636     }
   3637 
   3638     /**
   3639      * {@inheritDoc}
   3640      */
   3641     @Override
   3642     public void preInvocationSetup(IBuildInfo info)
   3643             throws TargetSetupError, DeviceNotAvailableException {
   3644         // Default implementation empty on purpose
   3645     }
   3646 
   3647     /**
   3648      * {@inheritDoc}
   3649      */
   3650     @Override
   3651     public void postInvocationTearDown() {
   3652         // Default implementation empty on purpose
   3653     }
   3654 
   3655     /**
   3656      * {@inheritDoc}
   3657      */
   3658     @Override
   3659     public boolean isHeadless() throws DeviceNotAvailableException {
   3660         if (getProperty(HEADLESS_PROP) != null) {
   3661             return true;
   3662         }
   3663         return false;
   3664     }
   3665 
   3666     protected void checkApiLevelAgainst(String feature, int strictMinLevel) {
   3667         try {
   3668             if (getApiLevel() < strictMinLevel){
   3669                 throw new IllegalArgumentException(String.format("%s not supported on %s. "
   3670                         + "Must be API %d.", feature, getSerialNumber(), strictMinLevel));
   3671             }
   3672         } catch (DeviceNotAvailableException e) {
   3673             throw new RuntimeException("Device became unavailable while checking API level", e);
   3674         }
   3675     }
   3676 
   3677     /**
   3678      * {@inheritDoc}
   3679      */
   3680     @Override
   3681     public DeviceDescriptor getDeviceDescriptor() {
   3682         IDeviceSelection selector = new DeviceSelectionOptions();
   3683         IDevice idevice = getIDevice();
   3684         return new DeviceDescriptor(
   3685                 idevice.getSerialNumber(),
   3686                 idevice instanceof StubDevice,
   3687                 getAllocationState(),
   3688                 getDisplayString(selector.getDeviceProductType(idevice)),
   3689                 getDisplayString(selector.getDeviceProductVariant(idevice)),
   3690                 getDisplayString(idevice.getProperty("ro.build.version.sdk")),
   3691                 getDisplayString(idevice.getProperty("ro.build.id")),
   3692                 getDisplayString(selector.getBatteryLevel(idevice)),
   3693                 getDeviceClass(),
   3694                 getDisplayString(getMacAddress()),
   3695                 getDisplayString(getSimState()),
   3696                 getDisplayString(getSimOperator()));
   3697     }
   3698 
   3699     /**
   3700      * Return the displayable string for given object
   3701      */
   3702     private String getDisplayString(Object o) {
   3703         return o == null ? "unknown" : o.toString();
   3704     }
   3705 
   3706     /**
   3707      * {@inheritDoc}
   3708      */
   3709     @Override
   3710     public List<ProcessInfo> getProcesses() throws DeviceNotAvailableException {
   3711         return PsParser.getProcesses(executeShellCommand(PS_COMMAND));
   3712     }
   3713 
   3714     /**
   3715      * {@inheritDoc}
   3716      */
   3717     @Override
   3718     public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException {
   3719         List<ProcessInfo> processList = getProcesses();
   3720         for (ProcessInfo processInfo : processList) {
   3721             if (processName.equals(processInfo.getName())) {
   3722                 return processInfo;
   3723             }
   3724         }
   3725         return null;
   3726     }
   3727 
   3728     /**
   3729      * Validates that the given input is a valid MAC address
   3730      *
   3731      * @param address input to validate
   3732      * @return true if the input is a valid MAC address
   3733      */
   3734     boolean isMacAddress(String address) {
   3735         Pattern macPattern = Pattern.compile(MAC_ADDRESS_PATTERN);
   3736         Matcher macMatcher = macPattern.matcher(address);
   3737         return macMatcher.find();
   3738     }
   3739 
   3740     /**
   3741      * {@inheritDoc}
   3742      */
   3743     @Override
   3744     public String getMacAddress() {
   3745         if (mIDevice instanceof StubDevice) {
   3746             // Do not query MAC addresses from stub devices.
   3747             return null;
   3748         }
   3749         if (!TestDeviceState.ONLINE.equals(mState)) {
   3750             // Only query MAC addresses from online devices.
   3751             return null;
   3752         }
   3753         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
   3754         try {
   3755             mIDevice.executeShellCommand(MAC_ADDRESS_COMMAND, receiver);
   3756         } catch (IOException | TimeoutException | AdbCommandRejectedException |
   3757                 ShellCommandUnresponsiveException e) {
   3758             CLog.w("Failed to query MAC address for %s", mIDevice.getSerialNumber());
   3759             CLog.w(e);
   3760         }
   3761         String output = receiver.getOutput().trim();
   3762         if (isMacAddress(output)) {
   3763             return output;
   3764         }
   3765         CLog.d("No valid MAC address queried from device %s", mIDevice.getSerialNumber());
   3766         return null;
   3767     }
   3768 
   3769     /** {@inheritDoc} */
   3770     @Override
   3771     public String getSimState() {
   3772         try {
   3773             return getProperty(SIM_STATE_PROP);
   3774         } catch (DeviceNotAvailableException e) {
   3775             CLog.w("Failed to query SIM state for %s", mIDevice.getSerialNumber());
   3776             CLog.w(e);
   3777             return null;
   3778         }
   3779     }
   3780 
   3781     /** {@inheritDoc} */
   3782     @Override
   3783     public String getSimOperator() {
   3784         try {
   3785             return getProperty(SIM_OPERATOR_PROP);
   3786         } catch (DeviceNotAvailableException e) {
   3787             CLog.w("Failed to query SIM operator for %s", mIDevice.getSerialNumber());
   3788             CLog.w(e);
   3789             return null;
   3790         }
   3791     }
   3792 }
   3793