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