• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.tradefed.device;
17 
18 import com.android.ddmlib.AdbCommandRejectedException;
19 import com.android.ddmlib.FileListingService;
20 import com.android.ddmlib.FileListingService.FileEntry;
21 import com.android.ddmlib.IDevice;
22 import com.android.ddmlib.IShellOutputReceiver;
23 import com.android.ddmlib.InstallException;
24 import com.android.ddmlib.Log.LogLevel;
25 import com.android.ddmlib.ShellCommandUnresponsiveException;
26 import com.android.ddmlib.SyncException;
27 import com.android.ddmlib.SyncException.SyncError;
28 import com.android.ddmlib.SyncService;
29 import com.android.ddmlib.TimeoutException;
30 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
31 import com.android.ddmlib.testrunner.ITestRunListener;
32 import com.android.tradefed.build.IBuildInfo;
33 import com.android.tradefed.command.remote.DeviceDescriptor;
34 import com.android.tradefed.config.ConfigurationException;
35 import com.android.tradefed.config.GlobalConfiguration;
36 import com.android.tradefed.config.IConfiguration;
37 import com.android.tradefed.config.IConfigurationReceiver;
38 import com.android.tradefed.config.OptionSetter;
39 import com.android.tradefed.device.IWifiHelper.WifiConnectionResult;
40 import com.android.tradefed.device.TestDeviceOptions.InstanceType;
41 import com.android.tradefed.device.cloud.GceAvdInfo;
42 import com.android.tradefed.device.connection.AbstractConnection;
43 import com.android.tradefed.device.connection.DefaultConnection;
44 import com.android.tradefed.device.connection.DefaultConnection.ConnectionBuilder;
45 import com.android.tradefed.device.contentprovider.ContentProviderHandler;
46 import com.android.tradefed.error.HarnessRuntimeException;
47 import com.android.tradefed.host.IHostOptions;
48 import com.android.tradefed.invoker.logger.InvocationMetricLogger;
49 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
50 import com.android.tradefed.invoker.tracing.CloseableTraceScope;
51 import com.android.tradefed.log.ITestLogger;
52 import com.android.tradefed.log.LogUtil;
53 import com.android.tradefed.log.LogUtil.CLog;
54 import com.android.tradefed.result.ByteArrayInputStreamSource;
55 import com.android.tradefed.result.FileInputStreamSource;
56 import com.android.tradefed.result.ITestLifeCycleReceiver;
57 import com.android.tradefed.result.ITestLoggerReceiver;
58 import com.android.tradefed.result.InputStreamSource;
59 import com.android.tradefed.result.LogDataType;
60 import com.android.tradefed.result.SnapshotInputStreamSource;
61 import com.android.tradefed.result.StubTestRunListener;
62 import com.android.tradefed.result.ddmlib.RemoteAndroidTestRunner;
63 import com.android.tradefed.result.ddmlib.TestRunToTestInvocationForwarder;
64 import com.android.tradefed.result.error.DeviceErrorIdentifier;
65 import com.android.tradefed.result.error.ErrorIdentifier;
66 import com.android.tradefed.result.error.InfraErrorIdentifier;
67 import com.android.tradefed.targetprep.TargetSetupError;
68 import com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain;
69 import com.android.tradefed.util.ArrayUtil;
70 import com.android.tradefed.util.Bugreport;
71 import com.android.tradefed.util.CommandResult;
72 import com.android.tradefed.util.CommandStatus;
73 import com.android.tradefed.util.DeviceInspectionResult;
74 import com.android.tradefed.util.FileUtil;
75 import com.android.tradefed.util.IRunUtil;
76 import com.android.tradefed.util.KeyguardControllerState;
77 import com.android.tradefed.util.MultiMap;
78 import com.android.tradefed.util.ProcessInfo;
79 import com.android.tradefed.util.QuotationAwareTokenizer;
80 import com.android.tradefed.util.RunUtil;
81 import com.android.tradefed.util.SizeLimitedOutputStream;
82 import com.android.tradefed.util.StringEscapeUtils;
83 import com.android.tradefed.util.SystemUtil;
84 import com.android.tradefed.util.TimeUtil;
85 import com.android.tradefed.util.ZipUtil2;
86 
87 import com.google.common.annotations.VisibleForTesting;
88 import com.google.common.base.Strings;
89 import com.google.common.cache.CacheBuilder;
90 import com.google.common.cache.CacheLoader;
91 import com.google.common.cache.LoadingCache;
92 import com.google.common.collect.ImmutableSet;
93 import com.google.errorprone.annotations.FormatMethod;
94 
95 import java.io.File;
96 import java.io.FilenameFilter;
97 import java.io.IOException;
98 import java.io.OutputStream;
99 import java.net.Inet6Address;
100 import java.net.UnknownHostException;
101 import java.text.ParseException;
102 import java.text.SimpleDateFormat;
103 import java.time.Clock;
104 import java.util.ArrayList;
105 import java.util.Arrays;
106 import java.util.Collection;
107 import java.util.Date;
108 import java.util.HashMap;
109 import java.util.HashSet;
110 import java.util.LinkedHashMap;
111 import java.util.LinkedList;
112 import java.util.List;
113 import java.util.Locale;
114 import java.util.Map;
115 import java.util.Random;
116 import java.util.Set;
117 import java.util.TimeZone;
118 import java.util.concurrent.ExecutionException;
119 import java.util.concurrent.Future;
120 import java.util.concurrent.TimeUnit;
121 import java.util.concurrent.locks.ReentrantLock;
122 import java.util.regex.Matcher;
123 import java.util.regex.Pattern;
124 
125 import javax.annotation.Nonnull;
126 import javax.annotation.Nullable;
127 import javax.annotation.concurrent.GuardedBy;
128 
129 /** Default implementation of a {@link ITestDevice} Non-full stack android devices. */
130 public class NativeDevice
131         implements IManagedTestDevice, IConfigurationReceiver, ITestLoggerReceiver {
132 
133     protected static final String SD_CARD = "/sdcard/";
134     protected static final String STORAGE_EMULATED = "/storage/emulated/";
135 
136     /** On-device path where we expect ANRs to be generated. */
137     private static final String ANRS_PATH = "/data/anr";
138 
139     /**
140      * Allow up to 2 minutes to receives the full logcat dump.
141      */
142     private static final int LOGCAT_DUMP_TIMEOUT = 2 * 60 * 1000;
143 
144     /** the default number of command retry attempts to perform */
145     protected static final int MAX_RETRY_ATTEMPTS = 2;
146 
147     /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value */
148     public static final int INVALID_USER_ID = -10000;
149 
150     /** regex to match input dispatch readiness line **/
151     static final Pattern INPUT_DISPATCH_STATE_REGEX =
152             Pattern.compile("DispatchEnabled:\\s?([01])");
153     /** regex to match build signing key type */
154     private static final Pattern KEYS_PATTERN = Pattern.compile("^.*-keys$");
155 
156     private static final Pattern DF_PATTERN =
157             Pattern.compile(
158                     // Fs 1K-blks Used    Available Use%      Mounted on
159                     "^/(\\S+)\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+\\d+%\\s+/\\S*$", Pattern.MULTILINE);
160 
161     protected static final long MAX_HOST_DEVICE_TIME_OFFSET = 5 * 1000;
162 
163     /** The password for encrypting and decrypting the device. */
164     private static final String ENCRYPTION_PASSWORD = "android";
165 
166     /** The maximum system_server start delay in seconds after device boot up */
167     private static final int MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC = 25;
168 
169     /** The time in ms to wait before starting logcat for a device */
170     private int mLogStartDelay = 0;
171 
172     /** The time in ms to wait for a device to become unavailable. Should usually be short */
173     private static final int DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000;
174 
175     private static final String SIM_STATE_PROP = "gsm.sim.state";
176     private static final String SIM_OPERATOR_PROP = "gsm.operator.alpha";
177 
178     static final String MAC_ADDRESS_PATTERN = "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}";
179     static final String MAC_ADDRESS_COMMAND = "su root cat /sys/class/net/wlan0/address";
180     static final String ETHERNET_MAC_ADDRESS_COMMAND = "cat /sys/class/net/eth0/address";
181 
182     static final int ETHER_ADDR_LEN = 6;
183 
184     /** The network monitoring interval in ms. */
185     private static final int NETWORK_MONITOR_INTERVAL = 10 * 1000;
186 
187     /** Wifi reconnect check interval in ms. */
188     private static final int WIFI_RECONNECT_CHECK_INTERVAL = 1 * 1000;
189 
190     /** Wifi reconnect timeout in ms. */
191     private static final int WIFI_RECONNECT_TIMEOUT = 60 * 1000;
192 
193     /** Pattern to find an executable file. */
194     private static final Pattern EXE_FILE = Pattern.compile("^[-l]r.x.+");
195 
196     /** Path of the device containing the tombstones */
197     private static final String TOMBSTONE_PATH = "/data/tombstones/";
198 
199     private static final long PROPERTY_GET_TIMEOUT = 45 * 1000L;
200 
201     public static final String DEBUGFS_PATH = "/sys/kernel/debug";
202     private static final String CHECK_DEBUGFS_MNT_COMMAND =
203             String.format("mountpoint -q %s", DEBUGFS_PATH);
204     private static final String MOUNT_DEBUGFS_COMMAND =
205             String.format("mount -t debugfs debugfs %s", DEBUGFS_PATH);
206     private static final String UNMOUNT_DEBUGFS_COMMAND = String.format("umount %s", DEBUGFS_PATH);
207 
208     /** Version number for a current development build */
209     private static final int CUR_DEVELOPMENT_VERSION = 10000;
210 
211     /** The time in ms to wait for a 'long' command to complete. */
212     private long mLongCmdTimeout = 25 * 60 * 1000L;
213 
214     /** The time in ms to pause after a trade-in mode reboot. */
215     private long mTradeInModePause = 6000;
216 
217     /**
218      * The delimiter that separates the actual shell output and the exit status.
219      *
220      * <p>Used to determine the exit status of the command run by adb shell for devices that do not
221      * support shell_v2.
222      */
223     private static final String EXIT_STATUS_DELIMITER = "x";
224 
225     private IConfiguration mConfiguration;
226     private IDevice mIDevice;
227     private IDeviceRecovery mRecovery = new WaitDeviceRecovery();
228     protected final IDeviceStateMonitor mStateMonitor;
229     private TestDeviceState mState = TestDeviceState.ONLINE;
230     private final ReentrantLock mFastbootLock = new ReentrantLock();
231     private LogcatReceiver mLogcatReceiver;
232     private boolean mFastbootEnabled = true;
233     private String mFastbootPath = "fastboot";
234 
235     protected TestDeviceOptions mOptions = new TestDeviceOptions();
236     private Process mEmulatorProcess;
237     private SizeLimitedOutputStream mEmulatorOutput;
238     private Clock mClock = Clock.systemUTC();
239 
240     private RecoveryMode mRecoveryMode = RecoveryMode.AVAILABLE;
241 
242     private Boolean mIsEncryptionSupported = null;
243     private ReentrantLock mAllocationStateLock = new ReentrantLock(true /*fair*/);
244 
245     @GuardedBy("mAllocationStateLock")
246     private DeviceAllocationState mAllocationState = DeviceAllocationState.Unknown;
247     private IDeviceMonitor mAllocationMonitor = null;
248 
249     private String mLastConnectedWifiSsid = null;
250     private String mLastConnectedWifiPsk = null;
251     private boolean mNetworkMonitorEnabled = false;
252 
253     private ContentProviderHandler mContentProvider = null;
254     private boolean mShouldSkipContentProviderSetup = false;
255     /** Keep track of the last time Tradefed itself triggered a reboot. */
256     private long mLastTradefedRebootTime = 0L;
257 
258     private File mExecuteShellCommandLogs = null;
259 
260     private DeviceDescriptor mCachedDeviceDescriptor = null;
261     private final Object mCacheLock = new Object();
262 
263     private String mTrackingSerialNumber = null;
264     private String mFastbootSerialNumber = null;
265     private File mUnpackedFastbootDir = null;
266     // Connection for the device.
267     private AbstractConnection mConnection;
268     private GceAvdInfo mConnectionAvd;
269 
270     private ITestLogger mTestLogger;
271 
272     private List<IDeviceActionReceiver> mDeviceActionReceivers = new LinkedList<>();
273     /**
274      * Whether callback for reboot is currently executing or not. Use this flag to avoid dead loop
275      * scenarios like calling reboot inside a callback happening for reboot.
276      */
277     private boolean inRebootCallback = false;
278 
279     /** If the device is a Microdroid, this refers to the VM process. Otherwise, it is null. */
280     private Process mMicrodroidProcess = null;
281 
282     private final LoadingCache<String, String> mPropertiesCache;
283 
284     // If we increase the number of props, then increase the cache size of mPropertiesCache.
285     private final Set<String> propsToPrefetch =
286             ImmutableSet.of("ro.build.version.sdk", "ro.build.version.codename", "ro.build.id");
287     // Avoid caching any properties in those namespace
288     private static final Set<String> NEVER_CACHE_PROPERTIES =
289             ImmutableSet.of("vendor.debug", "ro.boot");
290 
291     /** Interface for a generic device communication attempt. */
292     abstract interface DeviceAction {
293 
294         /**
295          * Execute the device operation.
296          *
297          * @return <code>true</code> if operation is performed successfully, <code>false</code>
298          *     otherwise
299          * @throws IOException, TimeoutException, AdbCommandRejectedException,
300          *     ShellCommandUnresponsiveException, InstallException, SyncException if operation
301          *     terminated abnormally
302          */
run()303         public boolean run()
304                 throws IOException, TimeoutException, AdbCommandRejectedException,
305                         ShellCommandUnresponsiveException, InstallException, SyncException,
306                         DeviceNotAvailableException;
307     }
308 
309     /**
310      * A {@link DeviceAction} for running a OS 'adb ....' command.
311      */
312     protected class AdbAction implements DeviceAction {
313         /** the output from the command */
314         String mOutput = null;
315         private String[] mCmd;
316         private long mTimeout;
317         private boolean mIsShellCommand;
318         private Map<String, String> mEnvMap;
319 
AdbAction(long timeout, String[] cmd, boolean isShell, Map<String, String> envMap)320         AdbAction(long timeout, String[] cmd, boolean isShell, Map<String, String> envMap) {
321             mTimeout = timeout;
322             mCmd = cmd;
323             mIsShellCommand = isShell;
324             mEnvMap = envMap;
325         }
326 
logExceptionAndOutput(CommandResult result)327         private void logExceptionAndOutput(CommandResult result) {
328             CLog.w("Command exited with status: %s", result.getStatus().toString());
329             CLog.w("Command stdout:\n%s\n", result.getStdout());
330             CLog.w("Command stderr:\n%s\n", result.getStderr());
331         }
332 
333         @Override
run()334         public boolean run() throws TimeoutException, IOException {
335             IRunUtil runUtil = getRunUtil();
336             if (!mEnvMap.isEmpty()) {
337                 runUtil = createRunUtil();
338             }
339             for (String key : mEnvMap.keySet()) {
340                 runUtil.setEnvVariable(key, mEnvMap.get(key));
341             }
342             CommandResult result = runUtil.runTimedCmd(mTimeout, mCmd);
343             // TODO: how to determine device not present with command failing for other reasons
344             if (result.getStatus() == CommandStatus.EXCEPTION) {
345                 logExceptionAndOutput(result);
346                 throw new IOException("CommandStatus was EXCEPTION, details in host log");
347             } else if (result.getStatus() == CommandStatus.TIMED_OUT) {
348                 logExceptionAndOutput(result);
349                 throw new TimeoutException("CommandStatus was TIMED_OUT, details in host log");
350             } else if (result.getStatus() == CommandStatus.FAILED) {
351 
352                 logExceptionAndOutput(result);
353                 if (mIsShellCommand) {
354                     // Interpret as communication failure for shell commands
355                     throw new IOException("CommandStatus was FAILED, details in host log");
356                 } else {
357                     mOutput = result.getStdout();
358                     return false;
359                 }
360             }
361             mOutput = result.getStdout();
362             return true;
363         }
364     }
365 
366     protected class AdbShellAction implements DeviceAction {
367         /** the output from the command */
368         CommandResult mResult = null;
369 
370         private String[] mCmd;
371         private long mTimeout;
372         private File mPipeAsInput; // Used in pushFile, uses local file as input to "content write"
373         private OutputStream mPipeToOutput; // Used in pullFile, to pipe content from "content read"
374         private OutputStream mPipeToError;
375 
AdbShellAction( String[] cmd, File pipeAsInput, OutputStream pipeToOutput, OutputStream pipeToError, long timeout)376         AdbShellAction(
377                 String[] cmd,
378                 File pipeAsInput,
379                 OutputStream pipeToOutput,
380                 OutputStream pipeToError,
381                 long timeout) {
382             mCmd = cmd;
383             mPipeAsInput = pipeAsInput;
384             mPipeToOutput = pipeToOutput;
385             mPipeToError = pipeToError;
386             mTimeout = timeout;
387         }
388 
389         @Override
run()390         public boolean run() throws TimeoutException, IOException {
391             if (mPipeAsInput != null) {
392                 mResult = getRunUtil().runTimedCmdWithInputRedirect(mTimeout, mPipeAsInput, mCmd);
393             } else {
394                 mResult = getRunUtil().runTimedCmd(mTimeout, mPipeToOutput, mPipeToError, mCmd);
395             }
396             if (mResult.getStatus() == CommandStatus.EXCEPTION) {
397                 throw new IOException(mResult.getStderr());
398             } else if (mResult.getStatus() == CommandStatus.TIMED_OUT) {
399                 throw new TimeoutException(mResult.getStderr());
400             }
401             String stdErr = mResult.getStderr();
402             if (stdErr != null) {
403                 stdErr = stdErr.trim();
404                 if (stdErr.contains("device offline")) {
405                     throw new IOException(stdErr);
406                 }
407             }
408             // If it's not some issue with running the adb command, then we return the CommandResult
409             // which will contain all the infos.
410             return true;
411         }
412     }
413 
414     /** {@link DeviceAction} for rebooting a device. */
415     protected class RebootDeviceAction implements DeviceAction {
416 
417         private final RebootMode mRebootMode;
418         @Nullable private final String mReason;
419 
RebootDeviceAction(RebootMode rebootMode, @Nullable String reason)420         RebootDeviceAction(RebootMode rebootMode, @Nullable String reason) {
421             mRebootMode = rebootMode;
422             mReason = reason;
423         }
424 
isFastbootOrBootloader()425         public boolean isFastbootOrBootloader() {
426             return mRebootMode == RebootMode.REBOOT_INTO_BOOTLOADER
427                     || mRebootMode == RebootMode.REBOOT_INTO_FASTBOOTD;
428         }
429 
430         @Override
run()431         public boolean run()
432                 throws TimeoutException, IOException, AdbCommandRejectedException,
433                         DeviceNotAvailableException {
434             // Notify of reboot started for all modes
435             notifyRebootStarted();
436             getIDevice().reboot(mRebootMode.formatRebootCommand(mReason));
437             return true;
438         }
439     }
440 
441     /**
442      * Creates a {@link TestDevice}.
443      *
444      * @param device the associated {@link IDevice}
445      * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use
446      * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes.
447      *            Can be null
448      */
NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor)449     public NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor,
450             IDeviceMonitor allocationMonitor) {
451         throwIfNull(device);
452         throwIfNull(stateMonitor);
453         mIDevice = device;
454         mStateMonitor = stateMonitor;
455         mAllocationMonitor = allocationMonitor;
456         // Keep a short timeout to expire key in case of large state changes
457         // such as flashing
458         mPropertiesCache =
459                 CacheBuilder.newBuilder()
460                         .maximumSize(50)
461                         .expireAfterAccess(2, TimeUnit.MINUTES)
462                         .build(
463                                 new CacheLoader<String, String>() {
464                                     @Override
465                                     public String load(String key) {
466                                         throw new IllegalStateException("Should never be called");
467                                     }
468                                 });
469     }
470 
471     /** Get the {@link RunUtil} instance to use. */
472     @VisibleForTesting
getRunUtil()473     protected IRunUtil getRunUtil() {
474         return RunUtil.getDefault();
475     }
476 
createRunUtil()477     protected IRunUtil createRunUtil() {
478         return new RunUtil();
479     }
480 
481     /** Set the Clock instance to use. */
482     @VisibleForTesting
setClock(Clock clock)483     protected void setClock(Clock clock) {
484         mClock = clock;
485     }
486 
487     /**
488      * {@inheritDoc}
489      */
490     @Override
setOptions(TestDeviceOptions options)491     public void setOptions(TestDeviceOptions options) {
492         throwIfNull(options);
493         mOptions = options;
494         if (mOptions.getFastbootBinary() != null) {
495             // Setup fastboot- if it's zipped, unzip it
496             // TODO: Dedup the logic with DeviceManager
497             if (".zip".equals(FileUtil.getExtension(mOptions.getFastbootBinary().getName()))) {
498                 // Unzip the fastboot files
499                 try {
500                     mUnpackedFastbootDir =
501                             ZipUtil2.extractZipToTemp(
502                                     mOptions.getFastbootBinary(), "unpacked-fastboot");
503                     File unpackedFastboot = FileUtil.findFile(mUnpackedFastbootDir, "fastboot");
504                     if (unpackedFastboot == null) {
505                         throw new HarnessRuntimeException(
506                                 String.format(
507                                         "device-fastboot-binary was set, but didn't contain a"
508                                                 + " fastboot binary."),
509                                 InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
510                     }
511                     setFastbootPath(unpackedFastboot.getAbsolutePath());
512                 } catch (IOException e) {
513                     CLog.e("Failed to unpacked zipped fastboot.");
514                     CLog.e(e);
515                     FileUtil.recursiveDelete(mUnpackedFastbootDir);
516                     mUnpackedFastbootDir = null;
517                 }
518             } else {
519                 setFastbootPath(mOptions.getFastbootBinary().getAbsolutePath());
520             }
521         }
522         mStateMonitor.setDefaultOnlineTimeout(options.getOnlineTimeout());
523         mStateMonitor.setDefaultAvailableTimeout(options.getAvailableTimeout());
524     }
525 
526     /**
527      * Sets the max size of a tmp logcat file.
528      *
529      * @param size max byte size of tmp file
530      */
setTmpLogcatSize(long size)531     void setTmpLogcatSize(long size) {
532         mOptions.setMaxLogcatDataSize(size);
533     }
534 
535     /**
536      * Sets the time in ms to wait before starting logcat capture for a online device.
537      *
538      * @param delay the delay in ms
539      */
setLogStartDelay(int delay)540     public void setLogStartDelay(int delay) {
541         mLogStartDelay = delay;
542     }
543 
544     /** {@inheritDoc} */
545     @Override
setConfiguration(IConfiguration configuration)546     public void setConfiguration(IConfiguration configuration) {
547         mConfiguration = configuration;
548     }
549 
550     /**
551      * {@inheritDoc}
552      */
553     @Override
getIDevice()554     public IDevice getIDevice() {
555         synchronized (mIDevice) {
556             return mIDevice;
557         }
558     }
559 
560     /**
561      * {@inheritDoc}
562      */
563     @Override
setIDevice(IDevice newDevice)564     public void setIDevice(IDevice newDevice) {
565         throwIfNull(newDevice);
566         IDevice currentDevice = mIDevice;
567         if (!getIDevice().equals(newDevice)) {
568             synchronized (currentDevice) {
569                 mIDevice = newDevice;
570             }
571             mStateMonitor.setIDevice(mIDevice);
572         }
573     }
574 
575     /**
576      * {@inheritDoc}
577      */
578     @Override
getSerialNumber()579     public String getSerialNumber() {
580         return getIDevice().getSerialNumber();
581     }
582 
583     @Override
setTrackingSerial(String trackingSerial)584     public void setTrackingSerial(String trackingSerial) {
585         mTrackingSerialNumber = trackingSerial;
586     }
587 
588     @Override
getTrackingSerial()589     public String getTrackingSerial() {
590         if (Strings.isNullOrEmpty(mTrackingSerialNumber)) {
591             return getSerialNumber();
592         }
593         return mTrackingSerialNumber;
594     }
595 
596     /**
597      * Fetch a device property, from the ddmlib cache by default, and falling back to either `adb
598      * shell getprop` or `fastboot getvar` depending on whether the device is in Fastboot or not.
599      *
600      * @param propName The name of the device property as returned by `adb shell getprop`
601      * @param fastbootVar The name of the equivalent fastboot variable to query. if {@code null},
602      *     fastboot query will not be attempted
603      * @param description A simple description of the variable. First letter should be capitalized.
604      * @return A string, possibly {@code null} or empty, containing the value of the given property
605      */
internalGetProperty(String propName, String fastbootVar, String description)606     protected String internalGetProperty(String propName, String fastbootVar, String description)
607             throws DeviceNotAvailableException, UnsupportedOperationException {
608         if (isStateBootloaderOrFastbootd() && fastbootVar != null) {
609             CLog.i("Device %s is in fastboot mode, re-querying with '%s' for %s", getSerialNumber(),
610                    fastbootVar, description);
611             return getFastbootVariable(fastbootVar);
612         }
613         String propValue = getProperty(propName);
614         if (propValue != null) {
615             return propValue;
616         } else {
617             CLog.d(
618                     "property collection '%s' for device %s is null.",
619                     description, getSerialNumber());
620             return null;
621         }
622     }
623 
624     /**
625      * {@inheritDoc}
626      */
627     @Override
getProperty(final String name)628     public String getProperty(final String name) throws DeviceNotAvailableException {
629         return getPropertyWithRecovery(name, false);
630     }
631 
632     /** Version of getProperty that allows to check device status and trigger recovery if needed. */
getPropertyWithRecovery(final String name, boolean recovery)633     private String getPropertyWithRecovery(final String name, boolean recovery)
634             throws DeviceNotAvailableException {
635         if (getIDevice() instanceof StubDevice) {
636             return null;
637         }
638         String property = mPropertiesCache.getIfPresent(name);
639         if (property != null) {
640             CLog.d("Using property %s=%s from cache.", name, property);
641             return property;
642         }
643         try (CloseableTraceScope getProp = new CloseableTraceScope("get_property:" + name)) {
644             TestDeviceState state = getDeviceState();
645             if (!TestDeviceState.ONLINE.equals(state) && !TestDeviceState.RECOVERY.equals(state)) {
646                 if (recovery) {
647                     // Only query property for online device so trigger recovery before getting
648                     // property.
649                     recoverDevice();
650                 } else {
651                     if (mStateMonitor.waitForDeviceOnline() == null) {
652                         String message =
653                                 String.format(
654                                         "Waited for device %s to be online but it is in state '%s',"
655                                                 + " cannot get property %s.",
656                                         getSerialNumber(), getDeviceState(), name);
657                         CLog.w(message);
658                         throw new DeviceNotAvailableException(message, getSerialNumber());
659                     }
660                 }
661             }
662             String cmd = String.format("getprop %s", name);
663             CommandResult result =
664                     executeShellV2Command(cmd, PROPERTY_GET_TIMEOUT, TimeUnit.MILLISECONDS, 0);
665             if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
666                 CLog.e(
667                         "Failed to run '%s' returning null. stdout: %s\nstderr: %s\nexit code: %s",
668                         cmd, result.getStdout(), result.getStderr(), result.getExitCode());
669                 if (result.getStderr().contains("device offline")) {
670                     if (recovery) {
671                         recoverDevice();
672                         return getPropertyWithRecovery(name, false);
673                     }
674                     throw new DeviceNotAvailableException(
675                             String.format("Device went offline when querying property: %s", name),
676                             getSerialNumber(),
677                             DeviceErrorIdentifier.DEVICE_UNAVAILABLE);
678                 }
679                 return null;
680             }
681             if (result.getStdout() == null || result.getStdout().trim().isEmpty()) {
682                 return null;
683             }
684             property = result.getStdout().trim();
685             if (property != null) {
686                 if (!NEVER_CACHE_PROPERTIES.stream().anyMatch(p -> name.startsWith(p))) {
687                     // Manage the cache manually to maintain exception handling
688                     mPropertiesCache.put(name, property);
689                 }
690             }
691             return property;
692         }
693     }
694 
695     /**
696      * Micro optimization (about 400 millis) by prefetching all props we need rather than call 'adb
697      * getprop' for each one. i.e. It is just as fast to fetch all properties as it is to fetch one.
698      * Things like device.getApiLevel(), checkApiLevelAgainstNextRelease and getBuildAlias all call
699      * `adb getprop` under the hood. We fetch them in one call and call NativeDevice.setProperty.
700      * Even if we don't do this, NativeDevice will itself call setProperty and cache the result for
701      * future calls. We are just doing it slightly earlier. If the device is in recovery or there
702      * are other errors fetching the props, we just ignore them.
703      */
batchPrefetchStartupBuildProps()704     public void batchPrefetchStartupBuildProps() {
705         String cmd = "getprop";
706         try (CloseableTraceScope ignored = new CloseableTraceScope("batchPrefetchProp")) {
707             // Skip refetching if we already have the props by counting the ones in the cache
708             // that we need to fetch.
709             int propsAlreadyPresent = 0;
710             for (String propName : propsToPrefetch) {
711                 if (mPropertiesCache.getIfPresent(propName) != null) {
712                     propsAlreadyPresent++;
713                 } else {
714                     break;
715                 }
716             }
717             if (propsAlreadyPresent == propsToPrefetch.size()) {
718                 return;
719             }
720 
721             try {
722                 CommandResult result =
723                         executeShellV2Command(cmd, PROPERTY_GET_TIMEOUT, TimeUnit.MILLISECONDS, 0);
724                 if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
725                     CLog.w(
726                             "Failed to run '%s' returning null. stdout: %s\n"
727                                     + "stderr: %s\n"
728                                     + "exit code: %s",
729                             cmd, result.getStdout(), result.getStderr(), result.getExitCode());
730                     if (result.getStdout() == null || result.getStdout().trim().isEmpty()) {
731                         return;
732                     }
733                 }
734                 for (String line : result.getStdout().split("\n")) {
735                     String[] parts = line.trim().split("]: \\[");
736                     if (parts.length != 2) {
737                         continue;
738                     }
739                     String propName = parts[0].substring(1).trim();
740                     String propValue = parts[1].substring(0, parts[1].length() - 1).trim();
741                     if (propValue != null) {
742                         if (propsToPrefetch.contains(propName)) {
743                             mPropertiesCache.put(propName, propValue);
744                         }
745                     }
746                 }
747             } catch (DeviceNotAvailableException e) {
748                 // okay to ignore, the real get property will deal with it.
749             }
750         }
751     }
752 
753     /** {@inheritDoc} */
754     @Override
getIntProperty(String name, long defaultValue)755     public long getIntProperty(String name, long defaultValue) throws DeviceNotAvailableException {
756         String value = getProperty(name);
757         if (value == null) {
758             return defaultValue;
759         }
760         try {
761             return Long.parseLong(value);
762         } catch (NumberFormatException e) {
763             return defaultValue;
764         }
765     }
766 
767     private static final List<String> TRUE_VALUES = Arrays.asList("1", "y", "yes", "on", "true");
768     private static final List<String> FALSE_VALUES = Arrays.asList("0", "n", "no", "off", "false");
769 
770     /** {@inheritDoc} */
771     @Override
getBooleanProperty(String name, boolean defaultValue)772     public boolean getBooleanProperty(String name, boolean defaultValue)
773             throws DeviceNotAvailableException {
774         String value = getProperty(name);
775         if (value == null) {
776             return defaultValue;
777         }
778         if (TRUE_VALUES.contains(value)) {
779             return true;
780         }
781         if (FALSE_VALUES.contains(value)) {
782             return false;
783         }
784         return defaultValue;
785     }
786 
787     /** {@inheritDoc} */
788     @Override
setProperty(String propKey, String propValue)789     public boolean setProperty(String propKey, String propValue)
790             throws DeviceNotAvailableException {
791         if (propKey == null || propValue == null) {
792             throw new IllegalArgumentException("set property key or value cannot be null.");
793         }
794         String setPropCmd = String.format("\"setprop %s '%s'\"", propKey, propValue);
795         CommandResult result = executeShellV2Command(setPropCmd);
796         if (CommandStatus.SUCCESS.equals(result.getStatus())) {
797             mPropertiesCache.invalidate(propKey);
798             return true;
799         }
800         CLog.e(
801                 "Something went wrong went setting property %s (command: %s): %s",
802                 propKey, setPropCmd, result.getStderr());
803         return false;
804     }
805 
806     /**
807      * {@inheritDoc}
808      */
809     @Override
getBootloaderVersion()810     public String getBootloaderVersion() throws UnsupportedOperationException,
811             DeviceNotAvailableException {
812         return internalGetProperty("ro.bootloader", "version-bootloader", "Bootloader");
813     }
814 
815     @Override
getBasebandVersion()816     public String getBasebandVersion() throws DeviceNotAvailableException {
817         return internalGetProperty("gsm.version.baseband", "version-baseband", "Baseband");
818     }
819 
820     /**
821      * {@inheritDoc}
822      */
823     @Override
getProductType()824     public String getProductType() throws DeviceNotAvailableException {
825         return internalGetProductType(MAX_RETRY_ATTEMPTS);
826     }
827 
828     /**
829      * {@link #getProductType()}
830      *
831      * @param retryAttempts The number of times to try calling {@link #recoverDevice()} if the
832      *        device's product type cannot be found.
833      */
internalGetProductType(int retryAttempts)834     private String internalGetProductType(int retryAttempts) throws DeviceNotAvailableException {
835         String productType = internalGetProperty(DeviceProperties.BOARD, "product", "Product type");
836         // fallback to ro.hardware for legacy devices
837         if (Strings.isNullOrEmpty(productType)) {
838             productType = internalGetProperty(DeviceProperties.HARDWARE, "product", "Product type");
839         }
840 
841         // Things will likely break if we don't have a valid product type.  Try recovery (in case
842         // the device is only partially booted for some reason), and if that doesn't help, bail.
843         if (Strings.isNullOrEmpty(productType)) {
844             if (retryAttempts > 0) {
845                 recoverDevice();
846                 productType = internalGetProductType(retryAttempts - 1);
847             }
848 
849             if (Strings.isNullOrEmpty(productType)) {
850                 throw new DeviceNotAvailableException(
851                         String.format(
852                                 "Could not determine product type for device %s.",
853                                 getSerialNumber()),
854                         getSerialNumber(),
855                         DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
856             }
857         }
858 
859         return productType.toLowerCase();
860     }
861 
862     /**
863      * {@inheritDoc}
864      */
865     @Override
getFastbootProductType()866     public String getFastbootProductType()
867             throws DeviceNotAvailableException, UnsupportedOperationException {
868         String prop = getFastbootVariable("product");
869         if (prop != null) {
870             prop = prop.toLowerCase();
871         }
872         return prop;
873     }
874 
875     /**
876      * {@inheritDoc}
877      */
878     @Override
getProductVariant()879     public String getProductVariant() throws DeviceNotAvailableException {
880         String prop = internalGetProperty(DeviceProperties.VARIANT, "variant", "Product variant");
881         if (prop == null) {
882             prop =
883                     internalGetProperty(
884                             DeviceProperties.VARIANT_LEGACY_O_MR1, "variant", "Product variant");
885         }
886         if (prop == null) {
887             prop =
888                     internalGetProperty(
889                             DeviceProperties.VARIANT_LEGACY_LESS_EQUAL_O,
890                             "variant",
891                             "Product variant");
892         }
893         if (prop != null) {
894             prop = prop.toLowerCase();
895         }
896         return prop;
897     }
898 
899     /**
900      * {@inheritDoc}
901      */
902     @Override
getFastbootProductVariant()903     public String getFastbootProductVariant()
904             throws DeviceNotAvailableException, UnsupportedOperationException {
905         String prop = getFastbootVariable("variant");
906         if (prop != null) {
907             prop = prop.toLowerCase();
908         }
909         return prop;
910     }
911 
912     /** {@inheritDoc} */
913     @Override
getFastbootVariable(String variableName)914     public String getFastbootVariable(String variableName)
915             throws DeviceNotAvailableException, UnsupportedOperationException {
916         CommandResult result = executeFastbootCommand("getvar", variableName);
917         CLog.d(
918                 "(getvar %s) output:\nstdout%s\nstderr:%s",
919                 variableName, result.getStdout(), result.getStderr());
920         if (result.getStatus() == CommandStatus.SUCCESS) {
921             Pattern fastbootProductPattern = Pattern.compile(variableName + ":\\s(.*)\\s");
922             // fastboot is weird, and may dump the output on stderr instead of stdout
923             String resultText = result.getStdout();
924             if (resultText == null || resultText.length() < 1) {
925                 resultText = result.getStderr();
926             }
927             Matcher matcher = fastbootProductPattern.matcher(resultText);
928             if (matcher.find()) {
929                 return matcher.group(1);
930             }
931         }
932         return null;
933     }
934 
935     /**
936      * {@inheritDoc}
937      */
938     @Override
getBuildAlias()939     public String getBuildAlias() throws DeviceNotAvailableException {
940         String alias = getProperty(DeviceProperties.BUILD_ALIAS);
941         if (alias == null || alias.isEmpty()) {
942             return getBuildId();
943         }
944         return alias;
945     }
946 
947     /**
948      * {@inheritDoc}
949      */
950     @Override
getBuildId()951     public String getBuildId() throws DeviceNotAvailableException {
952         String bid = getProperty(DeviceProperties.BUILD_ID);
953         if (bid == null) {
954             CLog.w("Could not get device %s build id.", getSerialNumber());
955             return IBuildInfo.UNKNOWN_BUILD_ID;
956         }
957         return bid;
958     }
959 
960     /**
961      * {@inheritDoc}
962      */
963     @Override
getBuildFlavor()964     public String getBuildFlavor() throws DeviceNotAvailableException {
965         String buildFlavor = getProperty(DeviceProperties.BUILD_FLAVOR);
966         if (buildFlavor != null && !buildFlavor.isEmpty()) {
967             return buildFlavor;
968         }
969         String productName = getProperty(DeviceProperties.PRODUCT);
970         String buildType = getProperty(DeviceProperties.BUILD_TYPE);
971         if (productName == null || buildType == null) {
972             CLog.w("Could not get device %s build flavor.", getSerialNumber());
973             return null;
974         }
975         return String.format("%s-%s", productName, buildType);
976     }
977 
978     /**
979      * {@inheritDoc}
980      */
981     @Override
executeShellCommand(final String command, final IShellOutputReceiver receiver)982     public void executeShellCommand(final String command, final IShellOutputReceiver receiver)
983             throws DeviceNotAvailableException {
984         DeviceAction action = new DeviceAction() {
985             @Override
986             public boolean run() throws TimeoutException, IOException,
987                     AdbCommandRejectedException, ShellCommandUnresponsiveException {
988                 getIDevice().executeShellCommand(command, receiver,
989                         getCommandTimeout(), TimeUnit.MILLISECONDS);
990                 return true;
991             }
992         };
993         performDeviceAction(String.format("shell %s", command), action, MAX_RETRY_ATTEMPTS);
994     }
995 
996     /**
997      * {@inheritDoc}
998      */
999     @Override
executeShellCommand(final String command, final IShellOutputReceiver receiver, final long maxTimeToOutputShellResponse, final TimeUnit timeUnit, final int retryAttempts)1000     public void executeShellCommand(final String command, final IShellOutputReceiver receiver,
1001             final long maxTimeToOutputShellResponse, final TimeUnit timeUnit,
1002             final int retryAttempts) throws DeviceNotAvailableException {
1003         DeviceAction action = new DeviceAction() {
1004             @Override
1005             public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1006                     ShellCommandUnresponsiveException {
1007                 getIDevice().executeShellCommand(command, receiver,
1008                         maxTimeToOutputShellResponse, timeUnit);
1009                 return true;
1010             }
1011         };
1012         performDeviceAction(String.format("shell %s", command), action, retryAttempts);
1013     }
1014 
1015     /** {@inheritDoc} */
1016     @Override
executeShellCommand( final String command, final IShellOutputReceiver receiver, final long maxTimeoutForCommand, final long maxTimeToOutputShellResponse, final TimeUnit timeUnit, final int retryAttempts)1017     public void executeShellCommand(
1018             final String command,
1019             final IShellOutputReceiver receiver,
1020             final long maxTimeoutForCommand,
1021             final long maxTimeToOutputShellResponse,
1022             final TimeUnit timeUnit,
1023             final int retryAttempts)
1024             throws DeviceNotAvailableException {
1025         DeviceAction action =
1026                 new DeviceAction() {
1027                     @Override
1028                     public boolean run()
1029                             throws TimeoutException, IOException, AdbCommandRejectedException,
1030                                     ShellCommandUnresponsiveException {
1031                         getIDevice()
1032                                 .executeShellCommand(
1033                                         command,
1034                                         receiver,
1035                                         maxTimeoutForCommand,
1036                                         maxTimeToOutputShellResponse,
1037                                         timeUnit);
1038                         return true;
1039                     }
1040                 };
1041         performDeviceAction(String.format("shell %s", command), action, retryAttempts);
1042     }
1043 
1044     /**
1045      * {@inheritDoc}
1046      */
1047     @Override
executeShellCommand(String command)1048     public String executeShellCommand(String command) throws DeviceNotAvailableException {
1049         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
1050         executeShellCommand(command, receiver);
1051         String output = receiver.getOutput();
1052         if (mExecuteShellCommandLogs != null) {
1053             // Log all output to a dedicated file as it can be very verbose.
1054             String formatted =
1055                     LogUtil.getLogFormatString(
1056                             LogLevel.VERBOSE,
1057                             "NativeDevice",
1058                             String.format(
1059                                     "%s on %s returned %s\n==== END OF OUTPUT ====\n",
1060                                     command, getSerialNumber(), output));
1061             try {
1062                 FileUtil.writeToFile(formatted, mExecuteShellCommandLogs, true);
1063             } catch (IOException e) {
1064                 // Ignore the full error
1065                 CLog.e("Failed to log to executeShellCommand log: %s", e.getMessage());
1066             }
1067         }
1068         if (output.length() > 80) {
1069             CLog.v(
1070                     "%s on %s returned %s <truncated - See executeShellCommand log for full trace>",
1071                     command, getSerialNumber(), output.substring(0, 80));
1072         } else {
1073             CLog.v("%s on %s returned %s", command, getSerialNumber(), output);
1074         }
1075         return output;
1076     }
1077 
1078     /** {@inheritDoc} */
1079     @Override
executeShellV2Command(String cmd)1080     public CommandResult executeShellV2Command(String cmd) throws DeviceNotAvailableException {
1081         return executeShellV2Command(cmd, getCommandTimeout(), TimeUnit.MILLISECONDS);
1082     }
1083 
1084     /** {@inheritDoc} */
1085     @Override
executeShellV2Command(String cmd, File pipeAsInput)1086     public CommandResult executeShellV2Command(String cmd, File pipeAsInput)
1087             throws DeviceNotAvailableException {
1088         return executeShellV2Command(
1089                 cmd,
1090                 pipeAsInput,
1091                 null,
1092                 getCommandTimeout(),
1093                 TimeUnit.MILLISECONDS,
1094                 MAX_RETRY_ATTEMPTS);
1095     }
1096 
1097     /** {@inheritDoc} */
1098     @Override
executeShellV2Command(String cmd, OutputStream pipeToOutput)1099     public CommandResult executeShellV2Command(String cmd, OutputStream pipeToOutput)
1100             throws DeviceNotAvailableException {
1101         return executeShellV2Command(
1102                 cmd,
1103                 null,
1104                 pipeToOutput,
1105                 getCommandTimeout(),
1106                 TimeUnit.MILLISECONDS,
1107                 MAX_RETRY_ATTEMPTS);
1108     }
1109 
1110     /** {@inheritDoc} */
1111     @Override
executeShellV2Command( String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit)1112     public CommandResult executeShellV2Command(
1113             String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit)
1114             throws DeviceNotAvailableException {
1115         return executeShellV2Command(
1116                 cmd, null, null, maxTimeoutForCommand, timeUnit, MAX_RETRY_ATTEMPTS);
1117     }
1118 
1119     /** {@inheritDoc} */
1120     @Override
executeShellV2Command( String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)1121     public CommandResult executeShellV2Command(
1122             String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)
1123             throws DeviceNotAvailableException {
1124         return executeShellV2Command(
1125                 cmd, null, null, maxTimeoutForCommand, timeUnit, retryAttempts);
1126     }
1127 
1128     /** {@inheritDoc} */
1129     @Override
executeShellV2Command( String cmd, File pipeAsInput, OutputStream pipeToOutput, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)1130     public CommandResult executeShellV2Command(
1131             String cmd,
1132             File pipeAsInput,
1133             OutputStream pipeToOutput,
1134             final long maxTimeoutForCommand,
1135             final TimeUnit timeUnit,
1136             int retryAttempts)
1137             throws DeviceNotAvailableException {
1138         return executeShellV2Command(
1139                 cmd,
1140                 pipeAsInput,
1141                 pipeToOutput,
1142                 /*pipeToError=*/ null,
1143                 maxTimeoutForCommand,
1144                 timeUnit,
1145                 retryAttempts);
1146     }
1147 
1148     /** {@inheritDoc} */
1149     @Override
executeShellV2Command( String cmd, File pipeAsInput, OutputStream pipeToOutput, OutputStream pipeToError, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)1150     public CommandResult executeShellV2Command(
1151             String cmd,
1152             File pipeAsInput,
1153             OutputStream pipeToOutput,
1154             OutputStream pipeToError,
1155             final long maxTimeoutForCommand,
1156             final TimeUnit timeUnit,
1157             int retryAttempts)
1158             throws DeviceNotAvailableException {
1159         // If the device does not support the v2 shell features. Exit status will not be propagated
1160         // and stdout/stderr will be merged in stdout.
1161         //
1162         // There's nothing we can do to separate the two streams, but we can alter the command to
1163         // retrieve the exit status.
1164         //
1165         // Note that this does *not* call `adb features` on each invocation. ddmlib caches all the
1166         // adb features on the first query.
1167         boolean parseExitStatus =
1168                 !getIDevice().supportsFeature(IDevice.Feature.SHELL_V2)
1169                         && getOptions().useExitStatusWorkaround();
1170 
1171         final String[] fullCmd = buildAdbShellCommand(cmd, parseExitStatus);
1172         AdbShellAction adbActionV2 =
1173                 new AdbShellAction(
1174                         fullCmd,
1175                         pipeAsInput,
1176                         pipeToOutput,
1177                         pipeToError,
1178                         timeUnit.toMillis(maxTimeoutForCommand));
1179         performDeviceAction(String.format("adb %s", fullCmd[4]), adbActionV2, retryAttempts);
1180         if (parseExitStatus) {
1181             postProcessExitStatus(adbActionV2.mResult);
1182         }
1183         return adbActionV2.mResult;
1184     }
1185 
postProcessExitStatus(@onnull CommandResult result)1186     private void postProcessExitStatus(@Nonnull CommandResult result) {
1187         String stdout = result.getStdout();
1188         int delimiterIndex = stdout.lastIndexOf(EXIT_STATUS_DELIMITER);
1189         if (delimiterIndex < 0) {
1190             result.setStatus(CommandStatus.FAILED);
1191             return;
1192         }
1193         String actualStdout = stdout.substring(0, delimiterIndex);
1194         String exitStatusText = stdout.substring(delimiterIndex + 1);
1195         result.setExitCode(Integer.parseUnsignedInt(exitStatusText.trim()));
1196         result.setStdout(actualStdout);
1197         if (result.getStatus() == CommandStatus.SUCCESS && result.getExitCode() != 0) {
1198             result.setStatus(CommandStatus.FAILED);
1199         }
1200     }
1201 
1202     /** {@inheritDoc} */
1203     @Override
runInstrumentationTests( final IRemoteAndroidTestRunner runner, final Collection<ITestLifeCycleReceiver> listeners)1204     public boolean runInstrumentationTests(
1205             final IRemoteAndroidTestRunner runner,
1206             final Collection<ITestLifeCycleReceiver> listeners)
1207             throws DeviceNotAvailableException {
1208         RunFailureListener failureListener = new RunFailureListener();
1209         List<ITestRunListener> runListeners = new ArrayList<>();
1210         runListeners.add(failureListener);
1211         runListeners.add(new TestRunToTestInvocationForwarder(listeners));
1212 
1213         if ((mConfiguration != null)
1214                 && mConfiguration.getCoverageOptions().isCoverageEnabled()
1215                 && mConfiguration
1216                         .getCoverageOptions()
1217                         .getCoverageToolchains()
1218                         .contains(Toolchain.JACOCO)) {
1219             runner.setCoverage(true);
1220         }
1221 
1222         DeviceAction runTestsAction =
1223                 new DeviceAction() {
1224                     @Override
1225                     public boolean run()
1226                             throws IOException, TimeoutException, AdbCommandRejectedException,
1227                                     ShellCommandUnresponsiveException, InstallException,
1228                                     SyncException {
1229                         runner.run(runListeners);
1230                         return true;
1231                     }
1232                 };
1233         boolean result = performDeviceAction(String.format("run %s instrumentation tests",
1234                 runner.getPackageName()), runTestsAction, 0);
1235         if (failureListener.isRunFailure()) {
1236             waitForDeviceAvailable();
1237         }
1238         return result;
1239     }
1240 
1241     /** {@inheritDoc} */
1242     @Override
runInstrumentationTests( IRemoteAndroidTestRunner runner, ITestLifeCycleReceiver... listeners)1243     public boolean runInstrumentationTests(
1244             IRemoteAndroidTestRunner runner, ITestLifeCycleReceiver... listeners)
1245             throws DeviceNotAvailableException {
1246         List<ITestLifeCycleReceiver> listenerList = new ArrayList<>();
1247         listenerList.addAll(Arrays.asList(listeners));
1248         return runInstrumentationTests(runner, listenerList);
1249     }
1250 
1251     /** {@inheritDoc} */
1252     @Override
runInstrumentationTestsAsUser( final IRemoteAndroidTestRunner runner, int userId, final Collection<ITestLifeCycleReceiver> listeners)1253     public boolean runInstrumentationTestsAsUser(
1254             final IRemoteAndroidTestRunner runner,
1255             int userId,
1256             final Collection<ITestLifeCycleReceiver> listeners)
1257             throws DeviceNotAvailableException {
1258         String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
1259         boolean result = runInstrumentationTests(runner, listeners);
1260         resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
1261         return result;
1262     }
1263 
1264     /** {@inheritDoc} */
1265     @Override
runInstrumentationTestsAsUser( IRemoteAndroidTestRunner runner, int userId, ITestLifeCycleReceiver... listeners)1266     public boolean runInstrumentationTestsAsUser(
1267             IRemoteAndroidTestRunner runner, int userId, ITestLifeCycleReceiver... listeners)
1268             throws DeviceNotAvailableException {
1269         String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
1270         boolean result = runInstrumentationTests(runner, listeners);
1271         resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
1272         return result;
1273     }
1274 
1275     /**
1276      * Helper method to add user run time option to {@link RemoteAndroidTestRunner}
1277      *
1278      * @param runner {@link IRemoteAndroidTestRunner}
1279      * @param userId the integer of the user id to run as.
1280      * @return original run time options.
1281      */
appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId)1282     private String appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId) {
1283         if (runner instanceof RemoteAndroidTestRunner) {
1284             String original = ((RemoteAndroidTestRunner) runner).getRunOptions();
1285             String userRunTimeOption = String.format("--user %s", Integer.toString(userId));
1286             String updated = (original != null) ? (original + " " + userRunTimeOption)
1287                     : userRunTimeOption;
1288             ((RemoteAndroidTestRunner) runner).setRunOptions(updated);
1289             return original;
1290         } else if (runner instanceof com.android.ddmlib.testrunner.RemoteAndroidTestRunner) {
1291             // Support a backward compatible runners through the interface
1292             String original =
1293                     ((com.android.ddmlib.testrunner.RemoteAndroidTestRunner) runner)
1294                             .getRunOptions();
1295             String userRunTimeOption = String.format("--user %s", Integer.toString(userId));
1296             String updated =
1297                     (original != null) ? (original + " " + userRunTimeOption) : userRunTimeOption;
1298             ((com.android.ddmlib.testrunner.RemoteAndroidTestRunner) runner).setRunOptions(updated);
1299             return original;
1300         } else {
1301             throw new IllegalStateException(String.format("%s runner does not support multi-user",
1302                     runner.getClass().getName()));
1303         }
1304     }
1305 
1306     /**
1307      * Helper method to reset the run time options to {@link RemoteAndroidTestRunner}
1308      *
1309      * @param runner {@link IRemoteAndroidTestRunner}
1310      * @param oldRunTimeOptions
1311      */
resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, String oldRunTimeOptions)1312     private void resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner,
1313             String oldRunTimeOptions) {
1314         if (oldRunTimeOptions == null) {
1315             return;
1316         }
1317         if (runner instanceof RemoteAndroidTestRunner) {
1318             ((RemoteAndroidTestRunner) runner).setRunOptions(oldRunTimeOptions);
1319         } else if (runner instanceof com.android.ddmlib.testrunner.RemoteAndroidTestRunner) {
1320             ((com.android.ddmlib.testrunner.RemoteAndroidTestRunner) runner)
1321                     .setRunOptions(oldRunTimeOptions);
1322         } else {
1323             throw new IllegalStateException(String.format("%s runner does not support multi-user",
1324                     runner.getClass().getName()));
1325         }
1326     }
1327 
1328     private static class RunFailureListener extends StubTestRunListener {
1329         private boolean mIsRunFailure = false;
1330 
1331         @Override
testRunFailed(String message)1332         public void testRunFailed(String message) {
1333             mIsRunFailure = true;
1334         }
1335 
isRunFailure()1336         public boolean isRunFailure() {
1337             return mIsRunFailure;
1338         }
1339     }
1340 
1341     /**
1342      * {@inheritDoc}
1343      */
1344     @Override
isRuntimePermissionSupported()1345     public boolean isRuntimePermissionSupported() throws DeviceNotAvailableException {
1346         int apiLevel = getApiLevel();
1347         boolean condition = apiLevel > 22;
1348         if (!condition) {
1349             CLog.w(
1350                     "isRuntimePermissionSupported requires api level above 22, device reported "
1351                             + "'%s'",
1352                     apiLevel);
1353         }
1354         return condition;
1355     }
1356 
1357     /**
1358      * {@inheritDoc}
1359      */
1360     @Override
isAppEnumerationSupported()1361     public boolean isAppEnumerationSupported() throws DeviceNotAvailableException {
1362         return false;
1363     }
1364 
1365     /** {@inheritDoc} */
1366     @Override
isBypassLowTargetSdkBlockSupported()1367     public boolean isBypassLowTargetSdkBlockSupported() throws DeviceNotAvailableException {
1368         return checkApiLevelAgainstNextRelease(34);
1369     }
1370 
1371     /**
1372      * helper method to throw exception if runtime permission isn't supported
1373      * @throws DeviceNotAvailableException
1374      */
ensureRuntimePermissionSupported()1375     protected void ensureRuntimePermissionSupported() throws DeviceNotAvailableException {
1376         boolean runtimePermissionSupported = isRuntimePermissionSupported();
1377         if (!runtimePermissionSupported) {
1378             throw new UnsupportedOperationException(
1379                     "platform on device does not support runtime permission granting!");
1380         }
1381     }
1382 
1383     /**
1384      * {@inheritDoc}
1385      */
1386     @Override
installPackage(final File packageFile, final boolean reinstall, final String... extraArgs)1387     public String installPackage(final File packageFile, final boolean reinstall,
1388             final String... extraArgs) throws DeviceNotAvailableException {
1389         throw new UnsupportedOperationException("No support for Package Manager's features");
1390     }
1391 
1392     /**
1393      * {@inheritDoc}
1394      */
1395     @Override
installPackage(File packageFile, boolean reinstall, boolean grantPermissions, String... extraArgs)1396     public String installPackage(File packageFile, boolean reinstall, boolean grantPermissions,
1397             String... extraArgs) throws DeviceNotAvailableException {
1398         throw new UnsupportedOperationException("No support for Package Manager's features");
1399     }
1400 
1401     /**
1402      * {@inheritDoc}
1403      */
1404     @Override
installPackageForUser(File packageFile, boolean reinstall, int userId, String... extraArgs)1405     public String installPackageForUser(File packageFile, boolean reinstall, int userId,
1406             String... extraArgs) throws DeviceNotAvailableException {
1407         throw new UnsupportedOperationException("No support for Package Manager's features");
1408     }
1409 
1410     /**
1411      * {@inheritDoc}
1412      */
1413     @Override
installPackageForUser(File packageFile, boolean reinstall, boolean grantPermissions, int userId, String... extraArgs)1414     public String installPackageForUser(File packageFile, boolean reinstall,
1415             boolean grantPermissions, int userId, String... extraArgs)
1416                     throws DeviceNotAvailableException {
1417         throw new UnsupportedOperationException("No support for Package Manager's features");
1418     }
1419 
1420     /**
1421      * {@inheritDoc}
1422      */
1423     @Override
uninstallPackage(final String packageName)1424     public String uninstallPackage(final String packageName) throws DeviceNotAvailableException {
1425         throw new UnsupportedOperationException("No support for Package Manager's features");
1426     }
1427 
1428     /** {@inheritDoc} */
1429     @Override
uninstallPackageForUser(final String packageName, int userId)1430     public String uninstallPackageForUser(final String packageName, int userId)
1431             throws DeviceNotAvailableException {
1432         throw new UnsupportedOperationException("No support for Package Manager's features");
1433     }
1434 
1435     /** {@inheritDoc} */
1436     @Override
pullFile(final String remoteFilePath, final File localFile, int userId)1437     public boolean pullFile(final String remoteFilePath, final File localFile, int userId)
1438             throws DeviceNotAvailableException {
1439         long startTime = System.currentTimeMillis();
1440         InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.PULL_FILE_COUNT, 1);
1441 
1442         try {
1443             if (isSdcardOrEmulated(remoteFilePath) && userId != 0) {
1444                 ContentProviderHandler handler = getContentProvider(userId);
1445                 if (handler != null) {
1446                     return handler.pullFile(remoteFilePath, localFile);
1447                 }
1448             }
1449             return pullFileInternal(remoteFilePath, localFile);
1450         } finally {
1451             long totalTime = System.currentTimeMillis() - startTime;
1452             InvocationMetricLogger.addInvocationMetrics(
1453                     InvocationMetricKey.PULL_FILE_TIME, totalTime);
1454         }
1455     }
1456 
1457     /** {@inheritDoc} */
1458     @Override
pullFile(final String remoteFilePath, final File localFile)1459     public boolean pullFile(final String remoteFilePath, final File localFile)
1460             throws DeviceNotAvailableException {
1461         return pullFile(remoteFilePath, localFile, getCurrentUserCompatible(remoteFilePath));
1462     }
1463 
1464     /** {@inheritDoc} */
1465     @Override
pullFile(String remoteFilePath, int userId)1466     public File pullFile(String remoteFilePath, int userId) throws DeviceNotAvailableException {
1467         File localFile = null;
1468         boolean success = false;
1469         try {
1470             localFile = FileUtil.createTempFileForRemote(remoteFilePath, null);
1471             if (pullFile(remoteFilePath, localFile, userId)) {
1472                 success = true;
1473                 return localFile;
1474             }
1475         } catch (IOException e) {
1476             CLog.w("Encountered IOException while trying to pull '%s':", remoteFilePath);
1477             CLog.e(e);
1478         } finally {
1479             if (!success) {
1480                 FileUtil.deleteFile(localFile);
1481             }
1482         }
1483         return null;
1484     }
1485 
1486     /** {@inheritDoc} */
1487     @Override
pullFile(String remoteFilePath)1488     public File pullFile(String remoteFilePath) throws DeviceNotAvailableException {
1489         return pullFile(remoteFilePath, getCurrentUserCompatible(remoteFilePath));
1490     }
1491 
1492     /**
1493      * {@inheritDoc}
1494      */
1495     @Override
pullFileContents(String remoteFilePath)1496     public String pullFileContents(String remoteFilePath) throws DeviceNotAvailableException {
1497         File temp = pullFile(remoteFilePath);
1498 
1499         if (temp != null) {
1500             try {
1501                 return FileUtil.readStringFromFile(temp);
1502             } catch (IOException e) {
1503                 CLog.e(String.format("Could not pull file: %s", remoteFilePath));
1504             } finally {
1505                 FileUtil.deleteFile(temp);
1506             }
1507         }
1508 
1509         return null;
1510     }
1511 
1512     /**
1513      * {@inheritDoc}
1514      */
1515     @Override
pullFileFromExternal(String remoteFilePath)1516     public File pullFileFromExternal(String remoteFilePath) throws DeviceNotAvailableException {
1517         String externalPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
1518         String fullPath = new File(externalPath, remoteFilePath).getPath();
1519         return pullFile(fullPath);
1520     }
1521 
pullFileInternal(String remoteFilePath, File localFile)1522     protected boolean pullFileInternal(String remoteFilePath, File localFile)
1523             throws DeviceNotAvailableException {
1524         DeviceAction pullAction =
1525                 new DeviceAction() {
1526                     @Override
1527                     public boolean run()
1528                             throws TimeoutException, IOException, AdbCommandRejectedException,
1529                                     SyncException {
1530                         SyncService syncService = null;
1531                         boolean status = false;
1532                         try {
1533                             syncService = getIDevice().getSyncService();
1534                             syncService.pullFile(
1535                                     interpolatePathVariables(remoteFilePath),
1536                                     localFile.getAbsolutePath(),
1537                                     SyncService.getNullProgressMonitor());
1538                             status = true;
1539                         } catch (SyncException e) {
1540                             CLog.w(
1541                                     "Failed to pull %s from %s to %s. Message %s",
1542                                     remoteFilePath,
1543                                     getSerialNumber(),
1544                                     localFile.getAbsolutePath(),
1545                                     e.getMessage());
1546                             throw e;
1547                         } finally {
1548                             if (syncService != null) {
1549                                 syncService.close();
1550                             }
1551                         }
1552                         return status;
1553                     }
1554                 };
1555         return performDeviceAction(
1556                 String.format("pull %s to %s", remoteFilePath, localFile.getAbsolutePath()),
1557                 pullAction,
1558                 MAX_RETRY_ATTEMPTS);
1559     }
1560 
1561     /**
1562      * Helper function that watches for the string "${EXTERNAL_STORAGE}" and replaces it with the
1563      * pathname of the EXTERNAL_STORAGE mountpoint.  Specifically intended to be used for pathnames
1564      * that are being passed to SyncService, which does not support variables inside of filenames.
1565      */
interpolatePathVariables(String path)1566     String interpolatePathVariables(String path) {
1567         final String esString = "${EXTERNAL_STORAGE}";
1568         if (path.contains(esString)) {
1569             final String esPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
1570             path = path.replace(esString, esPath);
1571         }
1572         return path;
1573     }
1574 
1575     /**
1576      * {@inheritDoc}
1577      */
1578     @Override
pushFile(final File localFile, final String remoteFilePath)1579     public boolean pushFile(final File localFile, final String remoteFilePath)
1580             throws DeviceNotAvailableException {
1581         return pushFile(localFile, remoteFilePath, getCurrentUserCompatible(remoteFilePath));
1582     }
1583 
1584     @Override
pushFile(final File localFile, final String remoteFilePath, int userId)1585     public boolean pushFile(final File localFile, final String remoteFilePath, int userId)
1586             throws DeviceNotAvailableException {
1587         return pushFileInternal(localFile, remoteFilePath, false, userId);
1588     }
1589 
1590     @Override
pushFile( final File localFile, final String remoteFilePath, boolean evaluateContentProviderNeeded)1591     public boolean pushFile(
1592             final File localFile,
1593             final String remoteFilePath,
1594             boolean evaluateContentProviderNeeded)
1595             throws DeviceNotAvailableException {
1596         boolean skipContentProvider = false;
1597         int userId = getCurrentUserCompatible(remoteFilePath);
1598         if (userId == INVALID_USER_ID) {
1599             throw new HarnessRuntimeException(
1600                     "Device didn't return a valid user id. It might have gone into a bad state.",
1601                     DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
1602         }
1603         if (evaluateContentProviderNeeded) {
1604             skipContentProvider = userId == 0;
1605         }
1606         return pushFileInternal(localFile, remoteFilePath, skipContentProvider, userId);
1607     }
1608 
1609     @VisibleForTesting
pushFileInternal( final File localFile, final String remoteFilePath, boolean skipContentProvider, int userId)1610     boolean pushFileInternal(
1611             final File localFile,
1612             final String remoteFilePath,
1613             boolean skipContentProvider,
1614             int userId)
1615             throws DeviceNotAvailableException {
1616         long startTime = System.currentTimeMillis();
1617         InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.PUSH_FILE_COUNT, 1);
1618         try {
1619             if (!skipContentProvider) {
1620                 // Skip Content provider for user 0
1621                 if (isSdcardOrEmulated(remoteFilePath) && userId != 0) {
1622                     ContentProviderHandler handler = getContentProvider(userId);
1623                     if (handler != null) {
1624                         return handler.pushFile(localFile, remoteFilePath);
1625                     }
1626                 }
1627             }
1628 
1629             DeviceAction pushAction =
1630                     new DeviceAction() {
1631                         @Override
1632                         public boolean run()
1633                                 throws TimeoutException, IOException, AdbCommandRejectedException,
1634                                         SyncException {
1635                             SyncService syncService = null;
1636                             boolean status = false;
1637                             try {
1638                                 syncService = getIDevice().getSyncService();
1639                                 if (syncService == null) {
1640                                     throw new IOException("SyncService returned null.");
1641                                 }
1642                                 syncService.pushFile(
1643                                         localFile.getAbsolutePath(),
1644                                         interpolatePathVariables(remoteFilePath),
1645                                         SyncService.getNullProgressMonitor());
1646                                 status = true;
1647                             } catch (SyncException e) {
1648                                 CLog.w(
1649                                         "Failed to push %s to %s on device %s. Message: '%s'. "
1650                                                 + "Error code: %s",
1651                                         localFile.getAbsolutePath(),
1652                                         remoteFilePath,
1653                                         getSerialNumber(),
1654                                         e.getMessage(),
1655                                         e.getErrorCode());
1656                                 // TODO: check if ddmlib can report a better error
1657                                 if (SyncError.TRANSFER_PROTOCOL_ERROR.equals(e.getErrorCode())) {
1658                                     if (e.getMessage().contains("Permission denied")) {
1659                                         return false;
1660                                     }
1661                                 }
1662                                 throw e;
1663                             } finally {
1664                                 if (syncService != null) {
1665                                     syncService.close();
1666                                 }
1667                             }
1668                             return status;
1669                         }
1670                     };
1671             return performDeviceAction(
1672                     String.format("push %s to %s", localFile.getAbsolutePath(), remoteFilePath),
1673                     pushAction,
1674                     MAX_RETRY_ATTEMPTS);
1675         } finally {
1676             long totalTime = System.currentTimeMillis() - startTime;
1677             InvocationMetricLogger.addInvocationMetrics(
1678                     InvocationMetricKey.PUSH_FILE_TIME, totalTime);
1679         }
1680     }
1681 
1682     /**
1683      * {@inheritDoc}
1684      */
1685     @Override
pushString(final String contents, final String remoteFilePath)1686     public boolean pushString(final String contents, final String remoteFilePath)
1687             throws DeviceNotAvailableException {
1688         File tmpFile = null;
1689         try {
1690             tmpFile = FileUtil.createTempFile("temp", ".txt");
1691             FileUtil.writeToFile(contents, tmpFile);
1692             return pushFile(tmpFile, remoteFilePath);
1693         } catch (IOException e) {
1694             CLog.e(e);
1695             return false;
1696         } finally {
1697             FileUtil.deleteFile(tmpFile);
1698         }
1699     }
1700 
1701     /** {@inheritDoc} */
1702     @Override
doesFileExist(String deviceFilePath)1703     public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException {
1704         return doesFileExist(deviceFilePath, getCurrentUserCompatible(deviceFilePath));
1705     }
1706 
1707     /** {@inheritDoc} */
1708     @Override
doesFileExist(String deviceFilePath, int userId)1709     public boolean doesFileExist(String deviceFilePath, int userId)
1710             throws DeviceNotAvailableException {
1711         long startTime = System.currentTimeMillis();
1712         try {
1713             // Skip ContentProvider for user 0
1714             if (isSdcardOrEmulated(deviceFilePath) && userId != 0) {
1715                 ContentProviderHandler handler = getContentProvider(userId);
1716                 if (handler != null) {
1717                     CLog.d("Delegating check to ContentProvider doesFileExist(%s)", deviceFilePath);
1718                     return handler.doesFileExist(deviceFilePath);
1719                 }
1720             }
1721             CLog.d("Using 'ls' to check doesFileExist(%s)", deviceFilePath);
1722             CommandResult result = executeShellV2Command(String.format("ls '%s'", deviceFilePath));
1723             if (CommandStatus.SUCCESS.equals(result.getStatus())
1724                     && !result.getStdout().contains("No such file or directory")) {
1725                 return true;
1726             } else {
1727                 CLog.d(
1728                         "File %s does not exist.\nstdout: %s\nstderr: %s",
1729                         deviceFilePath, result.getStdout(), result.getStderr());
1730                 return false;
1731             }
1732         } finally {
1733             InvocationMetricLogger.addInvocationMetrics(
1734                     InvocationMetricKey.DOES_FILE_EXISTS_TIME,
1735                     System.currentTimeMillis() - startTime);
1736             InvocationMetricLogger.addInvocationMetrics(
1737                     InvocationMetricKey.DOES_FILE_EXISTS_COUNT, 1);
1738         }
1739     }
1740 
1741     @Override
registerDeviceActionReceiver(IDeviceActionReceiver deviceActionReceiver)1742     public void registerDeviceActionReceiver(IDeviceActionReceiver deviceActionReceiver) {
1743         mDeviceActionReceivers.add(deviceActionReceiver);
1744     }
1745 
1746     @Override
deregisterDeviceActionReceiver(IDeviceActionReceiver deviceActionReceiver)1747     public void deregisterDeviceActionReceiver(IDeviceActionReceiver deviceActionReceiver) {
1748         mDeviceActionReceivers.remove(deviceActionReceiver);
1749     }
1750 
1751     /** {@inheritDoc} */
1752     @Override
deleteFile(String deviceFilePath)1753     public void deleteFile(String deviceFilePath) throws DeviceNotAvailableException {
1754         deleteFile(deviceFilePath, getCurrentUserCompatible(deviceFilePath));
1755     }
1756 
1757     /** {@inheritDoc} */
1758     @Override
deleteFile(String deviceFilePath, int userId)1759     public void deleteFile(String deviceFilePath, int userId) throws DeviceNotAvailableException {
1760         long startTime = System.currentTimeMillis();
1761         try {
1762             if (isSdcardOrEmulated(deviceFilePath)) {
1763                 if (userId != 0) {
1764                     ContentProviderHandler handler = getContentProvider(userId);
1765                     if (handler != null) {
1766                         if (handler.deleteFile(deviceFilePath)) {
1767                             return;
1768                         }
1769                     }
1770                 }
1771             }
1772             // Fallback to the direct command if content provider is unsuccessful
1773             String path = StringEscapeUtils.escapeShell(deviceFilePath);
1774             // Escape spaces to handle filename with spaces
1775             path = path.replaceAll(" ", "\\ ");
1776             executeShellCommand(String.format("rm -rf %s", StringEscapeUtils.escapeShell(path)));
1777         } finally {
1778             InvocationMetricLogger.addInvocationMetrics(
1779                     InvocationMetricKey.DELETE_DEVICE_FILE_TIME,
1780                     System.currentTimeMillis() - startTime);
1781             InvocationMetricLogger.addInvocationMetrics(
1782                     InvocationMetricKey.DELETE_DEVICE_FILE_COUNT, 1);
1783         }
1784     }
1785 
1786     /**
1787      * {@inheritDoc}
1788      */
1789     @Override
getExternalStoreFreeSpace()1790     public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
1791         String externalStorePath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
1792         return getPartitionFreeSpace(externalStorePath);
1793     }
1794 
1795     /** {@inheritDoc} */
1796     @Override
getPartitionFreeSpace(String partition)1797     public long getPartitionFreeSpace(String partition) throws DeviceNotAvailableException {
1798         CLog.i("Checking free space for %s on partition %s", getSerialNumber(), partition);
1799         String output = getDfOutput(partition);
1800         // Try coreutils/toybox style output first.
1801         Long available = parseFreeSpaceFromModernOutput(output);
1802         if (available != null) {
1803             return available;
1804         }
1805         // Then the two legacy toolbox formats.
1806         available = parseFreeSpaceFromAvailable(output);
1807         if (available != null) {
1808             return available;
1809         }
1810         available = parseFreeSpaceFromFree(partition, output);
1811         if (available != null) {
1812             return available;
1813         }
1814 
1815         CLog.e("free space command output \"%s\" did not match expected patterns", output);
1816         return 0;
1817     }
1818 
1819     /**
1820      * Run the 'df' shell command and return output, making multiple attempts if necessary.
1821      *
1822      * @param externalStorePath the path to check
1823      * @return the output from 'shell df path'
1824      * @throws DeviceNotAvailableException
1825      */
getDfOutput(String externalStorePath)1826     private String getDfOutput(String externalStorePath) throws DeviceNotAvailableException {
1827         for (int i=0; i < MAX_RETRY_ATTEMPTS; i++) {
1828             String output = executeShellCommand(String.format("df %s", externalStorePath));
1829             if (output.trim().length() > 0) {
1830                 return output;
1831             }
1832         }
1833         throw new DeviceUnresponsiveException(String.format(
1834                 "Device %s not returning output from df command after %d attempts",
1835                 getSerialNumber(), MAX_RETRY_ATTEMPTS), getSerialNumber());
1836     }
1837 
1838     /**
1839      * Parses a partition's available space from the legacy output of a 'df' command, used
1840      * pre-gingerbread.
1841      * <p/>
1842      * Assumes output format of:
1843      * <br>/
1844      * <code>
1845      * [partition]: 15659168K total, 51584K used, 15607584K available (block size 32768)
1846      * </code>
1847      * @param dfOutput the output of df command to parse
1848      * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1849      */
parseFreeSpaceFromAvailable(String dfOutput)1850     private Long parseFreeSpaceFromAvailable(String dfOutput) {
1851         final Pattern freeSpacePattern = Pattern.compile("(\\d+)K available");
1852         Matcher patternMatcher = freeSpacePattern.matcher(dfOutput);
1853         if (patternMatcher.find()) {
1854             String freeSpaceString = patternMatcher.group(1);
1855             try {
1856                 return Long.parseLong(freeSpaceString);
1857             } catch (NumberFormatException e) {
1858                 // fall through
1859             }
1860         }
1861         return null;
1862     }
1863 
1864     /**
1865      * Parses a partition's available space from the 'table-formatted' output of a toolbox 'df'
1866      * command, used from gingerbread to lollipop.
1867      * <p/>
1868      * Assumes output format of:
1869      * <br/>
1870      * <code>
1871      * Filesystem             Size   Used   Free   Blksize
1872      * <br/>
1873      * [partition]:              3G   790M  2G     4096
1874      * </code>
1875      * @param dfOutput the output of df command to parse
1876      * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1877      */
parseFreeSpaceFromFree(String externalStorePath, String dfOutput)1878     Long parseFreeSpaceFromFree(String externalStorePath, String dfOutput) {
1879         Long freeSpace = null;
1880         final Pattern freeSpaceTablePattern = Pattern.compile(String.format(
1881                 //fs   Size         Used         Free
1882                 "%s\\s+[\\w\\d\\.]+\\s+[\\w\\d\\.]+\\s+([\\d\\.]+)(\\w)", externalStorePath));
1883         Matcher tablePatternMatcher = freeSpaceTablePattern.matcher(dfOutput);
1884         if (tablePatternMatcher.find()) {
1885             String numericValueString = tablePatternMatcher.group(1);
1886             String unitType = tablePatternMatcher.group(2);
1887             try {
1888                 float freeSpaceFloat = Float.parseFloat(numericValueString);
1889                 if (unitType.equals("M")) {
1890                     freeSpaceFloat = freeSpaceFloat * 1024;
1891                 } else if (unitType.equals("G")) {
1892                     freeSpaceFloat = freeSpaceFloat * 1024 * 1024;
1893                 }
1894                 freeSpace = (long) freeSpaceFloat;
1895             } catch (NumberFormatException e) {
1896                 // fall through
1897             }
1898         }
1899         return freeSpace;
1900     }
1901 
1902     /**
1903      * Parses a partition's available space from the modern coreutils/toybox 'df' output, used
1904      * after lollipop.
1905      * <p/>
1906      * Assumes output format of:
1907      * <br/>
1908      * <code>
1909      * Filesystem      1K-blocks	Used  Available Use% Mounted on
1910      * <br/>
1911      * /dev/fuse        11585536    1316348   10269188  12% /mnt/shell/emulated
1912      * </code>
1913      * @param dfOutput the output of df command to parse
1914      * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1915      */
parseFreeSpaceFromModernOutput(String dfOutput)1916     Long parseFreeSpaceFromModernOutput(String dfOutput) {
1917         Matcher matcher = DF_PATTERN.matcher(dfOutput);
1918         if (matcher.find()) {
1919             try {
1920                 return Long.parseLong(matcher.group(2));
1921             } catch (NumberFormatException e) {
1922                 // fall through
1923             }
1924         }
1925         return null;
1926     }
1927 
1928     /**
1929      * {@inheritDoc}
1930      */
1931     @Override
getMountPoint(String mountName)1932     public String getMountPoint(String mountName) {
1933         try {
1934             return mStateMonitor.getMountPoint(mountName);
1935         } catch (DeviceNotAvailableException e) {
1936             CLog.e(e);
1937             return null;
1938         }
1939     }
1940 
1941     /**
1942      * {@inheritDoc}
1943      */
1944     @Override
getMountPointInfo()1945     public List<MountPointInfo> getMountPointInfo() throws DeviceNotAvailableException {
1946         final String mountInfo = executeShellCommand("cat /proc/mounts");
1947         final String[] mountInfoLines = mountInfo.split("\r?\n");
1948         List<MountPointInfo> list = new ArrayList<>(mountInfoLines.length);
1949 
1950         for (String line : mountInfoLines) {
1951             // We ignore the last two fields
1952             // /dev/block/mtdblock4 /cache yaffs2 rw,nosuid,nodev,relatime 0 0
1953             final String[] parts = line.split("\\s+", 5);
1954             list.add(new MountPointInfo(parts[0], parts[1], parts[2], parts[3]));
1955         }
1956 
1957         return list;
1958     }
1959 
1960     /**
1961      * {@inheritDoc}
1962      */
1963     @Override
getMountPointInfo(String mountpoint)1964     public MountPointInfo getMountPointInfo(String mountpoint) throws DeviceNotAvailableException {
1965         // The overhead of parsing all of the lines should be minimal
1966         List<MountPointInfo> mountpoints = getMountPointInfo();
1967         for (MountPointInfo info : mountpoints) {
1968             if (mountpoint.equals(info.mountpoint)) {
1969                 return info;
1970             }
1971         }
1972         return null;
1973     }
1974 
1975     /**
1976      * {@inheritDoc}
1977      */
1978     @Override
getFileEntry(String path)1979     public IFileEntry getFileEntry(String path) throws DeviceNotAvailableException {
1980         path = interpolatePathVariables(path);
1981         String[] pathComponents = path.split(FileListingService.FILE_SEPARATOR);
1982         FileListingService service = getFileListingService();
1983         IFileEntry rootFile = new FileEntryWrapper(this, service.getRoot());
1984         return FileEntryWrapper.getDescendant(rootFile, Arrays.asList(pathComponents));
1985     }
1986 
1987     /**
1988      * Unofficial helper to get a {@link FileEntry} from a non-root path. FIXME: Refactor the
1989      * FileEntry system to have it available from any path. (even non root).
1990      *
1991      * @param entry a {@link FileEntry} not necessarily root as Ddmlib requires.
1992      * @return a {@link FileEntryWrapper} representing the FileEntry.
1993      * @throws DeviceNotAvailableException
1994      */
getFileEntry(FileEntry entry)1995     public IFileEntry getFileEntry(FileEntry entry) throws DeviceNotAvailableException {
1996         // FileEntryWrapper is going to construct the list of child file internally.
1997         return new FileEntryWrapper(this, entry);
1998     }
1999 
2000     /** {@inheritDoc} */
2001     @Override
isExecutable(String fullPath)2002     public boolean isExecutable(String fullPath) throws DeviceNotAvailableException {
2003         String fileMode = executeShellCommand(String.format("ls -l %s", fullPath));
2004         if (fileMode != null) {
2005             return EXE_FILE.matcher(fileMode).find();
2006         }
2007         return false;
2008     }
2009 
2010     /**
2011      * {@inheritDoc}
2012      */
2013     @Override
isDirectory(String path)2014     public boolean isDirectory(String path) throws DeviceNotAvailableException {
2015         String output = executeShellCommand(String.format("ls -ld %s", path));
2016         return output != null && output.charAt(0) == 'd';
2017     }
2018 
2019     /**
2020      * {@inheritDoc}
2021      */
2022     @Override
getChildren(String path)2023     public String[] getChildren(String path) throws DeviceNotAvailableException {
2024         String lsOutput = executeShellCommand(String.format("ls -A1 %s", path));
2025         if (lsOutput.trim().isEmpty()) {
2026             return new String[0];
2027         }
2028         return lsOutput.split("\r?\n");
2029     }
2030 
2031     /**
2032      * Retrieve the {@link FileListingService} for the {@link IDevice}, making multiple attempts
2033      * and recovery operations if necessary.
2034      * <p/>
2035      * This is necessary because {@link IDevice#getFileListingService()} can return
2036      * <code>null</code> if device is in fastboot.  The symptom of this condition is that the
2037      * current {@link #getIDevice()} is a {@link StubDevice}.
2038      *
2039      * @return the {@link FileListingService}
2040      * @throws DeviceNotAvailableException if device communication is lost.
2041      */
getFileListingService()2042     private FileListingService getFileListingService() throws DeviceNotAvailableException  {
2043         final FileListingService[] service = new FileListingService[1];
2044         DeviceAction serviceAction = new DeviceAction() {
2045             @Override
2046             public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
2047                     ShellCommandUnresponsiveException, InstallException, SyncException {
2048                 service[0] = getIDevice().getFileListingService();
2049                 if (service[0] == null) {
2050                     // could not get file listing service - must be a stub device - enter recovery
2051                     throw new IOException("Could not get file listing service");
2052                 }
2053                 return true;
2054             }
2055         };
2056         performDeviceAction("getFileListingService", serviceAction, MAX_RETRY_ATTEMPTS);
2057         return service[0];
2058     }
2059 
2060     /**
2061      * {@inheritDoc}
2062      */
2063     @Override
pushDir(File localFileDir, String deviceFilePath)2064     public boolean pushDir(File localFileDir, String deviceFilePath)
2065             throws DeviceNotAvailableException {
2066         return pushDir(localFileDir, deviceFilePath, new HashSet<>());
2067     }
2068 
2069     /** {@inheritDoc} */
2070     @Override
pushDir(File localFileDir, String deviceFilePath, int userId)2071     public boolean pushDir(File localFileDir, String deviceFilePath, int userId)
2072             throws DeviceNotAvailableException {
2073         return pushDir(localFileDir, deviceFilePath, new HashSet<>(), userId);
2074     }
2075 
2076     /** {@inheritDoc} */
2077     @Override
pushDir( File localFileDir, String deviceFilePath, Set<String> excludedDirectories)2078     public boolean pushDir(
2079             File localFileDir, String deviceFilePath, Set<String> excludedDirectories)
2080             throws DeviceNotAvailableException {
2081         return pushDir(
2082                 localFileDir,
2083                 deviceFilePath,
2084                 excludedDirectories,
2085                 getCurrentUserCompatible(deviceFilePath));
2086     }
2087 
pushDir( File localFileDir, String deviceFilePath, Set<String> excludedDirectories, int userId)2088     private boolean pushDir(
2089             File localFileDir, String deviceFilePath, Set<String> excludedDirectories, int userId)
2090             throws DeviceNotAvailableException {
2091         long startTime = System.currentTimeMillis();
2092         try {
2093             if (isSdcardOrEmulated(deviceFilePath)) {
2094                 if (userId != 0) {
2095                     ContentProviderHandler handler = getContentProvider(userId);
2096                     if (handler != null) {
2097                         return handler.pushDir(localFileDir, deviceFilePath, excludedDirectories);
2098                     }
2099                 } else {
2100                     // Remove the special handling when content provider performance is better
2101                     CLog.d("Push without content provider for user '%s'", userId);
2102                 }
2103             }
2104             return pushDirInternal(localFileDir, deviceFilePath, excludedDirectories, userId);
2105         } finally {
2106             InvocationMetricLogger.addInvocationMetrics(
2107                     InvocationMetricKey.PUSH_DIR_TIME, System.currentTimeMillis() - startTime);
2108             InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.PUSH_DIR_COUNT, 1);
2109         }
2110     }
2111 
pushDirInternal( File localFileDir, String deviceFilePath, Set<String> excludedDirectories, int userId)2112     private boolean pushDirInternal(
2113             File localFileDir, String deviceFilePath, Set<String> excludedDirectories, int userId)
2114             throws DeviceNotAvailableException {
2115         if (!localFileDir.isDirectory()) {
2116             CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
2117             return false;
2118         }
2119         File[] childFiles = localFileDir.listFiles();
2120         if (childFiles == null) {
2121             CLog.e("Could not read files in %s", localFileDir.getAbsolutePath());
2122             return false;
2123         }
2124         for (File childFile : childFiles) {
2125             String remotePath = String.format("%s/%s", deviceFilePath, childFile.getName());
2126             if (childFile.isDirectory()) {
2127                 // If we encounter a filtered directory do not push it.
2128                 if (excludedDirectories.contains(childFile.getName())) {
2129                     CLog.d(
2130                             "%s directory was not pushed because it was filtered.",
2131                             childFile.getAbsolutePath());
2132                     continue;
2133                 }
2134                 executeShellCommand(String.format("mkdir -p \"%s\"", remotePath));
2135                 if (!pushDirInternal(childFile, remotePath, excludedDirectories, userId)) {
2136                     return false;
2137                 }
2138             } else if (childFile.isFile()) {
2139                 if (!pushFileInternal(childFile, remotePath, true, userId)) {
2140                     return false;
2141                 }
2142             }
2143         }
2144         return true;
2145     }
2146 
2147     /** {@inheritDoc} */
2148     @Override
pullDir(String deviceFilePath, File localDir)2149     public boolean pullDir(String deviceFilePath, File localDir)
2150             throws DeviceNotAvailableException {
2151         return pullDir(deviceFilePath, localDir, getCurrentUserCompatible(deviceFilePath));
2152     }
2153 
2154     /** {@inheritDoc} */
2155     @Override
pullDir(String deviceFilePath, File localDir, int userId)2156     public boolean pullDir(String deviceFilePath, File localDir, int userId)
2157             throws DeviceNotAvailableException {
2158         long startTime = System.currentTimeMillis();
2159         try {
2160             if (isSdcardOrEmulated(deviceFilePath)) {
2161                 if (userId != 0) {
2162                     ContentProviderHandler handler = getContentProvider(userId);
2163                     if (handler != null) {
2164                         return handler.pullDir(deviceFilePath, localDir);
2165                     }
2166                 }
2167             }
2168 
2169             return pullDirInternal(deviceFilePath, localDir, userId);
2170         } finally {
2171             InvocationMetricLogger.addInvocationMetrics(
2172                     InvocationMetricKey.PULL_DIR_TIME, System.currentTimeMillis() - startTime);
2173             InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.PULL_DIR_COUNT, 1);
2174         }
2175     }
2176 
pullDirInternal(String deviceFilePath, File localDir, int userId)2177     private boolean pullDirInternal(String deviceFilePath, File localDir, int userId)
2178             throws DeviceNotAvailableException {
2179         if (!localDir.isDirectory()) {
2180             CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
2181             return false;
2182         }
2183         if (!doesFileExist(deviceFilePath, userId)) {
2184             CLog.e("Device path %s does not exist to be pulled.", deviceFilePath);
2185             return false;
2186         }
2187         if (!isDirectory(deviceFilePath)) {
2188             CLog.e("Device path %s is not a directory", deviceFilePath);
2189             return false;
2190         }
2191         FileEntry entryRoot =
2192                 new FileEntry(null, deviceFilePath, FileListingService.TYPE_DIRECTORY, false);
2193         IFileEntry entry = getFileEntry(entryRoot);
2194         Collection<IFileEntry> children = entry.getChildren(false);
2195         if (children.isEmpty()) {
2196             CLog.i("Device path is empty, nothing to do.");
2197             return true;
2198         }
2199         for (IFileEntry item : children) {
2200             if (item.isDirectory()) {
2201                 // handle sub dir
2202                 File subDir = new File(localDir, item.getName());
2203                 if (!subDir.mkdir()) {
2204                     CLog.w(
2205                             "Failed to create sub directory %s, aborting.",
2206                             subDir.getAbsolutePath());
2207                     return false;
2208                 }
2209                 String deviceSubDir = item.getFullPath();
2210                 if (!pullDirInternal(deviceSubDir, subDir, userId)) {
2211                     CLog.w("Failed to pull sub directory %s from device, aborting", deviceSubDir);
2212                     return false;
2213                 }
2214             } else {
2215                 // handle regular file
2216                 File localFile = new File(localDir, item.getName());
2217                 String fullPath = item.getFullPath();
2218                 if (!pullFileInternal(fullPath, localFile)) {
2219                     CLog.w("Failed to pull file %s from device, aborting", fullPath);
2220                     return false;
2221                 }
2222             }
2223         }
2224         return true;
2225     }
2226 
2227     /** Checks whether path is external storage path. */
isSdcardOrEmulated(String path)2228     private boolean isSdcardOrEmulated(String path) {
2229         return path.startsWith(SD_CARD) || path.startsWith(STORAGE_EMULATED);
2230     }
2231 
2232     /**
2233      * {@inheritDoc}
2234      */
2235     @Override
syncFiles(File localFileDir, String deviceFilePath)2236     public boolean syncFiles(File localFileDir, String deviceFilePath)
2237             throws DeviceNotAvailableException {
2238         if (localFileDir == null || deviceFilePath == null) {
2239             throw new IllegalArgumentException("syncFiles does not take null arguments");
2240         }
2241         CLog.i("Syncing %s to %s on device %s",
2242                 localFileDir.getAbsolutePath(), deviceFilePath, getSerialNumber());
2243         if (!localFileDir.isDirectory()) {
2244             CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
2245             return false;
2246         }
2247         // get the real destination path. This is done because underlying syncService.push
2248         // implementation will add localFileDir.getName() to destination path
2249         deviceFilePath = String.format("%s/%s", interpolatePathVariables(deviceFilePath),
2250                 localFileDir.getName());
2251         if (!doesFileExist(deviceFilePath)) {
2252             executeShellCommand(String.format("mkdir -p \"%s\"", deviceFilePath));
2253         }
2254         IFileEntry remoteFileEntry = getFileEntry(deviceFilePath);
2255         if (remoteFileEntry == null) {
2256             CLog.e("Could not find remote file entry %s ", deviceFilePath);
2257             return false;
2258         }
2259 
2260         return syncFiles(localFileDir, remoteFileEntry);
2261     }
2262 
2263     /**
2264      * Recursively sync newer files.
2265      *
2266      * @param localFileDir the local {@link File} directory to sync
2267      * @param remoteFileEntry the remote destination {@link IFileEntry}
2268      * @return <code>true</code> if files were synced successfully
2269      * @throws DeviceNotAvailableException
2270      */
syncFiles(File localFileDir, final IFileEntry remoteFileEntry)2271     private boolean syncFiles(File localFileDir, final IFileEntry remoteFileEntry)
2272             throws DeviceNotAvailableException {
2273         CLog.d("Syncing %s to %s on %s", localFileDir.getAbsolutePath(),
2274                 remoteFileEntry.getFullPath(), getSerialNumber());
2275         // find newer files to sync
2276         File[] localFiles = localFileDir.listFiles(new NoHiddenFilesFilter());
2277         ArrayList<String> filePathsToSync = new ArrayList<>();
2278         for (File localFile : localFiles) {
2279             IFileEntry entry = remoteFileEntry.findChild(localFile.getName());
2280             if (entry == null) {
2281                 CLog.d("Detected missing file path %s", localFile.getAbsolutePath());
2282                 filePathsToSync.add(localFile.getAbsolutePath());
2283             } else if (localFile.isDirectory()) {
2284                 // This directory exists remotely. recursively sync it to sync only its newer files
2285                 // contents
2286                 if (!syncFiles(localFile, entry)) {
2287                     return false;
2288                 }
2289             } else if (isNewer(localFile, entry)) {
2290                 CLog.d("Detected newer file %s", localFile.getAbsolutePath());
2291                 filePathsToSync.add(localFile.getAbsolutePath());
2292             }
2293         }
2294 
2295         if (filePathsToSync.size() == 0) {
2296             CLog.d("No files to sync");
2297             return true;
2298         }
2299         final String files[] = filePathsToSync.toArray(new String[filePathsToSync.size()]);
2300         DeviceAction syncAction = new DeviceAction() {
2301             @Override
2302             public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
2303                     SyncException {
2304                 SyncService syncService = null;
2305                 boolean status = false;
2306                 try {
2307                     syncService = getIDevice().getSyncService();
2308                     syncService.push(files, remoteFileEntry.getFileEntry(),
2309                             SyncService.getNullProgressMonitor());
2310                     status = true;
2311                 } catch (SyncException e) {
2312                     CLog.w("Failed to sync files to %s on device %s. Message %s",
2313                             remoteFileEntry.getFullPath(), getSerialNumber(), e.getMessage());
2314                     throw e;
2315                 } finally {
2316                     if (syncService != null) {
2317                         syncService.close();
2318                     }
2319                 }
2320                 return status;
2321             }
2322         };
2323         return performDeviceAction(String.format("sync files %s", remoteFileEntry.getFullPath()),
2324                 syncAction, MAX_RETRY_ATTEMPTS);
2325     }
2326 
2327     /**
2328      * Queries the file listing service for a given directory
2329      *
2330      * @param remoteFileEntry
2331      * @throws DeviceNotAvailableException
2332      */
getFileChildren(final FileEntry remoteFileEntry)2333     FileEntry[] getFileChildren(final FileEntry remoteFileEntry)
2334             throws DeviceNotAvailableException {
2335         // time this operation because its known to hang
2336         FileQueryAction action = new FileQueryAction(remoteFileEntry,
2337                 getIDevice().getFileListingService());
2338         performDeviceAction("buildFileCache", action, 1 /* one retry */);
2339         return action.mFileContents;
2340     }
2341 
2342     private class FileQueryAction implements DeviceAction {
2343 
2344         FileEntry[] mFileContents = null;
2345         private final FileEntry mRemoteFileEntry;
2346         private final FileListingService mService;
2347 
FileQueryAction(FileEntry remoteFileEntry, FileListingService service)2348         FileQueryAction(FileEntry remoteFileEntry, FileListingService service) {
2349             throwIfNull(remoteFileEntry);
2350             throwIfNull(service);
2351             mRemoteFileEntry = remoteFileEntry;
2352             mService = service;
2353         }
2354 
2355         @Override
run()2356         public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
2357                 ShellCommandUnresponsiveException {
2358             mFileContents = mService.getChildrenSync(mRemoteFileEntry);
2359             return true;
2360         }
2361     }
2362 
2363     /**
2364      * A {@link FilenameFilter} that rejects hidden (ie starts with ".") files.
2365      */
2366     private static class NoHiddenFilesFilter implements FilenameFilter {
2367         /**
2368          * {@inheritDoc}
2369          */
2370         @Override
accept(File dir, String name)2371         public boolean accept(File dir, String name) {
2372             return !name.startsWith(".");
2373         }
2374     }
2375 
2376     /**
2377      * helper to get the timezone from the device. Example: "Europe/London"
2378      */
getDeviceTimezone()2379     private String getDeviceTimezone() {
2380         try {
2381             // This may not be set at first, default to GMT in this case.
2382             String timezone = getProperty("persist.sys.timezone");
2383             if (timezone != null) {
2384                 return timezone.trim();
2385             }
2386         } catch (DeviceNotAvailableException e) {
2387             // Fall through on purpose
2388         }
2389         return "GMT";
2390     }
2391 
2392     /**
2393      * Return <code>true</code> if local file is newer than remote file. {@link IFileEntry} being
2394      * accurate to the minute, in case of equal times, the file will be considered newer.
2395      */
2396     @VisibleForTesting
isNewer(File localFile, IFileEntry entry)2397     protected boolean isNewer(File localFile, IFileEntry entry) {
2398         final String entryTimeString = String.format("%s %s", entry.getDate(), entry.getTime());
2399         try {
2400             String timezone = getDeviceTimezone();
2401             // expected format of a FileEntry's date and time
2402             SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
2403             format.setTimeZone(TimeZone.getTimeZone(timezone));
2404             Date remoteDate = format.parse(entryTimeString);
2405 
2406             long offset = 0;
2407             try {
2408                 offset = getDeviceTimeOffset(null);
2409             } catch (DeviceNotAvailableException e) {
2410                 offset = 0;
2411             }
2412             CLog.i("Device offset time: %s", offset);
2413 
2414             // localFile.lastModified has granularity of ms, but remoteDate.getTime only has
2415             // granularity of minutes. Shift remoteDate.getTime() backward by one minute so newly
2416             // modified files get synced
2417             return localFile.lastModified() > (remoteDate.getTime() - 60 * 1000 + offset);
2418         } catch (ParseException e) {
2419             CLog.e("Error converting remote time stamp %s for %s on device %s", entryTimeString,
2420                     entry.getFullPath(), getSerialNumber());
2421         }
2422         // sync file by default
2423         return true;
2424     }
2425 
2426     /**
2427      * {@inheritDoc}
2428      */
2429     @Override
executeAdbCommand(String... cmdArgs)2430     public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
2431         return executeAdbCommand(getCommandTimeout(), cmdArgs);
2432     }
2433 
2434     /** {@inheritDoc} */
2435     @Override
executeAdbCommand(long timeout, String... cmdArgs)2436     public String executeAdbCommand(long timeout, String... cmdArgs)
2437             throws DeviceNotAvailableException {
2438         return executeAdbCommand(getCommandTimeout(), new HashMap<>(), cmdArgs);
2439     }
2440 
2441     /** {@inheritDoc} */
2442     @Override
executeAdbCommand(long timeout, Map<String, String> envMap, String... cmdArgs)2443     public String executeAdbCommand(long timeout, Map<String, String> envMap, String... cmdArgs)
2444             throws DeviceNotAvailableException {
2445         final String[] fullCmd = buildAdbCommand(cmdArgs);
2446         AdbAction adbAction = new AdbAction(timeout, fullCmd, "shell".equals(cmdArgs[0]), envMap);
2447         performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
2448         return adbAction.mOutput;
2449     }
2450 
2451     /**
2452      * {@inheritDoc}
2453      */
2454     @Override
executeFastbootCommand(String... cmdArgs)2455     public CommandResult executeFastbootCommand(String... cmdArgs)
2456             throws DeviceNotAvailableException, UnsupportedOperationException {
2457         // TODO: fix mixed use of fastboot timeout and command timeout
2458         return doFastbootCommand(getCommandTimeout(), cmdArgs);
2459     }
2460 
2461     /**
2462      * {@inheritDoc}
2463      */
2464     @Override
executeFastbootCommand(long timeout, String... cmdArgs)2465     public CommandResult executeFastbootCommand(long timeout, String... cmdArgs)
2466             throws DeviceNotAvailableException, UnsupportedOperationException {
2467         return doFastbootCommand(timeout, cmdArgs);
2468     }
2469 
2470     /**
2471      * {@inheritDoc}
2472      */
2473     @Override
executeLongFastbootCommand(String... cmdArgs)2474     public CommandResult executeLongFastbootCommand(String... cmdArgs)
2475             throws DeviceNotAvailableException, UnsupportedOperationException {
2476         return executeLongFastbootCommand(new HashMap<>(), cmdArgs);
2477     }
2478 
2479     /** {@inheritDoc} */
2480     @Override
executeLongFastbootCommand( Map<String, String> envVarMap, String... cmdArgs)2481     public CommandResult executeLongFastbootCommand(
2482             Map<String, String> envVarMap, String... cmdArgs)
2483             throws DeviceNotAvailableException, UnsupportedOperationException {
2484         // TODO: fix mixed use of fastboot timeout and command timeout
2485         return doFastbootCommand(getLongCommandTimeout(), envVarMap, cmdArgs);
2486     }
2487 
2488     /**
2489      * Do a fastboot command with environment variables set
2490      *
2491      * @param timeout timeout for the fastboot command
2492      * @param envVarMap environment variables that needs to be set before execute the fastboot
2493      *     command
2494      * @param cmdArgs
2495      * @return {@link CommandResult} of the fastboot command
2496      * @throws DeviceNotAvailableException
2497      * @throws UnsupportedOperationException
2498      */
doFastbootCommand( final long timeout, Map<String, String> envVarMap, String... cmdArgs)2499     private CommandResult doFastbootCommand(
2500             final long timeout, Map<String, String> envVarMap, String... cmdArgs)
2501             throws DeviceNotAvailableException, UnsupportedOperationException {
2502         if (!mFastbootEnabled) {
2503             throw new UnsupportedOperationException(String.format(
2504                     "Attempted to fastboot on device %s , but fastboot is not available. Aborting.",
2505                     getSerialNumber()));
2506         }
2507 
2508         File fastbootTmpDir = getHostOptions().getFastbootTmpDir();
2509         if (fastbootTmpDir != null) {
2510             envVarMap.put("TMPDIR", fastbootTmpDir.getAbsolutePath());
2511         }
2512 
2513         final String[] fullCmd = buildFastbootCommand(cmdArgs);
2514 
2515         for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
2516             try (CloseableTraceScope ignored = new CloseableTraceScope("fastboot " + cmdArgs[0])) {
2517                 CommandResult result = simpleFastbootCommand(timeout, envVarMap, fullCmd);
2518                 if (!isRecoveryNeeded(result)) {
2519                     return result;
2520                 }
2521                 CLog.w("Recovery needed after executing fastboot command");
2522                 if (result != null) {
2523                     CLog.v(
2524                             "fastboot command output:\nstdout: %s\nstderr:%s",
2525                             result.getStdout(), result.getStderr());
2526                 }
2527                 recoverDeviceFromBootloader();
2528             }
2529         }
2530         throw new DeviceUnresponsiveException(
2531                 String.format(
2532                         "Attempted fastboot %s multiple "
2533                                 + "times on device %s without communication success. Aborting.",
2534                         cmdArgs[0], getSerialNumber()),
2535                 getSerialNumber());
2536     }
2537 
2538     /**
2539      * Do a fastboot command
2540      *
2541      * @param timeout timeout for the fastboot command
2542      * @param cmdArgs
2543      * @return {@link CommandResult} of the fastboot command
2544      * @throws DeviceNotAvailableException
2545      * @throws UnsupportedOperationException
2546      */
doFastbootCommand(final long timeout, String... cmdArgs)2547     private CommandResult doFastbootCommand(final long timeout, String... cmdArgs)
2548             throws DeviceNotAvailableException, UnsupportedOperationException {
2549         return doFastbootCommand(timeout, new HashMap<>(), cmdArgs);
2550     }
2551 
2552     /**
2553      * {@inheritDoc}
2554      */
2555     @Override
getUseFastbootErase()2556     public boolean getUseFastbootErase() {
2557         return mOptions.getUseFastbootErase();
2558     }
2559 
2560     /**
2561      * {@inheritDoc}
2562      */
2563     @Override
setUseFastbootErase(boolean useFastbootErase)2564     public void setUseFastbootErase(boolean useFastbootErase) {
2565         mOptions.setUseFastbootErase(useFastbootErase);
2566     }
2567 
2568     /**
2569      * {@inheritDoc}
2570      */
2571     @Override
fastbootWipePartition(String partition)2572     public CommandResult fastbootWipePartition(String partition)
2573             throws DeviceNotAvailableException {
2574         if (mOptions.getUseFastbootErase()) {
2575             return executeLongFastbootCommand("erase", partition);
2576         } else {
2577             return executeLongFastbootCommand("format", partition);
2578         }
2579     }
2580 
2581     /**
2582      * Evaluate the given fastboot result to determine if recovery mode needs to be entered
2583      *
2584      * @param fastbootResult the {@link CommandResult} from a fastboot command
2585      * @return <code>true</code> if recovery mode should be entered, <code>false</code> otherwise.
2586      */
isRecoveryNeeded(CommandResult fastbootResult)2587     private boolean isRecoveryNeeded(CommandResult fastbootResult) {
2588         if (fastbootResult.getStatus().equals(CommandStatus.TIMED_OUT)) {
2589             // fastboot commands always time out if devices is not present
2590             return true;
2591         } else {
2592             // check for specific error messages in result that indicate bad device communication
2593             // and recovery mode is needed
2594             if (fastbootResult.getStderr() == null ||
2595                 fastbootResult.getStderr().contains("data transfer failure (Protocol error)") ||
2596                 fastbootResult.getStderr().contains("status read failed (No such device)")) {
2597                 CLog.w("Bad fastboot response from device %s. stderr: %s. Entering recovery",
2598                         getSerialNumber(), fastbootResult.getStderr());
2599                 return true;
2600             }
2601         }
2602         return false;
2603     }
2604 
2605     /** Get the max time allowed in ms for commands. */
getCommandTimeout()2606     long getCommandTimeout() {
2607         return mOptions.getAdbCommandTimeout();
2608     }
2609 
2610     /**
2611      * Set the max time allowed in ms for commands.
2612      */
setLongCommandTimeout(long timeout)2613     void setLongCommandTimeout(long timeout) {
2614         mLongCmdTimeout = timeout;
2615     }
2616 
2617     /**
2618      * Get the max time allowed in ms for commands.
2619      */
getLongCommandTimeout()2620     long getLongCommandTimeout() {
2621         return mLongCmdTimeout;
2622     }
2623 
2624     /** Set the max time allowed in ms for commands. */
setCommandTimeout(long timeout)2625     void setCommandTimeout(long timeout) {
2626         mOptions.setAdbCommandTimeout(timeout);
2627     }
2628 
2629     /**
2630      * Builds the OS command for the given adb command and args
2631      */
buildAdbCommand(String... commandArgs)2632     private String[] buildAdbCommand(String... commandArgs) {
2633         return ArrayUtil.buildArray(new String[] {"adb", "-s", getSerialNumber()},
2634                 commandArgs);
2635     }
2636 
2637     /** Builds the OS command for the given adb shell command session and args */
buildAdbShellCommand(String command, boolean forceExitStatusDetection)2638     protected String[] buildAdbShellCommand(String command, boolean forceExitStatusDetection) {
2639         // TODO: implement the shell v2 support in ddmlib itself.
2640         String[] commandArgs =
2641                 QuotationAwareTokenizer.tokenizeLine(
2642                         command,
2643                         /** No logging */
2644                         false);
2645 
2646         String[] exitStatusProbe;
2647         if (forceExitStatusDetection) {
2648             exitStatusProbe = new String[] {";", "echo", EXIT_STATUS_DELIMITER + "$?"};
2649         } else {
2650             exitStatusProbe = new String[] {};
2651         }
2652         return ArrayUtil.buildArray(
2653                 new String[] {"adb", "-s", getSerialNumber(), "shell"},
2654                 commandArgs,
2655                 exitStatusProbe);
2656     }
2657 
2658     /**
2659      * Builds the OS command for the given fastboot command and args
2660      */
buildFastbootCommand(String... commandArgs)2661     private String[] buildFastbootCommand(String... commandArgs) {
2662         return ArrayUtil.buildArray(
2663                 new String[] {getFastbootPath(), "-s", getFastbootSerialNumber()}, commandArgs);
2664     }
2665 
2666     /**
2667      * Performs an action on this device. Attempts to recover device and optionally retry command if
2668      * action fails.
2669      *
2670      * @param actionDescription a short description of action to be performed. Used for logging
2671      *     purposes only.
2672      * @param action the action to be performed
2673      * @param retryAttempts the retry attempts to make for action if it fails but recovery succeeds
2674      * @return <code>true</code> if action was performed successfully
2675      * @throws DeviceNotAvailableException if recovery attempt fails or max attempts done without
2676      *     success
2677      */
performDeviceAction( String actionDescription, final DeviceAction action, int retryAttempts)2678     protected boolean performDeviceAction(
2679             String actionDescription, final DeviceAction action, int retryAttempts)
2680             throws DeviceNotAvailableException {
2681         Exception lastException = null;
2682         try (CloseableTraceScope ignored = new CloseableTraceScope(actionDescription)) {
2683             for (int i = 0; i < retryAttempts + 1; i++) {
2684                 boolean shouldRecover = true;
2685                 try {
2686                     return action.run();
2687                 } catch (TimeoutException e) {
2688                     logDeviceActionException(actionDescription, e, false);
2689                     lastException = e;
2690                 } catch (IOException e) {
2691                     logDeviceActionException(actionDescription, e, true);
2692                     lastException = e;
2693                 } catch (InstallException e) {
2694                     logDeviceActionException(actionDescription, e, true);
2695                     lastException = e;
2696                 } catch (SyncException e) {
2697                     logDeviceActionException(actionDescription, e, true);
2698                     lastException = e;
2699                     // a SyncException is not necessarily a device communication problem
2700                     // do additional diagnosis
2701                     if (!e.getErrorCode().equals(SyncError.BUFFER_OVERRUN)
2702                             && !e.getErrorCode().equals(SyncError.TRANSFER_PROTOCOL_ERROR)) {
2703                         // this is a logic problem, doesn't need recovery or to be retried
2704                         return false;
2705                     }
2706                 } catch (AdbCommandRejectedException e) {
2707                     // Workaround to not recover device if TCP adb is used.
2708                     if (isAdbTcp()
2709                             && (action instanceof RebootDeviceAction)
2710                             && ((RebootDeviceAction) action).isFastbootOrBootloader()) {
2711                         CLog.d(
2712                                 "Ignore AdbCommandRejectedException when TCP device is rebooted"
2713                                         + " into fastboot.");
2714                         return true;
2715                     }
2716                     lastException = e;
2717                     logDeviceActionException(actionDescription, e, false);
2718                 } catch (ShellCommandUnresponsiveException e) {
2719                     // ShellCommandUnresponsiveException is thrown when no output occurs within the
2720                     // timeout. It doesn't necessarily mean the device is offline.
2721                     shouldRecover = false;
2722                     lastException = e;
2723                     CLog.w(
2724                             "Command: '%s' on '%s' went over its timeout for outputing a response.",
2725                             actionDescription, getSerialNumber());
2726                 }
2727                 if (shouldRecover) {
2728                     recoverDevice();
2729                 }
2730             }
2731             if (retryAttempts > 0) {
2732                 throw new DeviceUnresponsiveException(
2733                         String.format(
2734                                 "Attempted %s multiple times "
2735                                         + "on device %s without communication success. Aborting.",
2736                                 actionDescription, getSerialNumber()),
2737                         lastException,
2738                         getSerialNumber(),
2739                         DeviceErrorIdentifier.DEVICE_UNRESPONSIVE);
2740             }
2741             return false;
2742         }
2743     }
2744 
2745     /**
2746      * Log an entry for given exception
2747      *
2748      * @param actionDescription the action's description
2749      * @param e the exception
2750      * @param logFullTrace whether the full exception stack trace should be logged
2751      */
logDeviceActionException( String actionDescription, Exception e, boolean logFullTrace)2752     private void logDeviceActionException(
2753             String actionDescription, Exception e, boolean logFullTrace) {
2754         CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(),
2755                 getExceptionMessage(e), actionDescription, getSerialNumber());
2756         if (logFullTrace) {
2757             CLog.w(e);
2758         }
2759     }
2760 
2761     /**
2762      * Make a best effort attempt to retrieve a meaningful short descriptive message for given
2763      * {@link Exception}
2764      *
2765      * @param e the {@link Exception}
2766      * @return a short message
2767      */
getExceptionMessage(Exception e)2768     private String getExceptionMessage(Exception e) {
2769         StringBuilder msgBuilder = new StringBuilder();
2770         if (e.getMessage() != null) {
2771             msgBuilder.append(e.getMessage());
2772         }
2773         if (e.getCause() != null) {
2774             msgBuilder.append(" cause: ");
2775             msgBuilder.append(e.getCause().getClass().getSimpleName());
2776             if (e.getCause().getMessage() != null) {
2777                 msgBuilder.append(" (");
2778                 msgBuilder.append(e.getCause().getMessage());
2779                 msgBuilder.append(")");
2780             }
2781         }
2782         return msgBuilder.toString();
2783     }
2784 
2785     /**
2786      * Attempts to recover device communication.
2787      *
2788      * @throws DeviceNotAvailableException if device is no longer available
2789      */
2790     @Override
recoverDevice()2791     public boolean recoverDevice() throws DeviceNotAvailableException {
2792         getConnection().reconnectForRecovery(getSerialNumber());
2793         if (mRecoveryMode.equals(RecoveryMode.NONE)) {
2794             CLog.i("Skipping recovery on %s", getSerialNumber());
2795             return false;
2796         }
2797         CLog.i("Attempting recovery on %s", getSerialNumber());
2798         InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.RECOVERY_ROUTINE_COUNT, 1);
2799         long startTime = System.currentTimeMillis();
2800         try {
2801             try {
2802                 mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE));
2803             } catch (DeviceUnresponsiveException due) {
2804                 // Default error identifier to DEVICE_UNAVAILABLE
2805                 ErrorIdentifier errorIdentifier = DeviceErrorIdentifier.DEVICE_UNAVAILABLE;
2806                 DeviceInspectionResult inspectionResult = debugDeviceNotAvailable();
2807                 String extraErrorMessage = "";
2808                 if (inspectionResult != null && inspectionResult.getErrorIdentifier() != null) {
2809                     errorIdentifier = inspectionResult.getErrorIdentifier();
2810                     extraErrorMessage =
2811                             String.format(" Extra details: %s", inspectionResult.getDetails());
2812                 }
2813                 RecoveryMode previousRecoveryMode = mRecoveryMode;
2814                 mRecoveryMode = RecoveryMode.NONE;
2815                 try {
2816                     boolean enabled = enableAdbRoot();
2817                     CLog.d(
2818                             "Device Unresponsive during recovery, is root still enabled: %s",
2819                             enabled);
2820                 } catch (DeviceUnresponsiveException e) {
2821                     // Ignore exception thrown here to rethrow original exception.
2822                     CLog.e("Exception occurred during recovery adb root:");
2823                     CLog.e(e);
2824                     Throwable cause = e.getCause();
2825                     if (cause != null && cause instanceof AdbCommandRejectedException) {
2826                         AdbCommandRejectedException adbException =
2827                                 (AdbCommandRejectedException) cause;
2828                         if (adbException.isDeviceOffline()
2829                                 || adbException.wasErrorDuringDeviceSelection()) {
2830                             // Upgrade exception to DNAE to reflect gravity
2831                             throw new DeviceNotAvailableException(
2832                                     String.format("%s%s", cause.getMessage(), extraErrorMessage),
2833                                     adbException,
2834                                     getSerialNumber(),
2835                                     errorIdentifier);
2836                         }
2837                     }
2838                 }
2839                 mRecoveryMode = previousRecoveryMode;
2840                 if (inspectionResult != null && inspectionResult.getErrorIdentifier() != null) {
2841                     throw new DeviceNotAvailableException(
2842                             String.format("%s%s", due.getMessage(), extraErrorMessage),
2843                             due,
2844                             getSerialNumber(),
2845                             errorIdentifier);
2846                 }
2847                 throw due;
2848             }
2849             if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) {
2850                 // turn off recovery mode to prevent reentrant recovery
2851                 // TODO: look for a better way to handle this, such as doing postBootUp steps in
2852                 // recovery itself
2853                 mRecoveryMode = RecoveryMode.NONE;
2854                 // this might be a runtime reset - still need to run post boot setup steps
2855                 if (isEncryptionSupported() && isDeviceEncrypted()) {
2856                     unlockDevice();
2857                 }
2858                 postBootSetup();
2859                 mRecoveryMode = RecoveryMode.AVAILABLE;
2860             } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) {
2861                 // turn off recovery mode to prevent reentrant recovery
2862                 // TODO: look for a better way to handle this, such as doing postBootUp steps in
2863                 // recovery itself
2864                 mRecoveryMode = RecoveryMode.NONE;
2865                 enableAdbRoot();
2866                 mRecoveryMode = RecoveryMode.ONLINE;
2867             }
2868         } finally {
2869             InvocationMetricLogger.addInvocationMetrics(
2870                     InvocationMetricKey.RECOVERY_TIME, System.currentTimeMillis() - startTime);
2871         }
2872         CLog.i("Recovery successful for %s", getSerialNumber());
2873         return true;
2874     }
2875 
2876     /**
2877      * Attempts to recover device fastboot communication.
2878      *
2879      * @throws DeviceNotAvailableException if device is not longer available
2880      */
recoverDeviceFromBootloader()2881     private void recoverDeviceFromBootloader() throws DeviceNotAvailableException {
2882         CLog.i("Attempting recovery on %s in bootloader", getSerialNumber());
2883         mRecovery.recoverDeviceBootloader(mStateMonitor);
2884         CLog.i("Bootloader recovery successful for %s", getSerialNumber());
2885     }
2886 
recoverDeviceFromFastbootd()2887     private void recoverDeviceFromFastbootd() throws DeviceNotAvailableException {
2888         CLog.i("Attempting recovery on %s in fastbootd", getSerialNumber());
2889         mRecovery.recoverDeviceFastbootd(mStateMonitor);
2890         CLog.i("Fastbootd recovery successful for %s", getSerialNumber());
2891     }
2892 
recoverDeviceInRecovery()2893     private void recoverDeviceInRecovery() throws DeviceNotAvailableException {
2894         CLog.i("Attempting recovery on %s in recovery", getSerialNumber());
2895         mRecovery.recoverDeviceRecovery(mStateMonitor);
2896         CLog.i("Recovery mode recovery successful for %s", getSerialNumber());
2897     }
2898 
2899     /**
2900      * {@inheritDoc}
2901      */
2902     @Override
startLogcat()2903     public void startLogcat() {
2904         if (mLogcatReceiver != null) {
2905             CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber());
2906             return;
2907         }
2908         mLogcatReceiver = createLogcatReceiver();
2909         mLogcatReceiver.start();
2910     }
2911 
2912     /**
2913      * {@inheritDoc}
2914      */
2915     @Override
clearLogcat()2916     public void clearLogcat() {
2917         if (mLogcatReceiver != null) {
2918             mLogcatReceiver.clear();
2919         }
2920     }
2921 
2922     /** {@inheritDoc} */
2923     @Override
2924     @SuppressWarnings("MustBeClosedChecker")
getLogcat()2925     public InputStreamSource getLogcat() {
2926         if (mLogcatReceiver == null) {
2927             if (!(getIDevice() instanceof StubDevice)) {
2928                 TestDeviceState state = getDeviceState();
2929                 if (!TestDeviceState.ONLINE.equals(state)) {
2930                     CLog.w("Skipping logcat capture, no buffer and device state is '%s'", state);
2931                 } else {
2932                     CLog.w(
2933                             "Not capturing logcat for %s in background, returning a logcat dump",
2934                             getSerialNumber());
2935                     return getLogcatDump();
2936                 }
2937             }
2938             return new ByteArrayInputStreamSource(new byte[0]);
2939         } else {
2940             return mLogcatReceiver.getLogcatData();
2941         }
2942     }
2943 
2944     /** {@inheritDoc} */
2945     @Override
2946     @SuppressWarnings("MustBeClosedChecker")
getLogcat(int maxBytes)2947     public InputStreamSource getLogcat(int maxBytes) {
2948         if (mLogcatReceiver == null) {
2949             CLog.w("Not capturing logcat for %s in background, returning a logcat dump "
2950                     + "ignoring size", getSerialNumber());
2951             return getLogcatDump();
2952         } else {
2953             return mLogcatReceiver.getLogcatData(maxBytes);
2954         }
2955     }
2956 
2957     /**
2958      * {@inheritDoc}
2959      */
2960     @Override
getLogcatSince(long date)2961     public InputStreamSource getLogcatSince(long date) {
2962         int deviceApiLevel;
2963         try {
2964             deviceApiLevel = getApiLevel();
2965             if (deviceApiLevel <= 22) {
2966                 CLog.i("Api level too low to use logcat -t 'time' reverting to dump");
2967                 return getLogcatDump();
2968             }
2969         } catch (DeviceNotAvailableException e) {
2970             // For convenience of interface, we catch the DNAE here.
2971             CLog.e(e);
2972             return getLogcatDump();
2973         }
2974 
2975         String dateFormatted;
2976         if (deviceApiLevel >= 24) {
2977             // Use 'sssss.mmm' epoch time format supported since API 24.
2978             dateFormatted = String.format(Locale.US, "%d.%03d", date / 1000, date % 1000);
2979         } else {
2980             // Convert date to format needed by the command:
2981             // 'MM-DD HH:mm:ss.mmm' or 'YYYY-MM-DD HH:mm:ss.mmm'
2982             SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
2983             dateFormatted = format.format(new Date(date));
2984         }
2985 
2986         LargeOutputReceiver largeReceiver = null;
2987         try {
2988             // use IDevice directly because we don't want callers to handle
2989             // DeviceNotAvailableException for this method
2990             largeReceiver =
2991                     new LargeOutputReceiver(
2992                             "getLogcatSince",
2993                             getSerialNumber(),
2994                             getOptions().getMaxLogcatDataSize());
2995             String command =
2996                     String.format(
2997                             "%s -t '%s'", LogcatReceiver.getDefaultLogcatCmd(this), dateFormatted);
2998             getIDevice().executeShellCommand(command, largeReceiver);
2999             return largeReceiver.getData();
3000         } catch (IOException|AdbCommandRejectedException|
3001                 ShellCommandUnresponsiveException|TimeoutException e) {
3002             CLog.w("Failed to get logcat dump from %s: %s", getSerialNumber(), e.getMessage());
3003             CLog.e(e);
3004         } finally {
3005             if (largeReceiver != null) {
3006                 largeReceiver.cancel();
3007                 largeReceiver.delete();
3008             }
3009         }
3010         return new ByteArrayInputStreamSource(new byte[0]);
3011     }
3012 
3013     /**
3014      * {@inheritDoc}
3015      */
3016     @Override
getLogcatDump()3017     public InputStreamSource getLogcatDump() {
3018         long startTime = System.currentTimeMillis();
3019         LargeOutputReceiver largeReceiver = null;
3020         try (CloseableTraceScope ignored = new CloseableTraceScope("getLogcatDump")) {
3021             // use IDevice directly because we don't want callers to handle
3022             // DeviceNotAvailableException for this method
3023             largeReceiver =
3024                     new LargeOutputReceiver(
3025                             "getLogcatDump",
3026                             getSerialNumber(),
3027                             getOptions().getMaxLogcatDataSize());
3028             // add -d parameter to make this a non blocking call
3029             getIDevice()
3030                     .executeShellCommand(
3031                             LogcatReceiver.getDefaultLogcatCmd(this) + " -d",
3032                             largeReceiver,
3033                             LOGCAT_DUMP_TIMEOUT,
3034                             LOGCAT_DUMP_TIMEOUT,
3035                             TimeUnit.MILLISECONDS);
3036             return largeReceiver.getData();
3037         } catch (IOException e) {
3038             CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
3039         } catch (TimeoutException e) {
3040             CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber());
3041         } catch (AdbCommandRejectedException e) {
3042             CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
3043         } catch (ShellCommandUnresponsiveException e) {
3044             CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
3045         } finally {
3046             if (largeReceiver != null) {
3047                 largeReceiver.cancel();
3048                 largeReceiver.delete();
3049             }
3050             InvocationMetricLogger.addInvocationMetrics(
3051                     InvocationMetricKey.LOGCAT_DUMP_TIME, System.currentTimeMillis() - startTime);
3052             InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.LOGCAT_DUMP_COUNT, 1);
3053         }
3054         return new ByteArrayInputStreamSource(new byte[0]);
3055     }
3056 
3057     /**
3058      * {@inheritDoc}
3059      */
3060     @Override
stopLogcat()3061     public void stopLogcat() {
3062         if (mLogcatReceiver != null) {
3063             mLogcatReceiver.stop();
3064             mLogcatReceiver = null;
3065         } else {
3066             CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber());
3067         }
3068     }
3069 
3070     /** Factory method to create a {@link LogcatReceiver}. */
3071     @VisibleForTesting
createLogcatReceiver()3072     LogcatReceiver createLogcatReceiver() {
3073         String logcatOptions = mOptions.getLogcatOptions();
3074         if (SystemUtil.isLocalMode()) {
3075             mLogStartDelay = 0;
3076         }
3077         if (logcatOptions == null) {
3078             return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay);
3079         } else {
3080             return new LogcatReceiver(
3081                     this,
3082                     String.format("%s %s", LogcatReceiver.getDefaultLogcatCmd(this), logcatOptions),
3083                     mOptions.getMaxLogcatDataSize(),
3084                     mLogStartDelay);
3085         }
3086     }
3087 
3088     /**
3089      * {@inheritDoc}
3090      */
3091     @Override
getBugreport()3092     public InputStreamSource getBugreport() {
3093         return null;
3094     }
3095 
3096     /**
3097      * {@inheritDoc}
3098      */
3099     @Override
logBugreport(String dataName, ITestLogger listener)3100     public boolean logBugreport(String dataName, ITestLogger listener) {
3101         return true;
3102     }
3103 
3104     /**
3105      * {@inheritDoc}
3106      */
3107     @Override
takeBugreport()3108     public Bugreport takeBugreport() {
3109         return null;
3110     }
3111 
3112     /**
3113      * {@inheritDoc}
3114      */
3115     @Override
getBugreportz()3116     public InputStreamSource getBugreportz() {
3117         return null;
3118     }
3119 
3120     /** {@inheritDoc} */
3121     @Override
logAnrs(ITestLogger logger)3122     public boolean logAnrs(ITestLogger logger) throws DeviceNotAvailableException {
3123         if (!doesFileExist(ANRS_PATH)) {
3124             CLog.d("No ANRs at %s", ANRS_PATH);
3125             return true;
3126         }
3127         boolean root = enableAdbRoot();
3128         if (!root) {
3129             CLog.d("Skipping logAnrs, need to be root.");
3130         }
3131         File localDir = null;
3132         long startTime = System.currentTimeMillis();
3133         try {
3134             localDir = FileUtil.createTempDir("pulled-anrs");
3135             boolean success = pullDir(ANRS_PATH, localDir);
3136             if (!success) {
3137                 CLog.w("Failed to pull %s", ANRS_PATH);
3138                 return false;
3139             }
3140             if (localDir.listFiles().length == 0) {
3141                 return true;
3142             }
3143             for (File f : localDir.listFiles()) {
3144                 try (FileInputStreamSource source = new FileInputStreamSource(f)) {
3145                     String name = f.getName();
3146                     LogDataType type = LogDataType.ANRS;
3147                     if (name.startsWith("dumptrace")) {
3148                         type = LogDataType.DUMPTRACE;
3149                     }
3150                     logger.testLog(name, type, source);
3151                 }
3152             }
3153         } catch (IOException e) {
3154             CLog.e(e);
3155             return false;
3156         } finally {
3157             FileUtil.recursiveDelete(localDir);
3158         }
3159         InvocationMetricLogger.addInvocationMetrics(
3160                 InvocationMetricKey.ANR_TIME, System.currentTimeMillis() - startTime);
3161         InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.ANR_COUNT, 1);
3162         return true;
3163     }
3164 
3165     /**
3166      * {@inheritDoc}
3167      */
3168     @Override
getScreenshot()3169     public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
3170         throw new UnsupportedOperationException("No support for Screenshot");
3171     }
3172 
3173     /**
3174      * {@inheritDoc}
3175      */
3176     @Override
getScreenshot(String format)3177     public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
3178         throw new UnsupportedOperationException("No support for Screenshot");
3179     }
3180 
3181     /** {@inheritDoc} */
3182     @Override
getScreenshot(String format, boolean rescale)3183     public InputStreamSource getScreenshot(String format, boolean rescale)
3184             throws DeviceNotAvailableException {
3185         throw new UnsupportedOperationException("No support for Screenshot");
3186     }
3187 
3188     /** {@inheritDoc} */
3189     @Override
getScreenshot(long displayId)3190     public InputStreamSource getScreenshot(long displayId) throws DeviceNotAvailableException {
3191         throw new UnsupportedOperationException("No support for Screenshot");
3192     }
3193 
3194     /** {@inheritDoc} */
3195     @Override
clearLastConnectedWifiNetwork()3196     public void clearLastConnectedWifiNetwork() {
3197         mLastConnectedWifiSsid = null;
3198         mLastConnectedWifiPsk = null;
3199     }
3200 
3201     /**
3202      * {@inheritDoc}
3203      */
3204     @Override
connectToWifiNetwork(String wifiSsid, String wifiPsk)3205     public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk)
3206             throws DeviceNotAvailableException {
3207         return connectToWifiNetwork(wifiSsid, wifiPsk, false);
3208     }
3209 
3210     /**
3211      * {@inheritDoc}
3212      */
3213     @Override
connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)3214     public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)
3215             throws DeviceNotAvailableException {
3216         LinkedHashMap<String, String> ssidToPsk = new LinkedHashMap<>();
3217         ssidToPsk.put(wifiSsid, wifiPsk);
3218         return connectToWifiNetwork(ssidToPsk, scanSsid);
3219     }
3220 
3221     /** {@inheritDoc}f */
3222     @Override
connectToWifiNetwork(Map<String, String> wifiSsidToPsk)3223     public boolean connectToWifiNetwork(Map<String, String> wifiSsidToPsk)
3224             throws DeviceNotAvailableException {
3225         return connectToWifiNetwork(wifiSsidToPsk, false);
3226     }
3227 
3228     /** {@inheritDoc} */
3229     @Override
connectToWifiNetwork(Map<String, String> wifiSsidToPsk, boolean scanSsid)3230     public boolean connectToWifiNetwork(Map<String, String> wifiSsidToPsk, boolean scanSsid)
3231             throws DeviceNotAvailableException {
3232         // Clears the last connected wifi network.
3233         mLastConnectedWifiSsid = null;
3234         mLastConnectedWifiPsk = null;
3235 
3236         // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()}
3237         // times
3238         Random rnd = new Random();
3239         int backoffSlotCount = 2;
3240         int slotTime = mOptions.getWifiRetryWaitTime();
3241         int waitTime = 0;
3242         long startTime = mClock.millis();
3243         try (CloseableTraceScope ignored = new CloseableTraceScope("connectToWifiNetwork")) {
3244             for (int i = 1; i <= mOptions.getWifiAttempts(); i++) {
3245                 boolean failedToEnableWifi = false;
3246                 for (Map.Entry<String, String> ssidToPsk : wifiSsidToPsk.entrySet()) {
3247                     String wifiSsid = ssidToPsk.getKey();
3248                     String wifiPsk = Strings.emptyToNull(ssidToPsk.getValue());
3249 
3250                     InvocationMetricLogger.addInvocationMetrics(
3251                             InvocationMetricKey.WIFI_CONNECT_RETRY_COUNT, i);
3252                     CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber());
3253                     IWifiHelper wifi = null;
3254                     if (!getOptions().useCmdWifiCommands()
3255                             || !enableAdbRoot()
3256                             || getApiLevel() < 31) {
3257                         wifi = createWifiHelper(false);
3258                     } else {
3259                         wifi = createWifiHelper(true);
3260                     }
3261                     WifiConnectionResult result =
3262                             wifi.connectToNetwork(
3263                                     wifiSsid,
3264                                     wifiPsk,
3265                                     mOptions.getConnCheckUrl(),
3266                                     scanSsid,
3267                                     mOptions.getDefaultNetworkType());
3268 
3269                     final Map<String, String> wifiInfo = wifi.getWifiInfo();
3270                     if (WifiConnectionResult.SUCCESS.equals(result)) {
3271                         CLog.i(
3272                                 "Successfully connected to wifi network %s(%s) on %s",
3273                                 wifiSsid, wifiInfo.get("bssid"), getSerialNumber());
3274                         InvocationMetricLogger.addInvocationMetrics(
3275                                 InvocationMetricKey.WIFI_AP_NAME, wifiSsid);
3276                         mLastConnectedWifiSsid = wifiSsid;
3277                         mLastConnectedWifiPsk = wifiPsk;
3278 
3279                         return true;
3280                     } else if (WifiConnectionResult.FAILED_TO_ENABLE.equals(result)) {
3281                         CLog.w("Failed to enable wifi");
3282                         failedToEnableWifi = true;
3283                     } else {
3284                         failedToEnableWifi = false;
3285                         CLog.w(
3286                                 "Failed to connect to wifi network %s(%s) on %s on attempt %d of"
3287                                         + " %d",
3288                                 wifiSsid,
3289                                 wifiInfo.get("bssid"),
3290                                 getSerialNumber(),
3291                                 i,
3292                                 mOptions.getWifiAttempts());
3293                     }
3294                 }
3295                 if (mClock.millis() - startTime >= mOptions.getMaxWifiConnectTime()) {
3296                     CLog.e(
3297                             "Failed to connect to wifi after %d ms. Aborting.",
3298                             mOptions.getMaxWifiConnectTime());
3299                     break;
3300                 }
3301                 // Do not sleep for the last iteration and when failed to enable wifi.
3302                 if (i < mOptions.getWifiAttempts() && !failedToEnableWifi) {
3303                     if (mOptions.isWifiExpoRetryEnabled()) {
3304                         // use binary exponential back-offs when retrying.
3305                         waitTime = rnd.nextInt(backoffSlotCount) * slotTime;
3306                         backoffSlotCount *= 2;
3307                     }
3308                     CLog.e(
3309                             "Waiting for %d ms before reconnecting to %s...",
3310                             waitTime, wifiSsidToPsk.keySet().toString());
3311                     getRunUtil().sleep(waitTime);
3312                 }
3313             }
3314         } finally {
3315             InvocationMetricLogger.addInvocationMetrics(
3316                     InvocationMetricKey.WIFI_CONNECT_TIME, mClock.millis() - startTime);
3317             InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.WIFI_CONNECT_COUNT, 1);
3318         }
3319         return false;
3320     }
3321 
3322     /**
3323      * {@inheritDoc}
3324      */
3325     @Override
checkConnectivity()3326     public boolean checkConnectivity() throws DeviceNotAvailableException {
3327         IWifiHelper wifi = createWifiHelper();
3328         return wifi.checkConnectivity(mOptions.getConnCheckUrl());
3329     }
3330 
3331     /**
3332      * {@inheritDoc}
3333      */
3334     @Override
connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)3335     public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)
3336             throws DeviceNotAvailableException {
3337         return connectToWifiNetworkIfNeeded(wifiSsid, wifiPsk, false);
3338     }
3339 
3340     /**
3341      * {@inheritDoc}
3342      */
3343     @Override
connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)3344     public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)
3345             throws DeviceNotAvailableException {
3346         if (!checkConnectivity())  {
3347             return connectToWifiNetwork(wifiSsid, wifiPsk, scanSsid);
3348         }
3349         return true;
3350     }
3351 
3352     /**
3353      * {@inheritDoc}
3354      */
3355     @Override
isWifiEnabled()3356     public boolean isWifiEnabled() throws DeviceNotAvailableException {
3357         final IWifiHelper wifi = createWifiHelper();
3358         try {
3359             return wifi.isWifiEnabled();
3360         } catch (RuntimeException e) {
3361             CLog.w("Failed to create WifiHelper: %s", e.getMessage());
3362             return false;
3363         }
3364     }
3365 
3366     /**
3367      * Checks that the device is currently successfully connected to given wifi SSID.
3368      *
3369      * @param wifiSSID the wifi ssid
3370      * @return <code>true</code> if device is currently connected to wifiSSID and has network
3371      *         connectivity. <code>false</code> otherwise
3372      * @throws DeviceNotAvailableException if connection with device was lost
3373      */
checkWifiConnection(String wifiSSID)3374     boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException {
3375         CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber());
3376         final IWifiHelper wifi = createWifiHelper();
3377         // getSSID returns SSID as "SSID"
3378         final String quotedSSID = String.format("\"%s\"", wifiSSID);
3379 
3380         boolean test = wifi.isWifiEnabled();
3381         CLog.v("%s: wifi enabled? %b", getSerialNumber(), test);
3382 
3383         if (test) {
3384             final String actualSSID = wifi.getSSID();
3385             test = quotedSSID.equals(actualSSID);
3386             CLog.v("%s: SSID match (%s, %s, %b)", getSerialNumber(), quotedSSID, actualSSID, test);
3387         }
3388         if (test) {
3389             test = wifi.hasValidIp();
3390             CLog.v("%s: validIP? %b", getSerialNumber(), test);
3391         }
3392         if (test) {
3393             test = checkConnectivity();
3394             CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test);
3395         }
3396         return test;
3397     }
3398 
3399     /**
3400      * {@inheritDoc}
3401      */
3402     @Override
disconnectFromWifi()3403     public boolean disconnectFromWifi() throws DeviceNotAvailableException {
3404         CLog.i("Disconnecting from wifi on %s", getSerialNumber());
3405         // Clears the last connected wifi network.
3406         mLastConnectedWifiSsid = null;
3407         mLastConnectedWifiPsk = null;
3408 
3409         IWifiHelper wifi = null;
3410         if (!getOptions().useCmdWifiCommands() || !enableAdbRoot() || getApiLevel() < 31) {
3411             wifi = createWifiHelper(false);
3412         } else {
3413             wifi = createWifiHelper(true);
3414         }
3415         return wifi.disconnectFromNetwork();
3416     }
3417 
3418     /**
3419      * {@inheritDoc}
3420      */
3421     @Override
getIpAddress()3422     public String getIpAddress() throws DeviceNotAvailableException {
3423         IWifiHelper wifi = createWifiHelper();
3424         return wifi.getIpAddress();
3425     }
3426 
3427     /**
3428      * {@inheritDoc}
3429      */
3430     @Override
enableNetworkMonitor()3431     public boolean enableNetworkMonitor() throws DeviceNotAvailableException {
3432         mNetworkMonitorEnabled = false;
3433 
3434         IWifiHelper wifi = createWifiHelper();
3435         wifi.stopMonitor();
3436         if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) {
3437             mNetworkMonitorEnabled = true;
3438             return true;
3439         }
3440         return false;
3441     }
3442 
3443     /**
3444      * {@inheritDoc}
3445      */
3446     @Override
disableNetworkMonitor()3447     public boolean disableNetworkMonitor() throws DeviceNotAvailableException {
3448         mNetworkMonitorEnabled = false;
3449 
3450         IWifiHelper wifi = createWifiHelper();
3451         List<Long> samples = wifi.stopMonitor();
3452         if (!samples.isEmpty()) {
3453             int failures = 0;
3454             long totalLatency = 0;
3455             for (Long sample : samples) {
3456                 if (sample < 0) {
3457                     failures += 1;
3458                 } else {
3459                     totalLatency += sample;
3460                 }
3461             }
3462             double failureRate = failures * 100.0 / samples.size();
3463             double avgLatency = 0.0;
3464             if (failures < samples.size()) {
3465                 avgLatency = totalLatency / (samples.size() - failures);
3466             }
3467             CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f",
3468                     mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000,
3469                     failureRate, avgLatency);
3470         }
3471         return true;
3472     }
3473 
3474     /**
3475      * Create a {@link WifiHelper} to use
3476      *
3477      * <p>
3478      *
3479      * @throws DeviceNotAvailableException
3480      */
3481     @VisibleForTesting
createWifiHelper()3482     IWifiHelper createWifiHelper() throws DeviceNotAvailableException {
3483         // current wifi helper won't work on AndroidNativeDevice
3484         // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when
3485         // we learn what is available.
3486         throw new UnsupportedOperationException("Wifi helper is not supported.");
3487     }
3488 
3489     /**
3490      * Create a {@link WifiHelper} to use
3491      *
3492      * @param useV2 Whether to use WifiHelper v2 which does not install any apk.
3493      *     <p>
3494      * @throws DeviceNotAvailableException
3495      */
3496     @VisibleForTesting
createWifiHelper(boolean useV2)3497     IWifiHelper createWifiHelper(boolean useV2) throws DeviceNotAvailableException {
3498         // current wifi helper won't work on AndroidNativeDevice
3499         // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when
3500         // we learn what is available.
3501         throw new UnsupportedOperationException("Wifi helper is not supported.");
3502     }
3503 
3504     /**
3505      * {@inheritDoc}
3506      */
3507     @Override
clearErrorDialogs()3508     public boolean clearErrorDialogs() throws DeviceNotAvailableException {
3509         CLog.e("No support for Screen's features");
3510         return false;
3511     }
3512 
3513     /** {@inheritDoc} */
3514     @Override
getKeyguardState()3515     public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException {
3516         throw new UnsupportedOperationException("No support for keyguard querying.");
3517     }
3518 
getDeviceStateMonitor()3519     IDeviceStateMonitor getDeviceStateMonitor() {
3520         return mStateMonitor;
3521     }
3522 
3523     /** {@inheritDoc} */
3524     @Override
postBootSetup()3525     public void postBootSetup() throws DeviceNotAvailableException {
3526         if (getOptions().shouldDisableReboot()) {
3527             return;
3528         }
3529         getConnection().reconnect(getSerialNumber());
3530         CLog.d("postBootSetup started");
3531         long startTime = System.currentTimeMillis();
3532         try (CloseableTraceScope ignored = new CloseableTraceScope("postBootSetup")) {
3533             enableAdbRoot();
3534             prePostBootSetup();
3535             for (String command : mOptions.getPostBootCommands()) {
3536                 long start = System.currentTimeMillis();
3537                 try (CloseableTraceScope cmdTrace = new CloseableTraceScope(command)) {
3538                     executeShellCommand(command);
3539                 }
3540                 if (command.startsWith("sleep")) {
3541                     InvocationMetricLogger.addInvocationPairMetrics(
3542                             InvocationMetricKey.host_sleep, start, System.currentTimeMillis());
3543                 }
3544             }
3545         } finally {
3546             long elapsed = System.currentTimeMillis() - startTime;
3547             InvocationMetricLogger.addInvocationMetrics(
3548                     InvocationMetricKey.POSTBOOT_SETUP_TIME, elapsed);
3549             InvocationMetricLogger.addInvocationMetrics(
3550                     InvocationMetricKey.POSTBOOT_SETUP_COUNT, 1);
3551             CLog.d("postBootSetup done: %s", TimeUtil.formatElapsedTime(elapsed));
3552         }
3553     }
3554 
3555     /**
3556      * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for
3557      * specific post boot setup.
3558      *
3559      * @throws DeviceNotAvailableException
3560      */
prePostBootSetup()3561     protected void prePostBootSetup() throws DeviceNotAvailableException {
3562         // Empty on purpose.
3563     }
3564 
3565     /**
3566      * Ensure wifi connection is re-established after boot. This is intended to be called after TF
3567      * initiated reboots(ones triggered by {@link #reboot()}) only.
3568      *
3569      * @throws DeviceNotAvailableException
3570      */
postBootWifiSetup()3571     void postBootWifiSetup() throws DeviceNotAvailableException {
3572         CLog.d("postBootWifiSetup started");
3573         long startTime = System.currentTimeMillis();
3574         try (CloseableTraceScope ignored = new CloseableTraceScope("postBootWifiSetup")) {
3575             if (mLastConnectedWifiSsid != null) {
3576                 reconnectToWifiNetwork();
3577             }
3578             if (mNetworkMonitorEnabled) {
3579                 if (!enableNetworkMonitor()) {
3580                     CLog.w(
3581                             "Failed to enable network monitor on %s after reboot",
3582                             getSerialNumber());
3583                 }
3584             }
3585         } finally {
3586             long elapsed = System.currentTimeMillis() - startTime;
3587             InvocationMetricLogger.addInvocationMetrics(
3588                     InvocationMetricKey.POSTBOOT_WIFI_SETUP_TIME, elapsed);
3589             InvocationMetricLogger.addInvocationMetrics(
3590                     InvocationMetricKey.POSTBOOT_WIFI_SETUP_COUNT, 1);
3591             CLog.d("postBootWifiSetup done: %s", TimeUtil.formatElapsedTime(elapsed));
3592         }
3593     }
3594 
reconnectToWifiNetwork()3595     void reconnectToWifiNetwork() throws DeviceNotAvailableException {
3596         // First, wait for wifi to re-connect automatically.
3597         long startTime = System.currentTimeMillis();
3598         boolean isConnected = checkConnectivity();
3599         while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) {
3600             getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL);
3601             isConnected = checkConnectivity();
3602         }
3603 
3604         if (isConnected) {
3605             return;
3606         }
3607 
3608         // If wifi is still not connected, try to re-connect on our own.
3609         final String wifiSsid = mLastConnectedWifiSsid;
3610         if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) {
3611             throw new NetworkNotAvailableException(
3612                     String.format("Failed to connect to wifi network %s on %s after reboot",
3613                             wifiSsid, getSerialNumber()));
3614         }
3615     }
3616 
3617     /**
3618      * {@inheritDoc}
3619      */
3620     @Override
rebootIntoBootloader()3621     public void rebootIntoBootloader()
3622             throws DeviceNotAvailableException, UnsupportedOperationException {
3623         if (isInRebootCallback()) {
3624             CLog.d(
3625                     "'%s' action is disabled during reboot callback. Ignoring.",
3626                     "Reboot into Bootloader");
3627             return;
3628         }
3629         rebootIntoFastbootInternal(true);
3630     }
3631 
3632     /** {@inheritDoc} */
3633     @Override
rebootIntoFastbootd()3634     public void rebootIntoFastbootd()
3635             throws DeviceNotAvailableException, UnsupportedOperationException {
3636         if (isInRebootCallback()) {
3637             CLog.d(
3638                     "'%s' action is disabled during reboot callback. Ignoring.",
3639                     "Reboot into Fastbootd");
3640             return;
3641         }
3642         rebootIntoFastbootInternal(false);
3643     }
3644 
3645     /**
3646      * Reboots the device into bootloader or fastbootd mode.
3647      *
3648      * @param isBootloader true to boot the device into bootloader mode, false to boot the device
3649      *     into fastbootd mode.
3650      * @throws DeviceNotAvailableException if connection with device is lost and cannot be
3651      *     recovered.
3652      */
rebootIntoFastbootInternal(boolean isBootloader)3653     private void rebootIntoFastbootInternal(boolean isBootloader)
3654             throws DeviceNotAvailableException {
3655         invalidatePropertyCache();
3656         final RebootMode mode =
3657                 isBootloader ? RebootMode.REBOOT_INTO_BOOTLOADER : RebootMode.REBOOT_INTO_FASTBOOTD;
3658         if (!mFastbootEnabled) {
3659             throw new UnsupportedOperationException(
3660                     String.format("Fastboot is not available and cannot reboot into %s", mode));
3661         }
3662         // Force wait for snapuserd in progress just to be sure
3663         waitForSnapuserd(SnapuserdWaitPhase.BLOCK_BEFORE_RELEASING);
3664         long startTime = System.currentTimeMillis();
3665 
3666         try (CloseableTraceScope ignored =
3667                 new CloseableTraceScope("reboot_in_" + mode.toString())) {
3668             String fastbootSerial = getFastbootSerialNumber();
3669             // Update fastboot serial number before entering fastboot mode
3670             mStateMonitor.setFastbootSerialNumber(fastbootSerial);
3671             if (!fastbootSerial.equals(getSerialNumber())) {
3672                 CLog.d("Using serial '%s' for fastboot detection.", fastbootSerial);
3673             }
3674 
3675             // If we go to bootloader, it's probably for flashing so ensure we re-check the provider
3676             mShouldSkipContentProviderSetup = false;
3677             CLog.i(
3678                     "Rebooting device %s in state %s into %s",
3679                     getSerialNumber(), getDeviceState(), mode);
3680             if (isStateBootloaderOrFastbootd()) {
3681                 CLog.i(
3682                         "device %s already in %s. Rebooting anyway",
3683                         getSerialNumber(), getDeviceState());
3684                 InvocationMetricLogger.addInvocationMetrics(
3685                         InvocationMetricKey.BOOTLOADER_SAME_STATE_REBOOT, 1);
3686                 executeFastbootCommand(String.format("reboot-%s", mode));
3687             } else {
3688                 CLog.i("Booting device %s into %s", getSerialNumber(), mode);
3689                 doAdbReboot(mode, null);
3690             }
3691 
3692             // We want to wait on a command that verifies we've rebooted.
3693             // However, it is possible to issue this command too quickly and get
3694             // a response before the device has begun the reboot process (see
3695             // b/242200753).
3696             // While not as clean as we'd like, we wait 1.5 seconds before
3697             // issuing any waiting commands, as devices generally take much
3698             // longer than 1.5 seconds to reboot anyway.
3699             getRunUtil().sleep(1500);
3700 
3701             if (RebootMode.REBOOT_INTO_FASTBOOTD.equals(mode)
3702                     && getHostOptions().isFastbootdEnable()) {
3703                 if (!mStateMonitor.waitForDeviceFastbootd(
3704                         getFastbootPath(), mOptions.getFastbootTimeout())) {
3705                     recoverDeviceFromFastbootd();
3706                 }
3707             } else {
3708                 waitForDeviceBootloader();
3709             }
3710         } finally {
3711             long elapsedTime = System.currentTimeMillis() - startTime;
3712             if (RebootMode.REBOOT_INTO_FASTBOOTD.equals(mode)) {
3713                 InvocationMetricLogger.addInvocationMetrics(
3714                         InvocationMetricKey.FASTBOOTD_REBOOT_TIME, elapsedTime);
3715                 InvocationMetricLogger.addInvocationMetrics(
3716                         InvocationMetricKey.FASTBOOTD_REBOOT_COUNT, 1);
3717             } else {
3718                 InvocationMetricLogger.addInvocationMetrics(
3719                         InvocationMetricKey.BOOTLOADER_REBOOT_TIME, elapsedTime);
3720                 InvocationMetricLogger.addInvocationMetrics(
3721                         InvocationMetricKey.BOOTLOADER_REBOOT_COUNT, 1);
3722             }
3723         }
3724     }
3725 
3726     /** {@inheritDoc} */
3727     @Override
isStateBootloaderOrFastbootd()3728     public boolean isStateBootloaderOrFastbootd() {
3729         return TestDeviceState.FASTBOOT.equals(getDeviceState())
3730                 || TestDeviceState.FASTBOOTD.equals(getDeviceState());
3731     }
3732 
3733     /**
3734      * {@inheritDoc}
3735      */
3736     @Override
reboot()3737     public void reboot() throws DeviceNotAvailableException {
3738         reboot(null);
3739     }
3740 
3741     /** {@inheritDoc} */
3742     @Override
reboot(@ullable String reason)3743     public void reboot(@Nullable String reason) throws DeviceNotAvailableException {
3744         if (isInRebootCallback()) {
3745             CLog.d("'%s' action is disabled during reboot callback. Ignoring.", "Reboot");
3746             return;
3747         }
3748         internalRebootUntilOnline(reason);
3749 
3750         RecoveryMode cachedRecoveryMode = getRecoveryMode();
3751         setRecoveryMode(RecoveryMode.ONLINE);
3752 
3753         if (isEncryptionSupported() && isDeviceEncrypted()) {
3754             unlockDevice();
3755         }
3756 
3757         setRecoveryMode(cachedRecoveryMode);
3758 
3759         try (CloseableTraceScope ignored =
3760                 new CloseableTraceScope("reboot_waitForDeviceAvailable")) {
3761             waitForDeviceAvailable(mOptions.getRebootTimeout());
3762         }
3763         postBootSetup();
3764         postBootWifiSetup();
3765         // notify of reboot end here. Full reboots will end here as well as reboots from Bootloader
3766         // or Fastboot mode.
3767         notifyRebootEnded();
3768     }
3769 
3770     @Override
rebootUserspace()3771     public void rebootUserspace() throws DeviceNotAvailableException {
3772         if (isInRebootCallback()) {
3773             CLog.d("'%s' action is disabled during reboot callback. Ignoring.", "Reboot Userspace");
3774             return;
3775         }
3776         rebootUserspaceUntilOnline();
3777 
3778         RecoveryMode cachedRecoveryMode = getRecoveryMode();
3779         setRecoveryMode(RecoveryMode.ONLINE);
3780 
3781         if (isEncryptionSupported()) {
3782             if (isDeviceEncrypted()) {
3783                 CLog.e("Device is encrypted after userspace reboot!");
3784                 unlockDevice();
3785             }
3786         }
3787 
3788         setRecoveryMode(cachedRecoveryMode);
3789 
3790         waitForDeviceAvailable(mOptions.getRebootTimeout());
3791         postBootSetup();
3792         postBootWifiSetup();
3793     }
3794 
3795     @Override
rebootUntilOnline()3796     public void rebootUntilOnline() throws DeviceNotAvailableException {
3797         if (isInRebootCallback()) {
3798             CLog.d(
3799                     "'%s' action is disabled during reboot callback. Ignoring.",
3800                     "Reboot Until Online");
3801             return;
3802         }
3803         try {
3804             internalRebootUntilOnline(null);
3805         } finally {
3806             if (!mDeviceActionReceivers.isEmpty()) {
3807                 CLog.d(
3808                         "DeviceActionReceivers were not notified after rebootUntilOnline on %s.",
3809                         getSerialNumber());
3810             }
3811         }
3812     }
3813 
3814     /** {@inheritDoc} */
3815     @Override
rebootUntilOnline(@ullable String reason)3816     public void rebootUntilOnline(@Nullable String reason) throws DeviceNotAvailableException {
3817         if (isInRebootCallback()) {
3818             CLog.d(
3819                     "'%s' action is disabled during reboot callback. Ignoring.",
3820                     "Reboot Until Online");
3821             return;
3822         }
3823         try {
3824             internalRebootUntilOnline(reason);
3825         } finally {
3826             if (!mDeviceActionReceivers.isEmpty()) {
3827                 CLog.d(
3828                         "DeviceActionReceivers were not notified after rebootUntilOnline on %s.",
3829                         getSerialNumber());
3830             }
3831         }
3832     }
3833 
internalRebootUntilOnline(@ullable String reason)3834     private void internalRebootUntilOnline(@Nullable String reason)
3835             throws DeviceNotAvailableException {
3836         long rebootStart = System.currentTimeMillis();
3837         try (CloseableTraceScope ignored = new CloseableTraceScope("rebootUntilOnline")) {
3838             // Force wait to commit the image before the reboot
3839             waitForSnapuserd(SnapuserdWaitPhase.BLOCK_BEFORE_RELEASING);
3840             // Invalidate cache before reboots
3841             mPropertiesCache.invalidateAll();
3842             doReboot(RebootMode.REBOOT_FULL, reason);
3843             RecoveryMode cachedRecoveryMode = getRecoveryMode();
3844             setRecoveryMode(RecoveryMode.ONLINE);
3845             waitForDeviceOnline();
3846             enableAdbRoot();
3847             setRecoveryMode(cachedRecoveryMode);
3848         } finally {
3849             InvocationMetricLogger.addInvocationMetrics(
3850                     InvocationMetricKey.ADB_REBOOT_TIME, System.currentTimeMillis() - rebootStart);
3851             InvocationMetricLogger.addInvocationMetrics(
3852                     InvocationMetricKey.ADB_REBOOT_ROUTINE_COUNT, 1);
3853         }
3854     }
3855 
3856     @Override
rebootUserspaceUntilOnline()3857     public void rebootUserspaceUntilOnline() throws DeviceNotAvailableException {
3858         if (isInRebootCallback()) {
3859             CLog.d(
3860                     "'%s' action is disabled during reboot callback. Ignoring.",
3861                     "Reboot Userspace Until Online");
3862             return;
3863         }
3864         doReboot(RebootMode.REBOOT_USERSPACE, null);
3865         RecoveryMode cachedRecoveryMode = getRecoveryMode();
3866         setRecoveryMode(RecoveryMode.ONLINE);
3867         waitForDeviceOnline();
3868         enableAdbRoot();
3869         setRecoveryMode(cachedRecoveryMode);
3870     }
3871 
3872     /**
3873      * {@inheritDoc}
3874      */
3875     @Override
rebootIntoRecovery()3876     public void rebootIntoRecovery() throws DeviceNotAvailableException {
3877         if (isInRebootCallback()) {
3878             CLog.d(
3879                     "'%s' action is disabled during reboot callback. Ignoring.",
3880                     "Reboot into Recovery");
3881             return;
3882         }
3883         if (isStateBootloaderOrFastbootd()) {
3884             CLog.w("device %s in fastboot when requesting boot to recovery. " +
3885                     "Rebooting to userspace first.", getSerialNumber());
3886             internalRebootUntilOnline(null);
3887         }
3888         doAdbReboot(RebootMode.REBOOT_INTO_RECOVERY, null);
3889         if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) {
3890             recoverDeviceInRecovery();
3891         }
3892     }
3893 
3894 
3895     /** {@inheritDoc} */
3896     @Override
rebootIntoSideload()3897     public void rebootIntoSideload() throws DeviceNotAvailableException {
3898         rebootIntoSideload(false);
3899     }
3900     /** {@inheritDoc} */
3901     @Override
rebootIntoSideload(boolean autoReboot)3902     public void rebootIntoSideload(boolean autoReboot) throws DeviceNotAvailableException {
3903         if (isInRebootCallback()) {
3904             CLog.d(
3905                     "'%s' action is disabled during reboot callback. Ignoring.",
3906                     "Reboot into Sideload");
3907             return;
3908         }
3909         if (isStateBootloaderOrFastbootd()) {
3910             CLog.w(
3911                     "device %s in fastboot when requesting boot to sideload. "
3912                             + "Rebooting to userspace first.",
3913                     getSerialNumber());
3914             internalRebootUntilOnline(null);
3915         }
3916         final RebootMode rebootMode;
3917         if (autoReboot) {
3918             rebootMode = RebootMode.REBOOT_INTO_SIDELOAD_AUTO_REBOOT;
3919         } else {
3920             rebootMode = RebootMode.REBOOT_INTO_SIDELOAD;
3921         }
3922         doAdbReboot(rebootMode, null);
3923         if (!waitForDeviceInSideload(mOptions.getAdbRecoveryTimeout())) {
3924             // using recovery mode because sideload is a sub-mode under recovery
3925             recoverDeviceInRecovery();
3926         }
3927     }
3928 
3929     /**
3930      * {@inheritDoc}
3931      */
3932     @Override
nonBlockingReboot()3933     public void nonBlockingReboot() throws DeviceNotAvailableException {
3934         if (isInRebootCallback()) {
3935             CLog.d(
3936                     "'%s' action is disabled during reboot callback. Ignoring.",
3937                     "Non Blocking Reboot");
3938             return;
3939         }
3940         try {
3941             doReboot(RebootMode.REBOOT_FULL, null);
3942         } finally {
3943             if (!mDeviceActionReceivers.isEmpty()) {
3944                 CLog.d(
3945                         "DeviceActionReceivers were not notified after nonBlockingReboot on %s.",
3946                         getSerialNumber());
3947             }
3948         }
3949     }
3950 
3951     /**
3952      * A mode of a reboot.
3953      *
3954      * <p>Source of truth for available modes is defined in init.
3955      */
3956     @VisibleForTesting
3957     protected enum RebootMode {
3958         REBOOT_FULL(""),
3959         REBOOT_USERSPACE("userspace"),
3960         REBOOT_INTO_FASTBOOTD("fastboot"),
3961         REBOOT_INTO_BOOTLOADER("bootloader"),
3962         REBOOT_INTO_SIDELOAD("sideload"),
3963         REBOOT_INTO_SIDELOAD_AUTO_REBOOT("sideload-auto-reboot"),
3964         REBOOT_INTO_RECOVERY("recovery");
3965 
3966         private final String mRebootTarget;
3967 
RebootMode(String rebootTarget)3968         RebootMode(String rebootTarget) {
3969             mRebootTarget = rebootTarget;
3970         }
3971 
3972         @Nullable
formatRebootCommand(@ullable String reason)3973         String formatRebootCommand(@Nullable String reason) {
3974             if (this == REBOOT_FULL) {
3975                 return Strings.isNullOrEmpty(reason) ? null : reason;
3976             } else {
3977                 return Strings.isNullOrEmpty(reason) ? mRebootTarget : mRebootTarget + "," + reason;
3978             }
3979         }
3980 
3981         @Override
toString()3982         public String toString() {
3983             return mRebootTarget;
3984         }
3985     }
3986 
3987     /**
3988      * Trigger a reboot of the device, offers no guarantee of the device state after the call.
3989      *
3990      * @param rebootMode a mode of this reboot
3991      * @param reason reason for this reboot
3992      * @throws DeviceNotAvailableException
3993      * @throws UnsupportedOperationException
3994      */
3995     @VisibleForTesting
doReboot(RebootMode rebootMode, @Nullable final String reason)3996     void doReboot(RebootMode rebootMode, @Nullable final String reason)
3997             throws DeviceNotAvailableException, UnsupportedOperationException {
3998         // Track Tradefed reboot time
3999         mLastTradefedRebootTime = System.currentTimeMillis();
4000 
4001         if (isStateBootloaderOrFastbootd()) {
4002             CLog.i("device %s in %s. Rebooting to userspace.", getSerialNumber(), getDeviceState());
4003             executeFastbootCommand("reboot");
4004         } else {
4005             if (mOptions.shouldDisableReboot()) {
4006                 CLog.i("Device reboot disabled by options, skipped.");
4007                 return;
4008             }
4009             if (reason == null) {
4010                 CLog.i("Rebooting device %s mode: %s", getSerialNumber(), rebootMode.name());
4011             } else {
4012                 CLog.i(
4013                         "Rebooting device %s mode: %s reason: %s",
4014                         getSerialNumber(), rebootMode.name(), reason);
4015             }
4016             doAdbReboot(rebootMode, reason);
4017             postAdbReboot();
4018         }
4019     }
4020 
4021     /**
4022      * Possible extra actions that can be taken after a reboot.
4023      *
4024      * @throws DeviceNotAvailableException
4025      */
postAdbReboot()4026     protected void postAdbReboot() throws DeviceNotAvailableException {
4027         // Check if device shows as unavailable (as expected after reboot).
4028         boolean notAvailable = waitForDeviceNotAvailable(DEFAULT_UNAVAILABLE_TIMEOUT);
4029         if (!notAvailable) {
4030             CLog.w("Did not detect device %s becoming unavailable after reboot", getSerialNumber());
4031         }
4032         getConnection().reconnect(getSerialNumber());
4033     }
4034 
4035     /**
4036      * Perform a adb reboot.
4037      *
4038      * @param rebootMode a mode of this reboot.
4039      * @param reason for this reboot.
4040      * @throws DeviceNotAvailableException
4041      */
doAdbReboot(RebootMode rebootMode, @Nullable final String reason)4042     protected void doAdbReboot(RebootMode rebootMode, @Nullable final String reason)
4043             throws DeviceNotAvailableException {
4044         getConnection().notifyAdbRebootCalled();
4045         DeviceAction rebootAction = createRebootDeviceAction(rebootMode, reason);
4046         performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS);
4047     }
4048 
4049     /**
4050      * Create a {@link RebootDeviceAction} to be used when performing a reboot action.
4051      *
4052      * @param rebootMode a mode of this reboot.
4053      * @param reason for this reboot.
4054      * @return the created {@link RebootDeviceAction}.
4055      */
createRebootDeviceAction( RebootMode rebootMode, @Nullable final String reason)4056     protected RebootDeviceAction createRebootDeviceAction(
4057             RebootMode rebootMode, @Nullable final String reason) {
4058         return new RebootDeviceAction(rebootMode, reason);
4059     }
4060 
4061     /**
4062      * Wait to see the device going unavailable (stop reporting to adb).
4063      *
4064      * @param operationDesc The name of the operation that is waiting for unavailable.
4065      * @param time The time to wait for unavailable to occur.
4066      * @return True if device did become unavailable.
4067      */
waitForDeviceNotAvailable(String operationDesc, long time)4068     protected boolean waitForDeviceNotAvailable(String operationDesc, long time) {
4069         // TODO: a bit of a race condition here. Would be better to start a
4070         // before the operation
4071         if (!mStateMonitor.waitForDeviceNotAvailable(time)) {
4072             // above check is flaky, ignore till better solution is found
4073             CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(),
4074                     operationDesc);
4075             return false;
4076         }
4077         return true;
4078     }
4079 
4080     /**
4081      * {@inheritDoc}
4082      */
4083     @Override
waitForDeviceNotAvailable(long waitTime)4084     public boolean waitForDeviceNotAvailable(long waitTime) {
4085         return mStateMonitor.waitForDeviceNotAvailable(waitTime);
4086     }
4087 
4088     /**
4089      * {@inheritDoc}
4090      */
4091     @Override
enableAdbRoot()4092     public boolean enableAdbRoot() throws DeviceNotAvailableException {
4093         // adb root is a relatively intensive command, so do a brief check first to see
4094         // if its necessary or not
4095         if (isAdbRoot()) {
4096             CLog.i("adb is already running as root on %s", getSerialNumber());
4097             // Still check for online, in some case we could see the root, but device could be
4098             // very early in its cycle.
4099             waitForDeviceOnline();
4100             return true;
4101         }
4102         // Don't enable root if user requested no root
4103         if (!isEnableAdbRoot()) {
4104             CLog.i("\"enable-root\" set to false; ignoring 'adb root' request");
4105             return false;
4106         }
4107         InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.ADB_ROOT_ROUTINE_COUNT, 1);
4108         long startTime = System.currentTimeMillis();
4109         try (CloseableTraceScope ignored = new CloseableTraceScope("adb_root")) {
4110             CLog.i("adb root on device %s", getSerialNumber());
4111             int attempts = MAX_RETRY_ATTEMPTS + 1;
4112             for (int i = 1; i <= attempts; i++) {
4113                 String output = executeAdbCommand("root");
4114                 // wait for device to disappear from adb
4115                 boolean res =
4116                         waitForDeviceNotAvailable(
4117                                 "root", getOptions().getAdbRootUnavailableTimeout());
4118                 if (!res && TestDeviceState.ONLINE.equals(getDeviceState())) {
4119                     if (isAdbRoot()) {
4120                         return true;
4121                     }
4122                 }
4123 
4124                 postAdbRootAction();
4125 
4126                 // wait for device to be back online
4127                 waitForDeviceOnline();
4128 
4129                 if (isAdbRoot()) {
4130                     return true;
4131                 }
4132                 CLog.w(
4133                         "'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'",
4134                         getSerialNumber(), i, attempts, output);
4135             }
4136             return false;
4137         } finally {
4138             InvocationMetricLogger.addInvocationMetrics(
4139                     InvocationMetricKey.ADB_ROOT_TIME, System.currentTimeMillis() - startTime);
4140         }
4141     }
4142 
4143     /**
4144      * {@inheritDoc}
4145      */
4146     @Override
disableAdbRoot()4147     public boolean disableAdbRoot() throws DeviceNotAvailableException {
4148         if (!isAdbRoot()) {
4149             CLog.i("adb is already unroot on %s", getSerialNumber());
4150             return true;
4151         }
4152 
4153         CLog.i("adb unroot on device %s", getSerialNumber());
4154         int attempts = MAX_RETRY_ATTEMPTS + 1;
4155         for (int i=1; i <= attempts; i++) {
4156             String output = executeAdbCommand("unroot");
4157             // wait for device to disappear from adb
4158             waitForDeviceNotAvailable("unroot", 5 * 1000);
4159 
4160             postAdbUnrootAction();
4161 
4162             // wait for device to be back online
4163             waitForDeviceOnline();
4164 
4165             if (!isAdbRoot()) {
4166                 return true;
4167             }
4168             CLog.w("'adb unroot' on %s unsuccessful on attempt %d of %d. Output: '%s'",
4169                     getSerialNumber(), i, attempts, output);
4170         }
4171         return false;
4172     }
4173 
4174     /**
4175      * Override if the device needs some specific actions to be taken after adb root and before the
4176      * device is back online.
4177      * Default implementation doesn't include any addition actions.
4178      * adb root is not guaranteed to be enabled at this stage.
4179      * @throws DeviceNotAvailableException
4180      */
postAdbRootAction()4181     public void postAdbRootAction() throws DeviceNotAvailableException {
4182         getConnection().reconnect(getSerialNumber());
4183     }
4184 
4185     /**
4186      * Override if the device needs some specific actions to be taken after adb unroot and before
4187      * the device is back online.
4188      * Default implementation doesn't include any additional actions.
4189      * adb root is not guaranteed to be disabled at this stage.
4190      * @throws DeviceNotAvailableException
4191      */
postAdbUnrootAction()4192     public void postAdbUnrootAction() throws DeviceNotAvailableException {
4193         getConnection().reconnect(getSerialNumber());
4194     }
4195 
4196     /**
4197      * {@inheritDoc}
4198      */
4199     @Override
isAdbRoot()4200     public boolean isAdbRoot() throws DeviceNotAvailableException {
4201         String output = executeShellCommand("id");
4202         return output.contains("uid=0(root)");
4203     }
4204 
4205     /**
4206      * {@inheritDoc}
4207      */
4208     @Override
unlockDevice()4209     public boolean unlockDevice() throws DeviceNotAvailableException,
4210             UnsupportedOperationException {
4211         if (!isEncryptionSupported()) {
4212             throw new UnsupportedOperationException(String.format("Can't unlock device %s: "
4213                     + "encryption not supported", getSerialNumber()));
4214         }
4215 
4216         if (!isDeviceEncrypted()) {
4217             CLog.d("Device %s is not encrypted, skipping", getSerialNumber());
4218             return true;
4219         }
4220         String encryptionType = getProperty("ro.crypto.type");
4221         if (!"block".equals(encryptionType)) {
4222             CLog.d(
4223                     "Skipping unlockDevice since it's not encrypted. ro.crypto.type=%s",
4224                     encryptionType);
4225             return true;
4226         }
4227 
4228         CLog.i("Unlocking device %s", getSerialNumber());
4229 
4230         enableAdbRoot();
4231 
4232         // FIXME: currently, vcd checkpw can return an empty string when it never should.  Try 3
4233         // times.
4234         String output;
4235         int i = 0;
4236         do {
4237             // Enter the password. Output will be:
4238             // "200 [X] -1" if the password has already been entered correctly,
4239             // "200 [X] 0" if the password is entered correctly,
4240             // "200 [X] N" where N is any positive number if the password is incorrect,
4241             // any other string if there is an error.
4242             output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"",
4243                     ENCRYPTION_PASSWORD)).trim();
4244 
4245             if (output.startsWith("200 ") && output.endsWith(" -1")) {
4246                 return true;
4247             }
4248 
4249             if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) {
4250                 CLog.e("checkpw gave output '%s' while trying to unlock device %s",
4251                         output, getSerialNumber());
4252                 return false;
4253             }
4254 
4255             getRunUtil().sleep(500);
4256         } while (output.isEmpty() && ++i < 3);
4257 
4258         if (output.isEmpty()) {
4259             CLog.e("checkpw gave no output while trying to unlock device %s");
4260         }
4261 
4262         // Restart the framework. Output will be:
4263         // "200 [X] 0" if the user data partition can be mounted,
4264         // "200 [X] -1" if the user data partition can not be mounted (no correct password given),
4265         // any other string if there is an error.
4266         output = executeShellCommand("vdc cryptfs restart").trim();
4267 
4268         if (!(output.startsWith("200 ") &&  output.endsWith(" 0"))) {
4269             CLog.e("restart gave output '%s' while trying to unlock device %s", output,
4270                     getSerialNumber());
4271             return false;
4272         }
4273 
4274         waitForDeviceAvailable();
4275 
4276         return true;
4277     }
4278 
4279     /**
4280      * {@inheritDoc}
4281      */
4282     @Override
isDeviceEncrypted()4283     public boolean isDeviceEncrypted() throws DeviceNotAvailableException {
4284         String output = getProperty("ro.crypto.state");
4285 
4286         if (output == null && isEncryptionSupported()) {
4287             CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber());
4288         }
4289         if (output == null) {
4290             return false;
4291         }
4292         return "encrypted".equals(output.trim());
4293     }
4294 
4295     /**
4296      * {@inheritDoc}
4297      */
4298     @Override
isEncryptionSupported()4299     public boolean isEncryptionSupported() throws DeviceNotAvailableException {
4300         if (!isEnableAdbRoot()) {
4301             CLog.i("root is required for encryption");
4302             mIsEncryptionSupported = false;
4303             return mIsEncryptionSupported;
4304         }
4305         if (mIsEncryptionSupported != null) {
4306             return mIsEncryptionSupported.booleanValue();
4307         }
4308         enableAdbRoot();
4309 
4310         String output = getProperty("ro.crypto.state");
4311         if (output == null || "unsupported".equals(output.trim())) {
4312             mIsEncryptionSupported = false;
4313             return mIsEncryptionSupported;
4314         }
4315         mIsEncryptionSupported = true;
4316         return mIsEncryptionSupported;
4317     }
4318 
4319     /**
4320      * {@inheritDoc}
4321      */
4322     @Override
waitForDeviceOnline(long waitTime)4323     public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException {
4324         if (mStateMonitor.waitForDeviceOnline(waitTime) == null) {
4325             recoverDevice();
4326         }
4327     }
4328 
4329     /**
4330      * {@inheritDoc}
4331      */
4332     @Override
waitForDeviceOnline()4333     public void waitForDeviceOnline() throws DeviceNotAvailableException {
4334         if (mStateMonitor.waitForDeviceOnline() == null) {
4335             recoverDevice();
4336         }
4337     }
4338 
4339     /** {@inheritDoc} */
4340     @Override
waitForDeviceAvailable(long waitTime)4341     public boolean waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException {
4342         if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) {
4343             return recoverDevice();
4344         }
4345         return true;
4346     }
4347 
4348     /** {@inheritDoc} */
4349     @Override
waitForDeviceAvailable()4350     public boolean waitForDeviceAvailable() throws DeviceNotAvailableException {
4351         if (mStateMonitor.waitForDeviceAvailable() == null) {
4352             return recoverDevice();
4353         }
4354         return true;
4355     }
4356 
4357     /** {@inheritDoc} */
4358     @Override
waitForDeviceAvailableInRecoverPath(final long waitTime)4359     public boolean waitForDeviceAvailableInRecoverPath(final long waitTime)
4360             throws DeviceNotAvailableException {
4361         return mStateMonitor.waitForDeviceAvailableInRecoverPath(waitTime) != null;
4362     }
4363 
4364     /**
4365      * {@inheritDoc}
4366      */
4367     @Override
waitForDeviceInRecovery(long waitTime)4368     public boolean waitForDeviceInRecovery(long waitTime) {
4369         return mStateMonitor.waitForDeviceInRecovery(waitTime);
4370     }
4371 
4372     /** {@inheritDoc} */
4373     @Override
waitForDeviceBootloader()4374     public void waitForDeviceBootloader() throws DeviceNotAvailableException {
4375         if (mOptions.useUpdatedBootloaderStatus()) {
4376             CommandResult commandResult =
4377                     simpleFastbootCommand(
4378                             mOptions.getFastbootTimeout(),
4379                             buildFastbootCommand("getvar", "product"));
4380             if (!CommandStatus.SUCCESS.equals(commandResult.getStatus())) {
4381                 CLog.e(
4382                         "Waiting for device in bootloader. Status: %s.\nstdout:%s\nstderr:%s",
4383                         commandResult.getStatus(),
4384                         commandResult.getStdout(),
4385                         commandResult.getStderr());
4386                 recoverDeviceFromBootloader();
4387             } else {
4388                 setDeviceState(TestDeviceState.FASTBOOT);
4389             }
4390         } else {
4391             if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
4392                 recoverDeviceFromBootloader();
4393             }
4394         }
4395     }
4396 
4397     /** {@inheritDoc} */
4398     @Override
waitForDeviceInSideload(long waitTime)4399     public boolean waitForDeviceInSideload(long waitTime) {
4400         return mStateMonitor.waitForDeviceInSideload(waitTime);
4401     }
4402 
4403     /**
4404      * Small helper function to throw an NPE if the passed arg is null.  This should be used when
4405      * some value will be stored and used later, in which case it'll avoid hard-to-trace
4406      * asynchronous NullPointerExceptions by throwing the exception synchronously.  This is not
4407      * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care
4408      * of it in that case.
4409      */
throwIfNull(Object obj)4410     private void throwIfNull(Object obj) {
4411         if (obj == null) {
4412             throw new NullPointerException();
4413         }
4414     }
4415 
4416     /** Retrieve this device's recovery mechanism. */
4417     @VisibleForTesting
getRecovery()4418     IDeviceRecovery getRecovery() {
4419         return mRecovery;
4420     }
4421 
4422     /**
4423      * {@inheritDoc}
4424      */
4425     @Override
setRecovery(IDeviceRecovery recovery)4426     public void setRecovery(IDeviceRecovery recovery) {
4427         throwIfNull(recovery);
4428         mRecovery = recovery;
4429     }
4430 
4431     /**
4432      * {@inheritDoc}
4433      */
4434     @Override
setRecoveryMode(RecoveryMode mode)4435     public void setRecoveryMode(RecoveryMode mode) {
4436         throwIfNull(mRecoveryMode);
4437         mRecoveryMode = mode;
4438     }
4439 
4440     /**
4441      * {@inheritDoc}
4442      */
4443     @Override
getRecoveryMode()4444     public RecoveryMode getRecoveryMode() {
4445         return mRecoveryMode;
4446     }
4447 
4448     /**
4449      * {@inheritDoc}
4450      */
4451     @Override
setFastbootEnabled(boolean fastbootEnabled)4452     public void setFastbootEnabled(boolean fastbootEnabled) {
4453         mFastbootEnabled = fastbootEnabled;
4454     }
4455 
4456     /**
4457      * {@inheritDoc}
4458      */
4459     @Override
isFastbootEnabled()4460     public boolean isFastbootEnabled() {
4461         return mFastbootEnabled;
4462     }
4463 
4464     /**
4465      * {@inheritDoc}
4466      */
4467     @Override
setFastbootPath(String fastbootPath)4468     public void setFastbootPath(String fastbootPath) {
4469         mFastbootPath = fastbootPath;
4470         // ensure the device and its associated recovery use the same fastboot version.
4471         mRecovery.setFastbootPath(fastbootPath);
4472     }
4473 
4474     /**
4475      * {@inheritDoc}
4476      */
4477     @Override
getFastbootPath()4478     public String getFastbootPath() {
4479         return mFastbootPath;
4480     }
4481 
4482     /** {@inheritDoc} */
4483     @Override
getFastbootVersion()4484     public String getFastbootVersion() {
4485         try {
4486             CommandResult res = executeFastbootCommand("--version");
4487             return res.getStdout().trim();
4488         } catch (DeviceNotAvailableException e) {
4489             // Ignored for host side request
4490         }
4491         return null;
4492     }
4493 
4494     @Nullable
getLinkLocalIpv6FastbootSerial()4495     private String getLinkLocalIpv6FastbootSerial() {
4496         byte[] macEui48Bytes;
4497 
4498         try {
4499             boolean adbRoot = isAdbRoot();
4500             if (!adbRoot) {
4501                 enableAdbRoot();
4502             }
4503             macEui48Bytes = getEUI48MacAddressInBytes(ETHERNET_MAC_ADDRESS_COMMAND);
4504             if (!adbRoot) {
4505                 disableAdbRoot();
4506             }
4507         } catch (DeviceNotAvailableException e) {
4508             CLog.e("Device %s isn't available when get fastboot serial number", getSerialNumber());
4509             CLog.e(e);
4510             return null;
4511         }
4512 
4513         String net_interface = getHostOptions().getNetworkInterface();
4514         if (net_interface == null || macEui48Bytes == null) {
4515             return null;
4516         }
4517 
4518         // Create a link-local Inet6Address from the MAC address. The EUI-48 MAC address
4519         // is converted to an EUI-64 MAC address per RFC 4291. The resulting EUI-64 is
4520         // used to construct a link-local IPv6 address per RFC 4862.
4521         byte[] addr = new byte[16];
4522         addr[0] = (byte) 0xfe;
4523         addr[1] = (byte) 0x80;
4524         addr[8] = (byte) (macEui48Bytes[0] ^ (byte) 0x02); // flip the link-local bit
4525         addr[9] = macEui48Bytes[1];
4526         addr[10] = macEui48Bytes[2];
4527         addr[11] = (byte) 0xff;
4528         addr[12] = (byte) 0xfe;
4529         addr[13] = macEui48Bytes[3];
4530         addr[14] = macEui48Bytes[4];
4531         addr[15] = macEui48Bytes[5];
4532 
4533         try {
4534             String host_addr = Inet6Address.getByAddress(null, addr, 0).getHostAddress();
4535             return "tcp:" + host_addr.split("%")[0] + "%" + net_interface;
4536         } catch (UnknownHostException e) {
4537             CLog.w("Failed to get %s's IPv6 link-local address", getSerialNumber());
4538             CLog.w(e);
4539         }
4540 
4541         return null;
4542     }
4543 
4544     /** {@inheritDoc} */
4545     @Override
getFastbootSerialNumber()4546     public String getFastbootSerialNumber() {
4547         if (mFastbootSerialNumber != null) {
4548             return mFastbootSerialNumber;
4549         }
4550 
4551         // Only devices which use TCP adb have different fastboot serial number because IPv6
4552         // link-local address will be used in fastboot mode.
4553         if (!isAdbTcp()) {
4554             mFastbootSerialNumber = getSerialNumber();
4555             CLog.i(
4556                     "Device %s's fastboot serial number is %s",
4557                     getSerialNumber(), mFastbootSerialNumber);
4558             return mFastbootSerialNumber;
4559         }
4560 
4561         mFastbootSerialNumber = getLinkLocalIpv6FastbootSerial();
4562         if (mFastbootSerialNumber != null) {
4563             CLog.i(
4564                     "Device %s's fastboot serial number is %s",
4565                     getSerialNumber(), mFastbootSerialNumber);
4566             return mFastbootSerialNumber;
4567         }
4568 
4569         // Fallback to the same serial over TCP. Used for emulator cases (i.e Cuttlefish).
4570         if (getOptions().getInstanceType().equals(InstanceType.CUTTLEFISH)
4571                 || getOptions().getInstanceType().equals(InstanceType.REMOTE_AVD)) {
4572             CLog.d("Maintaining port for cuttlefish.");
4573             mFastbootSerialNumber = "tcp:" + getSerialNumber();
4574         } else {
4575             // 5554 is the default fastboot port
4576             String hostname = getSerialNumber().split(":")[0];
4577             mFastbootSerialNumber = "tcp:" + hostname + ":5554";
4578         }
4579 
4580         CLog.i(
4581                 "Device %s's fastboot serial number is %s",
4582                 getSerialNumber(), mFastbootSerialNumber);
4583         return mFastbootSerialNumber;
4584     }
4585 
4586     /**
4587      * {@inheritDoc}
4588      */
4589     @Override
setDeviceState(final TestDeviceState deviceState)4590     public void setDeviceState(final TestDeviceState deviceState) {
4591         if (!deviceState.equals(getDeviceState())) {
4592             // disable state changes while fastboot lock is held, because issuing fastboot command
4593             // will disrupt state
4594             if (isStateBootloaderOrFastbootd() && mFastbootLock.isLocked()) {
4595                 return;
4596             }
4597             mState = deviceState;
4598             if (!(getIDevice() instanceof StubDevice)) {
4599                 CLog.logAndDisplay(
4600                         LogLevel.DEBUG,
4601                         "Device %s state is now %s",
4602                         getSerialNumber(),
4603                         deviceState);
4604             }
4605             mStateMonitor.setState(deviceState);
4606         }
4607     }
4608 
4609     /**
4610      * {@inheritDoc}
4611      */
4612     @Override
getDeviceState()4613     public TestDeviceState getDeviceState() {
4614         return mState;
4615     }
4616 
4617     @Override
isAdbTcp()4618     public boolean isAdbTcp() {
4619         return mStateMonitor.isAdbTcp();
4620     }
4621 
4622     /**
4623      * {@inheritDoc}
4624      */
4625     @Override
switchToAdbTcp()4626     public String switchToAdbTcp() throws DeviceNotAvailableException {
4627         String ipAddress = getIpAddress();
4628         if (ipAddress == null) {
4629             CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber());
4630             return null;
4631         }
4632         String port = "5555";
4633         executeAdbCommand("tcpip", port);
4634         // TODO: analyze result? wait for device offline?
4635         return String.format("%s:%s", ipAddress, port);
4636     }
4637 
4638     /**
4639      * {@inheritDoc}
4640      */
4641     @Override
switchToAdbUsb()4642     public boolean switchToAdbUsb() throws DeviceNotAvailableException {
4643         executeAdbCommand("usb");
4644         // TODO: analyze result? wait for device offline?
4645         return true;
4646     }
4647 
4648     /**
4649      * {@inheritDoc}
4650      */
4651     @Override
setEmulatorProcess(Process p)4652     public void setEmulatorProcess(Process p) {
4653         mEmulatorProcess = p;
4654 
4655     }
4656 
4657     /**
4658      * For emulator set {@link SizeLimitedOutputStream} to log output
4659      * @param output to log the output
4660      */
setEmulatorOutputStream(SizeLimitedOutputStream output)4661     public void setEmulatorOutputStream(SizeLimitedOutputStream output) {
4662         mEmulatorOutput = output;
4663     }
4664 
4665     /**
4666      * {@inheritDoc}
4667      */
4668     @Override
stopEmulatorOutput()4669     public void stopEmulatorOutput() {
4670         if (mEmulatorOutput != null) {
4671             mEmulatorOutput.delete();
4672             mEmulatorOutput = null;
4673         }
4674     }
4675 
4676     /**
4677      * {@inheritDoc}
4678      */
4679     @Override
getEmulatorOutput()4680     public InputStreamSource getEmulatorOutput() {
4681         if (getIDevice().isEmulator()) {
4682             if (mEmulatorOutput == null) {
4683                 CLog.w("Emulator output for %s was not captured in background",
4684                         getSerialNumber());
4685             } else {
4686                 try {
4687                     return new SnapshotInputStreamSource(
4688                             "getEmulatorOutput", mEmulatorOutput.getData());
4689                 } catch (IOException e) {
4690                     CLog.e("Failed to get %s data.", getSerialNumber());
4691                     CLog.e(e);
4692                 }
4693             }
4694         }
4695         return new ByteArrayInputStreamSource(new byte[0]);
4696     }
4697 
4698     /**
4699      * {@inheritDoc}
4700      */
4701     @Override
getEmulatorProcess()4702     public Process getEmulatorProcess() {
4703         return mEmulatorProcess;
4704     }
4705 
4706     /**
4707      * @return <code>true</code> if adb root should be enabled on device
4708      */
isEnableAdbRoot()4709     public boolean isEnableAdbRoot() {
4710         return mOptions.isEnableAdbRoot();
4711     }
4712 
4713     /**
4714      * {@inheritDoc}
4715      */
4716     @Override
getInstalledPackageNames()4717     public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException {
4718         throw new UnsupportedOperationException("No support for Package's feature");
4719     }
4720 
4721     /** {@inheritDoc} */
4722     @Override
isPackageInstalled(String packageName)4723     public boolean isPackageInstalled(String packageName) throws DeviceNotAvailableException {
4724         throw new UnsupportedOperationException("No support for Package's feature");
4725     }
4726 
4727     /** {@inheritDoc} */
4728     @Override
isPackageInstalled(String packageName, String userId)4729     public boolean isPackageInstalled(String packageName, String userId)
4730             throws DeviceNotAvailableException {
4731         throw new UnsupportedOperationException("No support for Package's feature");
4732     }
4733 
4734     /** {@inheritDoc} */
4735     @Override
getActiveApexes()4736     public Set<ApexInfo> getActiveApexes() throws DeviceNotAvailableException {
4737         throw new UnsupportedOperationException("No support for Package's feature");
4738     }
4739 
4740     /** {@inheritDoc} */
4741     @Override
getAppPackageInfos()4742     public List<PackageInfo> getAppPackageInfos() throws DeviceNotAvailableException {
4743         throw new UnsupportedOperationException("No support for Package's feature");
4744     }
4745 
4746     /** {@inheritDoc} */
4747     @Override
getMainlineModuleInfo()4748     public Set<String> getMainlineModuleInfo() throws DeviceNotAvailableException {
4749         throw new UnsupportedOperationException("No support for Package's feature");
4750     }
4751 
4752     /**
4753      * {@inheritDoc}
4754      */
4755     @Override
getUninstallablePackageNames()4756     public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException {
4757         throw new UnsupportedOperationException("No support for Package's feature");
4758     }
4759 
4760     /**
4761      * {@inheritDoc}
4762      */
4763     @Override
getAppPackageInfo(String packageName)4764     public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException {
4765         throw new UnsupportedOperationException("No support for Package's feature");
4766     }
4767 
4768     /**
4769      * {@inheritDoc}
4770      */
4771     @Override
getOptions()4772     public TestDeviceOptions getOptions() {
4773         return mOptions;
4774     }
4775 
4776     /**
4777      * {@inheritDoc}
4778      */
4779     @Override
getApiLevel()4780     public int getApiLevel() throws DeviceNotAvailableException {
4781         int apiLevel = UNKNOWN_API_LEVEL;
4782         try {
4783             String prop = getProperty(DeviceProperties.SDK_VERSION);
4784             apiLevel = Integer.parseInt(prop);
4785         } catch (NumberFormatException nfe) {
4786             CLog.w(
4787                     "Unable to get API level from "
4788                             + DeviceProperties.SDK_VERSION
4789                             + ", falling back to UNKNOWN.",
4790                     nfe);
4791             // ignore, return unknown instead
4792         }
4793         return apiLevel;
4794     }
4795 
4796     /** {@inheritDoc} */
4797     @Override
checkApiLevelAgainstNextRelease(int strictMinLevel)4798     public boolean checkApiLevelAgainstNextRelease(int strictMinLevel)
4799             throws DeviceNotAvailableException {
4800         int apiLevel = getApiLevel();
4801         if (apiLevel > strictMinLevel) {
4802             return true;
4803         }
4804         String codeName = getPropertyWithRecovery(DeviceProperties.BUILD_CODENAME, true);
4805         if (codeName == null) {
4806             throw new DeviceRuntimeException(
4807                     String.format(
4808                             "Failed to query property '%s'. device returned null.",
4809                             DeviceProperties.BUILD_CODENAME),
4810                     DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
4811         }
4812         codeName = codeName.trim();
4813         // CUR_DEVELOPMENT_VERSION is the code used by Android for a pre-finalized SDK
4814         if (strictMinLevel == CUR_DEVELOPMENT_VERSION && !"REL".equals(codeName)) {
4815             return true;
4816         }
4817         apiLevel = apiLevel + ("REL".equals(codeName) ? 0 : 1);
4818         if (strictMinLevel > apiLevel) {
4819             return false;
4820         }
4821         return true;
4822     }
4823 
getApiLevelSafe()4824     protected int getApiLevelSafe() {
4825         try {
4826             return getApiLevel();
4827         } catch (DeviceNotAvailableException e) {
4828             CLog.e(e);
4829             return UNKNOWN_API_LEVEL;
4830         }
4831     }
4832 
4833     /** {@inheritDoc} */
4834     @Override
getLaunchApiLevel()4835     public int getLaunchApiLevel() throws DeviceNotAvailableException {
4836         try {
4837             String prop = getProperty(DeviceProperties.FIRST_API_LEVEL);
4838             return Integer.parseInt(prop);
4839         } catch (NumberFormatException nfe) {
4840             CLog.w(
4841                     "Unable to get first launch API level from "
4842                             + DeviceProperties.FIRST_API_LEVEL
4843                             + ", falling back to getApiLevel().",
4844                     nfe);
4845         }
4846         return getApiLevel();
4847     }
4848 
4849     @Override
getMonitor()4850     public IDeviceStateMonitor getMonitor() {
4851         return mStateMonitor;
4852     }
4853 
4854     /**
4855      * {@inheritDoc}
4856      */
4857     @Override
waitForDeviceShell(long waitTime)4858     public boolean waitForDeviceShell(long waitTime) {
4859         return mStateMonitor.waitForDeviceShell(waitTime);
4860     }
4861 
4862     @Override
getAllocationState()4863     public DeviceAllocationState getAllocationState() {
4864         return mAllocationState;
4865     }
4866 
4867     /**
4868      * {@inheritDoc}
4869      * <p>
4870      * Process the DeviceEvent, which may or may not transition this device to a new allocation
4871      * state.
4872      * </p>
4873      */
4874     @Override
handleAllocationEvent(DeviceEvent event)4875     public DeviceEventResponse handleAllocationEvent(DeviceEvent event) {
4876 
4877         // keep track of whether state has actually changed or not
4878         boolean stateChanged = false;
4879         DeviceAllocationState newState;
4880         DeviceAllocationState oldState = mAllocationState;
4881         mAllocationStateLock.lock();
4882         try {
4883             // update oldState here, just in case in changed before we got lock
4884             oldState = mAllocationState;
4885             newState = mAllocationState.handleDeviceEvent(event);
4886             if (oldState != newState) {
4887                 // state has changed! record this fact, and store the new state
4888                 stateChanged = true;
4889                 mAllocationState = newState;
4890             }
4891         } finally {
4892             mAllocationStateLock.unlock();
4893         }
4894         if (stateChanged && mAllocationMonitor != null) {
4895             // state has changed! Lets inform the allocation monitor listener
4896             mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState);
4897         }
4898         return new DeviceEventResponse(newState, stateChanged);
4899     }
4900 
4901     /** {@inheritDoc} */
4902     @Override
getDeviceTimeOffset(Date date)4903     public long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException {
4904         long deviceTime = getDeviceDate();
4905 
4906         if (date == null) {
4907             date = new Date();
4908         }
4909 
4910         long offset = date.getTime() - deviceTime;
4911         CLog.d("Time offset = %d ms", offset);
4912         return offset;
4913     }
4914 
4915     /**
4916      * {@inheritDoc}
4917      */
4918     @Override
setDate(Date date)4919     public void setDate(Date date) throws DeviceNotAvailableException {
4920         if (date == null) {
4921             date = new Date();
4922         }
4923         long timeOffset = getDeviceTimeOffset(date);
4924         // no need to set date
4925         if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) {
4926             return;
4927         }
4928         String dateString = null;
4929         if (getApiLevel() < 23) {
4930             // set date in epoch format
4931             dateString = Long.toString(date.getTime() / 1000); //ms to s
4932         } else {
4933             // set date with POSIX like params
4934             SimpleDateFormat sdf = new java.text.SimpleDateFormat(
4935                     "MMddHHmmyyyy.ss");
4936             sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
4937             dateString = sdf.format(date);
4938         }
4939         // best effort, no verification
4940         // Use TZ= to default to UTC timezone (b/128353510 for background)
4941         executeShellCommand("TZ=UTC date -u " + dateString);
4942     }
4943 
4944     /** {@inheritDoc} */
4945     @Override
getDeviceDate()4946     public long getDeviceDate() throws DeviceNotAvailableException {
4947         String deviceTimeString = executeShellCommand("date +%s");
4948         Long deviceTime = null;
4949         try {
4950             deviceTime = Long.valueOf(deviceTimeString.trim());
4951         } catch (NumberFormatException nfe) {
4952             CLog.i("Invalid device time: \"%s\", ignored.", nfe);
4953             return 0;
4954         }
4955         // Convert from seconds to milliseconds
4956         return deviceTime * 1000L;
4957     }
4958 
4959     /**
4960      * {@inheritDoc}
4961      */
4962     @Override
waitForBootComplete(long timeOut)4963     public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException {
4964         return mStateMonitor.waitForBootComplete(timeOut);
4965     }
4966 
4967     /**
4968      * {@inheritDoc}
4969      */
4970     @Override
listUsers()4971     public ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
4972         throw new UnsupportedOperationException("No support for user's feature.");
4973     }
4974 
4975     /** {@inheritDoc} */
4976     @Override
getUserInfos()4977     public Map<Integer, UserInfo> getUserInfos() throws DeviceNotAvailableException {
4978         throw new UnsupportedOperationException("No support for user's feature.");
4979     }
4980 
4981     /**
4982      * {@inheritDoc}
4983      */
4984     @Override
getMaxNumberOfUsersSupported()4985     public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
4986         throw new UnsupportedOperationException("No support for user's feature.");
4987     }
4988 
4989     @Override
getMaxNumberOfRunningUsersSupported()4990     public int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
4991         throw new UnsupportedOperationException("No support for user's feature.");
4992     }
4993 
4994     /**
4995      * {@inheritDoc}
4996      */
4997     @Override
isMultiUserSupported()4998     public boolean isMultiUserSupported() throws DeviceNotAvailableException {
4999         throw new UnsupportedOperationException("No support for user's feature.");
5000     }
5001 
5002     @Override
isHeadlessSystemUserMode()5003     public boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
5004         throw new UnsupportedOperationException("No support for user's feature.");
5005     }
5006 
5007     @Override
canSwitchToHeadlessSystemUser()5008     public boolean canSwitchToHeadlessSystemUser() throws DeviceNotAvailableException {
5009         throw new UnsupportedOperationException("No support for user's feature.");
5010     }
5011 
5012     @Override
isMainUserPermanentAdmin()5013     public boolean isMainUserPermanentAdmin() throws DeviceNotAvailableException {
5014         throw new UnsupportedOperationException("No support for user's feature.");
5015     }
5016 
5017     /** {@inheritDoc} */
5018     @Override
createUserNoThrow(String name)5019     public int createUserNoThrow(String name) throws DeviceNotAvailableException {
5020         throw new UnsupportedOperationException("No support for user's feature.");
5021     }
5022 
5023     /**
5024      * {@inheritDoc}
5025      */
5026     @Override
createUser(String name)5027     public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException {
5028         throw new UnsupportedOperationException("No support for user's feature.");
5029     }
5030 
5031     /**
5032      * {@inheritDoc}
5033      */
5034     @Override
createUser(String name, boolean guest, boolean ephemeral)5035     public int createUser(String name, boolean guest, boolean ephemeral)
5036             throws DeviceNotAvailableException, IllegalStateException {
5037         throw new UnsupportedOperationException("No support for user's feature.");
5038     }
5039 
5040     /** {@inheritDoc} */
5041     @Override
createUser(String name, boolean guest, boolean ephemeral, boolean forTesting)5042     public int createUser(String name, boolean guest, boolean ephemeral, boolean forTesting)
5043             throws DeviceNotAvailableException, IllegalStateException {
5044         throw new UnsupportedOperationException("No support for user's feature.");
5045     }
5046 
5047     /**
5048      * {@inheritDoc}
5049      */
5050     @Override
removeUser(int userId)5051     public boolean removeUser(int userId) throws DeviceNotAvailableException {
5052         throw new UnsupportedOperationException("No support for user's feature.");
5053     }
5054 
5055     /**
5056      * {@inheritDoc}
5057      */
5058     @Override
startUser(int userId)5059     public boolean startUser(int userId) throws DeviceNotAvailableException {
5060         throw new UnsupportedOperationException("No support for user's feature.");
5061     }
5062 
5063     /** {@inheritDoc} */
5064     @Override
startUser(int userId, boolean waitFlag)5065     public boolean startUser(int userId, boolean waitFlag) throws DeviceNotAvailableException {
5066         throw new UnsupportedOperationException("No support for user's feature.");
5067     }
5068 
5069     @Override
startVisibleBackgroundUser(int userId, int displayId, boolean waitFlag)5070     public boolean startVisibleBackgroundUser(int userId, int displayId, boolean waitFlag)
5071             throws DeviceNotAvailableException {
5072         throw new UnsupportedOperationException("No support for user's feature.");
5073     }
5074 
5075     /**
5076      * {@inheritDoc}
5077      */
5078     @Override
stopUser(int userId)5079     public boolean stopUser(int userId) throws DeviceNotAvailableException {
5080         throw new UnsupportedOperationException("No support for user's feature.");
5081     }
5082 
5083     /**
5084      * {@inheritDoc}
5085      */
5086     @Override
stopUser(int userId, boolean waitFlag, boolean forceFlag)5087     public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
5088             throws DeviceNotAvailableException {
5089         throw new UnsupportedOperationException("No support for user's feature.");
5090     }
5091 
5092     @Override
isVisibleBackgroundUsersSupported()5093     public boolean isVisibleBackgroundUsersSupported() throws DeviceNotAvailableException {
5094         throw new UnsupportedOperationException("No support for user's feature.");
5095     }
5096 
5097     @Override
isVisibleBackgroundUsersOnDefaultDisplaySupported()5098     public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported()
5099             throws DeviceNotAvailableException {
5100         throw new UnsupportedOperationException("No support for user's feature.");
5101     }
5102 
5103     /**
5104      * {@inheritDoc}
5105      */
5106     @Override
remountSystemWritable()5107     public void remountSystemWritable() throws DeviceNotAvailableException {
5108         // Need to ensure snapuserd is finished before remounting
5109         waitForSnapuserd(SnapuserdWaitPhase.BLOCK_BEFORE_RELEASING);
5110         String verity = getProperty("partition.system.verified");
5111         // have the property set (regardless state) implies verity is enabled, so we send adb
5112         // command to disable verity
5113         if (verity != null && !verity.isEmpty()) {
5114             executeAdbCommand("disable-verity");
5115             mPropertiesCache.invalidate("partition.system.verified");
5116             reboot();
5117         }
5118         enableAdbRoot();
5119         executeAdbCommand("remount");
5120         waitForDeviceAvailable();
5121     }
5122 
5123     /** {@inheritDoc} */
5124     @Override
remountVendorWritable()5125     public void remountVendorWritable() throws DeviceNotAvailableException {
5126         // Need to ensure snapuserd is finished before remounting
5127         waitForSnapuserd(SnapuserdWaitPhase.BLOCK_BEFORE_RELEASING);
5128         String verity = getProperty("partition.vendor.verified");
5129         // have the property set (regardless state) implies verity is enabled, so we send adb
5130         // command to disable verity
5131         if (verity != null && !verity.isEmpty()) {
5132             executeAdbCommand("disable-verity");
5133             mPropertiesCache.invalidate("partition.vendor.verified");
5134             reboot();
5135         }
5136         enableAdbRoot();
5137         executeAdbCommand("remount");
5138         waitForDeviceAvailable();
5139     }
5140 
5141     /** {@inheritDoc} */
5142     @Override
remountSystemReadOnly()5143     public void remountSystemReadOnly() throws DeviceNotAvailableException {
5144         String verity = getProperty("partition.system.verified");
5145         // have the property set (regardless state) implies verity is enabled, so we send adb
5146         // command to disable verity
5147         if (verity == null || verity.isEmpty()) {
5148             executeAdbCommand("enable-verity");
5149             reboot();
5150         }
5151     }
5152 
5153     /** {@inheritDoc} */
5154     @Override
remountVendorReadOnly()5155     public void remountVendorReadOnly() throws DeviceNotAvailableException {
5156         String verity = getProperty("partition.vendor.verified");
5157         // have the property set (regardless state) implies verity is enabled, so we send adb
5158         // command to disable verity
5159         if (verity == null || verity.isEmpty()) {
5160             executeAdbCommand("enable-verity");
5161             reboot();
5162         }
5163     }
5164 
5165     /** {@inheritDoc} */
5166     @Override
getPrimaryUserId()5167     public Integer getPrimaryUserId() throws DeviceNotAvailableException {
5168         throw new UnsupportedOperationException("No support for user's feature.");
5169     }
5170 
5171     /** {@inheritDoc} */
5172     @Override
getMainUserId()5173     public Integer getMainUserId() throws DeviceNotAvailableException {
5174         throw new UnsupportedOperationException("No support for user's feature.");
5175     }
5176 
5177     /** Used internally to fallback to non-user logic */
getCurrentUserCompatible(String devicePath)5178     private int getCurrentUserCompatible(String devicePath) throws DeviceNotAvailableException {
5179         if (!isSdcardOrEmulated(devicePath)) {
5180             return 0;
5181         }
5182         try {
5183             return getCurrentUser();
5184         } catch (RuntimeException e) {
5185             return 0;
5186         }
5187     }
5188 
5189     /**
5190      * {@inheritDoc}
5191      */
5192     @Override
getCurrentUser()5193     public int getCurrentUser() throws DeviceNotAvailableException {
5194         throw new UnsupportedOperationException("No support for user's feature.");
5195     }
5196 
5197     @Override
isUserVisible(int userId)5198     public boolean isUserVisible(int userId) throws DeviceNotAvailableException {
5199         throw new UnsupportedOperationException("No support for user's feature.");
5200     }
5201 
5202     @Override
isUserVisibleOnDisplay(int userId, int displayId)5203     public boolean isUserVisibleOnDisplay(int userId, int displayId)
5204             throws DeviceNotAvailableException {
5205         throw new UnsupportedOperationException("No support for user's feature.");
5206     }
5207 
5208     /** {@inheritDoc} */
5209     @Override
isUserSecondary(int userId)5210     public boolean isUserSecondary(int userId) throws DeviceNotAvailableException {
5211         throw new UnsupportedOperationException("No support for user's feature.");
5212     }
5213 
5214 
5215     /**
5216      * {@inheritDoc}
5217      */
5218     @Override
getUserFlags(int userId)5219     public int getUserFlags(int userId) throws DeviceNotAvailableException {
5220         throw new UnsupportedOperationException("No support for user's feature.");
5221     }
5222 
5223     /**
5224      * {@inheritDoc}
5225      */
5226     @Override
getUserSerialNumber(int userId)5227     public int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
5228         throw new UnsupportedOperationException("No support for user's feature.");
5229     }
5230 
5231     /**
5232      * {@inheritDoc}
5233      */
5234     @Override
switchUser(int userId)5235     public boolean switchUser(int userId) throws DeviceNotAvailableException {
5236         throw new UnsupportedOperationException("No support for user's feature.");
5237     }
5238 
5239     /**
5240      * {@inheritDoc}
5241      */
5242     @Override
switchUser(int userId, long timeout)5243     public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException {
5244         throw new UnsupportedOperationException("No support for user's feature.");
5245     }
5246 
5247     /**
5248      * {@inheritDoc}
5249      */
5250     @Override
isUserRunning(int userId)5251     public boolean isUserRunning(int userId) throws DeviceNotAvailableException {
5252         throw new UnsupportedOperationException("No support for user's feature.");
5253     }
5254 
5255     /**
5256      * {@inheritDoc}
5257      */
5258     @Override
hasFeature(String feature)5259     public boolean hasFeature(String feature) throws DeviceNotAvailableException {
5260         throw new UnsupportedOperationException("No support pm's features.");
5261     }
5262 
5263     /**
5264      * {@inheritDoc}
5265      */
5266     @Override
getSetting(String namespace, String key)5267     public String getSetting(String namespace, String key)
5268             throws DeviceNotAvailableException {
5269         throw new UnsupportedOperationException("No support for setting's feature.");
5270     }
5271 
5272     /**
5273      * {@inheritDoc}
5274      */
5275     @Override
getSetting(int userId, String namespace, String key)5276     public String getSetting(int userId, String namespace, String key)
5277             throws DeviceNotAvailableException {
5278         throw new UnsupportedOperationException("No support for setting's feature.");
5279     }
5280 
5281     /** {@inheritDoc} */
5282     @Override
getAllSettings(String namespace)5283     public Map<String, String> getAllSettings(String namespace) throws DeviceNotAvailableException {
5284         throw new UnsupportedOperationException("No support for setting's feature.");
5285     }
5286 
5287     /**
5288      * {@inheritDoc}
5289      */
5290     @Override
setSetting(String namespace, String key, String value)5291     public void setSetting(String namespace, String key, String value)
5292             throws DeviceNotAvailableException {
5293         throw new UnsupportedOperationException("No support for setting's feature.");
5294     }
5295 
5296     /**
5297      * {@inheritDoc}
5298      */
5299     @Override
setSetting(int userId, String namespace, String key, String value)5300     public void setSetting(int userId, String namespace, String key, String value)
5301             throws DeviceNotAvailableException {
5302         throw new UnsupportedOperationException("No support for setting's feature.");
5303     }
5304 
5305     /**
5306      * {@inheritDoc}
5307      */
5308     @Override
getBuildSigningKeys()5309     public String getBuildSigningKeys() throws DeviceNotAvailableException {
5310         String buildTags = getProperty(DeviceProperties.BUILD_TAGS);
5311         if (buildTags != null) {
5312             String[] tags = buildTags.split(",");
5313             for (String tag : tags) {
5314                 Matcher m = KEYS_PATTERN.matcher(tag);
5315                 if (m.matches()) {
5316                     return tag;
5317                 }
5318             }
5319         }
5320         return null;
5321     }
5322 
5323     /**
5324      * {@inheritDoc}
5325      */
5326     @Override
getAndroidId(int userId)5327     public String getAndroidId(int userId) throws DeviceNotAvailableException {
5328         throw new UnsupportedOperationException("No support for user's feature.");
5329     }
5330 
5331     /**
5332      * {@inheritDoc}
5333      */
5334     @Override
getAndroidIds()5335     public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
5336         throw new UnsupportedOperationException("No support for user's feature.");
5337     }
5338 
5339     /** {@inheritDoc} */
5340     @Override
setDeviceOwner(String componentName, int userId)5341     public boolean setDeviceOwner(String componentName, int userId)
5342             throws DeviceNotAvailableException {
5343         throw new UnsupportedOperationException("No support for user's feature.");
5344     }
5345 
5346     /** {@inheritDoc} */
5347     @Override
removeAdmin(String componentName, int userId)5348     public boolean removeAdmin(String componentName, int userId)
5349             throws DeviceNotAvailableException {
5350         throw new UnsupportedOperationException("No support for user's feature.");
5351     }
5352 
5353     /** {@inheritDoc} */
5354     @Override
removeOwners()5355     public void removeOwners() throws DeviceNotAvailableException {
5356         throw new UnsupportedOperationException("No support for user's feature.");
5357     }
5358 
5359     /**
5360      * {@inheritDoc}
5361      */
5362     @Override
disableKeyguard()5363     public void disableKeyguard() throws DeviceNotAvailableException {
5364         throw new UnsupportedOperationException("No support for Window Manager's features");
5365     }
5366 
5367     /** {@inheritDoc} */
5368     @Override
getDeviceClass()5369     public String getDeviceClass() {
5370         IDevice device = getIDevice();
5371         if (device == null) {
5372             CLog.w("No IDevice instance, cannot determine device class.");
5373             return "";
5374         }
5375         return device.getClass().getSimpleName();
5376     }
5377 
5378     /** {@inheritDoc} */
5379     @Override
preInvocationSetup(IBuildInfo info, MultiMap<String, String> attributes)5380     public void preInvocationSetup(IBuildInfo info, MultiMap<String, String> attributes)
5381             throws TargetSetupError, DeviceNotAvailableException {
5382         // Default implementation
5383         mContentProvider = null;
5384         mShouldSkipContentProviderSetup = false;
5385         try {
5386             mExecuteShellCommandLogs =
5387                     FileUtil.createTempFile("TestDevice_ExecuteShellCommands", ".txt");
5388         } catch (IOException e) {
5389             throw new TargetSetupError(
5390                     "Failed to create the executeShellCommand log file.",
5391                     e,
5392                     getDeviceDescriptor(),
5393                     InfraErrorIdentifier.FAIL_TO_CREATE_FILE);
5394         }
5395         if (getOptions().shouldUseConnection()) {
5396             initializeConnection(info, attributes);
5397         }
5398     }
5399 
5400     /**
5401      * Initialize the connection to the device. This is called by
5402      * preInvocationSetup but in rare cases might need to be called separately
5403      * when creating the connection during device setup.
5404      */
initializeConnection(IBuildInfo info, MultiMap<String, String> attributes)5405     public void initializeConnection(IBuildInfo info, MultiMap<String, String> attributes)
5406             throws DeviceNotAvailableException, TargetSetupError {
5407         try (CloseableTraceScope ignored = new CloseableTraceScope("initializeConnection")) {
5408             ConnectionBuilder builder =
5409                     new ConnectionBuilder(getRunUtil(), this, info, getLogger());
5410             if (attributes != null) {
5411                 builder.addAttributes(attributes);
5412             }
5413             addExtraConnectionBuilderArgs(builder);
5414             mConnection = DefaultConnection.createConnection(builder);
5415             CLog.d("Using connection: %s (%s)", mConnection, getIDevice());
5416             mConnection.initializeConnection();
5417         }
5418     }
5419 
addExtraConnectionBuilderArgs(ConnectionBuilder builder)5420     protected void addExtraConnectionBuilderArgs(ConnectionBuilder builder) {
5421         if (mConnectionAvd != null) {
5422             builder.setExistingAvdInfo(mConnectionAvd);
5423         }
5424     }
5425 
setConnectionAvdInfo(GceAvdInfo avdInfo)5426     public final void setConnectionAvdInfo(GceAvdInfo avdInfo) {
5427         mConnectionAvd = avdInfo;
5428     }
5429 
5430     /** {@inheritDoc} */
5431     @Override
postInvocationTearDown(Throwable exception)5432     public void postInvocationTearDown(Throwable exception) {
5433         invalidatePropertyCache();
5434         mConfiguration = null;
5435         mIsEncryptionSupported = null;
5436         FileUtil.deleteFile(mExecuteShellCommandLogs);
5437         mExecuteShellCommandLogs = null;
5438         FileUtil.recursiveDelete(mUnpackedFastbootDir);
5439         getConnection().tearDownConnection();
5440         mConnectionAvd = null;
5441         mDeviceActionReceivers.clear();
5442         mFastbootSerialNumber = null;
5443         // Default implementation
5444         if (getIDevice() instanceof StubDevice) {
5445             return;
5446         }
5447         // Reset the Content Provider bit.
5448         mShouldSkipContentProviderSetup = false;
5449         try {
5450             // If we never installed it, don't even bother checking for it during tear down.
5451             if (mContentProvider == null) {
5452                 return;
5453             }
5454             if (exception instanceof DeviceNotAvailableException) {
5455                 CLog.e(
5456                         "Skip Tradefed Content Provider teardown due to"
5457                                 + " DeviceNotAvailableException.");
5458                 return;
5459             }
5460             if (TestDeviceState.ONLINE.equals(getDeviceState())) {
5461                 mContentProvider.tearDown();
5462             }
5463         } catch (DeviceNotAvailableException e) {
5464             CLog.e(e);
5465         }
5466     }
5467 
5468     /**
5469      * {@inheritDoc}
5470      */
5471     @Override
isHeadless()5472     public boolean isHeadless() throws DeviceNotAvailableException {
5473         if (getProperty(DeviceProperties.BUILD_HEADLESS) != null) {
5474             return true;
5475         }
5476         return false;
5477     }
5478 
checkApiLevelAgainst(String feature, int strictMinLevel)5479     protected void checkApiLevelAgainst(String feature, int strictMinLevel) {
5480         try {
5481             if (getApiLevel() < strictMinLevel){
5482                 throw new HarnessRuntimeException(
5483                         String.format(
5484                                 "%s not supported on %s. " + "Must be API %d.",
5485                                 feature, getSerialNumber(), strictMinLevel),
5486                         DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
5487             }
5488         } catch (DeviceNotAvailableException e) {
5489             throw new HarnessRuntimeException(
5490                     "Device became unavailable while checking API level",
5491                     e,
5492                     DeviceErrorIdentifier.DEVICE_UNAVAILABLE);
5493         }
5494     }
5495 
5496     @Override
getCachedDeviceDescriptor()5497     public DeviceDescriptor getCachedDeviceDescriptor() {
5498         return getCachedDeviceDescriptor(false);
5499     }
5500 
5501     /** {@inheritDoc} */
5502     @Override
getCachedDeviceDescriptor(boolean shortDescriptor)5503     public DeviceDescriptor getCachedDeviceDescriptor(boolean shortDescriptor) {
5504         synchronized (mCacheLock) {
5505             if (DeviceAllocationState.Allocated.equals(getAllocationState())) {
5506                 if (mCachedDeviceDescriptor == null) {
5507                     // Create the cache the very first time when it's allocated.
5508                     mCachedDeviceDescriptor = getDeviceDescriptor(false);
5509                     return mCachedDeviceDescriptor;
5510                 }
5511                 return mCachedDeviceDescriptor;
5512             }
5513             // If device is not allocated, just return current information
5514             mCachedDeviceDescriptor = null;
5515             return getDeviceDescriptor(shortDescriptor);
5516         }
5517     }
5518 
5519     @Override
getDeviceDescriptor()5520     public DeviceDescriptor getDeviceDescriptor() {
5521         return getDeviceDescriptor(false);
5522     }
5523 
5524     /** {@inheritDoc} */
5525     @Override
getDeviceDescriptor(boolean shortDescriptor)5526     public DeviceDescriptor getDeviceDescriptor(boolean shortDescriptor) {
5527         IDeviceSelection selector = new DeviceSelectionOptions();
5528         IDevice idevice = getIDevice();
5529         try {
5530             boolean isTemporary = false;
5531             if (idevice instanceof NullDevice) {
5532                 isTemporary = ((NullDevice) idevice).isTemporary();
5533             }
5534             if (shortDescriptor) {
5535                 // Return only info that do not require device inspection
5536                 return new DeviceDescriptor(
5537                         idevice.getSerialNumber(),
5538                         null,
5539                         idevice instanceof StubDevice,
5540                         idevice.getState(),
5541                         getAllocationState(),
5542                         getDeviceState(),
5543                         null,
5544                         null,
5545                         null,
5546                         null,
5547                         null,
5548                         null,
5549                         getDeviceClass(),
5550                         null,
5551                         null,
5552                         null,
5553                         isTemporary,
5554                         null,
5555                         null,
5556                         idevice);
5557             }
5558             // All the operations to create the descriptor need to be safe (should not trigger any
5559             // device side effects like recovery)
5560             String sdkVersion = null;
5561             String buildAlias = null;
5562             String hardwareRev = null;
5563             if (TestDeviceState.ONLINE.equals(getDeviceState())) {
5564                 sdkVersion = getPropertyWithRecovery(DeviceProperties.SDK_VERSION, false);
5565                 buildAlias = getPropertyWithRecovery(DeviceProperties.BUILD_ALIAS, false);
5566                 hardwareRev = getPropertyWithRecovery(DeviceProperties.HARDWARE_REVISION, false);
5567             }
5568             return new DeviceDescriptor(
5569                     idevice.getSerialNumber(),
5570                     (mTrackingSerialNumber != null)
5571                             ? idevice.getSerialNumber() + "[" + mTrackingSerialNumber + "]"
5572                             : null,
5573                     idevice instanceof StubDevice,
5574                     idevice.getState(),
5575                     getAllocationState(),
5576                     getDeviceState(),
5577                     getDisplayString(selector.getDeviceProductType(idevice)),
5578                     getDisplayString(selector.getDeviceProductVariant(idevice)),
5579                     getDisplayString(sdkVersion),
5580                     getDisplayString(buildAlias),
5581                     getDisplayString(hardwareRev),
5582                     getDisplayString(getBattery()),
5583                     getDeviceClass(),
5584                     getDisplayString(getMacAddress()),
5585                     getDisplayString(getSimState()),
5586                     getDisplayString(getSimOperator()),
5587                     isTemporary,
5588                     null,
5589                     null,
5590                     idevice);
5591         } catch (RuntimeException|DeviceNotAvailableException e) {
5592             CLog.e("Exception while building device '%s' description:", getSerialNumber());
5593             CLog.e(e);
5594         }
5595         return null;
5596     }
5597 
5598     /**
5599      * Return the displayable string for given object
5600      */
getDisplayString(Object o)5601     private String getDisplayString(Object o) {
5602         return o == null ? "unknown" : o.toString();
5603     }
5604 
5605     /** {@inheritDoc} */
5606     @Override
getProcessByName(String processName)5607     public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException {
5608         String pidString = getProcessPid(processName);
5609         if (pidString == null) {
5610             return null;
5611         }
5612         long startTime = getProcessStartTimeByPid(pidString);
5613         if (startTime == -1L) {
5614             return null;
5615         }
5616         return new ProcessInfo(
5617                 getProcessUserByPid(pidString),
5618                 Integer.parseInt(pidString),
5619                 processName,
5620                 startTime);
5621     }
5622 
5623     /** Return the process start time since epoch for the given pid string */
getProcessStartTimeByPid(String pidString)5624     private long getProcessStartTimeByPid(String pidString) throws DeviceNotAvailableException {
5625         String output = executeShellCommand(String.format("ps -p %s -o stime=", pidString));
5626         if (output != null && !output.trim().isEmpty()) {
5627             output = output.trim();
5628 
5629             String dateInSeconds;
5630 
5631             // On API 28 and lower, there is a bug in toybox that prevents date from parsing
5632             // timestamps containing a space, e.g. -D"%Y-%m-%d %H:%M:%S" cannot be used to parse
5633             // the stime:19 output from ps. Instead, we'll reconstruct the timestamp.
5634             if (getApiLevel() <= 28) {
5635                 dateInSeconds =
5636                         executeShellCommand(
5637                                 "date -d \"$(date +%Y:%m:%d):"
5638                                         + output
5639                                         + "\" +%s -D \"%Y:%m:%d:%H:%M:%S\"");
5640             } else {
5641                 dateInSeconds = executeShellCommand("date -d\"" + output + "\" +%s");
5642             }
5643             if (Strings.isNullOrEmpty(dateInSeconds)) {
5644                 return -1L;
5645             }
5646             try {
5647                 return Long.parseLong(dateInSeconds.trim());
5648             } catch (NumberFormatException e) {
5649                 CLog.e("Failed to parse the start time for process:");
5650                 CLog.e(e);
5651                 return -1L;
5652             }
5653         }
5654         return -1L;
5655     }
5656 
5657     /** Return the process user for the given pid string */
getProcessUserByPid(String pidString)5658     private String getProcessUserByPid(String pidString) throws DeviceNotAvailableException {
5659         String output = executeShellCommand("stat -c%U /proc/" + pidString);
5660         if (output != null && !output.trim().isEmpty()) {
5661             try {
5662                 return output.trim();
5663             } catch (NumberFormatException e) {
5664                 return null;
5665             }
5666         }
5667         return null;
5668     }
5669 
5670     /** {@inheritDoc} */
5671     @Override
getBootHistory()5672     public Map<Long, String> getBootHistory() throws DeviceNotAvailableException {
5673         String output = getProperty(DeviceProperties.BOOT_REASON_HISTORY);
5674         /* Sample output:
5675         kernel_panic,1556587278
5676         reboot,,1556238008
5677         reboot,,1556237796
5678         reboot,,1556237725
5679         */
5680         Map<Long, String> bootHistory = new LinkedHashMap<Long, String>();
5681         if (Strings.isNullOrEmpty(output)) {
5682             return bootHistory;
5683         }
5684         for (String line : output.split("\\n")) {
5685             String infoStr[] = line.split(",");
5686             String startStr = infoStr[infoStr.length - 1];
5687             try {
5688                 long startTime = Long.parseLong(startStr.trim());
5689                 bootHistory.put(startTime, infoStr[0].trim());
5690             } catch (NumberFormatException e) {
5691                 CLog.e("Fail to parse boot time from line %s", line);
5692             }
5693         }
5694         return bootHistory;
5695     }
5696 
5697     /** {@inheritDoc} */
5698     @Override
getBootHistorySince(long utcEpochTime, TimeUnit timeUnit)5699     public Map<Long, String> getBootHistorySince(long utcEpochTime, TimeUnit timeUnit)
5700             throws DeviceNotAvailableException {
5701         long utcEpochTimeSec = TimeUnit.SECONDS.convert(utcEpochTime, timeUnit);
5702         Map<Long, String> bootHistory = new LinkedHashMap<Long, String>();
5703         for (Map.Entry<Long, String> entry : getBootHistory().entrySet()) {
5704             if (entry.getKey() >= utcEpochTimeSec) {
5705                 bootHistory.put(entry.getKey(), entry.getValue());
5706             }
5707         }
5708         return bootHistory;
5709     }
5710 
hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit)5711     private boolean hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit)
5712             throws DeviceNotAvailableException {
5713         Map<Long, String> bootHistory = getBootHistorySince(utcEpochTime, timeUnit);
5714         if (bootHistory.isEmpty()) {
5715             CLog.w("There is no reboot history since %s", utcEpochTime);
5716             return false;
5717         }
5718 
5719         CLog.i(
5720                 "There are new boot history since %d. NewBootHistory = %s",
5721                 utcEpochTime, bootHistory);
5722         // Check if there is reboot reason other than "reboot".
5723         // Raise RuntimeException if there is abnormal reboot.
5724         for (Map.Entry<Long, String> entry : bootHistory.entrySet()) {
5725             if (!"reboot".equals(entry.getValue())) {
5726                 throw new HarnessRuntimeException(
5727                         String.format(
5728                                 "Device %s has abnormal reboot reason %s at %d",
5729                                 getSerialNumber(), entry.getValue(), entry.getKey()),
5730                         DeviceErrorIdentifier.UNEXPECTED_REBOOT);
5731             }
5732         }
5733         return true;
5734     }
5735 
5736     /**
5737      * Check current system process is restarted after last reboot
5738      *
5739      * @param systemServerProcess the system_server {@link ProcessInfo}
5740      * @return true if system_server process restarted after last reboot; false if not
5741      * @throws DeviceNotAvailableException
5742      */
checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess)5743     private boolean checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess)
5744             throws DeviceNotAvailableException {
5745         // If time gap from last reboot to current system_server process start time is more than
5746         // MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP seconds, we conclude the system_server restarted
5747         // after boot up.
5748         if (!hasNormalRebootSince(
5749                 systemServerProcess.getStartTime() - MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC,
5750                 TimeUnit.SECONDS)) {
5751             CLog.i(
5752                     "Device last reboot is more than %s seconds away from current system_server "
5753                             + "process start time. The system_server process restarted after "
5754                             + "last boot up",
5755                     MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC);
5756             return true;
5757         } else {
5758             // Current system_server start within MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP
5759             // seconds after device last boot up
5760             return false;
5761         }
5762     }
5763 
5764     /** {@inheritDoc} */
5765     @Override
deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit)5766     public boolean deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit)
5767             throws DeviceNotAvailableException {
5768         ProcessInfo currSystemServerProcess = getProcessByName("system_server");
5769         if (currSystemServerProcess == null) {
5770             CLog.i("The system_server process is not available on the device.");
5771             return true;
5772         }
5773 
5774         // The system_server process started at or before utcEpochTime, there is no soft-restart
5775         if (Math.abs(
5776                         currSystemServerProcess.getStartTime()
5777                                 - TimeUnit.SECONDS.convert(utcEpochTime, timeUnit))
5778                 <= 1) {
5779             return false;
5780         }
5781 
5782         // The system_server process restarted after device utcEpochTime in second.
5783         // Check if there is new reboot history, if no new reboot, device soft-restarted.
5784         // If there is no normal reboot, soft-restart is detected.
5785         if (!hasNormalRebootSince(utcEpochTime, timeUnit)) {
5786             return true;
5787         }
5788 
5789         // There is new reboot since utcEpochTime. Check if system_server restarted after boot up.
5790         return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess);
5791     }
5792 
5793     /** {@inheritDoc} */
5794     @Override
deviceSoftRestarted(ProcessInfo prevSystemServerProcess)5795     public boolean deviceSoftRestarted(ProcessInfo prevSystemServerProcess)
5796             throws DeviceNotAvailableException {
5797         if (prevSystemServerProcess == null) {
5798             CLog.i("The given system_server process is null. Abort deviceSoftRestarted check.");
5799             return false;
5800         }
5801         ProcessInfo currSystemServerProcess = getProcessByName("system_server");
5802         if (currSystemServerProcess == null) {
5803             CLog.i("The system_server process is not available on the device.");
5804             return true;
5805         }
5806 
5807         // Compare the start time with a 1 seconds accuracy due to how the date is computed
5808         if (currSystemServerProcess.getPid() == prevSystemServerProcess.getPid()
5809                 && Math.abs(
5810                                 currSystemServerProcess.getStartTime()
5811                                         - prevSystemServerProcess.getStartTime())
5812                         <= 1) {
5813             return false;
5814         }
5815 
5816         CLog.v(
5817                 "current system_server: %s; prev system_server: %s",
5818                 currSystemServerProcess, prevSystemServerProcess);
5819 
5820         // The system_server process restarted.
5821         // Check boot history with previous system_server start time.
5822         // If there is no normal reboot, soft-restart is detected
5823         if (!hasNormalRebootSince(prevSystemServerProcess.getStartTime(), TimeUnit.SECONDS)) {
5824             return true;
5825         }
5826 
5827         // There is reboot since prevSystemServerProcess.getStartTime().
5828         // Check if system_server restarted after boot up.
5829         return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess);
5830 
5831     }
5832 
5833     /**
5834      * Validates that the given input is a valid MAC address
5835      *
5836      * @param address input to validate
5837      * @return true if the input is a valid MAC address
5838      */
isMacAddress(String address)5839     boolean isMacAddress(String address) {
5840         Pattern macPattern = Pattern.compile(MAC_ADDRESS_PATTERN);
5841         Matcher macMatcher = macPattern.matcher(address);
5842         return macMatcher.find();
5843     }
5844 
5845     /**
5846      * Query Mac address from the device
5847      *
5848      * @param command the query command
5849      * @return the MAC address of the device, null if it fails to query from the device
5850      */
getMacAddress(String command)5851     private String getMacAddress(String command) {
5852         if (getIDevice() instanceof StubDevice) {
5853             // Do not query MAC addresses from stub devices.
5854             return null;
5855         }
5856         if (!TestDeviceState.ONLINE.equals(mState)) {
5857             // Only query MAC addresses from online devices.
5858             return null;
5859         }
5860         CollectingOutputReceiver receiver = new CollectingOutputReceiver();
5861         try {
5862             mIDevice.executeShellCommand(command, receiver);
5863         } catch (IOException | TimeoutException | AdbCommandRejectedException |
5864                 ShellCommandUnresponsiveException e) {
5865             CLog.w(
5866                     "Failed to query MAC address for %s by '%s'",
5867                     mIDevice.getSerialNumber(), command);
5868             CLog.w(e);
5869         }
5870         String output = receiver.getOutput().trim();
5871         if (isMacAddress(output)) {
5872             return output;
5873         }
5874         CLog.d(
5875                 "No valid MAC address queried from device %s by '%s'",
5876                 mIDevice.getSerialNumber(), command);
5877         return null;
5878     }
5879 
5880     /** {@inheritDoc} */
5881     @Override
getMacAddress()5882     public String getMacAddress() {
5883         return getMacAddress(MAC_ADDRESS_COMMAND);
5884     }
5885 
5886     /**
5887      * Query EUI-48 MAC address from the device
5888      *
5889      * @param command the query command
5890      * @return the EUI-48 MAC address in long, 0 if it fails to query from the device
5891      * @throws IllegalArgumentException
5892      */
getEUI48MacAddressInLong(String command)5893     long getEUI48MacAddressInLong(String command) {
5894         String addr = getMacAddress(command);
5895         if (addr == null) {
5896             return 0;
5897         }
5898 
5899         String[] parts = addr.split(":");
5900         if (parts.length != ETHER_ADDR_LEN) {
5901             throw new IllegalArgumentException(addr + " was not a valid MAC address");
5902         }
5903         long longAddr = 0;
5904         for (int i = 0; i < parts.length; i++) {
5905             int x = Integer.valueOf(parts[i], 16);
5906             if (x < 0 || 0xff < x) {
5907                 throw new IllegalArgumentException(addr + "was not a valid MAC address");
5908             }
5909             longAddr = x + (longAddr << 8);
5910         }
5911 
5912         return longAddr;
5913     }
5914 
5915     /**
5916      * Query EUI-48 MAC address from the device
5917      *
5918      * @param command the query command
5919      * @return the EUI-48 MAC address in byte[], null if it fails to query from the device
5920      * @throws IllegalArgumentException
5921      */
getEUI48MacAddressInBytes(String command)5922     byte[] getEUI48MacAddressInBytes(String command) {
5923         long addr = getEUI48MacAddressInLong(command);
5924         if (addr == 0) {
5925             return null;
5926         }
5927 
5928         byte[] bytes = new byte[ETHER_ADDR_LEN];
5929         int index = ETHER_ADDR_LEN;
5930         while (index-- > 0) {
5931             bytes[index] = (byte) addr;
5932             addr = addr >> 8;
5933         }
5934         return bytes;
5935     }
5936 
5937     /** {@inheritDoc} */
5938     @Override
getSimState()5939     public String getSimState() {
5940         if (getIDevice() instanceof StubDevice) {
5941             // Do not query SIM state from stub devices.
5942             return null;
5943         }
5944         if (!TestDeviceState.ONLINE.equals(mState)) {
5945             // Only query SIM state from online devices.
5946             return null;
5947         }
5948         try {
5949             return getPropertyWithRecovery(SIM_STATE_PROP, false);
5950         } catch (DeviceNotAvailableException dnae) {
5951             CLog.w("DeviceNotAvailableException while fetching SIM state");
5952             return null;
5953         }
5954     }
5955 
5956     /** {@inheritDoc} */
5957     @Override
getSimOperator()5958     public String getSimOperator() {
5959         if (getIDevice() instanceof StubDevice) {
5960             // Do not query SIM operator from stub devices.
5961             return null;
5962         }
5963         if (!TestDeviceState.ONLINE.equals(mState)) {
5964             // Only query SIM operator from online devices.
5965             return null;
5966         }
5967         try {
5968             return getPropertyWithRecovery(SIM_OPERATOR_PROP, false);
5969         } catch (DeviceNotAvailableException dnae) {
5970             CLog.w("DeviceNotAvailableException while fetching SIM operator");
5971             return null;
5972         }
5973     }
5974 
5975     /** {@inheritDoc} */
5976     @Override
dumpHeap(String process, String devicePath)5977     public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException {
5978         throw new UnsupportedOperationException("dumpHeap is not supported.");
5979     }
5980 
5981     /** {@inheritDoc} */
5982     @Override
getProcessPid(String process)5983     public String getProcessPid(String process) throws DeviceNotAvailableException {
5984         String output = executeShellCommand(String.format("pidof %s", process)).trim();
5985         if (checkValidPid(output)) {
5986             return output;
5987         }
5988         CLog.e("Failed to find a valid pid for process '%s'.", process);
5989         return null;
5990     }
5991 
5992     /** {@inheritDoc} */
5993     @Override
5994     @FormatMethod
logOnDevice(String tag, LogLevel level, String format, Object... args)5995     public void logOnDevice(String tag, LogLevel level, String format, Object... args) {
5996         String message = String.format(format, args);
5997         try {
5998             String levelLetter = logLevelToLogcatLevel(level);
5999             String command = String.format("log -t %s -p %s '%s'", tag, levelLetter, message);
6000             executeShellCommand(command);
6001         } catch (DeviceNotAvailableException e) {
6002             CLog.e("Device went not available when attempting to log '%s'", message);
6003             CLog.e(e);
6004         }
6005     }
6006 
6007     /** Convert the {@link LogLevel} to the letter used in log (see 'adb shell log --help'). */
logLevelToLogcatLevel(LogLevel level)6008     private String logLevelToLogcatLevel(LogLevel level) {
6009         switch (level) {
6010             case DEBUG:
6011                 return "d";
6012             case ERROR:
6013                 return "e";
6014             case INFO:
6015                 return "i";
6016             case VERBOSE:
6017                 return "v";
6018             case WARN:
6019                 return "w";
6020             default:
6021                 return "i";
6022         }
6023     }
6024 
6025     /** {@inheritDoc} */
6026     @Override
getTotalMemory()6027     public long getTotalMemory() {
6028         // "/proc/meminfo" always returns value in kilobytes.
6029         long totalMemory = 0;
6030         String output = null;
6031         try {
6032             output = executeShellCommand("cat /proc/meminfo | grep MemTotal");
6033         } catch (DeviceNotAvailableException e) {
6034             CLog.e(e);
6035             return -1;
6036         }
6037         if (output.isEmpty()) {
6038             return -1;
6039         }
6040         String[] results = output.split("\\s+");
6041         try {
6042             totalMemory = Long.parseLong(results[1].replaceAll("\\D+", ""));
6043         } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
6044             CLog.e(e);
6045             return -1;
6046         }
6047         return totalMemory * 1024;
6048     }
6049 
6050     /** {@inheritDoc} */
6051     @Override
getBattery()6052     public Integer getBattery() {
6053         if (getIDevice() instanceof StubDevice) {
6054             return null;
6055         }
6056         if (isStateBootloaderOrFastbootd()) {
6057             return null;
6058         }
6059         try {
6060             // Use default 5 minutes freshness
6061             Future<Integer> batteryFuture = getIDevice().getBattery();
6062             // Get cached value or wait up to 500ms for battery level query
6063             return batteryFuture.get(500, TimeUnit.MILLISECONDS);
6064         } catch (InterruptedException
6065                 | ExecutionException
6066                 | java.util.concurrent.TimeoutException e) {
6067             CLog.w(
6068                     "Failed to query battery level for %s: %s",
6069                     getIDevice().getSerialNumber(), e.toString());
6070         }
6071         return null;
6072     }
6073 
6074     /** {@inheritDoc} */
6075     @Override
listDisplayIds()6076     public Set<Long> listDisplayIds() throws DeviceNotAvailableException {
6077         throw new UnsupportedOperationException("dumpsys SurfaceFlinger is not supported.");
6078     }
6079 
6080     @Override
listDisplayIdsForStartingVisibleBackgroundUsers()6081     public Set<Integer> listDisplayIdsForStartingVisibleBackgroundUsers()
6082             throws DeviceNotAvailableException {
6083         throw new UnsupportedOperationException("No support for user's feature.");
6084     }
6085 
6086     /** {@inheritDoc} */
6087     @Override
getLastExpectedRebootTimeMillis()6088     public long getLastExpectedRebootTimeMillis() {
6089         return mLastTradefedRebootTime;
6090     }
6091 
6092     /** {@inheritDoc} */
6093     @Override
getTombstones()6094     public List<File> getTombstones() throws DeviceNotAvailableException {
6095         List<File> tombstones = new ArrayList<>();
6096         if (!isAdbRoot()) {
6097             CLog.w("Device was not root, cannot collect tombstones.");
6098             return tombstones;
6099         }
6100         for (String tombName : getChildren(TOMBSTONE_PATH)) {
6101             File tombFile = pullFile(TOMBSTONE_PATH + tombName);
6102             if (tombFile != null) {
6103                 tombstones.add(tombFile);
6104             }
6105         }
6106         return tombstones;
6107     }
6108 
6109     @Override
getFoldableStates()6110     public Set<DeviceFoldableState> getFoldableStates() throws DeviceNotAvailableException {
6111         throw new UnsupportedOperationException("No support for foldable states.");
6112     }
6113 
6114     @Override
getCurrentFoldableState()6115     public DeviceFoldableState getCurrentFoldableState() throws DeviceNotAvailableException {
6116         throw new UnsupportedOperationException("No support for foldable states.");
6117     }
6118 
6119     /** Validate that pid is an integer and not empty. */
checkValidPid(String output)6120     private boolean checkValidPid(String output) {
6121         if (output.isEmpty()) {
6122             return false;
6123         }
6124         try {
6125             Integer.parseInt(output);
6126         } catch (NumberFormatException e) {
6127             CLog.e(e);
6128             return false;
6129         }
6130         return true;
6131     }
6132 
6133     /** Gets the {@link IHostOptions} instance to use. */
6134     @VisibleForTesting
getHostOptions()6135     IHostOptions getHostOptions() {
6136         return GlobalConfiguration.getInstance().getHostOptions();
6137     }
6138 
6139     /**
6140      * Returns the {@link ContentProviderHandler} or null if not available.
6141      *
6142      * <p>Content provider can be reused if it was constructed before with the same {@code userId}.
6143      *
6144      * @param userId the user id to initialize the content provider with.
6145      */
getContentProvider(int userId)6146     public ContentProviderHandler getContentProvider(int userId) throws DeviceNotAvailableException {
6147         // If disabled at the device level, don't attempt any checks.
6148         if (!getOptions().shouldUseContentProvider()) {
6149             return null;
6150         }
6151         // Prevent usage of content provider before API 28 as it would not work well since content
6152         // tool is not working before P.
6153         if (getApiLevel() < 28) {
6154             return null;
6155         }
6156         // Construct a content provider if null, or if the current user has changed since last time.
6157         if (mContentProvider == null || mContentProvider.getUserId() != userId) {
6158             mContentProvider = new ContentProviderHandler(this, userId);
6159         }
6160         // Force the install if we saw an error with content provider installation.
6161         if (mContentProvider.contentProviderNotFound()) {
6162             mShouldSkipContentProviderSetup = false;
6163         }
6164         if (!mShouldSkipContentProviderSetup) {
6165             boolean res = mContentProvider.setUp();
6166             if (!res) {
6167                 // TODO: once CP becomes a requirement, throw/fail the test if CP can't be found
6168                 return null;
6169             }
6170             mShouldSkipContentProviderSetup = true;
6171         }
6172         return mContentProvider;
6173     }
6174 
6175     /** Reset the flag for content provider setup in order to trigger it again. */
resetContentProviderSetup()6176     public void resetContentProviderSetup() {
6177         mShouldSkipContentProviderSetup = false;
6178     }
6179 
6180     /** The log that contains all the {@link #executeShellCommand(String)} logs. */
getExecuteShellCommandLog()6181     public final File getExecuteShellCommandLog() {
6182         return mExecuteShellCommandLogs;
6183     }
6184 
6185     /** Executes a simple fastboot command and report the status of the command. */
6186     @VisibleForTesting
simpleFastbootCommand(final long timeout, String[] fullCmd)6187     protected CommandResult simpleFastbootCommand(final long timeout, String[] fullCmd)
6188             throws UnsupportedOperationException {
6189         return simpleFastbootCommand(timeout, new HashMap<>(), fullCmd);
6190     }
6191 
6192     /**
6193      * Executes a simple fastboot command with environment variables and report the status of the
6194      * command.
6195      */
6196     @VisibleForTesting
simpleFastbootCommand( final long timeout, Map<String, String> envVarMap, String[] fullCmd)6197     protected CommandResult simpleFastbootCommand(
6198             final long timeout, Map<String, String> envVarMap, String[] fullCmd)
6199             throws UnsupportedOperationException {
6200         if (!mFastbootEnabled) {
6201             throw new UnsupportedOperationException(
6202                     String.format(
6203                             "Attempted to fastboot on device %s , but fastboot "
6204                                     + "is disabled. Aborting.",
6205                             getSerialNumber()));
6206         }
6207         IRunUtil runUtil;
6208         if (!envVarMap.isEmpty()) {
6209             runUtil = new RunUtil();
6210         } else {
6211             runUtil = getRunUtil();
6212         }
6213         for (Map.Entry<String, String> entry : envVarMap.entrySet()) {
6214             CLog.v(
6215                     String.format(
6216                             "Set environment variable %s to %s", entry.getKey(), entry.getValue()));
6217             runUtil.setEnvVariable(entry.getKey(), entry.getValue());
6218         }
6219         CommandResult result = new CommandResult(CommandStatus.EXCEPTION);
6220         // block state changes while executing a fastboot command, since
6221         // device will disappear from fastboot devices while command is being executed
6222         mFastbootLock.lock();
6223         try {
6224             if (mOptions.getFastbootOutputTimeout() > 0) {
6225                 result =
6226                         runUtil.runTimedCmdWithOutputMonitor(
6227                                 timeout, mOptions.getFastbootOutputTimeout(), fullCmd);
6228             } else {
6229                 result = runUtil.runTimedCmd(timeout, fullCmd);
6230             }
6231         } finally {
6232             mFastbootLock.unlock();
6233         }
6234         return result;
6235     }
6236 
6237     /** The current connection associated with the device. */
6238     @Override
getConnection()6239     public AbstractConnection getConnection() {
6240         if (mConnection == null) {
6241             mConnection =
6242                     DefaultConnection.createInopConnection(
6243                             new ConnectionBuilder(getRunUtil(), this, null, getLogger()));
6244         }
6245         return mConnection;
6246     }
6247 
6248     /** Check if debugfs is mounted. */
6249     @Override
isDebugfsMounted()6250     public boolean isDebugfsMounted() throws DeviceNotAvailableException {
6251         return CommandStatus.SUCCESS.equals(
6252                 executeShellV2Command(CHECK_DEBUGFS_MNT_COMMAND).getStatus());
6253     }
6254 
6255     /** Mount debugfs. */
6256     @Override
mountDebugfs()6257     public void mountDebugfs() throws DeviceNotAvailableException {
6258         if (isDebugfsMounted()) {
6259             CLog.w("debugfs already mounted.");
6260             return;
6261         }
6262 
6263         CommandResult result = executeShellV2Command(MOUNT_DEBUGFS_COMMAND);
6264         if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
6265             CLog.e("Failed to mount debugfs. %s", result);
6266             throw new DeviceRuntimeException(
6267                     "'" + MOUNT_DEBUGFS_COMMAND + "' has failed: " + result,
6268                     DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
6269         }
6270     }
6271 
6272     /** Unmount debugfs. */
6273     @Override
unmountDebugfs()6274     public void unmountDebugfs() throws DeviceNotAvailableException {
6275         if (!isDebugfsMounted()) {
6276             CLog.w("debugfs not mounted to unmount.");
6277             return;
6278         }
6279 
6280         CommandResult result = executeShellV2Command(UNMOUNT_DEBUGFS_COMMAND);
6281         if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
6282             CLog.e("Failed to unmount debugfs. %s", result);
6283             throw new DeviceRuntimeException(
6284                     "'" + UNMOUNT_DEBUGFS_COMMAND + "' has failed: " + result,
6285                     DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
6286         }
6287     }
6288 
6289     /**
6290      * Enable testing trade-in mode. The device will be wiped and will reboot.
6291      *
6292      * @throws DeviceNotAvailableException
6293      */
6294     @Override
startTradeInModeTesting(final int timeoutMs)6295     public boolean startTradeInModeTesting(final int timeoutMs) throws DeviceNotAvailableException {
6296         if (!enableAdbRoot()) {
6297             CLog.w("Trade in mode requires root.");
6298             return false;
6299         }
6300 
6301         final CommandResult result =
6302                 executeShellV2Command(
6303                         "tradeinmode wait-until-ready testing start",
6304                         timeoutMs,
6305                         TimeUnit.MILLISECONDS);
6306         if (!checkTradeInModeStartResult(result)) {
6307             CLog.w("tradeinmode start didn't succeed");
6308             return false;
6309         }
6310         // Wait a few seconds before issuing more commands.
6311         getRunUtil().sleep(mTradeInModePause);
6312         RecoveryMode mode = getRecoveryMode();
6313         try {
6314             setRecoveryMode(RecoveryMode.NONE);
6315             // TIM does not support normal ADB commands so we must not accidentally go into the
6316             // recovery flow.
6317             IDevice online = mStateMonitor.waitForDeviceOnline(timeoutMs);
6318             if (online != null) {
6319                 return true;
6320             }
6321             CLog.w("Device did not come online after tradeinmode start request");
6322             return false;
6323         } finally {
6324             setRecoveryMode(mode);
6325         }
6326     }
6327 
checkTradeInModeStartResult(CommandResult result)6328     private boolean checkTradeInModeStartResult(CommandResult result) {
6329         if (CommandStatus.SUCCESS.equals(result.getStatus())) {
6330             return true;
6331         }
6332         if (CommandStatus.FAILED.equals(result.getStatus())) {
6333             // If adb manages to disconnect fast enough, we'll get 255 as an exit code.
6334             final int exitCode = result.getExitCode().intValue();
6335             return exitCode == 0 || exitCode == 255;
6336         }
6337         return false;
6338     }
6339 
6340     /** Stop trade-in mode testing. */
6341     @Override
stopTradeInModeTesting()6342     public void stopTradeInModeTesting() throws DeviceNotAvailableException {
6343         // Either: we're still in trade-in mode, or we just factory reset and we're still in
6344         // SUW.
6345         CommandResult evaluateResult =
6346                 executeShellV2Command("tradeinmode wait-until-ready evaluate");
6347         if (!CommandStatus.SUCCESS.equals(evaluateResult.getStatus())) {
6348             CLog.w("tradeinmode evaluate didn't succeed");
6349         }
6350         getRunUtil().sleep(mTradeInModePause);
6351         CommandResult stopResult =
6352                 executeShellV2Command("tradeinmode wait-until-ready testing stop");
6353         if (!CommandStatus.SUCCESS.equals(stopResult.getStatus())) {
6354             CLog.w("tradeinmode stop didn't succeed");
6355         }
6356         getRunUtil().sleep(mTradeInModePause);
6357         waitForDeviceAvailable();
6358     }
6359 
6360     /**
6361      * Notifies all {@link IDeviceActionReceiver} about reboot start event.
6362      *
6363      * @throws DeviceNotAvailableException
6364      */
notifyRebootStarted()6365     protected void notifyRebootStarted() throws DeviceNotAvailableException {
6366         try (CloseableTraceScope ignored = new CloseableTraceScope("rebootStartedCallbacks")) {
6367             for (IDeviceActionReceiver dar : mDeviceActionReceivers) {
6368                 try {
6369                     inRebootCallback = true;
6370                     dar.rebootStarted(this);
6371                 } catch (DeviceNotAvailableException dnae) {
6372                     inRebootCallback = false;
6373                     throw dnae;
6374                 } catch (Exception e) {
6375                     logDeviceActionException("notifyRebootStarted", e, true);
6376                 } finally {
6377                     inRebootCallback = false;
6378                 }
6379             }
6380         }
6381     }
6382 
6383     /**
6384      * Notifies all {@link IDeviceActionReceiver} about reboot end event.
6385      *
6386      * @throws DeviceNotAvailableException
6387      */
notifyRebootEnded()6388     protected void notifyRebootEnded() throws DeviceNotAvailableException {
6389         try (CloseableTraceScope ignored = new CloseableTraceScope("rebootEndedCallbacks")) {
6390             for (IDeviceActionReceiver dar : mDeviceActionReceivers) {
6391                 try {
6392                     inRebootCallback = true;
6393                     dar.rebootEnded(this);
6394                 } catch (DeviceNotAvailableException dnae) {
6395                     inRebootCallback = false;
6396                     throw dnae;
6397                 } catch (Exception e) {
6398                     logDeviceActionException("notifyRebootEnded", e, true);
6399                 } finally {
6400                     inRebootCallback = false;
6401                 }
6402             }
6403         }
6404     }
6405 
6406     /**
6407      * Returns whether reboot callbacks is currently being executed or not. All public api's for
6408      * reboot should be disabled if true.
6409      */
isInRebootCallback()6410     protected boolean isInRebootCallback() {
6411         return inRebootCallback;
6412     }
6413 
6414     @Override
setTestLogger(ITestLogger testLogger)6415     public void setTestLogger(ITestLogger testLogger) {
6416         mTestLogger = testLogger;
6417     }
6418 
getLogger()6419     protected ITestLogger getLogger() {
6420         return mTestLogger;
6421     }
6422 
6423     /**
6424      * Marks the TestDevice as microdroid and sets its CID.
6425      *
6426      * @param process Process of the Microdroid VM.
6427      */
setMicrodroidProcess(Process process)6428     protected void setMicrodroidProcess(Process process) {
6429         mMicrodroidProcess = process;
6430     }
6431 
6432     /**
6433      * @return Returns the Process of the Microdroid VM. If TestDevice is not a Microdroid, returns
6434      *     null.
6435      */
getMicrodroidProcess()6436     public Process getMicrodroidProcess() {
6437         return mMicrodroidProcess;
6438     }
6439 
setTestDeviceOptions(Map<String, String> deviceOptions)6440     protected void setTestDeviceOptions(Map<String, String> deviceOptions) {
6441         try {
6442             OptionSetter setter = new OptionSetter(this.getOptions());
6443             for (Map.Entry<String, String> optionsKeyValue : deviceOptions.entrySet()) {
6444                 setter.setOptionValue(optionsKeyValue.getKey(), optionsKeyValue.getValue());
6445             }
6446         } catch (ConfigurationException e) {
6447             CLog.w(e);
6448         }
6449     }
6450 
invalidatePropertyCache()6451     public void invalidatePropertyCache() {
6452         mPropertiesCache.invalidateAll();
6453     }
6454 
6455     @Override
debugDeviceNotAvailable()6456     public DeviceInspectionResult debugDeviceNotAvailable() {
6457         return null;
6458     }
6459 }
6460