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