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