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