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          */
run()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 
AdbAction(String[] cmd)233         AdbAction(String[] cmd) {
234             mCmd = cmd;
235         }
236 
237         @Override
run()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 
AdbShellAction(String[] cmd, long timeout)261         AdbShellAction(String[] cmd, long timeout) {
262             mCmd = cmd;
263             mTimeout = timeout;
264         }
265 
266         @Override
run()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      */
NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor)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
getRunUtil()299     protected IRunUtil getRunUtil() {
300         return RunUtil.getDefault();
301     }
302 
303     /** Set the Clock instance to use. */
304     @VisibleForTesting
setClock(Clock clock)305     protected void setClock(Clock clock) {
306         mClock = clock;
307     }
308 
309     /**
310      * {@inheritDoc}
311      */
312     @Override
setOptions(TestDeviceOptions options)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      */
setTmpLogcatSize(long size)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      */
setLogStartDelay(int delay)334     protected void setLogStartDelay(int delay) {
335         mLogStartDelay = delay;
336     }
337 
338     /**
339      * {@inheritDoc}
340      */
341     @Override
getIDevice()342     public IDevice getIDevice() {
343         synchronized (mIDevice) {
344             return mIDevice;
345         }
346     }
347 
348     /**
349      * {@inheritDoc}
350      */
351     @Override
setIDevice(IDevice newDevice)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
getSerialNumber()367     public String getSerialNumber() {
368         return getIDevice().getSerialNumber();
369     }
370 
nullOrEmpty(String string)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      */
internalGetProperty(String propName, String fastbootVar, String description)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
getProperty(final String name)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
getBootloaderVersion()452     public String getBootloaderVersion() throws UnsupportedOperationException,
453             DeviceNotAvailableException {
454         return internalGetProperty("ro.bootloader", "version-bootloader", "Bootloader");
455     }
456 
457     @Override
getBasebandVersion()458     public String getBasebandVersion() throws DeviceNotAvailableException {
459         return internalGetProperty("gsm.version.baseband", "version-baseband", "Baseband");
460     }
461 
462     /**
463      * {@inheritDoc}
464      */
465     @Override
getProductType()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      */
internalGetProductType(int retryAttempts)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
getFastbootProductType()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
getProductVariant()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
getFastbootProductVariant()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 
getFastbootVariable(String variableName)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
getBuildAlias()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
getBuildId()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
getBuildFlavor()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
executeShellCommand(final String command, final IShellOutputReceiver receiver)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
executeShellCommand(final String command, final IShellOutputReceiver receiver, final long maxTimeToOutputShellResponse, final TimeUnit timeUnit, final int retryAttempts)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
executeShellCommand( final String command, final IShellOutputReceiver receiver, final long maxTimeoutForCommand, final long maxTimeToOutputShellResponse, final TimeUnit timeUnit, final int retryAttempts)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
executeShellCommand(String command)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
executeShellV2Command(String cmd)681     public CommandResult executeShellV2Command(String cmd) throws DeviceNotAvailableException {
682         return executeShellV2Command(cmd, getCommandTimeout(), TimeUnit.MILLISECONDS);
683     }
684 
685     /** {@inheritDoc} */
686     @Override
executeShellV2Command( String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit)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
executeShellV2Command( String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)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
runInstrumentationTests( final IRemoteAndroidTestRunner runner, final Collection<ITestLifeCycleReceiver> listeners)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
runInstrumentationTestsAsUser( final IRemoteAndroidTestRunner runner, int userId, final Collection<ITestLifeCycleReceiver> listeners)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      */
appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId)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      */
resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, String oldRunTimeOptions)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
testRunFailed(String message)793         public void testRunFailed(String message) {
794             mIsRunFailure = true;
795         }
796 
isRunFailure()797         public boolean isRunFailure() {
798             return mIsRunFailure;
799         }
800     }
801 
802     /** {@inheritDoc} */
803     @Override
runInstrumentationTests( IRemoteAndroidTestRunner runner, ITestLifeCycleReceiver... listeners)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
runInstrumentationTestsAsUser( IRemoteAndroidTestRunner runner, int userId, ITestLifeCycleReceiver... listeners)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
isRuntimePermissionSupported()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      */
ensureRuntimePermissionSupported()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
installPackage(final File packageFile, final boolean reinstall, final String... extraArgs)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
installPackage(File packageFile, boolean reinstall, boolean grantPermissions, String... extraArgs)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
installPackageForUser(File packageFile, boolean reinstall, int userId, String... extraArgs)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
installPackageForUser(File packageFile, boolean reinstall, boolean grantPermissions, int userId, String... extraArgs)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
uninstallPackage(final String packageName)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
pullFile(final String remoteFilePath, final File localFile)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
pullFile(String remoteFilePath)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
pullFileContents(String remoteFilePath)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
pullFileFromExternal(String remoteFilePath)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      */
interpolatePathVariables(String path)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
pushFile(final File localFile, final String remoteFilePath)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
pushString(final String contents, final String remoteFilePath)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
doesFileExist(String destPath)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
getExternalStoreFreeSpace()1074     public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
1075         String externalStorePath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
1076         return getPartitionFreeSpace(externalStorePath);
1077     }
1078 
1079     /** {@inheritDoc} */
1080     @Override
getPartitionFreeSpace(String partition)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      */
getDfOutput(String externalStorePath)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      */
parseFreeSpaceFromAvailable(String dfOutput)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      */
parseFreeSpaceFromFree(String externalStorePath, String dfOutput)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      */
parseFreeSpaceFromModernOutput(String dfOutput)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
getMountPoint(String mountName)1216     public String getMountPoint(String mountName) {
1217         return mStateMonitor.getMountPoint(mountName);
1218     }
1219 
1220     /**
1221      * {@inheritDoc}
1222      */
1223     @Override
getMountPointInfo()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
getMountPointInfo(String mountpoint)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
getFileEntry(String path)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      */
getFileEntry(FileEntry entry)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
isDirectory(String path)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
getChildren(String path)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      */
getFileListingService()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
pushDir(File localFileDir, String deviceFilePath)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
pullDir(String deviceFilePath, File localDir)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
syncFiles(File localFileDir, String deviceFilePath)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      */
syncFiles(File localFileDir, final IFileEntry remoteFileEntry)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      */
getFileChildren(final FileEntry remoteFileEntry)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 
FileQueryAction(FileEntry remoteFileEntry, FileListingService service)1522         FileQueryAction(FileEntry remoteFileEntry, FileListingService service) {
1523             throwIfNull(remoteFileEntry);
1524             throwIfNull(service);
1525             mRemoteFileEntry = remoteFileEntry;
1526             mService = service;
1527         }
1528 
1529         @Override
run()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
accept(File dir, String name)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      */
getDeviceTimezone()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
isNewer(File localFile, IFileEntry entry)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
executeAdbCommand(String... cmdArgs)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
executeFastbootCommand(String... cmdArgs)1616     public CommandResult executeFastbootCommand(String... cmdArgs)
1617             throws DeviceNotAvailableException, UnsupportedOperationException {
1618         return doFastbootCommand(getCommandTimeout(), cmdArgs);
1619     }
1620 
1621     /**
1622      * {@inheritDoc}
1623      */
1624     @Override
executeFastbootCommand(long timeout, String... cmdArgs)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
executeLongFastbootCommand(String... cmdArgs)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      */
doFastbootCommand(final long timeout, String... cmdArgs)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
getUseFastbootErase()1688     public boolean getUseFastbootErase() {
1689         return mOptions.getUseFastbootErase();
1690     }
1691 
1692     /**
1693      * {@inheritDoc}
1694      */
1695     @Override
setUseFastbootErase(boolean useFastbootErase)1696     public void setUseFastbootErase(boolean useFastbootErase) {
1697         mOptions.setUseFastbootErase(useFastbootErase);
1698     }
1699 
1700     /**
1701      * {@inheritDoc}
1702      */
1703     @Override
fastbootWipePartition(String partition)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      */
isRecoveryNeeded(CommandResult fastbootResult)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. */
getCommandTimeout()1738     long getCommandTimeout() {
1739         return mCmdTimeout;
1740     }
1741 
1742     /**
1743      * Set the max time allowed in ms for commands.
1744      */
setLongCommandTimeout(long timeout)1745     void setLongCommandTimeout(long timeout) {
1746         mLongCmdTimeout = timeout;
1747     }
1748 
1749     /**
1750      * Get the max time allowed in ms for commands.
1751      */
getLongCommandTimeout()1752     long getLongCommandTimeout() {
1753         return mLongCmdTimeout;
1754     }
1755 
1756     /** Set the max time allowed in ms for commands. */
setCommandTimeout(long timeout)1757     void setCommandTimeout(long timeout) {
1758         mCmdTimeout = timeout;
1759     }
1760 
1761     /**
1762      * Builds the OS command for the given adb command and args
1763      */
buildAdbCommand(String... commandArgs)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 */
buildAdbShellCommand(String command)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      */
buildFastbootCommand(String... commandArgs)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      */
performDeviceAction(String actionDescription, final DeviceAction action, int retryAttempts)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      */
logDeviceActionException(String actionDescription, Exception e)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      */
getExceptionMessage(Exception e)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
recoverDevice()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      */
recoverDeviceFromBootloader()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 
recoverDeviceInRecovery()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
startLogcat()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
clearLogcat()1951     public void clearLogcat() {
1952         if (mLogcatReceiver != null) {
1953             mLogcatReceiver.clear();
1954         }
1955     }
1956 
1957     /** {@inheritDoc} */
1958     @Override
1959     @SuppressWarnings("MustBeClosedChecker")
getLogcat()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")
getLogcat(int maxBytes)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
getLogcatSince(long date)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
getLogcatDump()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
stopLogcat()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
createLogcatReceiver()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
getBugreport()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      */
bugreportzFallback()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
logBugreport(String dataName, ITestLogger listener)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
takeBugreport()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
getBugreportz()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
getBugreportzInternal()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 
getBugreportInternal()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
getScreenshot()2293     public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
2294         throw new UnsupportedOperationException("No support for Screenshot");
2295     }
2296 
2297     /**
2298      * {@inheritDoc}
2299      */
2300     @Override
getScreenshot(String format)2301     public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
2302         throw new UnsupportedOperationException("No support for Screenshot");
2303     }
2304 
2305     /** {@inheritDoc} */
2306     @Override
getScreenshot(String format, boolean rescale)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
clearLastConnectedWifiNetwork()2314     public void clearLastConnectedWifiNetwork() {
2315         mLastConnectedWifiSsid = null;
2316         mLastConnectedWifiPsk = null;
2317     }
2318 
2319     /**
2320      * {@inheritDoc}
2321      */
2322     @Override
connectToWifiNetwork(String wifiSsid, String wifiPsk)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
connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)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
checkConnectivity()2392     public boolean checkConnectivity() throws DeviceNotAvailableException {
2393         IWifiHelper wifi = createWifiHelper();
2394         return wifi.checkConnectivity(mOptions.getConnCheckUrl());
2395     }
2396 
2397     /**
2398      * {@inheritDoc}
2399      */
2400     @Override
connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)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
connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)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
isWifiEnabled()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      */
checkWifiConnection(String wifiSSID)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
disconnectFromWifi()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
getIpAddress()2483     public String getIpAddress() throws DeviceNotAvailableException {
2484         IWifiHelper wifi = createWifiHelper();
2485         return wifi.getIpAddress();
2486     }
2487 
2488     /**
2489      * {@inheritDoc}
2490      */
2491     @Override
enableNetworkMonitor()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
disableNetworkMonitor()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
createWifiHelper()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
clearErrorDialogs()2554     public boolean clearErrorDialogs() throws DeviceNotAvailableException {
2555         throw new UnsupportedOperationException("No support for Screen's features");
2556     }
2557 
2558     /** {@inheritDoc} */
2559     @Override
getKeyguardState()2560     public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException {
2561         throw new UnsupportedOperationException("No support for keyguard querying.");
2562     }
2563 
getDeviceStateMonitor()2564     IDeviceStateMonitor getDeviceStateMonitor() {
2565         return mStateMonitor;
2566     }
2567 
2568     /**
2569      * {@inheritDoc}
2570      */
2571     @Override
postBootSetup()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      */
prePostBootSetup()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      */
postBootWifiSetup()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 
reconnectToWifiNetwork()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
rebootIntoBootloader()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 
doAdbRebootBootloader()2652     private void doAdbRebootBootloader() throws DeviceNotAvailableException {
2653         doAdbReboot("bootloader");
2654     }
2655 
2656     /**
2657      * {@inheritDoc}
2658      */
2659     @Override
reboot()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
rebootUntilOnline()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
rebootIntoRecovery()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
nonBlockingReboot()2717     public void nonBlockingReboot() throws DeviceNotAvailableException {
2718         doReboot();
2719     }
2720 
2721     @VisibleForTesting
doReboot()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      */
doAdbReboot(final String into)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 
waitForDeviceNotAvailable(String operationDesc, long time)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
enableAdbRoot()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
disableAdbRoot()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      */
postAdbRootAction()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      */
postAdbUnrootAction()2856     public void postAdbUnrootAction() throws DeviceNotAvailableException {
2857         // Empty on purpose.
2858     }
2859 
2860     /**
2861      * {@inheritDoc}
2862      */
2863     @Override
isAdbRoot()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
encryptDevice(boolean inplace)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
unencryptDevice()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
unlockDevice()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
isDeviceEncrypted()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
isEncryptionSupported()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
waitForDeviceOnline(long waitTime)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
waitForDeviceOnline()3117     public void waitForDeviceOnline() throws DeviceNotAvailableException {
3118         if (mStateMonitor.waitForDeviceOnline() == null) {
3119             recoverDevice();
3120         }
3121     }
3122 
3123     /**
3124      * {@inheritDoc}
3125      */
3126     @Override
waitForDeviceAvailable(long waitTime)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
waitForDeviceAvailable()3137     public void waitForDeviceAvailable() throws DeviceNotAvailableException {
3138         if (mStateMonitor.waitForDeviceAvailable() == null) {
3139             recoverDevice();
3140         }
3141     }
3142 
3143     /**
3144      * {@inheritDoc}
3145      */
3146     @Override
waitForDeviceNotAvailable(long waitTime)3147     public boolean waitForDeviceNotAvailable(long waitTime) {
3148         return mStateMonitor.waitForDeviceNotAvailable(waitTime);
3149     }
3150 
3151     /**
3152      * {@inheritDoc}
3153      */
3154     @Override
waitForDeviceInRecovery(long waitTime)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      */
throwIfNull(Object obj)3166     private void throwIfNull(Object obj) {
3167         if (obj == null) throw new NullPointerException();
3168     }
3169 
3170     /** Retrieve this device's recovery mechanism. */
3171     @VisibleForTesting
getRecovery()3172     IDeviceRecovery getRecovery() {
3173         return mRecovery;
3174     }
3175 
3176     /**
3177      * {@inheritDoc}
3178      */
3179     @Override
setRecovery(IDeviceRecovery recovery)3180     public void setRecovery(IDeviceRecovery recovery) {
3181         throwIfNull(recovery);
3182         mRecovery = recovery;
3183     }
3184 
3185     /**
3186      * {@inheritDoc}
3187      */
3188     @Override
setRecoveryMode(RecoveryMode mode)3189     public void setRecoveryMode(RecoveryMode mode) {
3190         throwIfNull(mRecoveryMode);
3191         mRecoveryMode = mode;
3192     }
3193 
3194     /**
3195      * {@inheritDoc}
3196      */
3197     @Override
getRecoveryMode()3198     public RecoveryMode getRecoveryMode() {
3199         return mRecoveryMode;
3200     }
3201 
3202     /**
3203      * {@inheritDoc}
3204      */
3205     @Override
setFastbootEnabled(boolean fastbootEnabled)3206     public void setFastbootEnabled(boolean fastbootEnabled) {
3207         mFastbootEnabled = fastbootEnabled;
3208     }
3209 
3210     /**
3211      * {@inheritDoc}
3212      */
3213     @Override
isFastbootEnabled()3214     public boolean isFastbootEnabled() {
3215         return mFastbootEnabled;
3216     }
3217 
3218     /**
3219      * {@inheritDoc}
3220      */
3221     @Override
setFastbootPath(String fastbootPath)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
getFastbootPath()3232     public String getFastbootPath() {
3233         return mFastbootPath;
3234     }
3235 
3236     /**
3237      * {@inheritDoc}
3238      */
3239     @Override
setDeviceState(final TestDeviceState deviceState)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
getDeviceState()3257     public TestDeviceState getDeviceState() {
3258         return mState;
3259     }
3260 
3261     @Override
isAdbTcp()3262     public boolean isAdbTcp() {
3263         return mStateMonitor.isAdbTcp();
3264     }
3265 
3266     /**
3267      * {@inheritDoc}
3268      */
3269     @Override
switchToAdbTcp()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
switchToAdbUsb()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
setEmulatorProcess(Process p)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      */
setEmulatorOutputStream(SizeLimitedOutputStream output)3305     public void setEmulatorOutputStream(SizeLimitedOutputStream output) {
3306         mEmulatorOutput = output;
3307     }
3308 
3309     /**
3310      * {@inheritDoc}
3311      */
3312     @Override
stopEmulatorOutput()3313     public void stopEmulatorOutput() {
3314         if (mEmulatorOutput != null) {
3315             mEmulatorOutput.delete();
3316             mEmulatorOutput = null;
3317         }
3318     }
3319 
3320     /**
3321      * {@inheritDoc}
3322      */
3323     @Override
getEmulatorOutput()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
getEmulatorProcess()3346     public Process getEmulatorProcess() {
3347         return mEmulatorProcess;
3348     }
3349 
3350     /**
3351      * @return <code>true</code> if adb root should be enabled on device
3352      */
isEnableAdbRoot()3353     public boolean isEnableAdbRoot() {
3354         return mOptions.isEnableAdbRoot();
3355     }
3356 
3357     /**
3358      * {@inheritDoc}
3359      */
3360     @Override
getInstalledPackageNames()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
getUninstallablePackageNames()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
getAppPackageInfo(String packageName)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
getOptions()3385     public TestDeviceOptions getOptions() {
3386         return mOptions;
3387     }
3388 
3389     /**
3390      * {@inheritDoc}
3391      */
3392     @Override
getApiLevel()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 
getApiLevelSafe()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
getMonitor()3414     public IDeviceStateMonitor getMonitor() {
3415         return mStateMonitor;
3416     }
3417 
3418     /**
3419      * {@inheritDoc}
3420      */
3421     @Override
waitForDeviceShell(long waitTime)3422     public boolean waitForDeviceShell(long waitTime) {
3423         return mStateMonitor.waitForDeviceShell(waitTime);
3424     }
3425 
3426     @Override
getAllocationState()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
handleAllocationEvent(DeviceEvent event)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
getDeviceTimeOffset(Date date)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
setDate(Date date)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
getDeviceDate()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
waitForBootComplete(long timeOut)3529     public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException {
3530         return mStateMonitor.waitForBootComplete(timeOut);
3531     }
3532 
3533     /**
3534      * {@inheritDoc}
3535      */
3536     @Override
listUsers()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
getMaxNumberOfUsersSupported()3545     public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
3546         throw new UnsupportedOperationException("No support for user's feature.");
3547     }
3548 
3549     @Override
getMaxNumberOfRunningUsersSupported()3550     public int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
3551         throw new UnsupportedOperationException("No support for user's feature.");
3552     }
3553 
3554     /**
3555      * {@inheritDoc}
3556      */
3557     @Override
isMultiUserSupported()3558     public boolean isMultiUserSupported() throws DeviceNotAvailableException {
3559         throw new UnsupportedOperationException("No support for user's feature.");
3560     }
3561 
3562     /**
3563      * {@inheritDoc}
3564      */
3565     @Override
createUser(String name)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
createUser(String name, boolean guest, boolean ephemeral)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
removeUser(int userId)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
startUser(int userId)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
stopUser(int userId)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
stopUser(int userId, boolean waitFlag, boolean forceFlag)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
remountSystemWritable()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
getPrimaryUserId()3632     public Integer getPrimaryUserId() throws DeviceNotAvailableException {
3633         throw new UnsupportedOperationException("No support for user's feature.");
3634     }
3635 
3636     /**
3637      * {@inheritDoc}
3638      */
3639     @Override
getCurrentUser()3640     public int getCurrentUser() throws DeviceNotAvailableException {
3641         throw new UnsupportedOperationException("No support for user's feature.");
3642     }
3643 
3644     /**
3645      * {@inheritDoc}
3646      */
3647     @Override
getUserFlags(int userId)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
getUserSerialNumber(int userId)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
switchUser(int userId)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
switchUser(int userId, long timeout)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
isUserRunning(int userId)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
hasFeature(String feature)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
getSetting(String namespace, String key)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
getSetting(int userId, String namespace, String key)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
setSetting(String namespace, String key, String value)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
setSetting(int userId, String namespace, String key, String value)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
getBuildSigningKeys()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
getAndroidId(int userId)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
getAndroidIds()3758     public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
3759         throw new UnsupportedOperationException("No support for user's feature.");
3760     }
3761 
3762     /** {@inheritDoc} */
3763     @Override
setDeviceOwner(String componentName, int userId)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
removeAdmin(String componentName, int userId)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
removeOwners()3778     public void removeOwners() throws DeviceNotAvailableException {
3779         throw new UnsupportedOperationException("No support for user's feature.");
3780     }
3781 
3782     /**
3783      * {@inheritDoc}
3784      */
3785     @Override
disableKeyguard()3786     public void disableKeyguard() throws DeviceNotAvailableException {
3787         throw new UnsupportedOperationException("No support for Window Manager's features");
3788     }
3789 
3790     /** {@inheritDoc} */
3791     @Override
getDeviceClass()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
preInvocationSetup(IBuildInfo info)3805     public void preInvocationSetup(IBuildInfo info)
3806             throws TargetSetupError, DeviceNotAvailableException {
3807         // Default implementation empty on purpose
3808     }
3809 
3810     /**
3811      * {@inheritDoc}
3812      */
3813     @Override
postInvocationTearDown()3814     public void postInvocationTearDown() {
3815         // Default implementation empty on purpose
3816     }
3817 
3818     /**
3819      * {@inheritDoc}
3820      */
3821     @Override
isHeadless()3822     public boolean isHeadless() throws DeviceNotAvailableException {
3823         if (getProperty(HEADLESS_PROP) != null) {
3824             return true;
3825         }
3826         return false;
3827     }
3828 
checkApiLevelAgainst(String feature, int strictMinLevel)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
getDeviceDescriptor()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      */
getDisplayString(Object o)3865     private String getDisplayString(Object o) {
3866         return o == null ? "unknown" : o.toString();
3867     }
3868 
3869     /**
3870      * {@inheritDoc}
3871      */
3872     @Override
getProcesses()3873     public List<ProcessInfo> getProcesses() throws DeviceNotAvailableException {
3874         return PsParser.getProcesses(executeShellCommand(PS_COMMAND));
3875     }
3876 
3877     /**
3878      * {@inheritDoc}
3879      */
3880     @Override
getProcessByName(String processName)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      */
isMacAddress(String address)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
getMacAddress()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
getSimState()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
getSimOperator()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
dumpHeap(String process, String devicePath)3958     public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException {
3959         throw new UnsupportedOperationException("dumpHeap is not supported.");
3960     }
3961 
3962     /** {@inheritDoc} */
3963     @Override
getProcessPid(String process)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
logOnDevice(String tag, LogLevel level, String format, Object... args)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'). */
logLevelToLogcatLevel(LogLevel level)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. */
checkValidPid(String output)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
getHostOptions()4021     IHostOptions getHostOptions() {
4022         return GlobalConfiguration.getInstance().getHostOptions();
4023     }
4024 }
4025