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(
2103                     String.format(
2104                             "Attempted %s multiple times "
2105                                     + "on device %s without communication success. Aborting.",
2106                             actionDescription, getSerialNumber()),
2107                     getSerialNumber(),
2108                     DeviceErrorIdentifier.DEVICE_UNRESPONSIVE);
2109         }
2110         return false;
2111     }
2112 
2113     /**
2114      * Log an entry for given exception
2115      *
2116      * @param actionDescription the action's description
2117      * @param e the exception
2118      */
logDeviceActionException(String actionDescription, Exception e)2119     private void logDeviceActionException(String actionDescription, Exception e) {
2120         CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(),
2121                 getExceptionMessage(e), actionDescription, getSerialNumber());
2122     }
2123 
2124     /**
2125      * Make a best effort attempt to retrieve a meaningful short descriptive message for given
2126      * {@link Exception}
2127      *
2128      * @param e the {@link Exception}
2129      * @return a short message
2130      */
getExceptionMessage(Exception e)2131     private String getExceptionMessage(Exception e) {
2132         StringBuilder msgBuilder = new StringBuilder();
2133         if (e.getMessage() != null) {
2134             msgBuilder.append(e.getMessage());
2135         }
2136         if (e.getCause() != null) {
2137             msgBuilder.append(" cause: ");
2138             msgBuilder.append(e.getCause().getClass().getSimpleName());
2139             if (e.getCause().getMessage() != null) {
2140                 msgBuilder.append(" (");
2141                 msgBuilder.append(e.getCause().getMessage());
2142                 msgBuilder.append(")");
2143             }
2144         }
2145         return msgBuilder.toString();
2146     }
2147 
2148     /**
2149      * Attempts to recover device communication.
2150      *
2151      * @throws DeviceNotAvailableException if device is not longer available
2152      */
2153     @Override
recoverDevice()2154     public void recoverDevice() throws DeviceNotAvailableException {
2155         if (mRecoveryMode.equals(RecoveryMode.NONE)) {
2156             CLog.i("Skipping recovery on %s", getSerialNumber());
2157             getRunUtil().sleep(NONE_RECOVERY_MODE_DELAY);
2158             return;
2159         }
2160         CLog.i("Attempting recovery on %s", getSerialNumber());
2161         try {
2162             mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE));
2163         } catch (DeviceUnresponsiveException due) {
2164             RecoveryMode previousRecoveryMode = mRecoveryMode;
2165             mRecoveryMode = RecoveryMode.NONE;
2166             try {
2167                 boolean enabled = enableAdbRoot();
2168                 CLog.d("Device Unresponsive during recovery, is root still enabled: %s", enabled);
2169             } catch (DeviceUnresponsiveException e) {
2170                 // Ignore exception thrown here to rethrow original exception.
2171                 CLog.e("Exception occurred during recovery adb root:");
2172                 CLog.e(e);
2173             }
2174             mRecoveryMode = previousRecoveryMode;
2175             throw due;
2176         }
2177         if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) {
2178             // turn off recovery mode to prevent reentrant recovery
2179             // TODO: look for a better way to handle this, such as doing postBootUp steps in
2180             // recovery itself
2181             mRecoveryMode = RecoveryMode.NONE;
2182             // this might be a runtime reset - still need to run post boot setup steps
2183             if (isEncryptionSupported() && isDeviceEncrypted()) {
2184                 unlockDevice();
2185             }
2186             postBootSetup();
2187             mRecoveryMode = RecoveryMode.AVAILABLE;
2188         } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) {
2189             // turn off recovery mode to prevent reentrant recovery
2190             // TODO: look for a better way to handle this, such as doing postBootUp steps in
2191             // recovery itself
2192             mRecoveryMode = RecoveryMode.NONE;
2193             enableAdbRoot();
2194             mRecoveryMode = RecoveryMode.ONLINE;
2195         }
2196         CLog.i("Recovery successful for %s", getSerialNumber());
2197     }
2198 
2199     /**
2200      * Attempts to recover device fastboot communication.
2201      *
2202      * @throws DeviceNotAvailableException if device is not longer available
2203      */
recoverDeviceFromBootloader()2204     private void recoverDeviceFromBootloader() throws DeviceNotAvailableException {
2205         CLog.i("Attempting recovery on %s in bootloader", getSerialNumber());
2206         mRecovery.recoverDeviceBootloader(mStateMonitor);
2207         CLog.i("Bootloader recovery successful for %s", getSerialNumber());
2208     }
2209 
recoverDeviceFromFastbootd()2210     private void recoverDeviceFromFastbootd() throws DeviceNotAvailableException {
2211         CLog.i("Attempting recovery on %s in fastbootd", getSerialNumber());
2212         mRecovery.recoverDeviceFastbootd(mStateMonitor);
2213         CLog.i("Fastbootd recovery successful for %s", getSerialNumber());
2214     }
2215 
recoverDeviceInRecovery()2216     private void recoverDeviceInRecovery() throws DeviceNotAvailableException {
2217         CLog.i("Attempting recovery on %s in recovery", getSerialNumber());
2218         mRecovery.recoverDeviceRecovery(mStateMonitor);
2219         CLog.i("Recovery mode recovery successful for %s", getSerialNumber());
2220     }
2221 
2222     /**
2223      * {@inheritDoc}
2224      */
2225     @Override
startLogcat()2226     public void startLogcat() {
2227         if (mLogcatReceiver != null) {
2228             CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber());
2229             return;
2230         }
2231         mLogcatReceiver = createLogcatReceiver();
2232         mLogcatReceiver.start();
2233     }
2234 
2235     /**
2236      * {@inheritDoc}
2237      */
2238     @Override
clearLogcat()2239     public void clearLogcat() {
2240         if (mLogcatReceiver != null) {
2241             mLogcatReceiver.clear();
2242         }
2243     }
2244 
2245     /** {@inheritDoc} */
2246     @Override
2247     @SuppressWarnings("MustBeClosedChecker")
getLogcat()2248     public InputStreamSource getLogcat() {
2249         if (mLogcatReceiver == null) {
2250             CLog.w("Not capturing logcat for %s in background, returning a logcat dump",
2251                     getSerialNumber());
2252             return getLogcatDump();
2253         } else {
2254             return mLogcatReceiver.getLogcatData();
2255         }
2256     }
2257 
2258     /** {@inheritDoc} */
2259     @Override
2260     @SuppressWarnings("MustBeClosedChecker")
getLogcat(int maxBytes)2261     public InputStreamSource getLogcat(int maxBytes) {
2262         if (mLogcatReceiver == null) {
2263             CLog.w("Not capturing logcat for %s in background, returning a logcat dump "
2264                     + "ignoring size", getSerialNumber());
2265             return getLogcatDump();
2266         } else {
2267             return mLogcatReceiver.getLogcatData(maxBytes);
2268         }
2269     }
2270 
2271     /**
2272      * {@inheritDoc}
2273      */
2274     @Override
getLogcatSince(long date)2275     public InputStreamSource getLogcatSince(long date) {
2276         try {
2277             if (getApiLevel() <= 22) {
2278                 CLog.i("Api level too low to use logcat -t 'time' reverting to dump");
2279                 return getLogcatDump();
2280             }
2281         } catch (DeviceNotAvailableException e) {
2282             // For convenience of interface, we catch the DNAE here.
2283             CLog.e(e);
2284             return getLogcatDump();
2285         }
2286 
2287         // Convert date to format needed by the command:
2288         // 'MM-DD HH:mm:ss.mmm' or 'YYYY-MM-DD HH:mm:ss.mmm'
2289         SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss.mmm");
2290         String dateFormatted = format.format(new Date(date));
2291 
2292         byte[] output = new byte[0];
2293         try {
2294             // use IDevice directly because we don't want callers to handle
2295             // DeviceNotAvailableException for this method
2296             CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2297             String command = String.format("%s -t '%s'", LogcatReceiver.LOGCAT_CMD, dateFormatted);
2298             getIDevice().executeShellCommand(command, receiver);
2299             output = receiver.getOutput();
2300         } catch (IOException|AdbCommandRejectedException|
2301                 ShellCommandUnresponsiveException|TimeoutException e) {
2302             CLog.w("Failed to get logcat dump from %s: %s", getSerialNumber(), e.getMessage());
2303             CLog.e(e);
2304         }
2305         return new ByteArrayInputStreamSource(output);
2306     }
2307 
2308     /**
2309      * {@inheritDoc}
2310      */
2311     @Override
getLogcatDump()2312     public InputStreamSource getLogcatDump() {
2313         byte[] output = new byte[0];
2314         try {
2315             // use IDevice directly because we don't want callers to handle
2316             // DeviceNotAvailableException for this method
2317             CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2318             // add -d parameter to make this a non blocking call
2319             getIDevice().executeShellCommand(LogcatReceiver.LOGCAT_CMD + " -d", receiver,
2320                     LOGCAT_DUMP_TIMEOUT, TimeUnit.MILLISECONDS);
2321             output = receiver.getOutput();
2322         } catch (IOException e) {
2323             CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2324         } catch (TimeoutException e) {
2325             CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber());
2326         } catch (AdbCommandRejectedException e) {
2327             CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2328         } catch (ShellCommandUnresponsiveException e) {
2329             CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2330         }
2331         return new ByteArrayInputStreamSource(output);
2332     }
2333 
2334     /**
2335      * {@inheritDoc}
2336      */
2337     @Override
stopLogcat()2338     public void stopLogcat() {
2339         if (mLogcatReceiver != null) {
2340             mLogcatReceiver.stop();
2341             mLogcatReceiver = null;
2342         } else {
2343             CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber());
2344         }
2345     }
2346 
2347     /** Factory method to create a {@link LogcatReceiver}. */
2348     @VisibleForTesting
createLogcatReceiver()2349     LogcatReceiver createLogcatReceiver() {
2350         String logcatOptions = mOptions.getLogcatOptions();
2351         if (logcatOptions == null) {
2352             return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay);
2353         } else {
2354             return new LogcatReceiver(this,
2355                     String.format("%s %s", LogcatReceiver.LOGCAT_CMD, logcatOptions),
2356                     mOptions.getMaxLogcatDataSize(), mLogStartDelay);
2357         }
2358     }
2359 
2360     /**
2361      * {@inheritDoc}
2362      */
2363     @Override
getBugreport()2364     public InputStreamSource getBugreport() {
2365         if (getApiLevelSafe() < 24) {
2366             InputStreamSource bugreport = getBugreportInternal();
2367             if (bugreport == null) {
2368                 // Safe call so we don't return null but an empty resource.
2369                 return new ByteArrayInputStreamSource("".getBytes());
2370             }
2371             return bugreport;
2372         }
2373         CLog.d("Api level above 24, using bugreportz instead.");
2374         File mainEntry = null;
2375         File bugreportzFile = null;
2376         try {
2377             bugreportzFile = getBugreportzInternal();
2378             if (bugreportzFile == null) {
2379                 bugreportzFile = bugreportzFallback();
2380             }
2381             if (bugreportzFile == null) {
2382                 // return empty buffer
2383                 return new ByteArrayInputStreamSource("".getBytes());
2384             }
2385             try (ZipFile zip = new ZipFile(bugreportzFile)) {
2386                 // We get the main_entry.txt that contains the bugreport name.
2387                 mainEntry = ZipUtil2.extractFileFromZip(zip, "main_entry.txt");
2388                 String bugreportName = FileUtil.readStringFromFile(mainEntry).trim();
2389                 CLog.d("bugreport name: '%s'", bugreportName);
2390                 File bugreport = ZipUtil2.extractFileFromZip(zip, bugreportName);
2391                 return new FileInputStreamSource(bugreport, true);
2392             }
2393         } catch (IOException e) {
2394             CLog.e("Error while unzipping bugreportz");
2395             CLog.e(e);
2396             return new ByteArrayInputStreamSource("corrupted bugreport.".getBytes());
2397         } finally {
2398             FileUtil.deleteFile(bugreportzFile);
2399             FileUtil.deleteFile(mainEntry);
2400         }
2401     }
2402 
2403     /**
2404      * If first bugreportz collection was interrupted for any reasons, the temporary file where the
2405      * dumpstate is redirected could exists if it started. We attempt to get it to have some partial
2406      * data.
2407      */
bugreportzFallback()2408     private File bugreportzFallback() {
2409         try {
2410             IFileEntry entries = getFileEntry(BUGREPORTZ_TMP_PATH);
2411             if (entries != null) {
2412                 for (IFileEntry f : entries.getChildren(false)) {
2413                     String name = f.getName();
2414                     CLog.d("bugreport entry: %s", name);
2415                     // Only get left-over zipped data to avoid confusing data types.
2416                     if (name.endsWith(".zip")) {
2417                         File pulledZip = pullFile(BUGREPORTZ_TMP_PATH + name);
2418                         try {
2419                             // Validate the zip before returning it.
2420                             if (ZipUtil.isZipFileValid(pulledZip, false)) {
2421                                 return pulledZip;
2422                             }
2423                         } catch (IOException e) {
2424                             CLog.e(e);
2425                         }
2426                         CLog.w("Failed to get a valid bugreportz.");
2427                         // if zip validation failed, delete it and return null.
2428                         FileUtil.deleteFile(pulledZip);
2429                         return null;
2430 
2431                     }
2432                 }
2433                 CLog.w("Could not find a tmp bugreport file in the directory.");
2434             } else {
2435                 CLog.w("Could not find the file entry: '%s' on the device.", BUGREPORTZ_TMP_PATH);
2436             }
2437         } catch (DeviceNotAvailableException e) {
2438             CLog.e(e);
2439         }
2440         return null;
2441     }
2442 
2443     /**
2444      * {@inheritDoc}
2445      */
2446     @Override
logBugreport(String dataName, ITestLogger listener)2447     public boolean logBugreport(String dataName, ITestLogger listener) {
2448         InputStreamSource bugreport = null;
2449         LogDataType type = null;
2450         try {
2451             bugreport = getBugreportz();
2452             type = LogDataType.BUGREPORTZ;
2453 
2454             if (bugreport == null) {
2455                 CLog.d("Bugreportz failed, attempting bugreport collection instead.");
2456                 bugreport = getBugreportInternal();
2457                 type = LogDataType.BUGREPORT;
2458             }
2459             // log what we managed to capture.
2460             if (bugreport != null) {
2461                 listener.testLog(dataName, type, bugreport);
2462                 return true;
2463             }
2464         } finally {
2465             StreamUtil.cancel(bugreport);
2466         }
2467         CLog.d(
2468                 "logBugreport() was not successful in collecting and logging the bugreport "
2469                         + "for device %s",
2470                 getSerialNumber());
2471         return false;
2472     }
2473 
2474     /**
2475      * {@inheritDoc}
2476      */
2477     @Override
takeBugreport()2478     public Bugreport takeBugreport() {
2479         File bugreportFile = null;
2480         int apiLevel = getApiLevelSafe();
2481         if (apiLevel == UNKNOWN_API_LEVEL) {
2482             return null;
2483         }
2484         if (apiLevel >= 24) {
2485             CLog.d("Api level above 24, using bugreportz.");
2486             bugreportFile = getBugreportzInternal();
2487             if (bugreportFile != null) {
2488                 return new Bugreport(bugreportFile, true);
2489             }
2490             return null;
2491         }
2492         // fall back to regular bugreport
2493         InputStreamSource bugreport = getBugreportInternal();
2494         if (bugreport == null) {
2495             CLog.e("Error when collecting the bugreport.");
2496             return null;
2497         }
2498         try {
2499             bugreportFile = FileUtil.createTempFile("bugreport", ".txt");
2500             FileUtil.writeToFile(bugreport.createInputStream(), bugreportFile);
2501             return new Bugreport(bugreportFile, false);
2502         } catch (IOException e) {
2503             CLog.e("Error when writing the bugreport file");
2504             CLog.e(e);
2505         }
2506         return null;
2507     }
2508 
2509     /**
2510      * {@inheritDoc}
2511      */
2512     @Override
getBugreportz()2513     public InputStreamSource getBugreportz() {
2514         if (getApiLevelSafe() < 24) {
2515             return null;
2516         }
2517         File bugreportZip = getBugreportzInternal();
2518         if (bugreportZip == null) {
2519             bugreportZip = bugreportzFallback();
2520         }
2521         if (bugreportZip != null) {
2522             return new FileInputStreamSource(bugreportZip, true);
2523         }
2524         return null;
2525     }
2526 
2527     /** Internal Helper method to get the bugreportz zip file as a {@link File}. */
2528     @VisibleForTesting
getBugreportzInternal()2529     protected File getBugreportzInternal() {
2530         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
2531         // Does not rely on {@link ITestDevice#executeAdbCommand(String...)} because it does not
2532         // provide a timeout.
2533         try {
2534             executeShellCommand(BUGREPORTZ_CMD, receiver,
2535                     BUGREPORTZ_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
2536             String output = receiver.getOutput().trim();
2537             Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
2538             if (!match.find()) {
2539                 CLog.e("Something went went wrong during bugreportz collection: '%s'", output);
2540                 return null;
2541             } else {
2542                 String remoteFilePath = match.group(2);
2543                 if (Strings.isNullOrEmpty(remoteFilePath)) {
2544                     CLog.e("Invalid bugreportz path found from output: %s", output);
2545                     return null;
2546                 }
2547                 File zipFile = null;
2548                 try {
2549                     if (!doesFileExist(remoteFilePath)) {
2550                         CLog.e("Did not find bugreportz at: '%s'", remoteFilePath);
2551                         return null;
2552                     }
2553                     // Create a placeholder to replace the file
2554                     zipFile = FileUtil.createTempFile("bugreportz", ".zip");
2555                     pullFile(remoteFilePath, zipFile);
2556                     String bugreportDir =
2557                             remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
2558                     if (!bugreportDir.isEmpty()) {
2559                         // clean bugreport files directory on device
2560                         deleteFile(String.format("%s/*", bugreportDir));
2561                     }
2562 
2563                     return zipFile;
2564                 } catch (IOException e) {
2565                     CLog.e("Failed to create the temporary file.");
2566                     return null;
2567                 }
2568             }
2569         } catch (DeviceNotAvailableException e) {
2570             CLog.e("Device %s became unresponsive while retrieving bugreportz", getSerialNumber());
2571             CLog.e(e);
2572         }
2573         return null;
2574     }
2575 
getBugreportInternal()2576     protected InputStreamSource getBugreportInternal() {
2577         CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2578         try {
2579             executeShellCommand(
2580                     BUGREPORT_CMD,
2581                     receiver,
2582                     BUGREPORT_TIMEOUT,
2583                     TimeUnit.MILLISECONDS,
2584                     0 /* don't retry */);
2585         } catch (DeviceNotAvailableException e) {
2586             // Log, but don't throw, so the caller can get the bugreport contents even
2587             // if the device goes away
2588             CLog.e("Device %s became unresponsive while retrieving bugreport", getSerialNumber());
2589             return null;
2590         }
2591         return new ByteArrayInputStreamSource(receiver.getOutput());
2592     }
2593 
2594     /**
2595      * {@inheritDoc}
2596      */
2597     @Override
getScreenshot()2598     public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
2599         throw new UnsupportedOperationException("No support for Screenshot");
2600     }
2601 
2602     /**
2603      * {@inheritDoc}
2604      */
2605     @Override
getScreenshot(String format)2606     public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
2607         throw new UnsupportedOperationException("No support for Screenshot");
2608     }
2609 
2610     /** {@inheritDoc} */
2611     @Override
getScreenshot(String format, boolean rescale)2612     public InputStreamSource getScreenshot(String format, boolean rescale)
2613             throws DeviceNotAvailableException {
2614         throw new UnsupportedOperationException("No support for Screenshot");
2615     }
2616 
2617     /** {@inheritDoc} */
2618     @Override
getScreenshot(long displayId)2619     public InputStreamSource getScreenshot(long displayId) throws DeviceNotAvailableException {
2620         throw new UnsupportedOperationException("No support for Screenshot");
2621     }
2622 
2623     /** {@inheritDoc} */
2624     @Override
clearLastConnectedWifiNetwork()2625     public void clearLastConnectedWifiNetwork() {
2626         mLastConnectedWifiSsid = null;
2627         mLastConnectedWifiPsk = null;
2628     }
2629 
2630     /**
2631      * {@inheritDoc}
2632      */
2633     @Override
connectToWifiNetwork(String wifiSsid, String wifiPsk)2634     public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk)
2635             throws DeviceNotAvailableException {
2636         return connectToWifiNetwork(wifiSsid, wifiPsk, false);
2637     }
2638 
2639     /**
2640      * {@inheritDoc}
2641      */
2642     @Override
connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)2643     public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)
2644             throws DeviceNotAvailableException {
2645         // Clears the last connected wifi network.
2646         mLastConnectedWifiSsid = null;
2647         mLastConnectedWifiPsk = null;
2648 
2649         // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()}
2650         // times
2651         Random rnd = new Random();
2652         int backoffSlotCount = 2;
2653         int slotTime = mOptions.getWifiRetryWaitTime();
2654         int waitTime = 0;
2655         IWifiHelper wifi = createWifiHelper();
2656         long startTime = mClock.millis();
2657         for (int i = 1; i <= mOptions.getWifiAttempts(); i++) {
2658             CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber());
2659             boolean success =
2660                     wifi.connectToNetwork(wifiSsid, wifiPsk, mOptions.getConnCheckUrl(), scanSsid);
2661             final Map<String, String> wifiInfo = wifi.getWifiInfo();
2662             if (success) {
2663                 CLog.i(
2664                         "Successfully connected to wifi network %s(%s) on %s",
2665                         wifiSsid, wifiInfo.get("bssid"), getSerialNumber());
2666 
2667                 mLastConnectedWifiSsid = wifiSsid;
2668                 mLastConnectedWifiPsk = wifiPsk;
2669 
2670                 return true;
2671             } else {
2672                 CLog.w(
2673                         "Failed to connect to wifi network %s(%s) on %s on attempt %d of %d",
2674                         wifiSsid,
2675                         wifiInfo.get("bssid"),
2676                         getSerialNumber(),
2677                         i,
2678                         mOptions.getWifiAttempts());
2679             }
2680             if (mClock.millis() - startTime >= mOptions.getMaxWifiConnectTime()) {
2681                 CLog.e(
2682                         "Failed to connect to wifi after %d ms. Aborting.",
2683                         mOptions.getMaxWifiConnectTime());
2684                 break;
2685             }
2686             if (i < mOptions.getWifiAttempts()) {
2687                 if (mOptions.isWifiExpoRetryEnabled()) {
2688                     // use binary exponential back-offs when retrying.
2689                     waitTime = rnd.nextInt(backoffSlotCount) * slotTime;
2690                     backoffSlotCount *= 2;
2691                 }
2692                 CLog.e("Waiting for %d ms before reconnecting to %s...", waitTime, wifiSsid);
2693                 getRunUtil().sleep(waitTime);
2694             }
2695         }
2696         return false;
2697     }
2698 
2699     /**
2700      * {@inheritDoc}
2701      */
2702     @Override
checkConnectivity()2703     public boolean checkConnectivity() throws DeviceNotAvailableException {
2704         IWifiHelper wifi = createWifiHelper();
2705         return wifi.checkConnectivity(mOptions.getConnCheckUrl());
2706     }
2707 
2708     /**
2709      * {@inheritDoc}
2710      */
2711     @Override
connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)2712     public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)
2713             throws DeviceNotAvailableException {
2714         return connectToWifiNetworkIfNeeded(wifiSsid, wifiPsk, false);
2715     }
2716 
2717     /**
2718      * {@inheritDoc}
2719      */
2720     @Override
connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)2721     public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)
2722             throws DeviceNotAvailableException {
2723         if (!checkConnectivity())  {
2724             return connectToWifiNetwork(wifiSsid, wifiPsk, scanSsid);
2725         }
2726         return true;
2727     }
2728 
2729     /**
2730      * {@inheritDoc}
2731      */
2732     @Override
isWifiEnabled()2733     public boolean isWifiEnabled() throws DeviceNotAvailableException {
2734         final IWifiHelper wifi = createWifiHelper();
2735         try {
2736             return wifi.isWifiEnabled();
2737         } catch (RuntimeException e) {
2738             CLog.w("Failed to create WifiHelper: %s", e.getMessage());
2739             return false;
2740         }
2741     }
2742 
2743     /**
2744      * Checks that the device is currently successfully connected to given wifi SSID.
2745      *
2746      * @param wifiSSID the wifi ssid
2747      * @return <code>true</code> if device is currently connected to wifiSSID and has network
2748      *         connectivity. <code>false</code> otherwise
2749      * @throws DeviceNotAvailableException if connection with device was lost
2750      */
checkWifiConnection(String wifiSSID)2751     boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException {
2752         CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber());
2753         final IWifiHelper wifi = createWifiHelper();
2754         // getSSID returns SSID as "SSID"
2755         final String quotedSSID = String.format("\"%s\"", wifiSSID);
2756 
2757         boolean test = wifi.isWifiEnabled();
2758         CLog.v("%s: wifi enabled? %b", getSerialNumber(), test);
2759 
2760         if (test) {
2761             final String actualSSID = wifi.getSSID();
2762             test = quotedSSID.equals(actualSSID);
2763             CLog.v("%s: SSID match (%s, %s, %b)", getSerialNumber(), quotedSSID, actualSSID, test);
2764         }
2765         if (test) {
2766             test = wifi.hasValidIp();
2767             CLog.v("%s: validIP? %b", getSerialNumber(), test);
2768         }
2769         if (test) {
2770             test = checkConnectivity();
2771             CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test);
2772         }
2773         return test;
2774     }
2775 
2776     /**
2777      * {@inheritDoc}
2778      */
2779     @Override
disconnectFromWifi()2780     public boolean disconnectFromWifi() throws DeviceNotAvailableException {
2781         CLog.i("Disconnecting from wifi on %s", getSerialNumber());
2782         // Clears the last connected wifi network.
2783         mLastConnectedWifiSsid = null;
2784         mLastConnectedWifiPsk = null;
2785 
2786         IWifiHelper wifi = createWifiHelper();
2787         return wifi.disconnectFromNetwork();
2788     }
2789 
2790     /**
2791      * {@inheritDoc}
2792      */
2793     @Override
getIpAddress()2794     public String getIpAddress() throws DeviceNotAvailableException {
2795         IWifiHelper wifi = createWifiHelper();
2796         return wifi.getIpAddress();
2797     }
2798 
2799     /**
2800      * {@inheritDoc}
2801      */
2802     @Override
enableNetworkMonitor()2803     public boolean enableNetworkMonitor() throws DeviceNotAvailableException {
2804         mNetworkMonitorEnabled = false;
2805 
2806         IWifiHelper wifi = createWifiHelper();
2807         wifi.stopMonitor();
2808         if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) {
2809             mNetworkMonitorEnabled = true;
2810             return true;
2811         }
2812         return false;
2813     }
2814 
2815     /**
2816      * {@inheritDoc}
2817      */
2818     @Override
disableNetworkMonitor()2819     public boolean disableNetworkMonitor() throws DeviceNotAvailableException {
2820         mNetworkMonitorEnabled = false;
2821 
2822         IWifiHelper wifi = createWifiHelper();
2823         List<Long> samples = wifi.stopMonitor();
2824         if (!samples.isEmpty()) {
2825             int failures = 0;
2826             long totalLatency = 0;
2827             for (Long sample : samples) {
2828                 if (sample < 0) {
2829                     failures += 1;
2830                 } else {
2831                     totalLatency += sample;
2832                 }
2833             }
2834             double failureRate = failures * 100.0 / samples.size();
2835             double avgLatency = 0.0;
2836             if (failures < samples.size()) {
2837                 avgLatency = totalLatency / (samples.size() - failures);
2838             }
2839             CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f",
2840                     mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000,
2841                     failureRate, avgLatency);
2842         }
2843         return true;
2844     }
2845 
2846     /**
2847      * Create a {@link WifiHelper} to use
2848      *
2849      * <p>
2850      *
2851      * @throws DeviceNotAvailableException
2852      */
2853     @VisibleForTesting
createWifiHelper()2854     IWifiHelper createWifiHelper() throws DeviceNotAvailableException {
2855         // current wifi helper won't work on AndroidNativeDevice
2856         // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when
2857         // we learn what is available.
2858         throw new UnsupportedOperationException("Wifi helper is not supported.");
2859     }
2860 
2861     /**
2862      * {@inheritDoc}
2863      */
2864     @Override
clearErrorDialogs()2865     public boolean clearErrorDialogs() throws DeviceNotAvailableException {
2866         throw new UnsupportedOperationException("No support for Screen's features");
2867     }
2868 
2869     /** {@inheritDoc} */
2870     @Override
getKeyguardState()2871     public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException {
2872         throw new UnsupportedOperationException("No support for keyguard querying.");
2873     }
2874 
getDeviceStateMonitor()2875     IDeviceStateMonitor getDeviceStateMonitor() {
2876         return mStateMonitor;
2877     }
2878 
2879     /**
2880      * {@inheritDoc}
2881      */
2882     @Override
postBootSetup()2883     public void postBootSetup() throws DeviceNotAvailableException  {
2884         enableAdbRoot();
2885         prePostBootSetup();
2886         for (String command : mOptions.getPostBootCommands()) {
2887             executeShellCommand(command);
2888         }
2889     }
2890 
2891     /**
2892      * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for
2893      * specific post boot setup.
2894      * @throws DeviceNotAvailableException
2895      */
prePostBootSetup()2896     protected void prePostBootSetup() throws DeviceNotAvailableException {
2897         // Empty on purpose.
2898     }
2899 
2900     /**
2901      * Ensure wifi connection is re-established after boot. This is intended to be called after TF
2902      * initiated reboots(ones triggered by {@link #reboot()}) only.
2903      *
2904      * @throws DeviceNotAvailableException
2905      */
postBootWifiSetup()2906     void postBootWifiSetup() throws DeviceNotAvailableException {
2907         if (mLastConnectedWifiSsid != null) {
2908             reconnectToWifiNetwork();
2909         }
2910         if (mNetworkMonitorEnabled) {
2911             if (!enableNetworkMonitor()) {
2912                 CLog.w("Failed to enable network monitor on %s after reboot", getSerialNumber());
2913             }
2914         }
2915     }
2916 
reconnectToWifiNetwork()2917     void reconnectToWifiNetwork() throws DeviceNotAvailableException {
2918         // First, wait for wifi to re-connect automatically.
2919         long startTime = System.currentTimeMillis();
2920         boolean isConnected = checkConnectivity();
2921         while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) {
2922             getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL);
2923             isConnected = checkConnectivity();
2924         }
2925 
2926         if (isConnected) {
2927             return;
2928         }
2929 
2930         // If wifi is still not connected, try to re-connect on our own.
2931         final String wifiSsid = mLastConnectedWifiSsid;
2932         if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) {
2933             throw new NetworkNotAvailableException(
2934                     String.format("Failed to connect to wifi network %s on %s after reboot",
2935                             wifiSsid, getSerialNumber()));
2936         }
2937     }
2938 
2939     /**
2940      * {@inheritDoc}
2941      */
2942     @Override
rebootIntoBootloader()2943     public void rebootIntoBootloader()
2944             throws DeviceNotAvailableException, UnsupportedOperationException {
2945         rebootIntoFastbootInternal(true);
2946     }
2947 
2948     /** {@inheritDoc} */
2949     @Override
rebootIntoFastbootd()2950     public void rebootIntoFastbootd()
2951             throws DeviceNotAvailableException, UnsupportedOperationException {
2952         rebootIntoFastbootInternal(false);
2953     }
2954 
2955     /**
2956      * Reboots the device into bootloader or fastbootd mode.
2957      *
2958      * @param isBootloader true to boot the device into bootloader mode, false to boot the device
2959      *     into fastbootd mode.
2960      * @throws DeviceNotAvailableException if connection with device is lost and cannot be
2961      *     recovered.
2962      */
rebootIntoFastbootInternal(boolean isBootloader)2963     private void rebootIntoFastbootInternal(boolean isBootloader)
2964             throws DeviceNotAvailableException {
2965         final RebootMode mode =
2966                 isBootloader ? RebootMode.REBOOT_INTO_BOOTLOADER : RebootMode.REBOOT_INTO_FASTBOOTD;
2967         if (!mFastbootEnabled) {
2968             throw new UnsupportedOperationException(
2969                     String.format("Fastboot is not available and cannot reboot into %s", mode));
2970         }
2971         // If we go to bootloader, it's probably for flashing so ensure we re-check the provider
2972         mShouldSkipContentProviderSetup = false;
2973         CLog.i(
2974                 "Rebooting device %s in state %s into %s",
2975                 getSerialNumber(), getDeviceState(), mode);
2976         if (isStateBootloaderOrFastbootd()) {
2977             CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
2978             executeFastbootCommand(String.format("reboot-%s", mode));
2979         } else {
2980             CLog.i("Booting device %s into %s", getSerialNumber(), mode);
2981             doAdbReboot(mode, null);
2982         }
2983 
2984         if (RebootMode.REBOOT_INTO_FASTBOOTD.equals(mode) && getHostOptions().isFastbootdEnable()) {
2985             if (!mStateMonitor.waitForDeviceFastbootd(
2986                     getFastbootPath(), mOptions.getFastbootTimeout())) {
2987                 recoverDeviceFromFastbootd();
2988             }
2989         } else {
2990             if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
2991                 recoverDeviceFromBootloader();
2992             }
2993         }
2994     }
2995 
2996     /** {@inheritDoc} */
2997     @Override
isStateBootloaderOrFastbootd()2998     public boolean isStateBootloaderOrFastbootd() {
2999         return TestDeviceState.FASTBOOT.equals(getDeviceState())
3000                 || TestDeviceState.FASTBOOTD.equals(getDeviceState());
3001     }
3002 
3003     /**
3004      * {@inheritDoc}
3005      */
3006     @Override
reboot()3007     public void reboot() throws DeviceNotAvailableException {
3008         reboot(null);
3009     }
3010 
3011     /** {@inheritDoc} */
3012     @Override
reboot(@ullable String reason)3013     public void reboot(@Nullable String reason) throws DeviceNotAvailableException {
3014         rebootUntilOnline(reason);
3015 
3016         RecoveryMode cachedRecoveryMode = getRecoveryMode();
3017         setRecoveryMode(RecoveryMode.ONLINE);
3018 
3019         if (isEncryptionSupported() && isDeviceEncrypted()) {
3020             unlockDevice();
3021         }
3022 
3023         setRecoveryMode(cachedRecoveryMode);
3024 
3025         if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) {
3026             postBootSetup();
3027             postBootWifiSetup();
3028             return;
3029         } else {
3030             recoverDevice();
3031         }
3032     }
3033 
3034     @Override
rebootUserspace()3035     public void rebootUserspace() throws DeviceNotAvailableException {
3036         rebootUserspaceUntilOnline();
3037 
3038         RecoveryMode cachedRecoveryMode = getRecoveryMode();
3039         setRecoveryMode(RecoveryMode.ONLINE);
3040 
3041         if (isEncryptionSupported()) {
3042             if (isDeviceEncrypted()) {
3043                 CLog.e("Device is encrypted after userspace reboot!");
3044                 unlockDevice();
3045             }
3046         }
3047 
3048         setRecoveryMode(cachedRecoveryMode);
3049 
3050         if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) {
3051             postBootSetup();
3052             postBootWifiSetup();
3053         } else {
3054             recoverDevice();
3055         }
3056     }
3057 
3058     @Override
rebootUntilOnline()3059     public void rebootUntilOnline() throws DeviceNotAvailableException {
3060         rebootUntilOnline(null);
3061     }
3062 
3063     /** {@inheritDoc} */
3064     @Override
rebootUntilOnline(@ullable String reason)3065     public void rebootUntilOnline(@Nullable String reason) throws DeviceNotAvailableException {
3066         doReboot(RebootMode.REBOOT_FULL, reason);
3067         RecoveryMode cachedRecoveryMode = getRecoveryMode();
3068         setRecoveryMode(RecoveryMode.ONLINE);
3069         waitForDeviceOnline();
3070         enableAdbRoot();
3071         setRecoveryMode(cachedRecoveryMode);
3072     }
3073 
3074     @Override
rebootUserspaceUntilOnline()3075     public void rebootUserspaceUntilOnline() throws DeviceNotAvailableException {
3076         doReboot(RebootMode.REBOOT_USERSPACE, null);
3077         RecoveryMode cachedRecoveryMode = getRecoveryMode();
3078         setRecoveryMode(RecoveryMode.ONLINE);
3079         waitForDeviceOnline();
3080         enableAdbRoot();
3081         setRecoveryMode(cachedRecoveryMode);
3082     }
3083 
3084     /**
3085      * {@inheritDoc}
3086      */
3087     @Override
rebootIntoRecovery()3088     public void rebootIntoRecovery() throws DeviceNotAvailableException {
3089         if (isStateBootloaderOrFastbootd()) {
3090             CLog.w("device %s in fastboot when requesting boot to recovery. " +
3091                     "Rebooting to userspace first.", getSerialNumber());
3092             rebootUntilOnline();
3093         }
3094         doAdbReboot(RebootMode.REBOOT_INTO_RECOVERY, null);
3095         if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) {
3096             recoverDeviceInRecovery();
3097         }
3098     }
3099 
3100 
3101     /** {@inheritDoc} */
3102     @Override
rebootIntoSideload()3103     public void rebootIntoSideload() throws DeviceNotAvailableException {
3104         rebootIntoSideload(false);
3105     }
3106     /** {@inheritDoc} */
3107     @Override
rebootIntoSideload(boolean autoReboot)3108     public void rebootIntoSideload(boolean autoReboot) throws DeviceNotAvailableException {
3109         if (isStateBootloaderOrFastbootd()) {
3110             CLog.w(
3111                     "device %s in fastboot when requesting boot to sideload. "
3112                             + "Rebooting to userspace first.",
3113                     getSerialNumber());
3114             rebootUntilOnline();
3115         }
3116         final RebootMode rebootMode;
3117         if (autoReboot) {
3118             rebootMode = RebootMode.REBOOT_INTO_SIDELOAD_AUTO_REBOOT;
3119         } else {
3120             rebootMode = RebootMode.REBOOT_INTO_SIDELOAD;
3121         }
3122         doAdbReboot(rebootMode, null);
3123         if (!waitForDeviceInSideload(mOptions.getAdbRecoveryTimeout())) {
3124             // using recovery mode because sideload is a sub-mode under recovery
3125             recoverDeviceInRecovery();
3126         }
3127     }
3128 
3129     /**
3130      * {@inheritDoc}
3131      */
3132     @Override
nonBlockingReboot()3133     public void nonBlockingReboot() throws DeviceNotAvailableException {
3134         doReboot(RebootMode.REBOOT_FULL, null);
3135     }
3136 
3137     /**
3138      * A mode of a reboot.
3139      *
3140      * <p>Source of truth for available modes is defined in init.
3141      */
3142     @VisibleForTesting
3143     protected enum RebootMode {
3144         REBOOT_FULL(""),
3145         REBOOT_USERSPACE("userspace"),
3146         REBOOT_INTO_FASTBOOTD("fastboot"),
3147         REBOOT_INTO_BOOTLOADER("bootloader"),
3148         REBOOT_INTO_SIDELOAD("sideload"),
3149         REBOOT_INTO_SIDELOAD_AUTO_REBOOT("sideload-auto-reboot"),
3150         REBOOT_INTO_RECOVERY("recovery");
3151 
3152         private final String mRebootTarget;
3153 
RebootMode(String rebootTarget)3154         RebootMode(String rebootTarget) {
3155             mRebootTarget = rebootTarget;
3156         }
3157 
3158         @Nullable
formatRebootCommand(@ullable String reason)3159         String formatRebootCommand(@Nullable String reason) {
3160             if (this == REBOOT_FULL) {
3161                 return Strings.isNullOrEmpty(reason) ? null : reason;
3162             } else {
3163                 return Strings.isNullOrEmpty(reason) ? mRebootTarget : mRebootTarget + "," + reason;
3164             }
3165         }
3166 
3167         @Override
toString()3168         public String toString() {
3169             return mRebootTarget;
3170         }
3171     }
3172 
3173     /**
3174      * Trigger a reboot of the device, offers no guarantee of the device state after the call.
3175      *
3176      * @param rebootMode a mode of this reboot
3177      * @param reason reason for this reboot
3178      * @throws DeviceNotAvailableException
3179      * @throws UnsupportedOperationException
3180      */
3181     @VisibleForTesting
doReboot(RebootMode rebootMode, @Nullable final String reason)3182     void doReboot(RebootMode rebootMode, @Nullable final String reason)
3183             throws DeviceNotAvailableException, UnsupportedOperationException {
3184         // Track Tradefed reboot time
3185         mLastTradefedRebootTime = System.currentTimeMillis();
3186 
3187         if (isStateBootloaderOrFastbootd()) {
3188             CLog.i("device %s in %s. Rebooting to userspace.", getSerialNumber(), getDeviceState());
3189             executeFastbootCommand("reboot");
3190         } else {
3191             if (mOptions.shouldDisableReboot()) {
3192                 CLog.i("Device reboot disabled by options, skipped.");
3193                 return;
3194             }
3195             if (reason == null) {
3196                 CLog.i("Rebooting device %s mode: %s", getSerialNumber(), rebootMode.name());
3197             } else {
3198                 CLog.i(
3199                         "Rebooting device %s mode: %s reason: %s",
3200                         getSerialNumber(), rebootMode.name(), reason);
3201             }
3202             doAdbReboot(rebootMode, reason);
3203             // Check if device shows as unavailable (as expected after reboot).
3204             boolean notAvailable = waitForDeviceNotAvailable(DEFAULT_UNAVAILABLE_TIMEOUT);
3205             if (notAvailable) {
3206                 postAdbReboot();
3207             } else {
3208                 CLog.w(
3209                         "Did not detect device %s becoming unavailable after reboot",
3210                         getSerialNumber());
3211             }
3212         }
3213     }
3214 
3215     /**
3216      * Possible extra actions that can be taken after a reboot.
3217      *
3218      * @throws DeviceNotAvailableException
3219      */
postAdbReboot()3220     protected void postAdbReboot() throws DeviceNotAvailableException {
3221         // Default implementation empty on purpose.
3222     }
3223 
3224     /**
3225      * Perform a adb reboot.
3226      *
3227      * @param rebootMode a mode of this reboot.
3228      * @param reason for this reboot.
3229      * @throws DeviceNotAvailableException
3230      */
doAdbReboot(RebootMode rebootMode, @Nullable final String reason)3231     protected void doAdbReboot(RebootMode rebootMode, @Nullable final String reason)
3232             throws DeviceNotAvailableException {
3233         DeviceAction rebootAction = createRebootDeviceAction(rebootMode, reason);
3234         performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS);
3235     }
3236 
3237     /**
3238      * Create a {@link RebootDeviceAction} to be used when performing a reboot action.
3239      *
3240      * @param rebootMode a mode of this reboot.
3241      * @param reason for this reboot.
3242      * @return the created {@link RebootDeviceAction}.
3243      */
createRebootDeviceAction( RebootMode rebootMode, @Nullable final String reason)3244     protected RebootDeviceAction createRebootDeviceAction(
3245             RebootMode rebootMode, @Nullable final String reason) {
3246         return new RebootDeviceAction(rebootMode, reason);
3247     }
3248 
waitForDeviceNotAvailable(String operationDesc, long time)3249     protected void waitForDeviceNotAvailable(String operationDesc, long time) {
3250         // TODO: a bit of a race condition here. Would be better to start a
3251         // before the operation
3252         if (!mStateMonitor.waitForDeviceNotAvailable(time)) {
3253             // above check is flaky, ignore till better solution is found
3254             CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(),
3255                     operationDesc);
3256         }
3257     }
3258 
3259     /**
3260      * {@inheritDoc}
3261      */
3262     @Override
enableAdbRoot()3263     public boolean enableAdbRoot() throws DeviceNotAvailableException {
3264         // adb root is a relatively intensive command, so do a brief check first to see
3265         // if its necessary or not
3266         if (isAdbRoot()) {
3267             CLog.i("adb is already running as root on %s", getSerialNumber());
3268             // Still check for online, in some case we could see the root, but device could be
3269             // very early in its cycle.
3270             waitForDeviceOnline();
3271             return true;
3272         }
3273         // Don't enable root if user requested no root
3274         if (!isEnableAdbRoot()) {
3275             CLog.i("\"enable-root\" set to false; ignoring 'adb root' request");
3276             return false;
3277         }
3278         CLog.i("adb root on device %s", getSerialNumber());
3279         int attempts = MAX_RETRY_ATTEMPTS + 1;
3280         for (int i=1; i <= attempts; i++) {
3281             String output = executeAdbCommand("root");
3282             // wait for device to disappear from adb
3283             waitForDeviceNotAvailable("root", 20 * 1000);
3284 
3285             postAdbRootAction();
3286 
3287             // wait for device to be back online
3288             waitForDeviceOnline();
3289 
3290             if (isAdbRoot()) {
3291                 return true;
3292             }
3293             CLog.w("'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'",
3294                     getSerialNumber(), i, attempts, output);
3295         }
3296         return false;
3297     }
3298 
3299     /**
3300      * {@inheritDoc}
3301      */
3302     @Override
disableAdbRoot()3303     public boolean disableAdbRoot() throws DeviceNotAvailableException {
3304         if (!isAdbRoot()) {
3305             CLog.i("adb is already unroot on %s", getSerialNumber());
3306             return true;
3307         }
3308 
3309         CLog.i("adb unroot on device %s", getSerialNumber());
3310         int attempts = MAX_RETRY_ATTEMPTS + 1;
3311         for (int i=1; i <= attempts; i++) {
3312             String output = executeAdbCommand("unroot");
3313             // wait for device to disappear from adb
3314             waitForDeviceNotAvailable("unroot", 5 * 1000);
3315 
3316             postAdbUnrootAction();
3317 
3318             // wait for device to be back online
3319             waitForDeviceOnline();
3320 
3321             if (!isAdbRoot()) {
3322                 return true;
3323             }
3324             CLog.w("'adb unroot' on %s unsuccessful on attempt %d of %d. Output: '%s'",
3325                     getSerialNumber(), i, attempts, output);
3326         }
3327         return false;
3328     }
3329 
3330     /**
3331      * Override if the device needs some specific actions to be taken after adb root and before the
3332      * device is back online.
3333      * Default implementation doesn't include any addition actions.
3334      * adb root is not guaranteed to be enabled at this stage.
3335      * @throws DeviceNotAvailableException
3336      */
postAdbRootAction()3337     public void postAdbRootAction() throws DeviceNotAvailableException {
3338         // Empty on purpose.
3339     }
3340 
3341     /**
3342      * Override if the device needs some specific actions to be taken after adb unroot and before
3343      * the device is back online.
3344      * Default implementation doesn't include any additional actions.
3345      * adb root is not guaranteed to be disabled at this stage.
3346      * @throws DeviceNotAvailableException
3347      */
postAdbUnrootAction()3348     public void postAdbUnrootAction() throws DeviceNotAvailableException {
3349         // Empty on purpose.
3350     }
3351 
3352     /**
3353      * {@inheritDoc}
3354      */
3355     @Override
isAdbRoot()3356     public boolean isAdbRoot() throws DeviceNotAvailableException {
3357         String output = executeShellCommand("id");
3358         return output.contains("uid=0(root)");
3359     }
3360 
3361     /**
3362      * {@inheritDoc}
3363      */
3364     @Override
encryptDevice(boolean inplace)3365     public boolean encryptDevice(boolean inplace) throws DeviceNotAvailableException,
3366             UnsupportedOperationException {
3367         if (!isEncryptionSupported()) {
3368             throw new UnsupportedOperationException(String.format("Can't encrypt device %s: "
3369                     + "encryption not supported", getSerialNumber()));
3370         }
3371 
3372         if (isDeviceEncrypted()) {
3373             CLog.d("Device %s is already encrypted, skipping", getSerialNumber());
3374             return true;
3375         }
3376 
3377         enableAdbRoot();
3378 
3379         String encryptMethod;
3380         long timeout;
3381         if (inplace) {
3382             encryptMethod = "inplace";
3383             timeout = ENCRYPTION_INPLACE_TIMEOUT_MIN;
3384         } else {
3385             encryptMethod = "wipe";
3386             timeout = ENCRYPTION_WIPE_TIMEOUT_MIN;
3387         }
3388 
3389         CLog.i("Encrypting device %s via %s", getSerialNumber(), encryptMethod);
3390 
3391         // enable crypto takes one of the following formats:
3392         // cryptfs enablecrypto <wipe|inplace> <passwd>
3393         // cryptfs enablecrypto <wipe|inplace> default|password|pin|pattern [passwd]
3394         // Try the first one first, if it outputs "500 0 Usage: ...", try the second.
3395         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
3396         String command = String.format("vdc cryptfs enablecrypto %s \"%s\"", encryptMethod,
3397                 ENCRYPTION_PASSWORD);
3398         executeShellCommand(command, receiver, timeout, TimeUnit.MINUTES, 1);
3399         if (receiver.getOutput().split(":")[0].matches("500 \\d+ Usage")) {
3400             command = String.format("vdc cryptfs enablecrypto %s default", encryptMethod);
3401             executeShellCommand(command, new NullOutputReceiver(), timeout, TimeUnit.MINUTES, 1);
3402         }
3403 
3404         waitForDeviceNotAvailable("reboot", getCommandTimeout());
3405         waitForDeviceOnline();  // Device will not become available until the user data is unlocked.
3406 
3407         return isDeviceEncrypted();
3408     }
3409 
3410     /**
3411      * {@inheritDoc}
3412      */
3413     @Override
unencryptDevice()3414     public boolean unencryptDevice() throws DeviceNotAvailableException,
3415             UnsupportedOperationException {
3416         if (!isEncryptionSupported()) {
3417             throw new UnsupportedOperationException(String.format("Can't unencrypt device %s: "
3418                     + "encryption not supported", getSerialNumber()));
3419         }
3420 
3421         if (!isDeviceEncrypted()) {
3422             CLog.d("Device %s is already unencrypted, skipping", getSerialNumber());
3423             return true;
3424         }
3425 
3426         CLog.i("Unencrypting device %s", getSerialNumber());
3427 
3428         // If the device supports fastboot format, then we're done.
3429         if (!mOptions.getUseFastbootErase()) {
3430             rebootIntoBootloader();
3431             fastbootWipePartition("userdata");
3432             rebootUntilOnline();
3433             waitForDeviceAvailable(ENCRYPTION_WIPE_TIMEOUT_MIN * 60 * 1000);
3434             return true;
3435         }
3436 
3437         // Determine if we need to format partition instead of wipe.
3438         boolean format = false;
3439         String output = executeShellCommand("vdc volume list");
3440         String[] splitOutput;
3441         if (output != null) {
3442             splitOutput = output.split("\r?\n");
3443             for (String line : splitOutput) {
3444                 if (line.startsWith("110 ") && line.contains("sdcard /mnt/sdcard") &&
3445                         !line.endsWith("0")) {
3446                     format = true;
3447                 }
3448             }
3449         }
3450 
3451         rebootIntoBootloader();
3452         fastbootWipePartition("userdata");
3453 
3454         // If the device requires time to format the filesystem after fastboot erase userdata, wait
3455         // for the device to reboot a second time.
3456         if (mOptions.getUnencryptRebootTimeout() > 0) {
3457             rebootUntilOnline();
3458             if (waitForDeviceNotAvailable(mOptions.getUnencryptRebootTimeout())) {
3459                 waitForDeviceOnline();
3460             }
3461         }
3462 
3463         if (format) {
3464             CLog.d("Need to format sdcard for device %s", getSerialNumber());
3465 
3466             RecoveryMode cachedRecoveryMode = getRecoveryMode();
3467             setRecoveryMode(RecoveryMode.ONLINE);
3468 
3469             output = executeShellCommand("vdc volume format sdcard");
3470             if (output == null) {
3471                 CLog.e("Command vdc volume format sdcard failed will no output for device %s:\n%s",
3472                         getSerialNumber());
3473                 setRecoveryMode(cachedRecoveryMode);
3474                 return false;
3475             }
3476             splitOutput = output.split("\r?\n");
3477             if (!splitOutput[splitOutput.length - 1].startsWith("200 ")) {
3478                 CLog.e("Command vdc volume format sdcard failed for device %s:\n%s",
3479                         getSerialNumber(), output);
3480                 setRecoveryMode(cachedRecoveryMode);
3481                 return false;
3482             }
3483 
3484             setRecoveryMode(cachedRecoveryMode);
3485         }
3486 
3487         reboot();
3488 
3489         return true;
3490     }
3491 
3492     /**
3493      * {@inheritDoc}
3494      */
3495     @Override
unlockDevice()3496     public boolean unlockDevice() throws DeviceNotAvailableException,
3497             UnsupportedOperationException {
3498         if (!isEncryptionSupported()) {
3499             throw new UnsupportedOperationException(String.format("Can't unlock device %s: "
3500                     + "encryption not supported", getSerialNumber()));
3501         }
3502 
3503         if (!isDeviceEncrypted()) {
3504             CLog.d("Device %s is not encrypted, skipping", getSerialNumber());
3505             return true;
3506         }
3507 
3508         CLog.i("Unlocking device %s", getSerialNumber());
3509 
3510         enableAdbRoot();
3511 
3512         // FIXME: currently, vcd checkpw can return an empty string when it never should.  Try 3
3513         // times.
3514         String output;
3515         int i = 0;
3516         do {
3517             // Enter the password. Output will be:
3518             // "200 [X] -1" if the password has already been entered correctly,
3519             // "200 [X] 0" if the password is entered correctly,
3520             // "200 [X] N" where N is any positive number if the password is incorrect,
3521             // any other string if there is an error.
3522             output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"",
3523                     ENCRYPTION_PASSWORD)).trim();
3524 
3525             if (output.startsWith("200 ") && output.endsWith(" -1")) {
3526                 return true;
3527             }
3528 
3529             if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) {
3530                 CLog.e("checkpw gave output '%s' while trying to unlock device %s",
3531                         output, getSerialNumber());
3532                 return false;
3533             }
3534 
3535             getRunUtil().sleep(500);
3536         } while (output.isEmpty() && ++i < 3);
3537 
3538         if (output.isEmpty()) {
3539             CLog.e("checkpw gave no output while trying to unlock device %s");
3540         }
3541 
3542         // Restart the framework. Output will be:
3543         // "200 [X] 0" if the user data partition can be mounted,
3544         // "200 [X] -1" if the user data partition can not be mounted (no correct password given),
3545         // any other string if there is an error.
3546         output = executeShellCommand("vdc cryptfs restart").trim();
3547 
3548         if (!(output.startsWith("200 ") &&  output.endsWith(" 0"))) {
3549             CLog.e("restart gave output '%s' while trying to unlock device %s", output,
3550                     getSerialNumber());
3551             return false;
3552         }
3553 
3554         waitForDeviceAvailable();
3555 
3556         return true;
3557     }
3558 
3559     /**
3560      * {@inheritDoc}
3561      */
3562     @Override
isDeviceEncrypted()3563     public boolean isDeviceEncrypted() throws DeviceNotAvailableException {
3564         String output = getProperty("ro.crypto.state");
3565 
3566         if (output == null && isEncryptionSupported()) {
3567             CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber());
3568         }
3569         if (output == null) {
3570             return false;
3571         }
3572         return "encrypted".equals(output.trim());
3573     }
3574 
3575     /**
3576      * {@inheritDoc}
3577      */
3578     @Override
isEncryptionSupported()3579     public boolean isEncryptionSupported() throws DeviceNotAvailableException {
3580         if (!isEnableAdbRoot()) {
3581             CLog.i("root is required for encryption");
3582             mIsEncryptionSupported = false;
3583             return mIsEncryptionSupported;
3584         }
3585         if (mIsEncryptionSupported != null) {
3586             return mIsEncryptionSupported.booleanValue();
3587         }
3588         enableAdbRoot();
3589 
3590         String output = getProperty("ro.crypto.state");
3591         if (output == null || "unsupported".equals(output.trim())) {
3592             mIsEncryptionSupported = false;
3593             return mIsEncryptionSupported;
3594         }
3595         mIsEncryptionSupported = true;
3596         return mIsEncryptionSupported;
3597     }
3598 
3599     /**
3600      * {@inheritDoc}
3601      */
3602     @Override
waitForDeviceOnline(long waitTime)3603     public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException {
3604         if (mStateMonitor.waitForDeviceOnline(waitTime) == null) {
3605             recoverDevice();
3606         }
3607     }
3608 
3609     /**
3610      * {@inheritDoc}
3611      */
3612     @Override
waitForDeviceOnline()3613     public void waitForDeviceOnline() throws DeviceNotAvailableException {
3614         if (mStateMonitor.waitForDeviceOnline() == null) {
3615             recoverDevice();
3616         }
3617     }
3618 
3619     /**
3620      * {@inheritDoc}
3621      */
3622     @Override
waitForDeviceAvailable(long waitTime)3623     public void waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException {
3624         if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) {
3625             recoverDevice();
3626         }
3627     }
3628 
3629     /**
3630      * {@inheritDoc}
3631      */
3632     @Override
waitForDeviceAvailable()3633     public void waitForDeviceAvailable() throws DeviceNotAvailableException {
3634         if (mStateMonitor.waitForDeviceAvailable() == null) {
3635             recoverDevice();
3636         }
3637     }
3638 
3639     /**
3640      * {@inheritDoc}
3641      */
3642     @Override
waitForDeviceNotAvailable(long waitTime)3643     public boolean waitForDeviceNotAvailable(long waitTime) {
3644         return mStateMonitor.waitForDeviceNotAvailable(waitTime);
3645     }
3646 
3647     /**
3648      * {@inheritDoc}
3649      */
3650     @Override
waitForDeviceInRecovery(long waitTime)3651     public boolean waitForDeviceInRecovery(long waitTime) {
3652         return mStateMonitor.waitForDeviceInRecovery(waitTime);
3653     }
3654 
3655     /** {@inheritDoc} */
3656     @Override
waitForDeviceInSideload(long waitTime)3657     public boolean waitForDeviceInSideload(long waitTime) {
3658         return mStateMonitor.waitForDeviceInSideload(waitTime);
3659     }
3660 
3661     /**
3662      * Small helper function to throw an NPE if the passed arg is null.  This should be used when
3663      * some value will be stored and used later, in which case it'll avoid hard-to-trace
3664      * asynchronous NullPointerExceptions by throwing the exception synchronously.  This is not
3665      * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care
3666      * of it in that case.
3667      */
throwIfNull(Object obj)3668     private void throwIfNull(Object obj) {
3669         if (obj == null) throw new NullPointerException();
3670     }
3671 
3672     /** Retrieve this device's recovery mechanism. */
3673     @VisibleForTesting
getRecovery()3674     IDeviceRecovery getRecovery() {
3675         return mRecovery;
3676     }
3677 
3678     /**
3679      * {@inheritDoc}
3680      */
3681     @Override
setRecovery(IDeviceRecovery recovery)3682     public void setRecovery(IDeviceRecovery recovery) {
3683         throwIfNull(recovery);
3684         mRecovery = recovery;
3685     }
3686 
3687     /**
3688      * {@inheritDoc}
3689      */
3690     @Override
setRecoveryMode(RecoveryMode mode)3691     public void setRecoveryMode(RecoveryMode mode) {
3692         throwIfNull(mRecoveryMode);
3693         mRecoveryMode = mode;
3694     }
3695 
3696     /**
3697      * {@inheritDoc}
3698      */
3699     @Override
getRecoveryMode()3700     public RecoveryMode getRecoveryMode() {
3701         return mRecoveryMode;
3702     }
3703 
3704     /**
3705      * {@inheritDoc}
3706      */
3707     @Override
setFastbootEnabled(boolean fastbootEnabled)3708     public void setFastbootEnabled(boolean fastbootEnabled) {
3709         mFastbootEnabled = fastbootEnabled;
3710     }
3711 
3712     /**
3713      * {@inheritDoc}
3714      */
3715     @Override
isFastbootEnabled()3716     public boolean isFastbootEnabled() {
3717         return mFastbootEnabled;
3718     }
3719 
3720     /**
3721      * {@inheritDoc}
3722      */
3723     @Override
setFastbootPath(String fastbootPath)3724     public void setFastbootPath(String fastbootPath) {
3725         mFastbootPath = fastbootPath;
3726         // ensure the device and its associated recovery use the same fastboot version.
3727         mRecovery.setFastbootPath(fastbootPath);
3728     }
3729 
3730     /**
3731      * {@inheritDoc}
3732      */
3733     @Override
getFastbootPath()3734     public String getFastbootPath() {
3735         return mFastbootPath;
3736     }
3737 
3738     /** {@inheritDoc} */
3739     @Override
getFastbootVersion()3740     public String getFastbootVersion() {
3741         try {
3742             CommandResult res = executeFastbootCommand("--version");
3743             return res.getStdout().trim();
3744         } catch (DeviceNotAvailableException e) {
3745             // Ignored for host side request
3746         }
3747         return null;
3748     }
3749 
3750     /**
3751      * {@inheritDoc}
3752      */
3753     @Override
setDeviceState(final TestDeviceState deviceState)3754     public void setDeviceState(final TestDeviceState deviceState) {
3755         if (!deviceState.equals(getDeviceState())) {
3756             // disable state changes while fastboot lock is held, because issuing fastboot command
3757             // will disrupt state
3758             if (isStateBootloaderOrFastbootd() && mFastbootLock.isLocked()) {
3759                 return;
3760             }
3761             mState = deviceState;
3762             CLog.d("Device %s state is now %s", getSerialNumber(), deviceState);
3763             mStateMonitor.setState(deviceState);
3764         }
3765     }
3766 
3767     /**
3768      * {@inheritDoc}
3769      */
3770     @Override
getDeviceState()3771     public TestDeviceState getDeviceState() {
3772         return mState;
3773     }
3774 
3775     @Override
isAdbTcp()3776     public boolean isAdbTcp() {
3777         return mStateMonitor.isAdbTcp();
3778     }
3779 
3780     /**
3781      * {@inheritDoc}
3782      */
3783     @Override
switchToAdbTcp()3784     public String switchToAdbTcp() throws DeviceNotAvailableException {
3785         String ipAddress = getIpAddress();
3786         if (ipAddress == null) {
3787             CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber());
3788             return null;
3789         }
3790         String port = "5555";
3791         executeAdbCommand("tcpip", port);
3792         // TODO: analyze result? wait for device offline?
3793         return String.format("%s:%s", ipAddress, port);
3794     }
3795 
3796     /**
3797      * {@inheritDoc}
3798      */
3799     @Override
switchToAdbUsb()3800     public boolean switchToAdbUsb() throws DeviceNotAvailableException {
3801         executeAdbCommand("usb");
3802         // TODO: analyze result? wait for device offline?
3803         return true;
3804     }
3805 
3806     /**
3807      * {@inheritDoc}
3808      */
3809     @Override
setEmulatorProcess(Process p)3810     public void setEmulatorProcess(Process p) {
3811         mEmulatorProcess = p;
3812 
3813     }
3814 
3815     /**
3816      * For emulator set {@link SizeLimitedOutputStream} to log output
3817      * @param output to log the output
3818      */
setEmulatorOutputStream(SizeLimitedOutputStream output)3819     public void setEmulatorOutputStream(SizeLimitedOutputStream output) {
3820         mEmulatorOutput = output;
3821     }
3822 
3823     /**
3824      * {@inheritDoc}
3825      */
3826     @Override
stopEmulatorOutput()3827     public void stopEmulatorOutput() {
3828         if (mEmulatorOutput != null) {
3829             mEmulatorOutput.delete();
3830             mEmulatorOutput = null;
3831         }
3832     }
3833 
3834     /**
3835      * {@inheritDoc}
3836      */
3837     @Override
getEmulatorOutput()3838     public InputStreamSource getEmulatorOutput() {
3839         if (getIDevice().isEmulator()) {
3840             if (mEmulatorOutput == null) {
3841                 CLog.w("Emulator output for %s was not captured in background",
3842                         getSerialNumber());
3843             } else {
3844                 try {
3845                     return new SnapshotInputStreamSource(
3846                             "getEmulatorOutput", mEmulatorOutput.getData());
3847                 } catch (IOException e) {
3848                     CLog.e("Failed to get %s data.", getSerialNumber());
3849                     CLog.e(e);
3850                 }
3851             }
3852         }
3853         return new ByteArrayInputStreamSource(new byte[0]);
3854     }
3855 
3856     /**
3857      * {@inheritDoc}
3858      */
3859     @Override
getEmulatorProcess()3860     public Process getEmulatorProcess() {
3861         return mEmulatorProcess;
3862     }
3863 
3864     /**
3865      * @return <code>true</code> if adb root should be enabled on device
3866      */
isEnableAdbRoot()3867     public boolean isEnableAdbRoot() {
3868         return mOptions.isEnableAdbRoot();
3869     }
3870 
3871     /**
3872      * {@inheritDoc}
3873      */
3874     @Override
getInstalledPackageNames()3875     public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException {
3876         throw new UnsupportedOperationException("No support for Package's feature");
3877     }
3878 
3879     /** {@inheritDoc} */
3880     @Override
isPackageInstalled(String packageName)3881     public boolean isPackageInstalled(String packageName) throws DeviceNotAvailableException {
3882         throw new UnsupportedOperationException("No support for Package's feature");
3883     }
3884 
3885     /** {@inheritDoc} */
3886     @Override
isPackageInstalled(String packageName, String userId)3887     public boolean isPackageInstalled(String packageName, String userId)
3888             throws DeviceNotAvailableException {
3889         throw new UnsupportedOperationException("No support for Package's feature");
3890     }
3891 
3892     /** {@inheritDoc} */
3893     @Override
getActiveApexes()3894     public Set<ApexInfo> getActiveApexes() throws DeviceNotAvailableException {
3895         throw new UnsupportedOperationException("No support for Package's feature");
3896     }
3897 
3898     /** {@inheritDoc} */
3899     @Override
getAppPackageInfos()3900     public List<PackageInfo> getAppPackageInfos() throws DeviceNotAvailableException {
3901         throw new UnsupportedOperationException("No support for Package's feature");
3902     }
3903 
3904     /**
3905      * {@inheritDoc}
3906      */
3907     @Override
getUninstallablePackageNames()3908     public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException {
3909         throw new UnsupportedOperationException("No support for Package's feature");
3910     }
3911 
3912     /**
3913      * {@inheritDoc}
3914      */
3915     @Override
getAppPackageInfo(String packageName)3916     public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException {
3917         throw new UnsupportedOperationException("No support for Package's feature");
3918     }
3919 
3920     /**
3921      * {@inheritDoc}
3922      */
3923     @Override
getOptions()3924     public TestDeviceOptions getOptions() {
3925         return mOptions;
3926     }
3927 
3928     /**
3929      * {@inheritDoc}
3930      */
3931     @Override
getApiLevel()3932     public int getApiLevel() throws DeviceNotAvailableException {
3933         int apiLevel = UNKNOWN_API_LEVEL;
3934         try {
3935             String prop = getProperty(DeviceProperties.SDK_VERSION);
3936             apiLevel = Integer.parseInt(prop);
3937         } catch (NumberFormatException nfe) {
3938             CLog.w(
3939                     "Unable to get API level from "
3940                             + DeviceProperties.SDK_VERSION
3941                             + ", falling back to UNKNOWN.",
3942                     nfe);
3943             // ignore, return unknown instead
3944         }
3945         return apiLevel;
3946     }
3947 
3948     /** {@inheritDoc} */
3949     @Override
checkApiLevelAgainstNextRelease(int strictMinLevel)3950     public boolean checkApiLevelAgainstNextRelease(int strictMinLevel)
3951             throws DeviceNotAvailableException {
3952         String codeName = getProperty(DeviceProperties.BUILD_CODENAME);
3953         if (codeName == null) {
3954             throw new DeviceRuntimeException(
3955                     String.format(
3956                             "Failed to query property '%s'. device returned null.",
3957                             DeviceProperties.BUILD_CODENAME));
3958         }
3959         codeName = codeName.trim();
3960         int apiLevel = getApiLevel() + ("REL".equals(codeName) ? 0 : 1);
3961         if (strictMinLevel > apiLevel) {
3962             return false;
3963         }
3964         return true;
3965     }
3966 
getApiLevelSafe()3967     private int getApiLevelSafe() {
3968         try {
3969             return getApiLevel();
3970         } catch (DeviceNotAvailableException e) {
3971             CLog.e(e);
3972             return UNKNOWN_API_LEVEL;
3973         }
3974     }
3975 
3976     /** {@inheritDoc} */
3977     @Override
getLaunchApiLevel()3978     public int getLaunchApiLevel() throws DeviceNotAvailableException {
3979         try {
3980             String prop = getProperty(DeviceProperties.FIRST_API_LEVEL);
3981             return Integer.parseInt(prop);
3982         } catch (NumberFormatException nfe) {
3983             CLog.w(
3984                     "Unable to get first launch API level from "
3985                             + DeviceProperties.FIRST_API_LEVEL
3986                             + ", falling back to getApiLevel().",
3987                     nfe);
3988         }
3989         return getApiLevel();
3990     }
3991 
3992     @Override
getMonitor()3993     public IDeviceStateMonitor getMonitor() {
3994         return mStateMonitor;
3995     }
3996 
3997     /**
3998      * {@inheritDoc}
3999      */
4000     @Override
waitForDeviceShell(long waitTime)4001     public boolean waitForDeviceShell(long waitTime) {
4002         return mStateMonitor.waitForDeviceShell(waitTime);
4003     }
4004 
4005     @Override
getAllocationState()4006     public DeviceAllocationState getAllocationState() {
4007         return mAllocationState;
4008     }
4009 
4010     /**
4011      * {@inheritDoc}
4012      * <p>
4013      * Process the DeviceEvent, which may or may not transition this device to a new allocation
4014      * state.
4015      * </p>
4016      */
4017     @Override
handleAllocationEvent(DeviceEvent event)4018     public DeviceEventResponse handleAllocationEvent(DeviceEvent event) {
4019 
4020         // keep track of whether state has actually changed or not
4021         boolean stateChanged = false;
4022         DeviceAllocationState newState;
4023         DeviceAllocationState oldState = mAllocationState;
4024         mAllocationStateLock.lock();
4025         try {
4026             // update oldState here, just in case in changed before we got lock
4027             oldState = mAllocationState;
4028             newState = mAllocationState.handleDeviceEvent(event);
4029             if (oldState != newState) {
4030                 // state has changed! record this fact, and store the new state
4031                 stateChanged = true;
4032                 mAllocationState = newState;
4033             }
4034         } finally {
4035             mAllocationStateLock.unlock();
4036         }
4037         if (stateChanged && mAllocationMonitor != null) {
4038             // state has changed! Lets inform the allocation monitor listener
4039             mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState);
4040         }
4041         return new DeviceEventResponse(newState, stateChanged);
4042     }
4043 
4044     /** {@inheritDoc} */
4045     @Override
getDeviceTimeOffset(Date date)4046     public long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException {
4047         Long deviceTime = getDeviceDate();
4048         long offset = 0;
4049 
4050         if (date == null) {
4051             date = new Date();
4052         }
4053 
4054         offset = date.getTime() - deviceTime;
4055         CLog.d("Time offset = %d ms", offset);
4056         return offset;
4057     }
4058 
4059     /**
4060      * {@inheritDoc}
4061      */
4062     @Override
setDate(Date date)4063     public void setDate(Date date) throws DeviceNotAvailableException {
4064         if (date == null) {
4065             date = new Date();
4066         }
4067         long timeOffset = getDeviceTimeOffset(date);
4068         // no need to set date
4069         if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) {
4070             return;
4071         }
4072         String dateString = null;
4073         if (getApiLevel() < 23) {
4074             // set date in epoch format
4075             dateString = Long.toString(date.getTime() / 1000); //ms to s
4076         } else {
4077             // set date with POSIX like params
4078             SimpleDateFormat sdf = new java.text.SimpleDateFormat(
4079                     "MMddHHmmyyyy.ss");
4080             sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
4081             dateString = sdf.format(date);
4082         }
4083         // best effort, no verification
4084         // Use TZ= to default to UTC timezone (b/128353510 for background)
4085         executeShellCommand("TZ=UTC date -u " + dateString);
4086     }
4087 
4088     /** {@inheritDoc} */
4089     @Override
getDeviceDate()4090     public long getDeviceDate() throws DeviceNotAvailableException {
4091         String deviceTimeString = executeShellCommand("date +%s");
4092         Long deviceTime = null;
4093         try {
4094             deviceTime = Long.valueOf(deviceTimeString.trim());
4095         } catch (NumberFormatException nfe) {
4096             CLog.i("Invalid device time: \"%s\", ignored.", nfe);
4097             return 0;
4098         }
4099         // Convert from seconds to milliseconds
4100         return deviceTime * 1000L;
4101     }
4102 
4103     /**
4104      * {@inheritDoc}
4105      */
4106     @Override
waitForBootComplete(long timeOut)4107     public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException {
4108         return mStateMonitor.waitForBootComplete(timeOut);
4109     }
4110 
4111     /**
4112      * {@inheritDoc}
4113      */
4114     @Override
listUsers()4115     public ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
4116         throw new UnsupportedOperationException("No support for user's feature.");
4117     }
4118 
4119     /** {@inheritDoc} */
4120     @Override
getUserInfos()4121     public Map<Integer, UserInfo> getUserInfos() throws DeviceNotAvailableException {
4122         throw new UnsupportedOperationException("No support for user's feature.");
4123     }
4124 
4125     /**
4126      * {@inheritDoc}
4127      */
4128     @Override
getMaxNumberOfUsersSupported()4129     public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
4130         throw new UnsupportedOperationException("No support for user's feature.");
4131     }
4132 
4133     @Override
getMaxNumberOfRunningUsersSupported()4134     public int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
4135         throw new UnsupportedOperationException("No support for user's feature.");
4136     }
4137 
4138     /**
4139      * {@inheritDoc}
4140      */
4141     @Override
isMultiUserSupported()4142     public boolean isMultiUserSupported() throws DeviceNotAvailableException {
4143         throw new UnsupportedOperationException("No support for user's feature.");
4144     }
4145 
4146     /** {@inheritDoc} */
4147     @Override
createUserNoThrow(String name)4148     public int createUserNoThrow(String name) throws DeviceNotAvailableException {
4149         throw new UnsupportedOperationException("No support for user's feature.");
4150     }
4151 
4152     /**
4153      * {@inheritDoc}
4154      */
4155     @Override
createUser(String name)4156     public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException {
4157         throw new UnsupportedOperationException("No support for user's feature.");
4158     }
4159 
4160     /**
4161      * {@inheritDoc}
4162      */
4163     @Override
createUser(String name, boolean guest, boolean ephemeral)4164     public int createUser(String name, boolean guest, boolean ephemeral)
4165             throws DeviceNotAvailableException, IllegalStateException {
4166         throw new UnsupportedOperationException("No support for user's feature.");
4167     }
4168 
4169     /**
4170      * {@inheritDoc}
4171      */
4172     @Override
removeUser(int userId)4173     public boolean removeUser(int userId) throws DeviceNotAvailableException {
4174         throw new UnsupportedOperationException("No support for user's feature.");
4175     }
4176 
4177     /**
4178      * {@inheritDoc}
4179      */
4180     @Override
startUser(int userId)4181     public boolean startUser(int userId) throws DeviceNotAvailableException {
4182         throw new UnsupportedOperationException("No support for user's feature.");
4183     }
4184 
4185     /** {@inheritDoc} */
4186     @Override
startUser(int userId, boolean waitFlag)4187     public boolean startUser(int userId, boolean waitFlag) throws DeviceNotAvailableException {
4188         throw new UnsupportedOperationException("No support for user's feature.");
4189     }
4190 
4191     /**
4192      * {@inheritDoc}
4193      */
4194     @Override
stopUser(int userId)4195     public boolean stopUser(int userId) throws DeviceNotAvailableException {
4196         throw new UnsupportedOperationException("No support for user's feature.");
4197     }
4198 
4199     /**
4200      * {@inheritDoc}
4201      */
4202     @Override
stopUser(int userId, boolean waitFlag, boolean forceFlag)4203     public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
4204             throws DeviceNotAvailableException {
4205         throw new UnsupportedOperationException("No support for user's feature.");
4206     }
4207 
4208     /**
4209      * {@inheritDoc}
4210      */
4211     @Override
remountSystemWritable()4212     public void remountSystemWritable() throws DeviceNotAvailableException {
4213         String verity = getProperty("partition.system.verified");
4214         // have the property set (regardless state) implies verity is enabled, so we send adb
4215         // command to disable verity
4216         if (verity != null && !verity.isEmpty()) {
4217             executeAdbCommand("disable-verity");
4218             reboot();
4219         }
4220         executeAdbCommand("remount");
4221         waitForDeviceAvailable();
4222     }
4223 
4224     /** {@inheritDoc} */
4225     @Override
remountVendorWritable()4226     public void remountVendorWritable() throws DeviceNotAvailableException {
4227         String verity = getProperty("partition.vendor.verified");
4228         // have the property set (regardless state) implies verity is enabled, so we send adb
4229         // command to disable verity
4230         if (verity != null && !verity.isEmpty()) {
4231             executeAdbCommand("disable-verity");
4232             reboot();
4233         }
4234         executeAdbCommand("remount");
4235         waitForDeviceAvailable();
4236     }
4237 
4238     /**
4239      * {@inheritDoc}
4240      */
4241     @Override
getPrimaryUserId()4242     public Integer getPrimaryUserId() throws DeviceNotAvailableException {
4243         throw new UnsupportedOperationException("No support for user's feature.");
4244     }
4245 
4246     /**
4247      * {@inheritDoc}
4248      */
4249     @Override
getCurrentUser()4250     public int getCurrentUser() throws DeviceNotAvailableException {
4251         throw new UnsupportedOperationException("No support for user's feature.");
4252     }
4253 
4254     /** {@inheritDoc} */
4255     @Override
isUserSecondary(int userId)4256     public boolean isUserSecondary(int userId) throws DeviceNotAvailableException {
4257         throw new UnsupportedOperationException("No support for user's feature.");
4258     }
4259 
4260 
4261     /**
4262      * {@inheritDoc}
4263      */
4264     @Override
getUserFlags(int userId)4265     public int getUserFlags(int userId) throws DeviceNotAvailableException {
4266         throw new UnsupportedOperationException("No support for user's feature.");
4267     }
4268 
4269     /**
4270      * {@inheritDoc}
4271      */
4272     @Override
getUserSerialNumber(int userId)4273     public int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
4274         throw new UnsupportedOperationException("No support for user's feature.");
4275     }
4276 
4277     /**
4278      * {@inheritDoc}
4279      */
4280     @Override
switchUser(int userId)4281     public boolean switchUser(int userId) throws DeviceNotAvailableException {
4282         throw new UnsupportedOperationException("No support for user's feature.");
4283     }
4284 
4285     /**
4286      * {@inheritDoc}
4287      */
4288     @Override
switchUser(int userId, long timeout)4289     public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException {
4290         throw new UnsupportedOperationException("No support for user's feature.");
4291     }
4292 
4293     /**
4294      * {@inheritDoc}
4295      */
4296     @Override
isUserRunning(int userId)4297     public boolean isUserRunning(int userId) throws DeviceNotAvailableException {
4298         throw new UnsupportedOperationException("No support for user's feature.");
4299     }
4300 
4301     /**
4302      * {@inheritDoc}
4303      */
4304     @Override
hasFeature(String feature)4305     public boolean hasFeature(String feature) throws DeviceNotAvailableException {
4306         throw new UnsupportedOperationException("No support pm's features.");
4307     }
4308 
4309     /**
4310      * {@inheritDoc}
4311      */
4312     @Override
getSetting(String namespace, String key)4313     public String getSetting(String namespace, String key)
4314             throws DeviceNotAvailableException {
4315         throw new UnsupportedOperationException("No support for setting's feature.");
4316     }
4317 
4318     /**
4319      * {@inheritDoc}
4320      */
4321     @Override
getSetting(int userId, String namespace, String key)4322     public String getSetting(int userId, String namespace, String key)
4323             throws DeviceNotAvailableException {
4324         throw new UnsupportedOperationException("No support for setting's feature.");
4325     }
4326 
4327     /** {@inheritDoc} */
4328     @Override
getAllSettings(String namespace)4329     public Map<String, String> getAllSettings(String namespace) throws DeviceNotAvailableException {
4330         throw new UnsupportedOperationException("No support for setting's feature.");
4331     }
4332 
4333     /**
4334      * {@inheritDoc}
4335      */
4336     @Override
setSetting(String namespace, String key, String value)4337     public void setSetting(String namespace, String key, String value)
4338             throws DeviceNotAvailableException {
4339         throw new UnsupportedOperationException("No support for setting's feature.");
4340     }
4341 
4342     /**
4343      * {@inheritDoc}
4344      */
4345     @Override
setSetting(int userId, String namespace, String key, String value)4346     public void setSetting(int userId, String namespace, String key, String value)
4347             throws DeviceNotAvailableException {
4348         throw new UnsupportedOperationException("No support for setting's feature.");
4349     }
4350 
4351     /**
4352      * {@inheritDoc}
4353      */
4354     @Override
getBuildSigningKeys()4355     public String getBuildSigningKeys() throws DeviceNotAvailableException {
4356         String buildTags = getProperty(DeviceProperties.BUILD_TAGS);
4357         if (buildTags != null) {
4358             String[] tags = buildTags.split(",");
4359             for (String tag : tags) {
4360                 Matcher m = KEYS_PATTERN.matcher(tag);
4361                 if (m.matches()) {
4362                     return tag;
4363                 }
4364             }
4365         }
4366         return null;
4367     }
4368 
4369     /**
4370      * {@inheritDoc}
4371      */
4372     @Override
getAndroidId(int userId)4373     public String getAndroidId(int userId) throws DeviceNotAvailableException {
4374         throw new UnsupportedOperationException("No support for user's feature.");
4375     }
4376 
4377     /**
4378      * {@inheritDoc}
4379      */
4380     @Override
getAndroidIds()4381     public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
4382         throw new UnsupportedOperationException("No support for user's feature.");
4383     }
4384 
4385     /** {@inheritDoc} */
4386     @Override
setDeviceOwner(String componentName, int userId)4387     public boolean setDeviceOwner(String componentName, int userId)
4388             throws DeviceNotAvailableException {
4389         throw new UnsupportedOperationException("No support for user's feature.");
4390     }
4391 
4392     /** {@inheritDoc} */
4393     @Override
removeAdmin(String componentName, int userId)4394     public boolean removeAdmin(String componentName, int userId)
4395             throws DeviceNotAvailableException {
4396         throw new UnsupportedOperationException("No support for user's feature.");
4397     }
4398 
4399     /** {@inheritDoc} */
4400     @Override
removeOwners()4401     public void removeOwners() throws DeviceNotAvailableException {
4402         throw new UnsupportedOperationException("No support for user's feature.");
4403     }
4404 
4405     /**
4406      * {@inheritDoc}
4407      */
4408     @Override
disableKeyguard()4409     public void disableKeyguard() throws DeviceNotAvailableException {
4410         throw new UnsupportedOperationException("No support for Window Manager's features");
4411     }
4412 
4413     /** {@inheritDoc} */
4414     @Override
getDeviceClass()4415     public String getDeviceClass() {
4416         IDevice device = getIDevice();
4417         if (device == null) {
4418             CLog.w("No IDevice instance, cannot determine device class.");
4419             return "";
4420         }
4421         return device.getClass().getSimpleName();
4422     }
4423 
4424     /** {@inheritDoc} */
4425     @Override
preInvocationSetup(IBuildInfo info)4426     public void preInvocationSetup(IBuildInfo info)
4427             throws TargetSetupError, DeviceNotAvailableException {
4428         // Default implementation
4429         mContentProvider = null;
4430         mShouldSkipContentProviderSetup = false;
4431         try {
4432             mExecuteShellCommandLogs =
4433                     FileUtil.createTempFile("TestDevice_ExecuteShellCommands", ".txt");
4434         } catch (IOException e) {
4435             throw new TargetSetupError(
4436                     "Failed to create the executeShellCommand log file.",
4437                     e,
4438                     getDeviceDescriptor(),
4439                     InfraErrorIdentifier.FAIL_TO_CREATE_FILE);
4440         }
4441     }
4442 
4443     /** {@inheritDoc} */
4444     @Override
postInvocationTearDown(Throwable exception)4445     public void postInvocationTearDown(Throwable exception) {
4446         mIsEncryptionSupported = null;
4447         FileUtil.deleteFile(mExecuteShellCommandLogs);
4448         mExecuteShellCommandLogs = null;
4449         // Default implementation
4450         if (getIDevice() instanceof StubDevice) {
4451             return;
4452         }
4453         // Reset the Content Provider bit.
4454         mShouldSkipContentProviderSetup = false;
4455         try {
4456             // If we never installed it, don't even bother checking for it during tear down.
4457             if (mContentProvider == null) {
4458                 return;
4459             }
4460             if (exception instanceof DeviceNotAvailableException) {
4461                 CLog.e(
4462                         "Skip Tradefed Content Provider teardown due to DeviceNotAvailableException.");
4463                 return;
4464             }
4465             if (TestDeviceState.ONLINE.equals(getDeviceState())) {
4466                 mContentProvider.tearDown();
4467             }
4468         } catch (DeviceNotAvailableException e) {
4469             CLog.e(e);
4470         }
4471     }
4472 
4473     /**
4474      * {@inheritDoc}
4475      */
4476     @Override
isHeadless()4477     public boolean isHeadless() throws DeviceNotAvailableException {
4478         if (getProperty(DeviceProperties.BUILD_HEADLESS) != null) {
4479             return true;
4480         }
4481         return false;
4482     }
4483 
checkApiLevelAgainst(String feature, int strictMinLevel)4484     protected void checkApiLevelAgainst(String feature, int strictMinLevel) {
4485         try {
4486             if (getApiLevel() < strictMinLevel){
4487                 throw new IllegalArgumentException(String.format("%s not supported on %s. "
4488                         + "Must be API %d.", feature, getSerialNumber(), strictMinLevel));
4489             }
4490         } catch (DeviceNotAvailableException e) {
4491             throw new HarnessRuntimeException(
4492                     "Device became unavailable while checking API level",
4493                     e,
4494                     DeviceErrorIdentifier.DEVICE_UNAVAILABLE);
4495         }
4496     }
4497 
4498     /** {@inheritDoc} */
4499     @Override
getCachedDeviceDescriptor()4500     public DeviceDescriptor getCachedDeviceDescriptor() {
4501         synchronized (mCacheLock) {
4502             if (DeviceAllocationState.Allocated.equals(getAllocationState())) {
4503                 if (mCachedDeviceDescriptor == null) {
4504                     // Create the cache the very first time when it's allocated.
4505                     mCachedDeviceDescriptor = getDeviceDescriptor();
4506                     return mCachedDeviceDescriptor;
4507                 }
4508                 return mCachedDeviceDescriptor;
4509             }
4510             // If device is not allocated, just return current information
4511             mCachedDeviceDescriptor = null;
4512             return getDeviceDescriptor();
4513         }
4514     }
4515 
4516     /**
4517      * {@inheritDoc}
4518      */
4519     @Override
getDeviceDescriptor()4520     public DeviceDescriptor getDeviceDescriptor() {
4521         IDeviceSelection selector = new DeviceSelectionOptions();
4522         IDevice idevice = getIDevice();
4523         try {
4524             boolean isTemporary = false;
4525             if (idevice instanceof NullDevice) {
4526                 isTemporary = ((NullDevice) idevice).isTemporary();
4527             }
4528             // All the operations to create the descriptor need to be safe (should not trigger any
4529             // device side effects like recovery)
4530             return new DeviceDescriptor(
4531                     idevice.getSerialNumber(),
4532                     null,
4533                     idevice instanceof StubDevice,
4534                     idevice.getState(),
4535                     getAllocationState(),
4536                     getDeviceState(),
4537                     getDisplayString(selector.getDeviceProductType(idevice)),
4538                     getDisplayString(selector.getDeviceProductVariant(idevice)),
4539                     getDisplayString(idevice.getProperty(DeviceProperties.SDK_VERSION)),
4540                     getDisplayString(idevice.getProperty(DeviceProperties.BUILD_ALIAS)),
4541                     getDisplayString(getBattery()),
4542                     getDeviceClass(),
4543                     getDisplayString(getMacAddress()),
4544                     getDisplayString(getSimState()),
4545                     getDisplayString(getSimOperator()),
4546                     isTemporary,
4547                     idevice);
4548         } catch (RuntimeException e) {
4549             CLog.e("Exception while building device '%s' description:", getSerialNumber());
4550             CLog.e(e);
4551         }
4552         return null;
4553     }
4554 
4555     /**
4556      * Return the displayable string for given object
4557      */
getDisplayString(Object o)4558     private String getDisplayString(Object o) {
4559         return o == null ? "unknown" : o.toString();
4560     }
4561 
4562     /** {@inheritDoc} */
4563     @Override
getProcessByName(String processName)4564     public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException {
4565         String pidString = getProcessPid(processName);
4566         if (pidString == null) {
4567             return null;
4568         }
4569         long startTime = getProcessStartTimeByPid(pidString);
4570         if (startTime == -1L) {
4571             return null;
4572         }
4573         return new ProcessInfo(
4574                 getProcessUserByPid(pidString),
4575                 Integer.parseInt(pidString),
4576                 processName,
4577                 startTime);
4578     }
4579 
4580     /** Return the process start time since epoch for the given pid string */
getProcessStartTimeByPid(String pidString)4581     private long getProcessStartTimeByPid(String pidString) throws DeviceNotAvailableException {
4582         String output = executeShellCommand(String.format("ps -p %s -o stime=", pidString));
4583         if (output != null && !output.trim().isEmpty()) {
4584             output = output.trim();
4585             String dateInSecond = executeShellCommand("date -d\"" + output + "\" +%s");
4586             if (Strings.isNullOrEmpty(dateInSecond)) {
4587                 return -1L;
4588             }
4589             try {
4590                 return Long.parseLong(dateInSecond.trim());
4591             } catch (NumberFormatException e) {
4592                 CLog.e("Failed to parse the start time for process:");
4593                 CLog.e(e);
4594                 return -1L;
4595             }
4596         }
4597         return -1L;
4598     }
4599 
4600     /** Return the process user for the given pid string */
getProcessUserByPid(String pidString)4601     private String getProcessUserByPid(String pidString) throws DeviceNotAvailableException {
4602         String output = executeShellCommand("stat -c%U /proc/" + pidString);
4603         if (output != null && !output.trim().isEmpty()) {
4604             try {
4605                 return output.trim();
4606             } catch (NumberFormatException e) {
4607                 return null;
4608             }
4609         }
4610         return null;
4611     }
4612 
4613     /** {@inheritDoc} */
4614     @Override
getBootHistory()4615     public Map<Long, String> getBootHistory() throws DeviceNotAvailableException {
4616         String output = getProperty(DeviceProperties.BOOT_REASON_HISTORY);
4617         /* Sample output:
4618         kernel_panic,1556587278
4619         reboot,,1556238008
4620         reboot,,1556237796
4621         reboot,,1556237725
4622         */
4623         Map<Long, String> bootHistory = new LinkedHashMap<Long, String>();
4624         if (Strings.isNullOrEmpty(output)) {
4625             return bootHistory;
4626         }
4627         for (String line : output.split("\\n")) {
4628             String infoStr[] = line.split(",");
4629             String startStr = infoStr[infoStr.length - 1];
4630             try {
4631                 long startTime = Long.parseLong(startStr.trim());
4632                 bootHistory.put(startTime, infoStr[0].trim());
4633             } catch (NumberFormatException e) {
4634                 CLog.e("Fail to parse boot time from line %s", line);
4635             }
4636         }
4637         return bootHistory;
4638     }
4639 
4640     /** {@inheritDoc} */
4641     @Override
getBootHistorySince(long utcEpochTime, TimeUnit timeUnit)4642     public Map<Long, String> getBootHistorySince(long utcEpochTime, TimeUnit timeUnit)
4643             throws DeviceNotAvailableException {
4644         long utcEpochTimeSec = TimeUnit.SECONDS.convert(utcEpochTime, timeUnit);
4645         Map<Long, String> bootHistory = new LinkedHashMap<Long, String>();
4646         for (Map.Entry<Long, String> entry : getBootHistory().entrySet()) {
4647             if (entry.getKey() >= utcEpochTimeSec) {
4648                 bootHistory.put(entry.getKey(), entry.getValue());
4649             }
4650         }
4651         return bootHistory;
4652     }
4653 
hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit)4654     private boolean hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit)
4655             throws DeviceNotAvailableException {
4656         Map<Long, String> bootHistory = getBootHistorySince(utcEpochTime, timeUnit);
4657         if (bootHistory.isEmpty()) {
4658             CLog.w("There is no reboot history since %s", utcEpochTime);
4659             return false;
4660         }
4661 
4662         CLog.i(
4663                 "There are new boot history since %d. NewBootHistory = %s",
4664                 utcEpochTime, bootHistory);
4665         // Check if there is reboot reason other than "reboot".
4666         // Raise RuntimeException if there is abnormal reboot.
4667         for (Map.Entry<Long, String> entry : bootHistory.entrySet()) {
4668             if (!"reboot".equals(entry.getValue())) {
4669                 throw new RuntimeException(
4670                         String.format(
4671                                 "Device %s has abnormal reboot reason %s at %d",
4672                                 getSerialNumber(), entry.getValue(), entry.getKey()));
4673             }
4674         }
4675         return true;
4676     }
4677 
4678     /**
4679      * Check current system process is restarted after last reboot
4680      *
4681      * @param systemServerProcess the system_server {@link ProcessInfo}
4682      * @return true if system_server process restarted after last reboot; false if not
4683      * @throws DeviceNotAvailableException
4684      */
checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess)4685     private boolean checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess)
4686             throws DeviceNotAvailableException {
4687         // If time gap from last reboot to current system_server process start time is more than
4688         // MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP seconds, we conclude the system_server restarted
4689         // after boot up.
4690         if (!hasNormalRebootSince(
4691                 systemServerProcess.getStartTime() - MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC,
4692                 TimeUnit.SECONDS)) {
4693             CLog.i(
4694                     "Device last reboot is more than %s seconds away from current system_server "
4695                             + "process start time. The system_server process restarted after "
4696                             + "last boot up",
4697                     MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC);
4698             return true;
4699         } else {
4700             // Current system_server start within MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP
4701             // seconds after device last boot up
4702             return false;
4703         }
4704     }
4705 
4706     /** {@inheritDoc} */
4707     @Override
deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit)4708     public boolean deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit)
4709             throws DeviceNotAvailableException {
4710         ProcessInfo currSystemServerProcess = getProcessByName("system_server");
4711         if (currSystemServerProcess == null) {
4712             CLog.i("The system_server process is not available on the device.");
4713             return true;
4714         }
4715 
4716         // The system_server process started at or before utcEpochTime, there is no soft-restart
4717         if (Math.abs(
4718                         currSystemServerProcess.getStartTime()
4719                                 - TimeUnit.SECONDS.convert(utcEpochTime, timeUnit))
4720                 <= 1) {
4721             return false;
4722         }
4723 
4724         // The system_server process restarted after device utcEpochTime in second.
4725         // Check if there is new reboot history, if no new reboot, device soft-restarted.
4726         // If there is no normal reboot, soft-restart is detected.
4727         if (!hasNormalRebootSince(utcEpochTime, timeUnit)) {
4728             return true;
4729         }
4730 
4731         // There is new reboot since utcEpochTime. Check if system_server restarted after boot up.
4732         return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess);
4733     }
4734 
4735     /** {@inheritDoc} */
4736     @Override
deviceSoftRestarted(ProcessInfo prevSystemServerProcess)4737     public boolean deviceSoftRestarted(ProcessInfo prevSystemServerProcess)
4738             throws DeviceNotAvailableException {
4739         if (prevSystemServerProcess == null) {
4740             CLog.i("The given system_server process is null. Abort deviceSoftRestarted check.");
4741             return false;
4742         }
4743         ProcessInfo currSystemServerProcess = getProcessByName("system_server");
4744         if (currSystemServerProcess == null) {
4745             CLog.i("The system_server process is not available on the device.");
4746             return true;
4747         }
4748 
4749         // Compare the start time with a 1 seconds accuracy due to how the date is computed
4750         if (currSystemServerProcess.getPid() == prevSystemServerProcess.getPid()
4751                 && Math.abs(
4752                                 currSystemServerProcess.getStartTime()
4753                                         - prevSystemServerProcess.getStartTime())
4754                         <= 1) {
4755             return false;
4756         }
4757 
4758         CLog.v(
4759                 "current system_server: %s; prev system_server: %s",
4760                 currSystemServerProcess, prevSystemServerProcess);
4761 
4762         // The system_server process restarted.
4763         // Check boot history with previous system_server start time.
4764         // If there is no normal reboot, soft-restart is detected
4765         if (!hasNormalRebootSince(prevSystemServerProcess.getStartTime(), TimeUnit.SECONDS)) {
4766             return true;
4767         }
4768 
4769         // There is reboot since prevSystemServerProcess.getStartTime().
4770         // Check if system_server restarted after boot up.
4771         return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess);
4772 
4773     }
4774 
4775     /**
4776      * Validates that the given input is a valid MAC address
4777      *
4778      * @param address input to validate
4779      * @return true if the input is a valid MAC address
4780      */
isMacAddress(String address)4781     boolean isMacAddress(String address) {
4782         Pattern macPattern = Pattern.compile(MAC_ADDRESS_PATTERN);
4783         Matcher macMatcher = macPattern.matcher(address);
4784         return macMatcher.find();
4785     }
4786 
4787     /**
4788      * {@inheritDoc}
4789      */
4790     @Override
getMacAddress()4791     public String getMacAddress() {
4792         if (getIDevice() instanceof StubDevice) {
4793             // Do not query MAC addresses from stub devices.
4794             return null;
4795         }
4796         if (!TestDeviceState.ONLINE.equals(mState)) {
4797             // Only query MAC addresses from online devices.
4798             return null;
4799         }
4800         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
4801         try {
4802             mIDevice.executeShellCommand(MAC_ADDRESS_COMMAND, receiver);
4803         } catch (IOException | TimeoutException | AdbCommandRejectedException |
4804                 ShellCommandUnresponsiveException e) {
4805             CLog.w("Failed to query MAC address for %s", mIDevice.getSerialNumber());
4806             CLog.w(e);
4807         }
4808         String output = receiver.getOutput().trim();
4809         if (isMacAddress(output)) {
4810             return output;
4811         }
4812         CLog.d("No valid MAC address queried from device %s", mIDevice.getSerialNumber());
4813         return null;
4814     }
4815 
4816     /** {@inheritDoc} */
4817     @Override
getSimState()4818     public String getSimState() {
4819         // Use ddmlib getProperty directly to avoid possible recovery path
4820         return getIDevice().getProperty(SIM_STATE_PROP);
4821     }
4822 
4823     /** {@inheritDoc} */
4824     @Override
getSimOperator()4825     public String getSimOperator() {
4826         // Use ddmlib getProperty directly to avoid possible recovery path
4827         return getIDevice().getProperty(SIM_OPERATOR_PROP);
4828     }
4829 
4830     /** {@inheritDoc} */
4831     @Override
dumpHeap(String process, String devicePath)4832     public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException {
4833         throw new UnsupportedOperationException("dumpHeap is not supported.");
4834     }
4835 
4836     /** {@inheritDoc} */
4837     @Override
getProcessPid(String process)4838     public String getProcessPid(String process) throws DeviceNotAvailableException {
4839         String output = executeShellCommand(String.format("pidof %s", process)).trim();
4840         if (checkValidPid(output)) {
4841             return output;
4842         }
4843         CLog.e("Failed to find a valid pid for process.");
4844         return null;
4845     }
4846 
4847     /** {@inheritDoc} */
4848     @Override
logOnDevice(String tag, LogLevel level, String format, Object... args)4849     public void logOnDevice(String tag, LogLevel level, String format, Object... args) {
4850         String message = String.format(format, args);
4851         try {
4852             String levelLetter = logLevelToLogcatLevel(level);
4853             String command = String.format("log -t %s -p %s '%s'", tag, levelLetter, message);
4854             executeShellCommand(command);
4855         } catch (DeviceNotAvailableException e) {
4856             CLog.e("Device went not available when attempting to log '%s'", message);
4857             CLog.e(e);
4858         }
4859     }
4860 
4861     /** Convert the {@link LogLevel} to the letter used in log (see 'adb shell log --help'). */
logLevelToLogcatLevel(LogLevel level)4862     private String logLevelToLogcatLevel(LogLevel level) {
4863         switch (level) {
4864             case DEBUG:
4865                 return "d";
4866             case ERROR:
4867                 return "e";
4868             case INFO:
4869                 return "i";
4870             case VERBOSE:
4871                 return "v";
4872             case WARN:
4873                 return "w";
4874             default:
4875                 return "i";
4876         }
4877     }
4878 
4879     /** {@inheritDoc} */
4880     @Override
getTotalMemory()4881     public long getTotalMemory() {
4882         // "/proc/meminfo" always returns value in kilobytes.
4883         long totalMemory = 0;
4884         String output = null;
4885         try {
4886             output = executeShellCommand("cat /proc/meminfo | grep MemTotal");
4887         } catch (DeviceNotAvailableException e) {
4888             CLog.e(e);
4889             return -1;
4890         }
4891         if (output.isEmpty()) {
4892             return -1;
4893         }
4894         String[] results = output.split("\\s+");
4895         try {
4896             totalMemory = Long.parseLong(results[1].replaceAll("\\D+", ""));
4897         } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
4898             CLog.e(e);
4899             return -1;
4900         }
4901         return totalMemory * 1024;
4902     }
4903 
4904     /** {@inheritDoc} */
4905     @Override
getBattery()4906     public Integer getBattery() {
4907         if (getIDevice() instanceof StubDevice) {
4908             return null;
4909         }
4910         if (isStateBootloaderOrFastbootd()) {
4911             return null;
4912         }
4913         try {
4914             // Use default 5 minutes freshness
4915             Future<Integer> batteryFuture = getIDevice().getBattery();
4916             // Get cached value or wait up to 500ms for battery level query
4917             return batteryFuture.get(500, TimeUnit.MILLISECONDS);
4918         } catch (InterruptedException
4919                 | ExecutionException
4920                 | java.util.concurrent.TimeoutException e) {
4921             CLog.w(
4922                     "Failed to query battery level for %s: %s",
4923                     getIDevice().getSerialNumber(), e.toString());
4924         }
4925         return null;
4926     }
4927 
4928     /** {@inheritDoc} */
4929     @Override
listDisplayIds()4930     public Set<Long> listDisplayIds() throws DeviceNotAvailableException {
4931         throw new UnsupportedOperationException("dumpsys SurfaceFlinger is not supported.");
4932     }
4933 
4934     /** {@inheritDoc} */
4935     @Override
getLastExpectedRebootTimeMillis()4936     public long getLastExpectedRebootTimeMillis() {
4937         return mLastTradefedRebootTime;
4938     }
4939 
4940     /** {@inheritDoc} */
4941     @Override
getTombstones()4942     public List<File> getTombstones() throws DeviceNotAvailableException {
4943         List<File> tombstones = new ArrayList<>();
4944         if (!isAdbRoot()) {
4945             CLog.w("Device was not root, cannot collect tombstones.");
4946             return tombstones;
4947         }
4948         for (String tombName : getChildren(TOMBSTONE_PATH)) {
4949             File tombFile = pullFile(TOMBSTONE_PATH + tombName);
4950             if (tombFile != null) {
4951                 tombstones.add(tombFile);
4952             }
4953         }
4954         return tombstones;
4955     }
4956 
4957     /** Validate that pid is an integer and not empty. */
checkValidPid(String output)4958     private boolean checkValidPid(String output) {
4959         if (output.isEmpty()) {
4960             return false;
4961         }
4962         try {
4963             Integer.parseInt(output);
4964         } catch (NumberFormatException e) {
4965             CLog.e(e);
4966             return false;
4967         }
4968         return true;
4969     }
4970 
4971     /** Gets the {@link IHostOptions} instance to use. */
4972     @VisibleForTesting
getHostOptions()4973     IHostOptions getHostOptions() {
4974         return GlobalConfiguration.getInstance().getHostOptions();
4975     }
4976 
4977     /** Returns the {@link ContentProviderHandler} or null if not available. */
4978     @VisibleForTesting
getContentProvider()4979     ContentProviderHandler getContentProvider() throws DeviceNotAvailableException {
4980         // If disabled at the device level, don't attempt any checks.
4981         if (!getOptions().shouldUseContentProvider()) {
4982             return null;
4983         }
4984         // Prevent usage of content provider before API 28 as it would not work well since content
4985         // tool is not working before P.
4986         if (getApiLevel() < 28) {
4987             return null;
4988         }
4989         if (mContentProvider == null) {
4990             mContentProvider = new ContentProviderHandler(this);
4991         }
4992         // Force the install if we saw an error with content provider installation.
4993         if (mContentProvider.contentProviderNotFound()) {
4994             mShouldSkipContentProviderSetup = false;
4995         }
4996         if (!mShouldSkipContentProviderSetup) {
4997             boolean res = mContentProvider.setUp();
4998             if (!res) {
4999                 // TODO: once CP becomes a requirement, throw/fail the test if CP can't be found
5000                 return null;
5001             }
5002             mShouldSkipContentProviderSetup = true;
5003         }
5004         return mContentProvider;
5005     }
5006 
5007     /** Reset the flag for content provider setup in order to trigger it again. */
resetContentProviderSetup()5008     void resetContentProviderSetup() {
5009         mShouldSkipContentProviderSetup = false;
5010     }
5011 
5012     /** The log that contains all the {@link #executeShellCommand(String)} logs. */
getExecuteShellCommandLog()5013     public final File getExecuteShellCommandLog() {
5014         return mExecuteShellCommandLogs;
5015     }
5016 }
5017