blob: 1845f4a2206fc260fa3b7da7035359beb458600e [file] [log] [blame]
Julien Desprez6961b272016-02-01 09:58:23 +00001/*
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 */
Julien Desprez6961b272016-02-01 09:58:23 +000016package com.android.tradefed.device;
17
18import com.android.ddmlib.AdbCommandRejectedException;
19import com.android.ddmlib.FileListingService;
20import com.android.ddmlib.FileListingService.FileEntry;
21import com.android.ddmlib.IDevice;
22import com.android.ddmlib.IShellOutputReceiver;
23import com.android.ddmlib.InstallException;
jdesprez1fbb2472018-03-07 11:15:38 -080024import com.android.ddmlib.Log.LogLevel;
Julien Desprez6961b272016-02-01 09:58:23 +000025import com.android.ddmlib.NullOutputReceiver;
Julien Desprez6961b272016-02-01 09:58:23 +000026import com.android.ddmlib.ShellCommandUnresponsiveException;
27import com.android.ddmlib.SyncException;
28import com.android.ddmlib.SyncException.SyncError;
29import com.android.ddmlib.SyncService;
30import com.android.ddmlib.TimeoutException;
31import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
32import com.android.ddmlib.testrunner.ITestRunListener;
33import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
34import com.android.tradefed.build.IBuildInfo;
Julien Desprez07fe1532016-08-12 11:48:32 +010035import com.android.tradefed.command.remote.DeviceDescriptor;
Guang Zhu26cca482017-11-01 18:18:08 -070036import com.android.tradefed.config.GlobalConfiguration;
37import com.android.tradefed.host.IHostOptions;
Julien Desprez0c836c92016-08-10 14:40:41 +010038import com.android.tradefed.log.ITestLogger;
Julien Desprez6961b272016-02-01 09:58:23 +000039import com.android.tradefed.log.LogUtil.CLog;
40import com.android.tradefed.result.ByteArrayInputStreamSource;
Julien Desprez16184162016-06-10 08:56:17 +010041import com.android.tradefed.result.FileInputStreamSource;
jdesprez4de2f552018-02-02 14:38:13 -080042import com.android.tradefed.result.ITestLifeCycleReceiver;
Julien Desprez6961b272016-02-01 09:58:23 +000043import com.android.tradefed.result.InputStreamSource;
Julien Desprez0c836c92016-08-10 14:40:41 +010044import com.android.tradefed.result.LogDataType;
Julien Desprez6961b272016-02-01 09:58:23 +000045import com.android.tradefed.result.SnapshotInputStreamSource;
46import com.android.tradefed.result.StubTestRunListener;
jdesprez4de2f552018-02-02 14:38:13 -080047import com.android.tradefed.result.ddmlib.TestRunToTestInvocationForwarder;
Julien Desprez26bee8d2016-03-29 12:09:48 +010048import com.android.tradefed.targetprep.TargetSetupError;
Julien Desprez6961b272016-02-01 09:58:23 +000049import com.android.tradefed.util.ArrayUtil;
Julien Desprez0c836c92016-08-10 14:40:41 +010050import com.android.tradefed.util.Bugreport;
Julien Desprez6961b272016-02-01 09:58:23 +000051import com.android.tradefed.util.CommandResult;
52import com.android.tradefed.util.CommandStatus;
53import com.android.tradefed.util.FileUtil;
54import com.android.tradefed.util.IRunUtil;
Julien Desprez14e96692017-01-12 12:31:29 +000055import com.android.tradefed.util.KeyguardControllerState;
Gopinath65e837a2016-10-06 17:55:12 -070056import com.android.tradefed.util.ProcessInfo;
57import com.android.tradefed.util.PsParser;
jdesprez3d7b9142018-03-02 12:11:23 -080058import com.android.tradefed.util.QuotationAwareTokenizer;
Julien Desprez6961b272016-02-01 09:58:23 +000059import com.android.tradefed.util.RunUtil;
60import com.android.tradefed.util.SizeLimitedOutputStream;
Julien Desprez0c836c92016-08-10 14:40:41 +010061import com.android.tradefed.util.StreamUtil;
Julien Desprez96c00252018-06-06 03:25:40 -070062import com.android.tradefed.util.ZipUtil;
Guang Zhu91fdf442017-01-03 17:59:55 -080063import com.android.tradefed.util.ZipUtil2;
Julien Desprez6961b272016-02-01 09:58:23 +000064
Jeffrey Lu279122f2018-01-29 17:25:08 -080065import com.google.common.annotations.VisibleForTesting;
66
Guang Zhu91fdf442017-01-03 17:59:55 -080067import org.apache.commons.compress.archivers.zip.ZipFile;
68
Julien Desprez6961b272016-02-01 09:58:23 +000069import java.io.File;
70import java.io.FilenameFilter;
71import java.io.IOException;
72import java.text.ParseException;
73import java.text.SimpleDateFormat;
Jeffrey Lu279122f2018-01-29 17:25:08 -080074import java.time.Clock;
Julien Desprez6961b272016-02-01 09:58:23 +000075import java.util.ArrayList;
76import java.util.Arrays;
77import java.util.Collection;
78import java.util.Date;
Julien Desprez69324072018-08-14 14:08:47 -070079import java.util.HashSet;
Julien Desprez6961b272016-02-01 09:58:23 +000080import java.util.List;
81import java.util.Map;
82import java.util.Random;
83import java.util.Set;
Julien Desprez7c15ac42016-08-10 16:45:44 +010084import java.util.TimeZone;
Julien Desprez6961b272016-02-01 09:58:23 +000085import java.util.concurrent.ExecutionException;
86import java.util.concurrent.TimeUnit;
87import java.util.concurrent.locks.ReentrantLock;
88import java.util.regex.Matcher;
89import java.util.regex.Pattern;
90
91import javax.annotation.concurrent.GuardedBy;
Julien Desprez6961b272016-02-01 09:58:23 +000092
93/**
94 * Default implementation of a {@link ITestDevice}
95 * Non-full stack android devices.
96 */
Julien Desprez2f34e382016-06-21 12:30:39 +010097public class NativeDevice implements IManagedTestDevice {
Julien Desprez6961b272016-02-01 09:58:23 +000098
Julien Desprez16184162016-06-10 08:56:17 +010099 /**
100 * Allow pauses of up to 2 minutes while receiving bugreport.
101 * <p/>
102 * Note that dumpsys may pause up to a minute while waiting for unresponsive components.
103 * It still should bail after that minute, if it will ever terminate on its own.
104 */
105 private static final int BUGREPORT_TIMEOUT = 2 * 60 * 1000;
Julien Desprezf54735c2016-08-16 09:17:07 +0100106 /**
107 * Allow a little more time for bugreportz because there are extra steps.
108 */
109 private static final int BUGREPORTZ_TIMEOUT = 5 * 60 * 1000;
Julien Desprez16184162016-06-10 08:56:17 +0100110 private static final String BUGREPORT_CMD = "bugreport";
111 private static final String BUGREPORTZ_CMD = "bugreportz";
Nick Kralevich5487ad22016-11-19 12:16:15 -0800112 private static final String BUGREPORTZ_TMP_PATH = "/bugreports/";
Julien Desprez16184162016-06-10 08:56:17 +0100113
Julien Desprez73c55bf2016-09-01 09:27:37 +0100114 /**
115 * Allow up to 2 minutes to receives the full logcat dump.
116 */
117 private static final int LOGCAT_DUMP_TIMEOUT = 2 * 60 * 1000;
118
Julien Desprez6961b272016-02-01 09:58:23 +0000119 /** the default number of command retry attempts to perform */
Julien Desprezc8474552016-02-17 10:59:27 +0000120 protected static final int MAX_RETRY_ATTEMPTS = 2;
121
Julien Desprez4c8aabc2016-03-14 15:53:48 +0000122 /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value **/
123 protected static final int INVALID_USER_ID = -10000;
124
Julien Desprez6961b272016-02-01 09:58:23 +0000125 /** regex to match input dispatch readiness line **/
126 static final Pattern INPUT_DISPATCH_STATE_REGEX =
127 Pattern.compile("DispatchEnabled:\\s?([01])");
128 /** regex to match build signing key type */
129 private static final Pattern KEYS_PATTERN = Pattern.compile("^.*-keys$");
130 private static final Pattern DF_PATTERN = Pattern.compile(
131 //Fs 1K-blks Used Available Use% Mounted on
Julien Desprez3d8c1472016-09-19 11:18:09 +0100132 "^/\\S+\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+\\d+%\\s+/\\S*$", Pattern.MULTILINE);
Julien Desprez16184162016-06-10 08:56:17 +0100133 private static final Pattern BUGREPORTZ_RESPONSE_PATTERN = Pattern.compile("(OK:)(.*)");
Julien Desprez6961b272016-02-01 09:58:23 +0000134
Julien Desprez1fadf1a2016-10-20 15:50:46 +0100135 protected static final long MAX_HOST_DEVICE_TIME_OFFSET = 5 * 1000;
Julien Desprez6961b272016-02-01 09:58:23 +0000136
137 /** The password for encrypting and decrypting the device. */
138 private static final String ENCRYPTION_PASSWORD = "android";
139 /** Encrypting with inplace can take up to 2 hours. */
140 private static final int ENCRYPTION_INPLACE_TIMEOUT_MIN = 2 * 60;
141 /** Encrypting with wipe can take up to 20 minutes. */
142 private static final long ENCRYPTION_WIPE_TIMEOUT_MIN = 20;
Julien Desprez6961b272016-02-01 09:58:23 +0000143
144 /** The time in ms to wait before starting logcat for a device */
145 private int mLogStartDelay = 5*1000;
146
147 /** The time in ms to wait for a device to become unavailable. Should usually be short */
148 private static final int DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000;
149 /** The time in ms to wait for a recovery that we skip because of the NONE mode */
150 static final int NONE_RECOVERY_MODE_DELAY = 1000;
Julien Desprez6961b272016-02-01 09:58:23 +0000151
152 static final String BUILD_ID_PROP = "ro.build.version.incremental";
153 private static final String PRODUCT_NAME_PROP = "ro.product.name";
154 private static final String BUILD_TYPE_PROP = "ro.build.type";
155 private static final String BUILD_ALIAS_PROP = "ro.build.id";
156 private static final String BUILD_FLAVOR = "ro.build.flavor";
Julien Despreze824e8b2016-06-08 17:21:58 +0100157 private static final String HEADLESS_PROP = "ro.build.headless";
Julien Desprez6961b272016-02-01 09:58:23 +0000158 static final String BUILD_CODENAME_PROP = "ro.build.version.codename";
159 static final String BUILD_TAGS = "ro.build.tags";
Gopinath65e837a2016-10-06 17:55:12 -0700160 private static final String PS_COMMAND = "ps -A || ps";
Julien Desprez6961b272016-02-01 09:58:23 +0000161
jdesprezbc580f92017-06-02 11:41:40 -0700162 private static final String SIM_STATE_PROP = "gsm.sim.state";
163 private static final String SIM_OPERATOR_PROP = "gsm.operator.alpha";
164
Kevin Lau Fang805161d2016-12-07 21:06:38 -0800165 static final String MAC_ADDRESS_PATTERN = "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}";
Kevin Lau Fang27ec20c2017-08-22 10:54:27 -0700166 static final String MAC_ADDRESS_COMMAND = "su root cat /sys/class/net/wlan0/address";
Kevin Lau Fang805161d2016-12-07 21:06:38 -0800167
Julien Desprez6961b272016-02-01 09:58:23 +0000168 /** The network monitoring interval in ms. */
169 private static final int NETWORK_MONITOR_INTERVAL = 10 * 1000;
170
171 /** Wifi reconnect check interval in ms. */
172 private static final int WIFI_RECONNECT_CHECK_INTERVAL = 1 * 1000;
173
174 /** Wifi reconnect timeout in ms. */
175 private static final int WIFI_RECONNECT_TIMEOUT = 60 * 1000;
176
Julien Desprez41e09ab2018-07-24 11:07:17 -0700177 /** Pattern to find an executable file. */
178 private static final Pattern EXE_FILE = Pattern.compile("^[-l]r.x.+");
179
Julien Desprez6961b272016-02-01 09:58:23 +0000180 /** The time in ms to wait for a command to complete. */
jdesprez3d7b9142018-03-02 12:11:23 -0800181 private long mCmdTimeout = 2 * 60 * 1000L;
Julien Desprez6961b272016-02-01 09:58:23 +0000182 /** The time in ms to wait for a 'long' command to complete. */
jdesprez3d7b9142018-03-02 12:11:23 -0800183 private long mLongCmdTimeout = 25 * 60 * 1000L;
Julien Desprez6961b272016-02-01 09:58:23 +0000184
Julien Desprez6961b272016-02-01 09:58:23 +0000185 private IDevice mIDevice;
186 private IDeviceRecovery mRecovery = new WaitDeviceRecovery();
Julien Desprezc8474552016-02-17 10:59:27 +0000187 protected final IDeviceStateMonitor mStateMonitor;
Julien Desprez6961b272016-02-01 09:58:23 +0000188 private TestDeviceState mState = TestDeviceState.ONLINE;
189 private final ReentrantLock mFastbootLock = new ReentrantLock();
190 private LogcatReceiver mLogcatReceiver;
191 private boolean mFastbootEnabled = true;
Julien Desprez0a7d67d2016-07-21 16:05:57 +0100192 private String mFastbootPath = "fastboot";
Julien Desprez6961b272016-02-01 09:58:23 +0000193
Julien Desprezc8474552016-02-17 10:59:27 +0000194 protected TestDeviceOptions mOptions = new TestDeviceOptions();
Julien Desprez6961b272016-02-01 09:58:23 +0000195 private Process mEmulatorProcess;
196 private SizeLimitedOutputStream mEmulatorOutput;
Jeffrey Lu279122f2018-01-29 17:25:08 -0800197 private Clock mClock = Clock.systemUTC();
Julien Desprez6961b272016-02-01 09:58:23 +0000198
199 private RecoveryMode mRecoveryMode = RecoveryMode.AVAILABLE;
200
201 private Boolean mIsEncryptionSupported = null;
202 private ReentrantLock mAllocationStateLock = new ReentrantLock();
203 @GuardedBy("mAllocationStateLock")
204 private DeviceAllocationState mAllocationState = DeviceAllocationState.Unknown;
205 private IDeviceMonitor mAllocationMonitor = null;
206
207 private String mLastConnectedWifiSsid = null;
208 private String mLastConnectedWifiPsk = null;
209 private boolean mNetworkMonitorEnabled = false;
210
211 /**
212 * Interface for a generic device communication attempt.
213 */
Julien Desprezc8474552016-02-17 10:59:27 +0000214 abstract interface DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000215
216 /**
217 * Execute the device operation.
218 *
219 * @return <code>true</code> if operation is performed successfully, <code>false</code>
220 * otherwise
Julien Desprezc8474552016-02-17 10:59:27 +0000221 * @throws IOException, TimeoutException, AdbCommandRejectedException,
222 * ShellCommandUnresponsiveException, InstallException,
223 * SyncException if operation terminated abnormally
Julien Desprez6961b272016-02-01 09:58:23 +0000224 */
225 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
226 ShellCommandUnresponsiveException, InstallException, SyncException;
227 }
228
229 /**
230 * A {@link DeviceAction} for running a OS 'adb ....' command.
231 */
Julien Desprezc8474552016-02-17 10:59:27 +0000232 protected class AdbAction implements DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000233 /** the output from the command */
234 String mOutput = null;
235 private String[] mCmd;
236
237 AdbAction(String[] cmd) {
238 mCmd = cmd;
239 }
240
241 @Override
242 public boolean run() throws TimeoutException, IOException {
243 CommandResult result = getRunUtil().runTimedCmd(getCommandTimeout(), mCmd);
244 // TODO: how to determine device not present with command failing for other reasons
245 if (result.getStatus() == CommandStatus.EXCEPTION) {
246 throw new IOException();
247 } else if (result.getStatus() == CommandStatus.TIMED_OUT) {
248 throw new TimeoutException();
249 } else if (result.getStatus() == CommandStatus.FAILED) {
250 // interpret as communication failure
251 throw new IOException();
252 }
253 mOutput = result.getStdout();
254 return true;
255 }
256 }
257
jdesprez3d7b9142018-03-02 12:11:23 -0800258 protected class AdbShellAction implements DeviceAction {
259 /** the output from the command */
260 CommandResult mResult = null;
261
262 private String[] mCmd;
263 private long mTimeout;
264
265 AdbShellAction(String[] cmd, long timeout) {
266 mCmd = cmd;
267 mTimeout = timeout;
268 }
269
270 @Override
271 public boolean run() throws TimeoutException, IOException {
272 mResult = getRunUtil().runTimedCmd(mTimeout, mCmd);
273 if (mResult.getStatus() == CommandStatus.EXCEPTION) {
274 throw new IOException(mResult.getStderr());
275 } else if (mResult.getStatus() == CommandStatus.TIMED_OUT) {
276 throw new TimeoutException(mResult.getStderr());
277 }
278 // If it's not some issue with running the adb command, then we return the CommandResult
279 // which will contain all the infos.
280 return true;
281 }
282 }
283
Julien Desprez78344aa2018-09-04 16:06:05 -0700284 /** {@link DeviceAction} for rebooting a device. */
285 protected class RebootDeviceAction implements DeviceAction {
286
287 private final String mInto;
288
289 RebootDeviceAction(String into) {
290 mInto = into;
291 }
292
293 @Override
294 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException {
295 getIDevice().reboot(mInto);
296 return true;
297 }
298 }
299
Julien Desprez6961b272016-02-01 09:58:23 +0000300 /**
301 * Creates a {@link TestDevice}.
302 *
303 * @param device the associated {@link IDevice}
304 * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use
305 * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes.
306 * Can be null
307 */
Julien Desprez2f34e382016-06-21 12:30:39 +0100308 public NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor,
Julien Desprezc8474552016-02-17 10:59:27 +0000309 IDeviceMonitor allocationMonitor) {
Julien Desprez6961b272016-02-01 09:58:23 +0000310 throwIfNull(device);
311 throwIfNull(stateMonitor);
312 mIDevice = device;
313 mStateMonitor = stateMonitor;
314 mAllocationMonitor = allocationMonitor;
315 }
316
Jeffrey Lu279122f2018-01-29 17:25:08 -0800317 /** Get the {@link RunUtil} instance to use. */
318 @VisibleForTesting
Julien Desprezc8474552016-02-17 10:59:27 +0000319 protected IRunUtil getRunUtil() {
Julien Desprez6961b272016-02-01 09:58:23 +0000320 return RunUtil.getDefault();
321 }
322
Jeffrey Lu279122f2018-01-29 17:25:08 -0800323 /** Set the Clock instance to use. */
324 @VisibleForTesting
325 protected void setClock(Clock clock) {
326 mClock = clock;
327 }
328
Julien Desprez6961b272016-02-01 09:58:23 +0000329 /**
330 * {@inheritDoc}
331 */
332 @Override
333 public void setOptions(TestDeviceOptions options) {
334 throwIfNull(options);
335 mOptions = options;
336 mStateMonitor.setDefaultOnlineTimeout(options.getOnlineTimeout());
337 mStateMonitor.setDefaultAvailableTimeout(options.getAvailableTimeout());
338 }
339
340 /**
341 * Sets the max size of a tmp logcat file.
342 *
343 * @param size max byte size of tmp file
344 */
345 void setTmpLogcatSize(long size) {
346 mOptions.setMaxLogcatDataSize(size);
347 }
348
349 /**
350 * Sets the time in ms to wait before starting logcat capture for a online device.
351 *
352 * @param delay the delay in ms
353 */
Julien Desprezbca52e02017-01-23 09:50:07 +0000354 protected void setLogStartDelay(int delay) {
Julien Desprez6961b272016-02-01 09:58:23 +0000355 mLogStartDelay = delay;
356 }
357
358 /**
359 * {@inheritDoc}
360 */
361 @Override
362 public IDevice getIDevice() {
363 synchronized (mIDevice) {
364 return mIDevice;
365 }
366 }
367
368 /**
369 * {@inheritDoc}
370 */
371 @Override
372 public void setIDevice(IDevice newDevice) {
373 throwIfNull(newDevice);
374 IDevice currentDevice = mIDevice;
375 if (!getIDevice().equals(newDevice)) {
376 synchronized (currentDevice) {
377 mIDevice = newDevice;
378 }
379 mStateMonitor.setIDevice(mIDevice);
380 }
381 }
382
383 /**
384 * {@inheritDoc}
385 */
386 @Override
387 public String getSerialNumber() {
388 return getIDevice().getSerialNumber();
389 }
390
391 private boolean nullOrEmpty(String string) {
392 return string == null || string.isEmpty();
393 }
394
395 /**
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800396 * Fetch a device property, from the ddmlib cache by default, and falling back to either `adb
397 * shell getprop` or `fastboot getvar` depending on whether the device is in Fastboot or not.
Julien Desprez6961b272016-02-01 09:58:23 +0000398 *
399 * @param propName The name of the device property as returned by `adb shell getprop`
400 * @param fastbootVar The name of the equivalent fastboot variable to query. if {@code null},
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800401 * fastboot query will not be attempted
402 * @param description A simple description of the variable. First letter should be capitalized.
Julien Desprez6961b272016-02-01 09:58:23 +0000403 * @return A string, possibly {@code null} or empty, containing the value of the given property
404 */
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800405 protected String internalGetProperty(String propName, String fastbootVar, String description)
Julien Desprez6961b272016-02-01 09:58:23 +0000406 throws DeviceNotAvailableException, UnsupportedOperationException {
407 String propValue = getIDevice().getProperty(propName);
408 if (propValue != null) {
409 return propValue;
410 } else if (TestDeviceState.FASTBOOT.equals(getDeviceState()) &&
411 fastbootVar != null) {
412 CLog.i("%s for device %s is null, re-querying in fastboot", description,
413 getSerialNumber());
414 return getFastbootVariable(fastbootVar);
415 } else {
416 CLog.d("property collection for device %s is null, re-querying for prop %s",
417 getSerialNumber(), description);
418 return getProperty(propName);
419 }
420 }
421
422 /**
423 * {@inheritDoc}
424 */
425 @Override
426 public String getProperty(final String name) throws DeviceNotAvailableException {
jdesprez5fb89732017-08-22 14:50:23 -0700427 if (getIDevice() instanceof StubDevice) {
428 return null;
429 }
Julien Desprez30715d62018-10-12 12:01:53 -0700430 if (!TestDeviceState.ONLINE.equals(getDeviceState())) {
431 // Only query property for online device
jdesprezbc580f92017-06-02 11:41:40 -0700432 CLog.d("Device %s is not online cannot get property %s.", getSerialNumber(), name);
433 return null;
434 }
Julien Desprez6961b272016-02-01 09:58:23 +0000435 final String[] result = new String[1];
436 DeviceAction propAction = new DeviceAction() {
437
438 @Override
439 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
440 ShellCommandUnresponsiveException, InstallException, SyncException {
441 try {
442 result[0] = getIDevice().getSystemProperty(name).get();
443 } catch (InterruptedException | ExecutionException e) {
444 // getProperty will stash the original exception inside
445 // ExecutionException.getCause
446 // throw the specific original exception if available in case TF ever does
447 // specific handling for different exceptions
448 if (e.getCause() instanceof IOException) {
449 throw (IOException)e.getCause();
450 } else if (e.getCause() instanceof TimeoutException) {
451 throw (TimeoutException)e.getCause();
452 } else if (e.getCause() instanceof AdbCommandRejectedException) {
453 throw (AdbCommandRejectedException)e.getCause();
454 } else if (e.getCause() instanceof ShellCommandUnresponsiveException) {
455 throw (ShellCommandUnresponsiveException)e.getCause();
456 }
457 else {
458 throw new IOException(e);
459 }
460 }
461 return true;
462 }
463
464 };
465 performDeviceAction("getprop", propAction, MAX_RETRY_ATTEMPTS);
466 return result[0];
467 }
468
Julien Desprez499c5df2018-07-16 14:05:29 -0700469 /** {@inheritDoc} */
470 @Override
471 public boolean setProperty(String propKey, String propValue)
472 throws DeviceNotAvailableException {
473 if (propKey == null || propValue == null) {
474 throw new IllegalArgumentException("set property key or value cannot be null.");
475 }
476 if (!isAdbRoot()) {
477 CLog.e("setProperty requires adb root = true.");
478 return false;
479 }
480 CommandResult result =
481 executeShellV2Command(String.format("setprop \"%s\" \"%s\"", propKey, propValue));
482 if (CommandStatus.SUCCESS.equals(result.getStatus())) {
483 return true;
484 }
485 CLog.e("Something went wrong went setting property %s: %s", propKey, result.getStderr());
486 return false;
487 }
488
Julien Desprez6961b272016-02-01 09:58:23 +0000489 /**
490 * {@inheritDoc}
491 */
Julien Desprez6961b272016-02-01 09:58:23 +0000492 @Override
493 public String getBootloaderVersion() throws UnsupportedOperationException,
494 DeviceNotAvailableException {
495 return internalGetProperty("ro.bootloader", "version-bootloader", "Bootloader");
496 }
497
498 @Override
499 public String getBasebandVersion() throws DeviceNotAvailableException {
500 return internalGetProperty("gsm.version.baseband", "version-baseband", "Baseband");
501 }
502
503 /**
504 * {@inheritDoc}
505 */
506 @Override
507 public String getProductType() throws DeviceNotAvailableException {
508 return internalGetProductType(MAX_RETRY_ATTEMPTS);
509 }
510
511 /**
Julien Desprezc8474552016-02-17 10:59:27 +0000512 * {@link #getProductType()}
Julien Desprez6961b272016-02-01 09:58:23 +0000513 *
Julien Desprezc8474552016-02-17 10:59:27 +0000514 * @param retryAttempts The number of times to try calling {@link #recoverDevice()} if the
Julien Desprez6961b272016-02-01 09:58:23 +0000515 * device's product type cannot be found.
516 */
517 private String internalGetProductType(int retryAttempts) throws DeviceNotAvailableException {
Guang Zhu3ad4f832017-08-28 15:13:25 -0700518 String productType = internalGetProperty(DeviceProperties.BOARD, "product", "Product type");
Julien Desprez6961b272016-02-01 09:58:23 +0000519
520 // Things will likely break if we don't have a valid product type. Try recovery (in case
521 // the device is only partially booted for some reason), and if that doesn't help, bail.
522 if (nullOrEmpty(productType)) {
523 if (retryAttempts > 0) {
524 recoverDevice();
525 productType = internalGetProductType(retryAttempts - 1);
526 }
527
528 if (nullOrEmpty(productType)) {
529 throw new DeviceNotAvailableException(String.format(
Julien Desprez0c6c77c2016-05-31 16:35:57 +0100530 "Could not determine product type for device %s.", getSerialNumber()),
531 getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +0000532 }
533 }
534
Guang Zhud95f1722017-08-31 17:16:47 -0700535 return productType.toLowerCase();
Julien Desprez6961b272016-02-01 09:58:23 +0000536 }
537
538 /**
539 * {@inheritDoc}
540 */
541 @Override
542 public String getFastbootProductType()
543 throws DeviceNotAvailableException, UnsupportedOperationException {
Guang Zhud95f1722017-08-31 17:16:47 -0700544 String prop = getFastbootVariable("product");
545 if (prop != null) {
546 prop = prop.toLowerCase();
547 }
548 return prop;
Julien Desprez6961b272016-02-01 09:58:23 +0000549 }
550
551 /**
552 * {@inheritDoc}
553 */
554 @Override
555 public String getProductVariant() throws DeviceNotAvailableException {
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800556 String prop = internalGetProperty(DeviceProperties.VARIANT, "variant", "Product variant");
557 if (prop == null) {
558 prop =
559 internalGetProperty(
Bowgo Tsai2ae19b52018-10-18 11:58:53 +0800560 DeviceProperties.VARIANT_LEGACY_O_MR1, "variant", "Product variant");
561 }
562 if (prop == null) {
563 prop =
564 internalGetProperty(
565 DeviceProperties.VARIANT_LEGACY_LESS_EQUAL_O,
566 "variant",
567 "Product variant");
Michael Schwartzb537f1a2018-01-08 20:14:25 -0800568 }
Guang Zhud95f1722017-08-31 17:16:47 -0700569 if (prop != null) {
570 prop = prop.toLowerCase();
571 }
572 return prop;
Julien Desprez6961b272016-02-01 09:58:23 +0000573 }
574
575 /**
576 * {@inheritDoc}
577 */
578 @Override
579 public String getFastbootProductVariant()
580 throws DeviceNotAvailableException, UnsupportedOperationException {
Guang Zhud95f1722017-08-31 17:16:47 -0700581 String prop = getFastbootVariable("variant");
582 if (prop != null) {
583 prop = prop.toLowerCase();
584 }
585 return prop;
Julien Desprez6961b272016-02-01 09:58:23 +0000586 }
587
588 private String getFastbootVariable(String variableName)
589 throws DeviceNotAvailableException, UnsupportedOperationException {
590 CommandResult result = executeFastbootCommand("getvar", variableName);
591 if (result.getStatus() == CommandStatus.SUCCESS) {
592 Pattern fastbootProductPattern = Pattern.compile(variableName + ":\\s(.*)\\s");
593 // fastboot is weird, and may dump the output on stderr instead of stdout
594 String resultText = result.getStdout();
595 if (resultText == null || resultText.length() < 1) {
596 resultText = result.getStderr();
597 }
598 Matcher matcher = fastbootProductPattern.matcher(resultText);
599 if (matcher.find()) {
600 return matcher.group(1);
601 }
602 }
603 return null;
604 }
605
606 /**
607 * {@inheritDoc}
608 */
609 @Override
610 public String getBuildAlias() throws DeviceNotAvailableException {
611 String alias = getProperty(BUILD_ALIAS_PROP);
612 if (alias == null || alias.isEmpty()) {
613 return getBuildId();
614 }
615 return alias;
616 }
617
618 /**
619 * {@inheritDoc}
620 */
621 @Override
622 public String getBuildId() throws DeviceNotAvailableException {
623 String bid = getProperty(BUILD_ID_PROP);
624 if (bid == null) {
625 CLog.w("Could not get device %s build id.", getSerialNumber());
626 return IBuildInfo.UNKNOWN_BUILD_ID;
627 }
628 return bid;
629 }
630
631 /**
632 * {@inheritDoc}
633 */
634 @Override
635 public String getBuildFlavor() throws DeviceNotAvailableException {
636 String buildFlavor = getProperty(BUILD_FLAVOR);
637 if (buildFlavor != null && !buildFlavor.isEmpty()) {
638 return buildFlavor;
639 }
640 String productName = getProperty(PRODUCT_NAME_PROP);
641 String buildType = getProperty(BUILD_TYPE_PROP);
642 if (productName == null || buildType == null) {
643 CLog.w("Could not get device %s build flavor.", getSerialNumber());
644 return null;
645 }
646 return String.format("%s-%s", productName, buildType);
647 }
648
649 /**
650 * {@inheritDoc}
651 */
652 @Override
653 public void executeShellCommand(final String command, final IShellOutputReceiver receiver)
654 throws DeviceNotAvailableException {
655 DeviceAction action = new DeviceAction() {
656 @Override
657 public boolean run() throws TimeoutException, IOException,
658 AdbCommandRejectedException, ShellCommandUnresponsiveException {
659 getIDevice().executeShellCommand(command, receiver,
660 mCmdTimeout, TimeUnit.MILLISECONDS);
661 return true;
662 }
663 };
664 performDeviceAction(String.format("shell %s", command), action, MAX_RETRY_ATTEMPTS);
665 }
666
667 /**
668 * {@inheritDoc}
669 */
Julien Desprez6961b272016-02-01 09:58:23 +0000670 @Override
671 public void executeShellCommand(final String command, final IShellOutputReceiver receiver,
672 final long maxTimeToOutputShellResponse, final TimeUnit timeUnit,
673 final int retryAttempts) throws DeviceNotAvailableException {
674 DeviceAction action = new DeviceAction() {
675 @Override
676 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
677 ShellCommandUnresponsiveException {
678 getIDevice().executeShellCommand(command, receiver,
679 maxTimeToOutputShellResponse, timeUnit);
680 return true;
681 }
682 };
683 performDeviceAction(String.format("shell %s", command), action, retryAttempts);
684 }
685
jdesprez2c37cec2017-08-25 12:03:55 -0700686 /** {@inheritDoc} */
687 @Override
688 public void executeShellCommand(
689 final String command,
690 final IShellOutputReceiver receiver,
691 final long maxTimeoutForCommand,
692 final long maxTimeToOutputShellResponse,
693 final TimeUnit timeUnit,
694 final int retryAttempts)
695 throws DeviceNotAvailableException {
696 DeviceAction action =
697 new DeviceAction() {
698 @Override
699 public boolean run()
700 throws TimeoutException, IOException, AdbCommandRejectedException,
701 ShellCommandUnresponsiveException {
702 getIDevice()
703 .executeShellCommand(
704 command,
705 receiver,
706 maxTimeoutForCommand,
707 maxTimeToOutputShellResponse,
708 timeUnit);
709 return true;
710 }
711 };
712 performDeviceAction(String.format("shell %s", command), action, retryAttempts);
713 }
714
Julien Desprez6961b272016-02-01 09:58:23 +0000715 /**
716 * {@inheritDoc}
717 */
718 @Override
719 public String executeShellCommand(String command) throws DeviceNotAvailableException {
720 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
721 executeShellCommand(command, receiver);
722 String output = receiver.getOutput();
723 CLog.v("%s on %s returned %s", command, getSerialNumber(), output);
724 return output;
725 }
726
jdesprez4de2f552018-02-02 14:38:13 -0800727 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000728 @Override
jdesprez3d7b9142018-03-02 12:11:23 -0800729 public CommandResult executeShellV2Command(String cmd) throws DeviceNotAvailableException {
730 return executeShellV2Command(cmd, getCommandTimeout(), TimeUnit.MILLISECONDS);
731 }
732
733 /** {@inheritDoc} */
734 @Override
735 public CommandResult executeShellV2Command(
736 String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit)
737 throws DeviceNotAvailableException {
Julien Desprez59147a72018-05-17 11:26:03 -0700738 return executeShellV2Command(cmd, maxTimeoutForCommand, timeUnit, MAX_RETRY_ATTEMPTS);
jdesprez3d7b9142018-03-02 12:11:23 -0800739 }
740
741 /** {@inheritDoc} */
742 @Override
743 public CommandResult executeShellV2Command(
744 String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)
745 throws DeviceNotAvailableException {
746 final String[] fullCmd = buildAdbShellCommand(cmd);
747 AdbShellAction adbActionV2 =
748 new AdbShellAction(fullCmd, timeUnit.toMillis(maxTimeoutForCommand));
749 performDeviceAction(String.format("adb %s", fullCmd[4]), adbActionV2, retryAttempts);
750 return adbActionV2.mResult;
751 }
752
753 /** {@inheritDoc} */
754 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800755 public boolean runInstrumentationTests(
756 final IRemoteAndroidTestRunner runner,
757 final Collection<ITestLifeCycleReceiver> listeners)
758 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000759 RunFailureListener failureListener = new RunFailureListener();
jdesprez4de2f552018-02-02 14:38:13 -0800760 List<ITestRunListener> runListeners = new ArrayList<>();
761 runListeners.add(failureListener);
762 runListeners.add(new TestRunToTestInvocationForwarder(listeners));
Julien Desprez6961b272016-02-01 09:58:23 +0000763
jdesprez4de2f552018-02-02 14:38:13 -0800764 DeviceAction runTestsAction =
765 new DeviceAction() {
766 @Override
767 public boolean run()
768 throws IOException, TimeoutException, AdbCommandRejectedException,
769 ShellCommandUnresponsiveException, InstallException,
770 SyncException {
771 runner.run(runListeners);
772 return true;
773 }
774 };
Julien Desprez6961b272016-02-01 09:58:23 +0000775 boolean result = performDeviceAction(String.format("run %s instrumentation tests",
776 runner.getPackageName()), runTestsAction, 0);
777 if (failureListener.isRunFailure()) {
778 // run failed, might be system crash. Ensure device is up
779 if (mStateMonitor.waitForDeviceAvailable(5 * 1000) == null) {
780 // device isn't up, recover
781 recoverDevice();
782 }
783 }
784 return result;
785 }
786
jdesprez4de2f552018-02-02 14:38:13 -0800787 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000788 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800789 public boolean runInstrumentationTestsAsUser(
790 final IRemoteAndroidTestRunner runner,
791 int userId,
792 final Collection<ITestLifeCycleReceiver> listeners)
793 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000794 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
795 boolean result = runInstrumentationTests(runner, listeners);
796 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
797 return result;
798 }
799
800 /**
801 * Helper method to add user run time option to {@link RemoteAndroidTestRunner}
802 *
803 * @param runner {@link IRemoteAndroidTestRunner}
804 * @param userId the integer of the user id to run as.
805 * @return original run time options.
806 */
807 private String appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId) {
808 if (runner instanceof RemoteAndroidTestRunner) {
809 String original = ((RemoteAndroidTestRunner) runner).getRunOptions();
810 String userRunTimeOption = String.format("--user %s", Integer.toString(userId));
Jeff Sharkeydb4518e2018-10-27 16:10:51 -0600811 String updated = (original != null) ? (original + " " + userRunTimeOption)
812 : userRunTimeOption;
813 ((RemoteAndroidTestRunner) runner).setRunOptions(updated);
Julien Desprez6961b272016-02-01 09:58:23 +0000814 return original;
815 } else {
816 throw new IllegalStateException(String.format("%s runner does not support multi-user",
817 runner.getClass().getName()));
818 }
819 }
820
821 /**
822 * Helper method to reset the run time options to {@link RemoteAndroidTestRunner}
823 *
824 * @param runner {@link IRemoteAndroidTestRunner}
825 * @param oldRunTimeOptions
826 */
827 private void resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner,
828 String oldRunTimeOptions) {
829 if (runner instanceof RemoteAndroidTestRunner) {
830 if (oldRunTimeOptions != null) {
831 ((RemoteAndroidTestRunner) runner).setRunOptions(oldRunTimeOptions);
832 }
833 } else {
834 throw new IllegalStateException(String.format("%s runner does not support multi-user",
835 runner.getClass().getName()));
836 }
837 }
838
839 private static class RunFailureListener extends StubTestRunListener {
840 private boolean mIsRunFailure = false;
841
842 @Override
843 public void testRunFailed(String message) {
844 mIsRunFailure = true;
845 }
846
847 public boolean isRunFailure() {
848 return mIsRunFailure;
849 }
850 }
851
jdesprez4de2f552018-02-02 14:38:13 -0800852 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000853 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800854 public boolean runInstrumentationTests(
855 IRemoteAndroidTestRunner runner, ITestLifeCycleReceiver... listeners)
856 throws DeviceNotAvailableException {
857 List<ITestLifeCycleReceiver> listenerList = new ArrayList<>();
Julien Desprez6961b272016-02-01 09:58:23 +0000858 listenerList.addAll(Arrays.asList(listeners));
859 return runInstrumentationTests(runner, listenerList);
860 }
861
jdesprez4de2f552018-02-02 14:38:13 -0800862 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +0000863 @Override
jdesprez4de2f552018-02-02 14:38:13 -0800864 public boolean runInstrumentationTestsAsUser(
865 IRemoteAndroidTestRunner runner, int userId, ITestLifeCycleReceiver... listeners)
866 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000867 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
868 boolean result = runInstrumentationTests(runner, listeners);
869 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
870 return result;
871 }
872
873 /**
874 * {@inheritDoc}
875 */
876 @Override
877 public boolean isRuntimePermissionSupported() throws DeviceNotAvailableException {
Guang Zhu1fb39eb2016-03-27 15:57:28 -0700878 return getApiLevel() > 22;
Julien Desprez6961b272016-02-01 09:58:23 +0000879 }
880
881 /**
882 * helper method to throw exception if runtime permission isn't supported
883 * @throws DeviceNotAvailableException
884 */
Julien Desprezc8474552016-02-17 10:59:27 +0000885 protected void ensureRuntimePermissionSupported() throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000886 boolean runtimePermissionSupported = isRuntimePermissionSupported();
887 if (!runtimePermissionSupported) {
888 throw new UnsupportedOperationException(
889 "platform on device does not support runtime permission granting!");
890 }
891 }
892
893 /**
Julien Desprez6961b272016-02-01 09:58:23 +0000894 * {@inheritDoc}
895 */
896 @Override
897 public String installPackage(final File packageFile, final boolean reinstall,
898 final String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000899 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000900 }
901
902 /**
903 * {@inheritDoc}
904 */
905 @Override
906 public String installPackage(File packageFile, boolean reinstall, boolean grantPermissions,
907 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000908 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000909 }
910
911 /**
912 * {@inheritDoc}
913 */
914 @Override
915 public String installPackageForUser(File packageFile, boolean reinstall, int userId,
916 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000917 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000918 }
919
920 /**
921 * {@inheritDoc}
922 */
923 @Override
924 public String installPackageForUser(File packageFile, boolean reinstall,
925 boolean grantPermissions, int userId, String... extraArgs)
926 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000927 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000928 }
929
930 /**
931 * {@inheritDoc}
932 */
933 @Override
934 public String uninstallPackage(final String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000935 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000936 }
937
938 /**
939 * {@inheritDoc}
940 */
941 @Override
942 public boolean pullFile(final String remoteFilePath, final File localFile)
943 throws DeviceNotAvailableException {
944
945 DeviceAction pullAction = new DeviceAction() {
946 @Override
947 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
948 SyncException {
949 SyncService syncService = null;
950 boolean status = false;
951 try {
952 syncService = getIDevice().getSyncService();
953 syncService.pullFile(interpolatePathVariables(remoteFilePath),
954 localFile.getAbsolutePath(), SyncService.getNullProgressMonitor());
955 status = true;
956 } catch (SyncException e) {
957 CLog.w("Failed to pull %s from %s to %s. Message %s", remoteFilePath,
958 getSerialNumber(), localFile.getAbsolutePath(), e.getMessage());
959 throw e;
960 } finally {
961 if (syncService != null) {
962 syncService.close();
963 }
964 }
965 return status;
966 }
967 };
968 return performDeviceAction(String.format("pull %s to %s", remoteFilePath,
969 localFile.getAbsolutePath()), pullAction, MAX_RETRY_ATTEMPTS);
970 }
971
972 /**
973 * {@inheritDoc}
974 */
975 @Override
976 public File pullFile(String remoteFilePath) throws DeviceNotAvailableException {
977 File localFile = null;
978 boolean success = false;
979 try {
980 localFile = FileUtil.createTempFileForRemote(remoteFilePath, null);
981 if (pullFile(remoteFilePath, localFile)) {
982 success = true;
983 return localFile;
984 }
985 } catch (IOException e) {
Julien Desprez9cd6ca72016-12-19 12:35:15 +0000986 CLog.w("Encountered IOException while trying to pull '%s':", remoteFilePath);
987 CLog.e(e);
Julien Desprez6961b272016-02-01 09:58:23 +0000988 } finally {
989 if (!success) {
990 FileUtil.deleteFile(localFile);
991 }
992 }
993 return null;
994 }
995
996 /**
997 * {@inheritDoc}
998 */
999 @Override
Zach Riggle8c6fce62018-03-03 16:19:28 -06001000 public String pullFileContents(String remoteFilePath) throws DeviceNotAvailableException {
1001 File temp = pullFile(remoteFilePath);
1002
1003 if (temp != null) {
1004 try {
1005 return FileUtil.readStringFromFile(temp);
1006 } catch (IOException e) {
1007 CLog.e(String.format("Could not pull file: %s", remoteFilePath));
1008 } finally {
1009 FileUtil.deleteFile(temp);
1010 }
1011 }
1012
1013 return null;
1014 }
1015
1016 /**
1017 * {@inheritDoc}
1018 */
1019 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001020 public File pullFileFromExternal(String remoteFilePath) throws DeviceNotAvailableException {
1021 String externalPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
1022 String fullPath = (new File(externalPath, remoteFilePath)).getPath();
1023 return pullFile(fullPath);
1024 }
1025
1026 /**
1027 * Helper function that watches for the string "${EXTERNAL_STORAGE}" and replaces it with the
1028 * pathname of the EXTERNAL_STORAGE mountpoint. Specifically intended to be used for pathnames
1029 * that are being passed to SyncService, which does not support variables inside of filenames.
1030 */
1031 String interpolatePathVariables(String path) {
1032 final String esString = "${EXTERNAL_STORAGE}";
1033 if (path.contains(esString)) {
1034 final String esPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
1035 path = path.replace(esString, esPath);
1036 }
1037 return path;
1038 }
1039
1040 /**
1041 * {@inheritDoc}
1042 */
1043 @Override
1044 public boolean pushFile(final File localFile, final String remoteFilePath)
1045 throws DeviceNotAvailableException {
jdesprez3dff70b2017-04-18 10:39:13 -07001046 DeviceAction pushAction =
1047 new DeviceAction() {
1048 @Override
1049 public boolean run()
1050 throws TimeoutException, IOException, AdbCommandRejectedException,
1051 SyncException {
1052 SyncService syncService = null;
1053 boolean status = false;
1054 try {
1055 syncService = getIDevice().getSyncService();
1056 if (syncService == null) {
1057 throw new IOException("SyncService returned null.");
1058 }
1059 syncService.pushFile(
1060 localFile.getAbsolutePath(),
1061 interpolatePathVariables(remoteFilePath),
1062 SyncService.getNullProgressMonitor());
1063 status = true;
1064 } catch (SyncException e) {
1065 CLog.w(
jdesprez791fc5d2017-08-02 11:25:48 -07001066 "Failed to push %s to %s on device %s. Message: '%s'. "
1067 + "Error code: %s",
jdesprez3dff70b2017-04-18 10:39:13 -07001068 localFile.getAbsolutePath(),
1069 remoteFilePath,
1070 getSerialNumber(),
jdesprez791fc5d2017-08-02 11:25:48 -07001071 e.getMessage(),
1072 e.getErrorCode());
1073 // TODO: check if ddmlib can report a better error
1074 if (SyncError.TRANSFER_PROTOCOL_ERROR.equals(e.getErrorCode())) {
1075 if (e.getMessage().contains("Permission denied")) {
1076 return false;
1077 }
1078 }
jdesprez3dff70b2017-04-18 10:39:13 -07001079 throw e;
1080 } finally {
1081 if (syncService != null) {
1082 syncService.close();
1083 }
1084 }
1085 return status;
Julien Desprez6961b272016-02-01 09:58:23 +00001086 }
jdesprez3dff70b2017-04-18 10:39:13 -07001087 };
Julien Desprez6961b272016-02-01 09:58:23 +00001088 return performDeviceAction(String.format("push %s to %s", localFile.getAbsolutePath(),
1089 remoteFilePath), pushAction, MAX_RETRY_ATTEMPTS);
1090 }
1091
1092 /**
1093 * {@inheritDoc}
1094 */
1095 @Override
1096 public boolean pushString(final String contents, final String remoteFilePath)
1097 throws DeviceNotAvailableException {
1098 File tmpFile = null;
1099 try {
1100 tmpFile = FileUtil.createTempFile("temp", ".txt");
1101 FileUtil.writeToFile(contents, tmpFile);
1102 return pushFile(tmpFile, remoteFilePath);
1103 } catch (IOException e) {
1104 CLog.e(e);
1105 return false;
1106 } finally {
Julien Desprez1320e592016-12-06 09:51:53 +00001107 FileUtil.deleteFile(tmpFile);
Julien Desprez6961b272016-02-01 09:58:23 +00001108 }
1109 }
1110
1111 /**
1112 * {@inheritDoc}
1113 */
1114 @Override
1115 public boolean doesFileExist(String destPath) throws DeviceNotAvailableException {
1116 String lsGrep = executeShellCommand(String.format("ls \"%s\"", destPath));
1117 return !lsGrep.contains("No such file or directory");
1118 }
1119
1120 /**
1121 * {@inheritDoc}
1122 */
1123 @Override
1124 public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00001125 String externalStorePath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
Arthur Eubankse4531612017-10-23 11:30:52 -07001126 return getPartitionFreeSpace(externalStorePath);
1127 }
1128
1129 /** {@inheritDoc} */
1130 @Override
1131 public long getPartitionFreeSpace(String partition) throws DeviceNotAvailableException {
1132 CLog.i("Checking free space for %s on partition %s", getSerialNumber(), partition);
1133 String output = getDfOutput(partition);
Julien Desprez6961b272016-02-01 09:58:23 +00001134 // Try coreutils/toybox style output first.
1135 Long available = parseFreeSpaceFromModernOutput(output);
1136 if (available != null) {
1137 return available;
1138 }
1139 // Then the two legacy toolbox formats.
1140 available = parseFreeSpaceFromAvailable(output);
1141 if (available != null) {
1142 return available;
1143 }
Arthur Eubankse4531612017-10-23 11:30:52 -07001144 available = parseFreeSpaceFromFree(partition, output);
Julien Desprez6961b272016-02-01 09:58:23 +00001145 if (available != null) {
1146 return available;
1147 }
1148
1149 CLog.e("free space command output \"%s\" did not match expected patterns", output);
1150 return 0;
1151 }
1152
1153 /**
1154 * Run the 'df' shell command and return output, making multiple attempts if necessary.
1155 *
1156 * @param externalStorePath the path to check
1157 * @return the output from 'shell df path'
1158 * @throws DeviceNotAvailableException
1159 */
1160 private String getDfOutput(String externalStorePath) throws DeviceNotAvailableException {
1161 for (int i=0; i < MAX_RETRY_ATTEMPTS; i++) {
1162 String output = executeShellCommand(String.format("df %s", externalStorePath));
1163 if (output.trim().length() > 0) {
1164 return output;
1165 }
1166 }
1167 throw new DeviceUnresponsiveException(String.format(
1168 "Device %s not returning output from df command after %d attempts",
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001169 getSerialNumber(), MAX_RETRY_ATTEMPTS), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001170 }
1171
1172 /**
1173 * Parses a partition's available space from the legacy output of a 'df' command, used
1174 * pre-gingerbread.
1175 * <p/>
1176 * Assumes output format of:
1177 * <br>/
1178 * <code>
1179 * [partition]: 15659168K total, 51584K used, 15607584K available (block size 32768)
1180 * </code>
1181 * @param dfOutput the output of df command to parse
1182 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1183 */
1184 private Long parseFreeSpaceFromAvailable(String dfOutput) {
1185 final Pattern freeSpacePattern = Pattern.compile("(\\d+)K available");
1186 Matcher patternMatcher = freeSpacePattern.matcher(dfOutput);
1187 if (patternMatcher.find()) {
1188 String freeSpaceString = patternMatcher.group(1);
1189 try {
1190 return Long.parseLong(freeSpaceString);
1191 } catch (NumberFormatException e) {
1192 // fall through
1193 }
1194 }
1195 return null;
1196 }
1197
1198 /**
1199 * Parses a partition's available space from the 'table-formatted' output of a toolbox 'df'
1200 * command, used from gingerbread to lollipop.
1201 * <p/>
1202 * Assumes output format of:
1203 * <br/>
1204 * <code>
1205 * Filesystem Size Used Free Blksize
1206 * <br/>
1207 * [partition]: 3G 790M 2G 4096
1208 * </code>
1209 * @param dfOutput the output of df command to parse
1210 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1211 */
1212 Long parseFreeSpaceFromFree(String externalStorePath, String dfOutput) {
1213 Long freeSpace = null;
1214 final Pattern freeSpaceTablePattern = Pattern.compile(String.format(
1215 //fs Size Used Free
1216 "%s\\s+[\\w\\d\\.]+\\s+[\\w\\d\\.]+\\s+([\\d\\.]+)(\\w)", externalStorePath));
1217 Matcher tablePatternMatcher = freeSpaceTablePattern.matcher(dfOutput);
1218 if (tablePatternMatcher.find()) {
1219 String numericValueString = tablePatternMatcher.group(1);
1220 String unitType = tablePatternMatcher.group(2);
1221 try {
1222 Float freeSpaceFloat = Float.parseFloat(numericValueString);
1223 if (unitType.equals("M")) {
1224 freeSpaceFloat = freeSpaceFloat * 1024;
1225 } else if (unitType.equals("G")) {
1226 freeSpaceFloat = freeSpaceFloat * 1024 * 1024;
1227 }
1228 freeSpace = freeSpaceFloat.longValue();
1229 } catch (NumberFormatException e) {
1230 // fall through
1231 }
1232 }
1233 return freeSpace;
1234 }
1235
1236 /**
1237 * Parses a partition's available space from the modern coreutils/toybox 'df' output, used
1238 * after lollipop.
1239 * <p/>
1240 * Assumes output format of:
1241 * <br/>
1242 * <code>
1243 * Filesystem 1K-blocks Used Available Use% Mounted on
1244 * <br/>
1245 * /dev/fuse 11585536 1316348 10269188 12% /mnt/shell/emulated
1246 * </code>
1247 * @param dfOutput the output of df command to parse
1248 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1249 */
1250 Long parseFreeSpaceFromModernOutput(String dfOutput) {
1251 Matcher matcher = DF_PATTERN.matcher(dfOutput);
1252 if (matcher.find()) {
1253 try {
1254 return Long.parseLong(matcher.group(1));
1255 } catch (NumberFormatException e) {
1256 // fall through
1257 }
1258 }
1259 return null;
1260 }
1261
1262 /**
1263 * {@inheritDoc}
1264 */
1265 @Override
1266 public String getMountPoint(String mountName) {
1267 return mStateMonitor.getMountPoint(mountName);
1268 }
1269
1270 /**
1271 * {@inheritDoc}
1272 */
1273 @Override
1274 public List<MountPointInfo> getMountPointInfo() throws DeviceNotAvailableException {
1275 final String mountInfo = executeShellCommand("cat /proc/mounts");
1276 final String[] mountInfoLines = mountInfo.split("\r?\n");
Eric Rowe1abf2c02017-03-20 17:12:33 -07001277 List<MountPointInfo> list = new ArrayList<>(mountInfoLines.length);
Julien Desprez6961b272016-02-01 09:58:23 +00001278
1279 for (String line : mountInfoLines) {
1280 // We ignore the last two fields
1281 // /dev/block/mtdblock4 /cache yaffs2 rw,nosuid,nodev,relatime 0 0
1282 final String[] parts = line.split("\\s+", 5);
1283 list.add(new MountPointInfo(parts[0], parts[1], parts[2], parts[3]));
1284 }
1285
1286 return list;
1287 }
1288
1289 /**
1290 * {@inheritDoc}
1291 */
1292 @Override
1293 public MountPointInfo getMountPointInfo(String mountpoint) throws DeviceNotAvailableException {
1294 // The overhead of parsing all of the lines should be minimal
1295 List<MountPointInfo> mountpoints = getMountPointInfo();
1296 for (MountPointInfo info : mountpoints) {
1297 if (mountpoint.equals(info.mountpoint)) return info;
1298 }
1299 return null;
1300 }
1301
1302 /**
1303 * {@inheritDoc}
1304 */
1305 @Override
1306 public IFileEntry getFileEntry(String path) throws DeviceNotAvailableException {
1307 path = interpolatePathVariables(path);
1308 String[] pathComponents = path.split(FileListingService.FILE_SEPARATOR);
1309 FileListingService service = getFileListingService();
1310 IFileEntry rootFile = new FileEntryWrapper(this, service.getRoot());
1311 return FileEntryWrapper.getDescendant(rootFile, Arrays.asList(pathComponents));
1312 }
1313
1314 /**
jdesprez934653e2018-02-13 12:48:48 -08001315 * Unofficial helper to get a {@link FileEntry} from a non-root path. FIXME: Refactor the
1316 * FileEntry system to have it available from any path. (even non root).
1317 *
1318 * @param entry a {@link FileEntry} not necessarily root as Ddmlib requires.
1319 * @return a {@link FileEntryWrapper} representing the FileEntry.
1320 * @throws DeviceNotAvailableException
1321 */
1322 public IFileEntry getFileEntry(FileEntry entry) throws DeviceNotAvailableException {
Julien Desprez41e09ab2018-07-24 11:07:17 -07001323 // FileEntryWrapper is going to construct the list of child file internally.
jdesprez934653e2018-02-13 12:48:48 -08001324 return new FileEntryWrapper(this, entry);
1325 }
1326
Julien Desprez41e09ab2018-07-24 11:07:17 -07001327 /** {@inheritDoc} */
1328 @Override
1329 public boolean isExecutable(String fullPath) throws DeviceNotAvailableException {
1330 String fileMode = executeShellCommand(String.format("ls -l %s", fullPath));
1331 if (fileMode != null) {
1332 return EXE_FILE.matcher(fileMode).find();
1333 }
1334 return false;
1335 }
1336
jdesprez934653e2018-02-13 12:48:48 -08001337 /**
Julien Desprez56f18e02016-03-11 14:40:18 +00001338 * {@inheritDoc}
1339 */
1340 @Override
1341 public boolean isDirectory(String path) throws DeviceNotAvailableException {
1342 return executeShellCommand(String.format("ls -ld %s", path)).charAt(0) == 'd';
1343 }
1344
1345 /**
1346 * {@inheritDoc}
1347 */
1348 @Override
1349 public String[] getChildren(String path) throws DeviceNotAvailableException {
1350 String lsOutput = executeShellCommand(String.format("ls -A1 %s", path));
1351 if (lsOutput.trim().isEmpty()) {
1352 return new String[0];
1353 }
1354 return lsOutput.split("\r?\n");
1355 }
1356
1357 /**
Julien Desprez6961b272016-02-01 09:58:23 +00001358 * Retrieve the {@link FileListingService} for the {@link IDevice}, making multiple attempts
1359 * and recovery operations if necessary.
1360 * <p/>
1361 * This is necessary because {@link IDevice#getFileListingService()} can return
1362 * <code>null</code> if device is in fastboot. The symptom of this condition is that the
1363 * current {@link #getIDevice()} is a {@link StubDevice}.
1364 *
1365 * @return the {@link FileListingService}
1366 * @throws DeviceNotAvailableException if device communication is lost.
1367 */
1368 private FileListingService getFileListingService() throws DeviceNotAvailableException {
1369 final FileListingService[] service = new FileListingService[1];
1370 DeviceAction serviceAction = new DeviceAction() {
1371 @Override
1372 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
1373 ShellCommandUnresponsiveException, InstallException, SyncException {
1374 service[0] = getIDevice().getFileListingService();
1375 if (service[0] == null) {
1376 // could not get file listing service - must be a stub device - enter recovery
1377 throw new IOException("Could not get file listing service");
1378 }
1379 return true;
1380 }
1381 };
1382 performDeviceAction("getFileListingService", serviceAction, MAX_RETRY_ATTEMPTS);
1383 return service[0];
1384 }
1385
1386 /**
1387 * {@inheritDoc}
1388 */
1389 @Override
1390 public boolean pushDir(File localFileDir, String deviceFilePath)
1391 throws DeviceNotAvailableException {
Julien Desprez69324072018-08-14 14:08:47 -07001392 return pushDir(localFileDir, deviceFilePath, new HashSet<>());
1393 }
1394
1395 /** {@inheritDoc} */
1396 @Override
1397 public boolean pushDir(
1398 File localFileDir, String deviceFilePath, Set<String> excludedDirectories)
1399 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00001400 if (!localFileDir.isDirectory()) {
1401 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1402 return false;
1403 }
1404 File[] childFiles = localFileDir.listFiles();
1405 if (childFiles == null) {
1406 CLog.e("Could not read files in %s", localFileDir.getAbsolutePath());
1407 return false;
1408 }
1409 for (File childFile : childFiles) {
1410 String remotePath = String.format("%s/%s", deviceFilePath, childFile.getName());
1411 if (childFile.isDirectory()) {
Julien Desprez69324072018-08-14 14:08:47 -07001412 // If we encounter a filtered directory do not push it.
1413 if (excludedDirectories.contains(childFile.getName())) {
1414 CLog.d(
1415 "%s directory was not pushed because it was filtered.",
1416 childFile.getAbsolutePath());
1417 continue;
1418 }
Hyungtae Tim Kim4cff14e2016-08-01 18:29:48 +09001419 executeShellCommand(String.format("mkdir -p \"%s\"", remotePath));
Julien Desprez69324072018-08-14 14:08:47 -07001420 if (!pushDir(childFile, remotePath, excludedDirectories)) {
Julien Desprez6961b272016-02-01 09:58:23 +00001421 return false;
1422 }
1423 } else if (childFile.isFile()) {
1424 if (!pushFile(childFile, remotePath)) {
1425 return false;
1426 }
1427 }
1428 }
1429 return true;
1430 }
1431
1432 /**
1433 * {@inheritDoc}
1434 */
1435 @Override
Guang Zhud7088362016-06-28 18:41:10 -07001436 public boolean pullDir(String deviceFilePath, File localDir)
1437 throws DeviceNotAvailableException {
1438 if (!localDir.isDirectory()) {
1439 CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
1440 return false;
1441 }
1442 if (!isDirectory(deviceFilePath)) {
1443 CLog.e("Device path %s is not a directory", deviceFilePath);
1444 return false;
1445 }
jdesprezb1469112018-02-15 09:57:25 -08001446 FileEntry entryRoot =
1447 new FileEntry(null, deviceFilePath, FileListingService.TYPE_DIRECTORY, false);
jdesprez934653e2018-02-13 12:48:48 -08001448 IFileEntry entry = getFileEntry(entryRoot);
jdesprez104458a2018-02-06 11:32:59 -08001449 Collection<IFileEntry> children = entry.getChildren(false);
1450 if (children.isEmpty()) {
Guang Zhud7088362016-06-28 18:41:10 -07001451 CLog.i("Device path is empty, nothing to do.");
1452 return true;
1453 }
jdesprez104458a2018-02-06 11:32:59 -08001454 for (IFileEntry item : children) {
1455 if (item.isDirectory()) {
Guang Zhud7088362016-06-28 18:41:10 -07001456 // handle sub dir
jdesprez104458a2018-02-06 11:32:59 -08001457 File subDir = new File(localDir, item.getName());
Guang Zhud7088362016-06-28 18:41:10 -07001458 if (!subDir.mkdir()) {
1459 CLog.w("Failed to create sub directory %s, aborting.",
1460 subDir.getAbsolutePath());
1461 return false;
1462 }
jdesprez104458a2018-02-06 11:32:59 -08001463 String deviceSubDir = item.getFullPath();
Guang Zhud7088362016-06-28 18:41:10 -07001464 if (!pullDir(deviceSubDir, subDir)) {
1465 CLog.w("Failed to pull sub directory %s from device, aborting", deviceSubDir);
1466 return false;
1467 }
1468 } else {
1469 // handle regular file
jdesprez104458a2018-02-06 11:32:59 -08001470 File localFile = new File(localDir, item.getName());
1471 String fullPath = item.getFullPath();
1472 if (!pullFile(fullPath, localFile)) {
1473 CLog.w("Failed to pull file %s from device, aborting", fullPath);
Guang Zhud7088362016-06-28 18:41:10 -07001474 return false;
1475 }
1476 }
1477 }
1478 return true;
1479 }
1480
1481 /**
1482 * {@inheritDoc}
1483 */
1484 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001485 public boolean syncFiles(File localFileDir, String deviceFilePath)
1486 throws DeviceNotAvailableException {
1487 if (localFileDir == null || deviceFilePath == null) {
1488 throw new IllegalArgumentException("syncFiles does not take null arguments");
1489 }
1490 CLog.i("Syncing %s to %s on device %s",
1491 localFileDir.getAbsolutePath(), deviceFilePath, getSerialNumber());
1492 if (!localFileDir.isDirectory()) {
1493 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1494 return false;
1495 }
1496 // get the real destination path. This is done because underlying syncService.push
1497 // implementation will add localFileDir.getName() to destination path
1498 deviceFilePath = String.format("%s/%s", interpolatePathVariables(deviceFilePath),
1499 localFileDir.getName());
1500 if (!doesFileExist(deviceFilePath)) {
Hyungtae Tim Kim4cff14e2016-08-01 18:29:48 +09001501 executeShellCommand(String.format("mkdir -p \"%s\"", deviceFilePath));
Julien Desprez6961b272016-02-01 09:58:23 +00001502 }
1503 IFileEntry remoteFileEntry = getFileEntry(deviceFilePath);
1504 if (remoteFileEntry == null) {
1505 CLog.e("Could not find remote file entry %s ", deviceFilePath);
1506 return false;
1507 }
1508
1509 return syncFiles(localFileDir, remoteFileEntry);
1510 }
1511
1512 /**
1513 * Recursively sync newer files.
1514 *
1515 * @param localFileDir the local {@link File} directory to sync
1516 * @param remoteFileEntry the remote destination {@link IFileEntry}
1517 * @return <code>true</code> if files were synced successfully
1518 * @throws DeviceNotAvailableException
1519 */
1520 private boolean syncFiles(File localFileDir, final IFileEntry remoteFileEntry)
1521 throws DeviceNotAvailableException {
1522 CLog.d("Syncing %s to %s on %s", localFileDir.getAbsolutePath(),
1523 remoteFileEntry.getFullPath(), getSerialNumber());
1524 // find newer files to sync
1525 File[] localFiles = localFileDir.listFiles(new NoHiddenFilesFilter());
Eric Rowe1abf2c02017-03-20 17:12:33 -07001526 ArrayList<String> filePathsToSync = new ArrayList<>();
Julien Desprez6961b272016-02-01 09:58:23 +00001527 for (File localFile : localFiles) {
1528 IFileEntry entry = remoteFileEntry.findChild(localFile.getName());
1529 if (entry == null) {
1530 CLog.d("Detected missing file path %s", localFile.getAbsolutePath());
1531 filePathsToSync.add(localFile.getAbsolutePath());
1532 } else if (localFile.isDirectory()) {
1533 // This directory exists remotely. recursively sync it to sync only its newer files
1534 // contents
1535 if (!syncFiles(localFile, entry)) {
1536 return false;
1537 }
1538 } else if (isNewer(localFile, entry)) {
1539 CLog.d("Detected newer file %s", localFile.getAbsolutePath());
1540 filePathsToSync.add(localFile.getAbsolutePath());
1541 }
1542 }
1543
1544 if (filePathsToSync.size() == 0) {
1545 CLog.d("No files to sync");
1546 return true;
1547 }
1548 final String files[] = filePathsToSync.toArray(new String[filePathsToSync.size()]);
1549 DeviceAction syncAction = new DeviceAction() {
1550 @Override
1551 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1552 SyncException {
1553 SyncService syncService = null;
1554 boolean status = false;
1555 try {
1556 syncService = getIDevice().getSyncService();
1557 syncService.push(files, remoteFileEntry.getFileEntry(),
1558 SyncService.getNullProgressMonitor());
1559 status = true;
1560 } catch (SyncException e) {
1561 CLog.w("Failed to sync files to %s on device %s. Message %s",
1562 remoteFileEntry.getFullPath(), getSerialNumber(), e.getMessage());
1563 throw e;
1564 } finally {
1565 if (syncService != null) {
1566 syncService.close();
1567 }
1568 }
1569 return status;
1570 }
1571 };
1572 return performDeviceAction(String.format("sync files %s", remoteFileEntry.getFullPath()),
1573 syncAction, MAX_RETRY_ATTEMPTS);
1574 }
1575
1576 /**
1577 * Queries the file listing service for a given directory
1578 *
1579 * @param remoteFileEntry
1580 * @throws DeviceNotAvailableException
1581 */
1582 FileEntry[] getFileChildren(final FileEntry remoteFileEntry)
1583 throws DeviceNotAvailableException {
1584 // time this operation because its known to hang
1585 FileQueryAction action = new FileQueryAction(remoteFileEntry,
1586 getIDevice().getFileListingService());
1587 performDeviceAction("buildFileCache", action, MAX_RETRY_ATTEMPTS);
1588 return action.mFileContents;
1589 }
1590
1591 private class FileQueryAction implements DeviceAction {
1592
1593 FileEntry[] mFileContents = null;
1594 private final FileEntry mRemoteFileEntry;
1595 private final FileListingService mService;
1596
1597 FileQueryAction(FileEntry remoteFileEntry, FileListingService service) {
1598 throwIfNull(remoteFileEntry);
1599 throwIfNull(service);
1600 mRemoteFileEntry = remoteFileEntry;
1601 mService = service;
1602 }
1603
1604 @Override
1605 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1606 ShellCommandUnresponsiveException {
1607 mFileContents = mService.getChildrenSync(mRemoteFileEntry);
1608 return true;
1609 }
1610 }
1611
1612 /**
1613 * A {@link FilenameFilter} that rejects hidden (ie starts with ".") files.
1614 */
1615 private static class NoHiddenFilesFilter implements FilenameFilter {
1616 /**
1617 * {@inheritDoc}
1618 */
1619 @Override
1620 public boolean accept(File dir, String name) {
1621 return !name.startsWith(".");
1622 }
1623 }
1624
1625 /**
Julien Desprez7c15ac42016-08-10 16:45:44 +01001626 * helper to get the timezone from the device. Example: "Europe/London"
Julien Desprez6961b272016-02-01 09:58:23 +00001627 */
Julien Desprez7c15ac42016-08-10 16:45:44 +01001628 private String getDeviceTimezone() {
Julien Desprez6961b272016-02-01 09:58:23 +00001629 try {
Julien Desprez7c15ac42016-08-10 16:45:44 +01001630 // This may not be set at first, default to GMT in this case.
1631 String timezone = getProperty("persist.sys.timezone");
1632 if (timezone != null) {
1633 return timezone.trim();
1634 }
1635 } catch (DeviceNotAvailableException e) {
1636 // Fall through on purpose
1637 }
1638 return "GMT";
1639 }
1640
1641 /**
Julien Desprez75518d32016-11-18 09:54:34 +00001642 * Return <code>true</code> if local file is newer than remote file. {@link IFileEntry} being
1643 * accurate to the minute, in case of equal times, the file will be considered newer.
Julien Desprez7c15ac42016-08-10 16:45:44 +01001644 */
Jeffrey Lu279122f2018-01-29 17:25:08 -08001645 @VisibleForTesting
Julien Desprez7c15ac42016-08-10 16:45:44 +01001646 protected boolean isNewer(File localFile, IFileEntry entry) {
1647 final String entryTimeString = String.format("%s %s", entry.getDate(), entry.getTime());
1648 try {
1649 String timezone = getDeviceTimezone();
Julien Desprez6961b272016-02-01 09:58:23 +00001650 // expected format of a FileEntry's date and time
Julien Desprez7c15ac42016-08-10 16:45:44 +01001651 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
1652 format.setTimeZone(TimeZone.getTimeZone(timezone));
Julien Desprez6961b272016-02-01 09:58:23 +00001653 Date remoteDate = format.parse(entryTimeString);
Julien Desprez7c15ac42016-08-10 16:45:44 +01001654
1655 long offset = 0;
1656 try {
1657 offset = getDeviceTimeOffset(null);
1658 } catch (DeviceNotAvailableException e) {
1659 offset = 0;
1660 }
1661 CLog.i("Device offset time: %s", offset);
1662
Julien Desprez6961b272016-02-01 09:58:23 +00001663 // localFile.lastModified has granularity of ms, but remoteDate.getTime only has
1664 // granularity of minutes. Shift remoteDate.getTime() backward by one minute so newly
1665 // modified files get synced
Julien Desprez5aa8c6e2016-10-27 10:12:13 +01001666 return localFile.lastModified() > (remoteDate.getTime() - 60 * 1000 + offset);
Julien Desprez6961b272016-02-01 09:58:23 +00001667 } catch (ParseException e) {
1668 CLog.e("Error converting remote time stamp %s for %s on device %s", entryTimeString,
1669 entry.getFullPath(), getSerialNumber());
1670 }
1671 // sync file by default
1672 return true;
1673 }
1674
1675 /**
1676 * {@inheritDoc}
1677 */
1678 @Override
1679 public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
1680 final String[] fullCmd = buildAdbCommand(cmdArgs);
1681 AdbAction adbAction = new AdbAction(fullCmd);
1682 performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
1683 return adbAction.mOutput;
1684 }
1685
jdesprez3d7b9142018-03-02 12:11:23 -08001686
Julien Desprez6961b272016-02-01 09:58:23 +00001687 /**
1688 * {@inheritDoc}
1689 */
1690 @Override
1691 public CommandResult executeFastbootCommand(String... cmdArgs)
1692 throws DeviceNotAvailableException, UnsupportedOperationException {
1693 return doFastbootCommand(getCommandTimeout(), cmdArgs);
1694 }
1695
1696 /**
1697 * {@inheritDoc}
1698 */
1699 @Override
Julien Desprezf7d1e0d2016-06-01 09:32:38 +01001700 public CommandResult executeFastbootCommand(long timeout, String... cmdArgs)
1701 throws DeviceNotAvailableException, UnsupportedOperationException {
1702 return doFastbootCommand(timeout, cmdArgs);
1703 }
1704
1705 /**
1706 * {@inheritDoc}
1707 */
1708 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001709 public CommandResult executeLongFastbootCommand(String... cmdArgs)
1710 throws DeviceNotAvailableException, UnsupportedOperationException {
1711 return doFastbootCommand(getLongCommandTimeout(), cmdArgs);
1712 }
1713
1714 /**
1715 * @param cmdArgs
1716 * @throws DeviceNotAvailableException
1717 */
1718 private CommandResult doFastbootCommand(final long timeout, String... cmdArgs)
1719 throws DeviceNotAvailableException, UnsupportedOperationException {
1720 if (!mFastbootEnabled) {
1721 throw new UnsupportedOperationException(String.format(
1722 "Attempted to fastboot on device %s , but fastboot is not available. Aborting.",
1723 getSerialNumber()));
1724 }
1725 final String[] fullCmd = buildFastbootCommand(cmdArgs);
1726 for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
Guang Zhu98e1e5c2017-11-03 09:47:37 -07001727 File fastbootTmpDir = getHostOptions().getFastbootTmpDir();
Guang Zhu26cca482017-11-01 18:18:08 -07001728 IRunUtil runUtil = null;
1729 if (fastbootTmpDir != null) {
1730 runUtil = new RunUtil();
Guang Zhu98e1e5c2017-11-03 09:47:37 -07001731 runUtil.setEnvVariable("TMPDIR", fastbootTmpDir.getAbsolutePath());
Guang Zhu26cca482017-11-01 18:18:08 -07001732 } else {
1733 runUtil = getRunUtil();
1734 }
Julien Desprez6961b272016-02-01 09:58:23 +00001735 CommandResult result = new CommandResult(CommandStatus.EXCEPTION);
1736 // block state changes while executing a fastboot command, since
1737 // device will disappear from fastboot devices while command is being executed
1738 mFastbootLock.lock();
1739 try {
Guang Zhu26cca482017-11-01 18:18:08 -07001740 result = runUtil.runTimedCmd(timeout, fullCmd);
Julien Desprez6961b272016-02-01 09:58:23 +00001741 } finally {
1742 mFastbootLock.unlock();
1743 }
1744 if (!isRecoveryNeeded(result)) {
1745 return result;
1746 }
1747 CLog.w("Recovery needed after executing fastboot command");
1748 if (result != null) {
1749 CLog.v("fastboot command output:\nstdout: %s\nstderr:%s",
1750 result.getStdout(), result.getStderr());
1751 }
1752 recoverDeviceFromBootloader();
1753 }
1754 throw new DeviceUnresponsiveException(String.format("Attempted fastboot %s multiple "
1755 + "times on device %s without communication success. Aborting.", cmdArgs[0],
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001756 getSerialNumber()), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001757 }
1758
1759 /**
1760 * {@inheritDoc}
1761 */
1762 @Override
1763 public boolean getUseFastbootErase() {
1764 return mOptions.getUseFastbootErase();
1765 }
1766
1767 /**
1768 * {@inheritDoc}
1769 */
1770 @Override
1771 public void setUseFastbootErase(boolean useFastbootErase) {
1772 mOptions.setUseFastbootErase(useFastbootErase);
1773 }
1774
1775 /**
1776 * {@inheritDoc}
1777 */
1778 @Override
1779 public CommandResult fastbootWipePartition(String partition)
1780 throws DeviceNotAvailableException {
1781 if (mOptions.getUseFastbootErase()) {
1782 return executeLongFastbootCommand("erase", partition);
1783 } else {
1784 return executeLongFastbootCommand("format", partition);
1785 }
1786 }
1787
1788 /**
1789 * Evaluate the given fastboot result to determine if recovery mode needs to be entered
1790 *
1791 * @param fastbootResult the {@link CommandResult} from a fastboot command
1792 * @return <code>true</code> if recovery mode should be entered, <code>false</code> otherwise.
1793 */
1794 private boolean isRecoveryNeeded(CommandResult fastbootResult) {
1795 if (fastbootResult.getStatus().equals(CommandStatus.TIMED_OUT)) {
1796 // fastboot commands always time out if devices is not present
1797 return true;
1798 } else {
1799 // check for specific error messages in result that indicate bad device communication
1800 // and recovery mode is needed
1801 if (fastbootResult.getStderr() == null ||
1802 fastbootResult.getStderr().contains("data transfer failure (Protocol error)") ||
1803 fastbootResult.getStderr().contains("status read failed (No such device)")) {
1804 CLog.w("Bad fastboot response from device %s. stderr: %s. Entering recovery",
1805 getSerialNumber(), fastbootResult.getStderr());
1806 return true;
1807 }
1808 }
1809 return false;
1810 }
1811
jdesprez3d7b9142018-03-02 12:11:23 -08001812 /** Get the max time allowed in ms for commands. */
1813 long getCommandTimeout() {
Julien Desprez6961b272016-02-01 09:58:23 +00001814 return mCmdTimeout;
1815 }
1816
1817 /**
1818 * Set the max time allowed in ms for commands.
1819 */
1820 void setLongCommandTimeout(long timeout) {
1821 mLongCmdTimeout = timeout;
1822 }
1823
1824 /**
1825 * Get the max time allowed in ms for commands.
1826 */
1827 long getLongCommandTimeout() {
1828 return mLongCmdTimeout;
1829 }
1830
jdesprez3d7b9142018-03-02 12:11:23 -08001831 /** Set the max time allowed in ms for commands. */
1832 void setCommandTimeout(long timeout) {
Julien Desprez6961b272016-02-01 09:58:23 +00001833 mCmdTimeout = timeout;
1834 }
1835
1836 /**
1837 * Builds the OS command for the given adb command and args
1838 */
1839 private String[] buildAdbCommand(String... commandArgs) {
1840 return ArrayUtil.buildArray(new String[] {"adb", "-s", getSerialNumber()},
1841 commandArgs);
1842 }
1843
jdesprez3d7b9142018-03-02 12:11:23 -08001844 /** Builds the OS command for the given adb shell command session and args */
1845 private String[] buildAdbShellCommand(String command) {
1846 // TODO: implement the shell v2 support in ddmlib itself.
1847 String[] commandArgs = QuotationAwareTokenizer.tokenizeLine(command);
1848 return ArrayUtil.buildArray(
1849 new String[] {"adb", "-s", getSerialNumber(), "shell"}, commandArgs);
1850 }
1851
Julien Desprez6961b272016-02-01 09:58:23 +00001852 /**
1853 * Builds the OS command for the given fastboot command and args
1854 */
1855 private String[] buildFastbootCommand(String... commandArgs) {
Julien Desprez0a7d67d2016-07-21 16:05:57 +01001856 return ArrayUtil.buildArray(new String[] {getFastbootPath(), "-s", getSerialNumber()},
Julien Desprez6961b272016-02-01 09:58:23 +00001857 commandArgs);
1858 }
1859
1860 /**
1861 * Performs an action on this device. Attempts to recover device and optionally retry command
1862 * if action fails.
1863 *
1864 * @param actionDescription a short description of action to be performed. Used for logging
1865 * purposes only.
1866 * @param action the action to be performed
1867 * @param retryAttempts the retry attempts to make for action if it fails but
1868 * recovery succeeds
Julien Desprezd0c379a2016-11-04 11:00:54 +00001869 * @return <code>true</code> if action was performed successfully
Julien Desprez6961b272016-02-01 09:58:23 +00001870 * @throws DeviceNotAvailableException if recovery attempt fails or max attempts done without
1871 * success
1872 */
Julien Desprezc8474552016-02-17 10:59:27 +00001873 protected boolean performDeviceAction(String actionDescription, final DeviceAction action,
Julien Desprez6961b272016-02-01 09:58:23 +00001874 int retryAttempts) throws DeviceNotAvailableException {
1875
1876 for (int i = 0; i < retryAttempts + 1; i++) {
1877 try {
1878 return action.run();
1879 } catch (TimeoutException e) {
1880 logDeviceActionException(actionDescription, e);
1881 } catch (IOException e) {
1882 logDeviceActionException(actionDescription, e);
1883 } catch (InstallException e) {
1884 logDeviceActionException(actionDescription, e);
1885 } catch (SyncException e) {
1886 logDeviceActionException(actionDescription, e);
1887 // a SyncException is not necessarily a device communication problem
1888 // do additional diagnosis
1889 if (!e.getErrorCode().equals(SyncError.BUFFER_OVERRUN) &&
1890 !e.getErrorCode().equals(SyncError.TRANSFER_PROTOCOL_ERROR)) {
1891 // this is a logic problem, doesn't need recovery or to be retried
1892 return false;
1893 }
1894 } catch (AdbCommandRejectedException e) {
1895 logDeviceActionException(actionDescription, e);
1896 } catch (ShellCommandUnresponsiveException e) {
1897 CLog.w("Device %s stopped responding when attempting %s", getSerialNumber(),
1898 actionDescription);
1899 }
1900 // TODO: currently treat all exceptions the same. In future consider different recovery
1901 // mechanisms for time out's vs IOExceptions
1902 recoverDevice();
1903 }
1904 if (retryAttempts > 0) {
1905 throw new DeviceUnresponsiveException(String.format("Attempted %s multiple times "
1906 + "on device %s without communication success. Aborting.", actionDescription,
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001907 getSerialNumber()), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001908 }
1909 return false;
1910 }
1911
1912 /**
1913 * Log an entry for given exception
1914 *
1915 * @param actionDescription the action's description
1916 * @param e the exception
1917 */
1918 private void logDeviceActionException(String actionDescription, Exception e) {
1919 CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(),
1920 getExceptionMessage(e), actionDescription, getSerialNumber());
1921 }
1922
1923 /**
1924 * Make a best effort attempt to retrieve a meaningful short descriptive message for given
1925 * {@link Exception}
1926 *
1927 * @param e the {@link Exception}
1928 * @return a short message
1929 */
1930 private String getExceptionMessage(Exception e) {
1931 StringBuilder msgBuilder = new StringBuilder();
1932 if (e.getMessage() != null) {
1933 msgBuilder.append(e.getMessage());
1934 }
1935 if (e.getCause() != null) {
1936 msgBuilder.append(" cause: ");
1937 msgBuilder.append(e.getCause().getClass().getSimpleName());
1938 if (e.getCause().getMessage() != null) {
1939 msgBuilder.append(" (");
1940 msgBuilder.append(e.getCause().getMessage());
1941 msgBuilder.append(")");
1942 }
1943 }
1944 return msgBuilder.toString();
1945 }
1946
1947 /**
1948 * Attempts to recover device communication.
1949 *
1950 * @throws DeviceNotAvailableException if device is not longer available
1951 */
1952 @Override
1953 public void recoverDevice() throws DeviceNotAvailableException {
1954 if (mRecoveryMode.equals(RecoveryMode.NONE)) {
1955 CLog.i("Skipping recovery on %s", getSerialNumber());
1956 getRunUtil().sleep(NONE_RECOVERY_MODE_DELAY);
1957 return;
1958 }
1959 CLog.i("Attempting recovery on %s", getSerialNumber());
Julien Desprez7a7d97e2016-02-05 12:27:49 +00001960 try {
1961 mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE));
1962 } catch (DeviceUnresponsiveException due) {
1963 RecoveryMode previousRecoveryMode = mRecoveryMode;
1964 mRecoveryMode = RecoveryMode.NONE;
1965 boolean enabled = enableAdbRoot();
1966 CLog.d("Device Unresponsive during recovery, is root still enabled: %s", enabled);
1967 mRecoveryMode = previousRecoveryMode;
1968 throw due;
1969 }
Julien Desprez6961b272016-02-01 09:58:23 +00001970 if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) {
1971 // turn off recovery mode to prevent reentrant recovery
1972 // TODO: look for a better way to handle this, such as doing postBootUp steps in
1973 // recovery itself
1974 mRecoveryMode = RecoveryMode.NONE;
1975 // this might be a runtime reset - still need to run post boot setup steps
1976 if (isEncryptionSupported() && isDeviceEncrypted()) {
1977 unlockDevice();
1978 }
1979 postBootSetup();
1980 mRecoveryMode = RecoveryMode.AVAILABLE;
1981 } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) {
1982 // turn off recovery mode to prevent reentrant recovery
1983 // TODO: look for a better way to handle this, such as doing postBootUp steps in
1984 // recovery itself
1985 mRecoveryMode = RecoveryMode.NONE;
1986 enableAdbRoot();
1987 mRecoveryMode = RecoveryMode.ONLINE;
1988 }
1989 CLog.i("Recovery successful for %s", getSerialNumber());
1990 }
1991
1992 /**
1993 * Attempts to recover device fastboot communication.
1994 *
1995 * @throws DeviceNotAvailableException if device is not longer available
1996 */
1997 private void recoverDeviceFromBootloader() throws DeviceNotAvailableException {
1998 CLog.i("Attempting recovery on %s in bootloader", getSerialNumber());
1999 mRecovery.recoverDeviceBootloader(mStateMonitor);
2000 CLog.i("Bootloader recovery successful for %s", getSerialNumber());
2001 }
2002
2003 private void recoverDeviceInRecovery() throws DeviceNotAvailableException {
2004 CLog.i("Attempting recovery on %s in recovery", getSerialNumber());
2005 mRecovery.recoverDeviceRecovery(mStateMonitor);
2006 CLog.i("Recovery mode recovery successful for %s", getSerialNumber());
2007 }
2008
2009 /**
2010 * {@inheritDoc}
2011 */
2012 @Override
2013 public void startLogcat() {
2014 if (mLogcatReceiver != null) {
2015 CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber());
2016 return;
2017 }
2018 mLogcatReceiver = createLogcatReceiver();
2019 mLogcatReceiver.start();
2020 }
2021
2022 /**
2023 * {@inheritDoc}
2024 */
2025 @Override
2026 public void clearLogcat() {
2027 if (mLogcatReceiver != null) {
2028 mLogcatReceiver.clear();
2029 }
2030 }
2031
jdesprezd0e00da2017-09-07 18:54:49 -07002032 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002033 @Override
jdesprezd0e00da2017-09-07 18:54:49 -07002034 @SuppressWarnings("MustBeClosedChecker")
Julien Desprez6961b272016-02-01 09:58:23 +00002035 public InputStreamSource getLogcat() {
2036 if (mLogcatReceiver == null) {
2037 CLog.w("Not capturing logcat for %s in background, returning a logcat dump",
2038 getSerialNumber());
2039 return getLogcatDump();
2040 } else {
2041 return mLogcatReceiver.getLogcatData();
2042 }
2043 }
2044
jdesprezd0e00da2017-09-07 18:54:49 -07002045 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002046 @Override
jdesprezcdd03352017-10-06 18:04:49 -07002047 @SuppressWarnings("MustBeClosedChecker")
Julien Desprez6961b272016-02-01 09:58:23 +00002048 public InputStreamSource getLogcat(int maxBytes) {
2049 if (mLogcatReceiver == null) {
2050 CLog.w("Not capturing logcat for %s in background, returning a logcat dump "
2051 + "ignoring size", getSerialNumber());
2052 return getLogcatDump();
2053 } else {
2054 return mLogcatReceiver.getLogcatData(maxBytes);
2055 }
2056 }
2057
2058 /**
2059 * {@inheritDoc}
2060 */
2061 @Override
Julien Desprezdcb19d52016-06-20 12:23:12 +01002062 public InputStreamSource getLogcatSince(long date) {
2063 try {
2064 if (getApiLevel() <= 22) {
2065 CLog.i("Api level too low to use logcat -t 'time' reverting to dump");
2066 return getLogcatDump();
2067 }
2068 } catch (DeviceNotAvailableException e) {
2069 // For convenience of interface, we catch the DNAE here.
2070 CLog.e(e);
2071 return getLogcatDump();
2072 }
2073
jdesprez0b9d69e2017-12-11 03:40:39 -08002074 // Convert date to format needed by the command:
jdesprez6a2f1d22018-03-20 11:31:54 -07002075 // 'MM-DD HH:mm:ss.mmm' or 'YYYY-MM-DD HH:mm:ss.mmm'
2076 SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss.mmm");
jdesprez0b9d69e2017-12-11 03:40:39 -08002077 String dateFormatted = format.format(new Date(date));
2078
Julien Desprezdcb19d52016-06-20 12:23:12 +01002079 byte[] output = new byte[0];
2080 try {
2081 // use IDevice directly because we don't want callers to handle
2082 // DeviceNotAvailableException for this method
2083 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
jdesprez0b9d69e2017-12-11 03:40:39 -08002084 String command = String.format("%s -t '%s'", LogcatReceiver.LOGCAT_CMD, dateFormatted);
Julien Desprezdcb19d52016-06-20 12:23:12 +01002085 getIDevice().executeShellCommand(command, receiver);
2086 output = receiver.getOutput();
2087 } catch (IOException|AdbCommandRejectedException|
2088 ShellCommandUnresponsiveException|TimeoutException e) {
2089 CLog.w("Failed to get logcat dump from %s: %s", getSerialNumber(), e.getMessage());
2090 CLog.e(e);
2091 }
2092 return new ByteArrayInputStreamSource(output);
2093 }
2094
2095 /**
2096 * {@inheritDoc}
2097 */
2098 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00002099 public InputStreamSource getLogcatDump() {
2100 byte[] output = new byte[0];
2101 try {
2102 // use IDevice directly because we don't want callers to handle
2103 // DeviceNotAvailableException for this method
2104 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2105 // add -d parameter to make this a non blocking call
Julien Desprez73c55bf2016-09-01 09:27:37 +01002106 getIDevice().executeShellCommand(LogcatReceiver.LOGCAT_CMD + " -d", receiver,
2107 LOGCAT_DUMP_TIMEOUT, TimeUnit.MILLISECONDS);
Julien Desprez6961b272016-02-01 09:58:23 +00002108 output = receiver.getOutput();
2109 } catch (IOException e) {
2110 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2111 } catch (TimeoutException e) {
2112 CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber());
2113 } catch (AdbCommandRejectedException e) {
2114 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2115 } catch (ShellCommandUnresponsiveException e) {
2116 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
2117 }
2118 return new ByteArrayInputStreamSource(output);
2119 }
2120
2121 /**
2122 * {@inheritDoc}
2123 */
2124 @Override
2125 public void stopLogcat() {
2126 if (mLogcatReceiver != null) {
2127 mLogcatReceiver.stop();
2128 mLogcatReceiver = null;
2129 } else {
2130 CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber());
2131 }
2132 }
2133
Jeffrey Lu279122f2018-01-29 17:25:08 -08002134 /** Factory method to create a {@link LogcatReceiver}. */
2135 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00002136 LogcatReceiver createLogcatReceiver() {
2137 String logcatOptions = mOptions.getLogcatOptions();
2138 if (logcatOptions == null) {
2139 return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay);
2140 } else {
2141 return new LogcatReceiver(this,
2142 String.format("%s %s", LogcatReceiver.LOGCAT_CMD, logcatOptions),
2143 mOptions.getMaxLogcatDataSize(), mLogStartDelay);
2144 }
2145 }
2146
2147 /**
2148 * {@inheritDoc}
2149 */
2150 @Override
2151 public InputStreamSource getBugreport() {
jdesprezc3537f72017-09-05 17:27:14 -07002152 if (getApiLevelSafe() < 24) {
2153 InputStreamSource bugreport = getBugreportInternal();
2154 if (bugreport == null) {
2155 // Safe call so we don't return null but an empty resource.
2156 return new ByteArrayInputStreamSource("".getBytes());
2157 }
2158 return bugreport;
Julien Desprez16184162016-06-10 08:56:17 +01002159 }
jdesprezc3537f72017-09-05 17:27:14 -07002160 CLog.d("Api level above 24, using bugreportz instead.");
2161 File mainEntry = null;
2162 File bugreportzFile = null;
2163 try {
2164 bugreportzFile = getBugreportzInternal();
2165 if (bugreportzFile == null) {
2166 bugreportzFile = bugreportzFallback();
Julien Desprez15de6812016-08-08 15:08:06 +01002167 }
jdesprezc3537f72017-09-05 17:27:14 -07002168 if (bugreportzFile == null) {
2169 // return empty buffer
2170 return new ByteArrayInputStreamSource("".getBytes());
Julien Desprez15de6812016-08-08 15:08:06 +01002171 }
jdesprezc3537f72017-09-05 17:27:14 -07002172 try (ZipFile zip = new ZipFile(bugreportzFile)) {
2173 // We get the main_entry.txt that contains the bugreport name.
2174 mainEntry = ZipUtil2.extractFileFromZip(zip, "main_entry.txt");
2175 String bugreportName = FileUtil.readStringFromFile(mainEntry).trim();
2176 CLog.d("bugreport name: '%s'", bugreportName);
2177 File bugreport = ZipUtil2.extractFileFromZip(zip, bugreportName);
2178 return new FileInputStreamSource(bugreport, true);
2179 }
2180 } catch (IOException e) {
2181 CLog.e("Error while unzipping bugreportz");
2182 CLog.e(e);
2183 return new ByteArrayInputStreamSource("corrupted bugreport.".getBytes());
2184 } finally {
2185 FileUtil.deleteFile(bugreportzFile);
2186 FileUtil.deleteFile(mainEntry);
Julien Desprez15de6812016-08-08 15:08:06 +01002187 }
Julien Desprez16184162016-06-10 08:56:17 +01002188 }
2189
2190 /**
jdesprezc3537f72017-09-05 17:27:14 -07002191 * If first bugreportz collection was interrupted for any reasons, the temporary file where the
2192 * dumpstate is redirected could exists if it started. We attempt to get it to have some partial
2193 * data.
Julien Desprez56d044d2016-08-17 12:28:10 +01002194 */
jdesprezc3537f72017-09-05 17:27:14 -07002195 private File bugreportzFallback() {
Julien Desprez56d044d2016-08-17 12:28:10 +01002196 try {
2197 IFileEntry entries = getFileEntry(BUGREPORTZ_TMP_PATH);
2198 if (entries != null) {
2199 for (IFileEntry f : entries.getChildren(false)) {
2200 String name = f.getName();
Julien Desprezf20644b2016-08-31 09:31:27 +01002201 CLog.d("bugreport entry: %s", name);
jdesprezc3537f72017-09-05 17:27:14 -07002202 // Only get left-over zipped data to avoid confusing data types.
2203 if (name.endsWith(".zip")) {
Julien Desprez96c00252018-06-06 03:25:40 -07002204 File pulledZip = pullFile(BUGREPORTZ_TMP_PATH + name);
2205 try {
2206 // Validate the zip before returning it.
2207 if (ZipUtil.isZipFileValid(pulledZip, false)) {
2208 return pulledZip;
2209 }
2210 } catch (IOException e) {
2211 CLog.e(e);
2212 }
2213 CLog.w("Failed to get a valid bugreportz.");
2214 // if zip validation failed, delete it and return null.
2215 FileUtil.deleteFile(pulledZip);
2216 return null;
2217
Julien Desprez56d044d2016-08-17 12:28:10 +01002218 }
2219 }
Julien Desprezf20644b2016-08-31 09:31:27 +01002220 CLog.w("Could not find a tmp bugreport file in the directory.");
2221 } else {
2222 CLog.w("Could not find the file entry: '%s' on the device.", BUGREPORTZ_TMP_PATH);
Julien Desprez56d044d2016-08-17 12:28:10 +01002223 }
2224 } catch (DeviceNotAvailableException e) {
2225 CLog.e(e);
2226 }
jdesprezc3537f72017-09-05 17:27:14 -07002227 return null;
Julien Desprez56d044d2016-08-17 12:28:10 +01002228 }
2229
2230 /**
Julien Desprez16184162016-06-10 08:56:17 +01002231 * {@inheritDoc}
2232 */
2233 @Override
Julien Desprez0c836c92016-08-10 14:40:41 +01002234 public boolean logBugreport(String dataName, ITestLogger listener) {
jdesprezc3537f72017-09-05 17:27:14 -07002235 InputStreamSource bugreport = null;
2236 LogDataType type = null;
Julien Desprez0c836c92016-08-10 14:40:41 +01002237 try {
jdesprezc3537f72017-09-05 17:27:14 -07002238 bugreport = getBugreportz();
2239 type = LogDataType.BUGREPORTZ;
2240
Julien Desprez0c836c92016-08-10 14:40:41 +01002241 if (bugreport == null) {
jdesprezc3537f72017-09-05 17:27:14 -07002242 CLog.d("Bugreportz failed, attempting bugreport collection instead.");
2243 bugreport = getBugreportInternal();
Julien Desprez0c836c92016-08-10 14:40:41 +01002244 type = LogDataType.BUGREPORT;
2245 }
jdesprezc3537f72017-09-05 17:27:14 -07002246 // log what we managed to capture.
Julien Desprez0c836c92016-08-10 14:40:41 +01002247 if (bugreport != null) {
2248 listener.testLog(dataName, type, bugreport);
2249 return true;
2250 }
2251 } finally {
2252 StreamUtil.cancel(bugreport);
2253 }
jdesprezc3537f72017-09-05 17:27:14 -07002254 CLog.d(
2255 "logBugreport() was not successful in collecting and logging the bugreport "
2256 + "for device %s",
2257 getSerialNumber());
Julien Desprez0c836c92016-08-10 14:40:41 +01002258 return false;
2259 }
2260
2261 /**
2262 * {@inheritDoc}
2263 */
2264 @Override
2265 public Bugreport takeBugreport() {
jdesprezc3537f72017-09-05 17:27:14 -07002266 File bugreportFile = null;
2267 int apiLevel = getApiLevelSafe();
2268 if (apiLevel == UNKNOWN_API_LEVEL) {
Julien Desprez0c836c92016-08-10 14:40:41 +01002269 return null;
2270 }
jdesprezc3537f72017-09-05 17:27:14 -07002271 if (apiLevel >= 24) {
2272 CLog.d("Api level above 24, using bugreportz.");
Julien Desprez0c836c92016-08-10 14:40:41 +01002273 bugreportFile = getBugreportzInternal();
2274 if (bugreportFile != null) {
2275 return new Bugreport(bugreportFile, true);
Julien Desprez0c836c92016-08-10 14:40:41 +01002276 }
jdesprezc3537f72017-09-05 17:27:14 -07002277 return null;
Julien Desprez0c836c92016-08-10 14:40:41 +01002278 }
jdesprezc3537f72017-09-05 17:27:14 -07002279 // fall back to regular bugreport
2280 InputStreamSource bugreport = getBugreportInternal();
2281 if (bugreport == null) {
2282 CLog.e("Error when collecting the bugreport.");
2283 return null;
2284 }
2285 try {
2286 bugreportFile = FileUtil.createTempFile("bugreport", ".txt");
2287 FileUtil.writeToFile(bugreport.createInputStream(), bugreportFile);
2288 return new Bugreport(bugreportFile, false);
2289 } catch (IOException e) {
2290 CLog.e("Error when writing the bugreport file");
2291 CLog.e(e);
2292 }
2293 return null;
Julien Desprez0c836c92016-08-10 14:40:41 +01002294 }
2295
2296 /**
2297 * {@inheritDoc}
2298 */
2299 @Override
Julien Desprez16184162016-06-10 08:56:17 +01002300 public InputStreamSource getBugreportz() {
jdesprezc3537f72017-09-05 17:27:14 -07002301 if (getApiLevelSafe() < 24) {
2302 return null;
2303 }
2304 File bugreportZip = getBugreportzInternal();
2305 if (bugreportZip == null) {
2306 bugreportZip = bugreportzFallback();
2307 }
2308 if (bugreportZip != null) {
2309 return new FileInputStreamSource(bugreportZip, true);
Julien Desprez15de6812016-08-08 15:08:06 +01002310 }
2311 return null;
2312 }
2313
Jeffrey Lu279122f2018-01-29 17:25:08 -08002314 /** Internal Helper method to get the bugreportz zip file as a {@link File}. */
2315 @VisibleForTesting
Julien Desprez0c836c92016-08-10 14:40:41 +01002316 protected File getBugreportzInternal() {
Julien Desprez15de6812016-08-08 15:08:06 +01002317 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
Julien Desprez16184162016-06-10 08:56:17 +01002318 // Does not rely on {@link ITestDevice#executeAdbCommand(String...)} because it does not
2319 // provide a timeout.
Julien Desprez16184162016-06-10 08:56:17 +01002320 try {
2321 executeShellCommand(BUGREPORTZ_CMD, receiver,
Julien Desprezf54735c2016-08-16 09:17:07 +01002322 BUGREPORTZ_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
Julien Desprez16184162016-06-10 08:56:17 +01002323 String output = receiver.getOutput().trim();
2324 Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
2325 if (!match.find()) {
2326 CLog.e("Something went went wrong during bugreportz collection: '%s'", output);
2327 return null;
2328 } else {
2329 String remoteFilePath = match.group(2);
2330 File zipFile = null;
2331 try {
2332 if (!doesFileExist(remoteFilePath)) {
2333 CLog.e("Did not find bugreportz at: %s", remoteFilePath);
2334 return null;
2335 }
2336 // Create a placeholder to replace the file
2337 zipFile = FileUtil.createTempFile("bugreportz", ".zip");
2338 pullFile(remoteFilePath, zipFile);
2339 String bugreportDir =
2340 remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
2341 if (!bugreportDir.isEmpty()) {
2342 // clean bugreport files directory on device
2343 executeShellCommand(String.format("rm %s/*", bugreportDir));
2344 }
2345
Julien Desprez15de6812016-08-08 15:08:06 +01002346 return zipFile;
Julien Desprez16184162016-06-10 08:56:17 +01002347 } catch (IOException e) {
2348 CLog.e("Failed to create the temporary file.");
2349 return null;
2350 }
2351 }
2352 } catch (DeviceNotAvailableException e) {
2353 CLog.e("Device %s became unresponsive while retrieving bugreportz", getSerialNumber());
2354 CLog.e(e);
2355 }
2356 return null;
Julien Desprez6961b272016-02-01 09:58:23 +00002357 }
2358
jdesprezc3537f72017-09-05 17:27:14 -07002359 protected InputStreamSource getBugreportInternal() {
2360 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2361 try {
2362 executeShellCommand(
2363 BUGREPORT_CMD,
2364 receiver,
2365 BUGREPORT_TIMEOUT,
2366 TimeUnit.MILLISECONDS,
2367 0 /* don't retry */);
2368 } catch (DeviceNotAvailableException e) {
2369 // Log, but don't throw, so the caller can get the bugreport contents even
2370 // if the device goes away
2371 CLog.e("Device %s became unresponsive while retrieving bugreport", getSerialNumber());
2372 return null;
2373 }
2374 return new ByteArrayInputStreamSource(receiver.getOutput());
2375 }
2376
Julien Desprez6961b272016-02-01 09:58:23 +00002377 /**
2378 * {@inheritDoc}
2379 */
2380 @Override
2381 public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002382 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00002383 }
2384
2385 /**
2386 * {@inheritDoc}
2387 */
2388 @Override
2389 public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002390 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00002391 }
2392
Insaf Latypov73f7aeb2017-03-03 11:05:08 +00002393 /** {@inheritDoc} */
2394 @Override
2395 public InputStreamSource getScreenshot(String format, boolean rescale)
2396 throws DeviceNotAvailableException {
2397 throw new UnsupportedOperationException("No support for Screenshot");
2398 }
2399
2400 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002401 @Override
2402 public void clearLastConnectedWifiNetwork() {
2403 mLastConnectedWifiSsid = null;
2404 mLastConnectedWifiPsk = null;
2405 }
2406
2407 /**
2408 * {@inheritDoc}
2409 */
2410 @Override
2411 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk)
2412 throws DeviceNotAvailableException {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002413 return connectToWifiNetwork(wifiSsid, wifiPsk, false);
2414 }
2415
2416 /**
2417 * {@inheritDoc}
2418 */
2419 @Override
2420 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)
2421 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00002422 // Clears the last connected wifi network.
2423 mLastConnectedWifiSsid = null;
2424 mLastConnectedWifiPsk = null;
2425
2426 // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()}
Julien Desprez8539c082016-03-04 13:39:19 +00002427 // times
Julien Desprez6961b272016-02-01 09:58:23 +00002428 Random rnd = new Random();
2429 int backoffSlotCount = 2;
Jeffrey Lu279122f2018-01-29 17:25:08 -08002430 int slotTime = mOptions.getWifiRetryWaitTime();
2431 int waitTime = 0;
Julien Desprez6961b272016-02-01 09:58:23 +00002432 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002433 long startTime = mClock.millis();
2434 for (int i = 1; i <= mOptions.getWifiAttempts(); i++) {
2435 CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber());
2436 boolean success =
2437 wifi.connectToNetwork(wifiSsid, wifiPsk, mOptions.getConnCheckUrl(), scanSsid);
2438 final Map<String, String> wifiInfo = wifi.getWifiInfo();
2439 if (success) {
2440 CLog.i(
2441 "Successfully connected to wifi network %s(%s) on %s",
2442 wifiSsid, wifiInfo.get("bssid"), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00002443
Julien Desprezb1301842018-04-16 10:12:52 -07002444 mLastConnectedWifiSsid = wifiSsid;
2445 mLastConnectedWifiPsk = wifiPsk;
Julien Desprez6961b272016-02-01 09:58:23 +00002446
Julien Desprezb1301842018-04-16 10:12:52 -07002447 return true;
2448 } else {
2449 CLog.w(
2450 "Failed to connect to wifi network %s(%s) on %s on attempt %d of %d",
2451 wifiSsid,
2452 wifiInfo.get("bssid"),
2453 getSerialNumber(),
2454 i,
2455 mOptions.getWifiAttempts());
Julien Desprez6961b272016-02-01 09:58:23 +00002456 }
Julien Desprezb1301842018-04-16 10:12:52 -07002457 if (mClock.millis() - startTime >= mOptions.getMaxWifiConnectTime()) {
2458 CLog.e(
2459 "Failed to connect to wifi after %d ms. Aborting.",
2460 mOptions.getMaxWifiConnectTime());
2461 break;
2462 }
2463 if (i < mOptions.getWifiAttempts()) {
2464 if (mOptions.isWifiExpoRetryEnabled()) {
2465 // use binary exponential back-offs when retrying.
2466 waitTime = rnd.nextInt(backoffSlotCount) * slotTime;
2467 backoffSlotCount *= 2;
2468 }
2469 CLog.e("Waiting for %d ms before reconnecting to %s...", waitTime, wifiSsid);
2470 getRunUtil().sleep(waitTime);
2471 }
Julien Desprez6961b272016-02-01 09:58:23 +00002472 }
2473 return false;
2474 }
2475
2476 /**
2477 * {@inheritDoc}
2478 */
2479 @Override
2480 public boolean checkConnectivity() throws DeviceNotAvailableException {
2481 IWifiHelper wifi = createWifiHelper();
2482 return wifi.checkConnectivity(mOptions.getConnCheckUrl());
2483 }
2484
2485 /**
2486 * {@inheritDoc}
2487 */
2488 @Override
2489 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)
2490 throws DeviceNotAvailableException {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002491 return connectToWifiNetworkIfNeeded(wifiSsid, wifiPsk, false);
2492 }
2493
2494 /**
2495 * {@inheritDoc}
2496 */
2497 @Override
2498 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)
2499 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00002500 if (!checkConnectivity()) {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002501 return connectToWifiNetwork(wifiSsid, wifiPsk, scanSsid);
Julien Desprez6961b272016-02-01 09:58:23 +00002502 }
2503 return true;
2504 }
2505
2506 /**
2507 * {@inheritDoc}
2508 */
2509 @Override
2510 public boolean isWifiEnabled() throws DeviceNotAvailableException {
Julien Desprezac5f37f2017-02-13 10:41:22 +00002511 final IWifiHelper wifi = createWifiHelper();
Julien Desprez6961b272016-02-01 09:58:23 +00002512 try {
Julien Desprez6961b272016-02-01 09:58:23 +00002513 return wifi.isWifiEnabled();
2514 } catch (RuntimeException e) {
2515 CLog.w("Failed to create WifiHelper: %s", e.getMessage());
2516 return false;
2517 }
2518 }
2519
2520 /**
2521 * Checks that the device is currently successfully connected to given wifi SSID.
2522 *
2523 * @param wifiSSID the wifi ssid
2524 * @return <code>true</code> if device is currently connected to wifiSSID and has network
2525 * connectivity. <code>false</code> otherwise
2526 * @throws DeviceNotAvailableException if connection with device was lost
2527 */
2528 boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException {
2529 CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber());
2530 final IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002531 // getSSID returns SSID as "SSID"
2532 final String quotedSSID = String.format("\"%s\"", wifiSSID);
Julien Desprez6961b272016-02-01 09:58:23 +00002533
Julien Desprezb1301842018-04-16 10:12:52 -07002534 boolean test = wifi.isWifiEnabled();
2535 CLog.v("%s: wifi enabled? %b", getSerialNumber(), test);
Julien Desprez6961b272016-02-01 09:58:23 +00002536
Julien Desprezb1301842018-04-16 10:12:52 -07002537 if (test) {
2538 final String actualSSID = wifi.getSSID();
2539 test = quotedSSID.equals(actualSSID);
2540 CLog.v("%s: SSID match (%s, %s, %b)", getSerialNumber(), quotedSSID, actualSSID, test);
Julien Desprez6961b272016-02-01 09:58:23 +00002541 }
Julien Desprezb1301842018-04-16 10:12:52 -07002542 if (test) {
2543 test = wifi.hasValidIp();
2544 CLog.v("%s: validIP? %b", getSerialNumber(), test);
2545 }
2546 if (test) {
2547 test = checkConnectivity();
2548 CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test);
2549 }
2550 return test;
Julien Desprez6961b272016-02-01 09:58:23 +00002551 }
2552
2553 /**
2554 * {@inheritDoc}
2555 */
2556 @Override
2557 public boolean disconnectFromWifi() throws DeviceNotAvailableException {
2558 CLog.i("Disconnecting from wifi on %s", getSerialNumber());
2559 // Clears the last connected wifi network.
2560 mLastConnectedWifiSsid = null;
2561 mLastConnectedWifiPsk = null;
2562
2563 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002564 return wifi.disconnectFromNetwork();
Julien Desprez6961b272016-02-01 09:58:23 +00002565 }
2566
2567 /**
2568 * {@inheritDoc}
2569 */
2570 @Override
2571 public String getIpAddress() throws DeviceNotAvailableException {
2572 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002573 return wifi.getIpAddress();
Julien Desprez6961b272016-02-01 09:58:23 +00002574 }
2575
2576 /**
2577 * {@inheritDoc}
2578 */
2579 @Override
2580 public boolean enableNetworkMonitor() throws DeviceNotAvailableException {
2581 mNetworkMonitorEnabled = false;
2582
2583 IWifiHelper wifi = createWifiHelper();
Julien Desprezb1301842018-04-16 10:12:52 -07002584 wifi.stopMonitor();
2585 if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) {
2586 mNetworkMonitorEnabled = true;
2587 return true;
Julien Desprez6961b272016-02-01 09:58:23 +00002588 }
2589 return false;
2590 }
2591
2592 /**
2593 * {@inheritDoc}
2594 */
2595 @Override
2596 public boolean disableNetworkMonitor() throws DeviceNotAvailableException {
2597 mNetworkMonitorEnabled = false;
2598
2599 IWifiHelper wifi = createWifiHelper();
2600 List<Long> samples = wifi.stopMonitor();
2601 if (!samples.isEmpty()) {
2602 int failures = 0;
2603 long totalLatency = 0;
2604 for (Long sample : samples) {
2605 if (sample < 0) {
2606 failures += 1;
2607 } else {
2608 totalLatency += sample;
2609 }
2610 }
2611 double failureRate = failures * 100.0 / samples.size();
2612 double avgLatency = 0.0;
2613 if (failures < samples.size()) {
2614 avgLatency = totalLatency / (samples.size() - failures);
2615 }
2616 CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f",
2617 mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000,
2618 failureRate, avgLatency);
2619 }
2620 return true;
2621 }
2622
2623 /**
2624 * Create a {@link WifiHelper} to use
Jeffrey Lu279122f2018-01-29 17:25:08 -08002625 *
2626 * <p>
2627 *
Julien Desprez021af1d2016-11-29 14:54:07 +00002628 * @throws DeviceNotAvailableException
Julien Desprez6961b272016-02-01 09:58:23 +00002629 */
Jeffrey Lu279122f2018-01-29 17:25:08 -08002630 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00002631 IWifiHelper createWifiHelper() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002632 // current wifi helper won't work on AndroidNativeDevice
2633 // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when
2634 // we learn what is available.
2635 throw new UnsupportedOperationException("Wifi helper is not supported.");
Julien Desprez6961b272016-02-01 09:58:23 +00002636 }
2637
2638 /**
2639 * {@inheritDoc}
2640 */
2641 @Override
2642 public boolean clearErrorDialogs() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002643 throw new UnsupportedOperationException("No support for Screen's features");
Julien Desprez6961b272016-02-01 09:58:23 +00002644 }
2645
Julien Desprez14e96692017-01-12 12:31:29 +00002646 /** {@inheritDoc} */
2647 @Override
2648 public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException {
2649 throw new UnsupportedOperationException("No support for keyguard querying.");
2650 }
2651
Julien Desprez6961b272016-02-01 09:58:23 +00002652 IDeviceStateMonitor getDeviceStateMonitor() {
2653 return mStateMonitor;
2654 }
2655
2656 /**
2657 * {@inheritDoc}
2658 */
2659 @Override
2660 public void postBootSetup() throws DeviceNotAvailableException {
2661 enableAdbRoot();
Julien Desprezc8474552016-02-17 10:59:27 +00002662 prePostBootSetup();
Julien Desprez6961b272016-02-01 09:58:23 +00002663 for (String command : mOptions.getPostBootCommands()) {
2664 executeShellCommand(command);
2665 }
2666 }
2667
2668 /**
Julien Desprezc8474552016-02-17 10:59:27 +00002669 * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for
2670 * specific post boot setup.
2671 * @throws DeviceNotAvailableException
2672 */
2673 protected void prePostBootSetup() throws DeviceNotAvailableException {
2674 // Empty on purpose.
2675 }
2676
2677 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002678 * Ensure wifi connection is re-established after boot. This is intended to be called after TF
2679 * initiated reboots(ones triggered by {@link #reboot()}) only.
2680 *
2681 * @throws DeviceNotAvailableException
2682 */
2683 void postBootWifiSetup() throws DeviceNotAvailableException {
2684 if (mLastConnectedWifiSsid != null) {
2685 reconnectToWifiNetwork();
2686 }
2687 if (mNetworkMonitorEnabled) {
2688 if (!enableNetworkMonitor()) {
2689 CLog.w("Failed to enable network monitor on %s after reboot", getSerialNumber());
2690 }
2691 }
2692 }
2693
2694 void reconnectToWifiNetwork() throws DeviceNotAvailableException {
2695 // First, wait for wifi to re-connect automatically.
2696 long startTime = System.currentTimeMillis();
2697 boolean isConnected = checkConnectivity();
2698 while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) {
2699 getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL);
2700 isConnected = checkConnectivity();
2701 }
2702
2703 if (isConnected) {
2704 return;
2705 }
2706
2707 // If wifi is still not connected, try to re-connect on our own.
2708 final String wifiSsid = mLastConnectedWifiSsid;
2709 if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) {
2710 throw new NetworkNotAvailableException(
2711 String.format("Failed to connect to wifi network %s on %s after reboot",
2712 wifiSsid, getSerialNumber()));
2713 }
2714 }
2715
2716 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002717 * {@inheritDoc}
2718 */
2719 @Override
2720 public void rebootIntoBootloader()
2721 throws DeviceNotAvailableException, UnsupportedOperationException {
2722 if (!mFastbootEnabled) {
2723 throw new UnsupportedOperationException(
2724 "Fastboot is not available and cannot reboot into bootloader");
2725 }
2726 CLog.i("Rebooting device %s in state %s into bootloader", getSerialNumber(),
2727 getDeviceState());
2728 if (TestDeviceState.FASTBOOT.equals(getDeviceState())) {
2729 CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
2730 executeFastbootCommand("reboot-bootloader");
2731 } else {
2732 CLog.i("Booting device %s into bootloader", getSerialNumber());
2733 doAdbRebootBootloader();
2734 }
2735 if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
2736 recoverDeviceFromBootloader();
2737 }
2738 }
2739
2740 private void doAdbRebootBootloader() throws DeviceNotAvailableException {
2741 doAdbReboot("bootloader");
2742 }
2743
2744 /**
2745 * {@inheritDoc}
2746 */
2747 @Override
2748 public void reboot() throws DeviceNotAvailableException {
2749 rebootUntilOnline();
2750
2751 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2752 setRecoveryMode(RecoveryMode.ONLINE);
2753
2754 if (isEncryptionSupported() && isDeviceEncrypted()) {
2755 unlockDevice();
2756 }
2757
2758 setRecoveryMode(cachedRecoveryMode);
2759
2760 if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) {
2761 postBootSetup();
2762 postBootWifiSetup();
2763 return;
2764 } else {
2765 recoverDevice();
2766 }
2767 }
2768
2769 /**
2770 * {@inheritDoc}
2771 */
2772 @Override
2773 public void rebootUntilOnline() throws DeviceNotAvailableException {
2774 doReboot();
2775 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2776 setRecoveryMode(RecoveryMode.ONLINE);
2777 if (mStateMonitor.waitForDeviceOnline() != null) {
2778 enableAdbRoot();
2779 } else {
2780 recoverDevice();
2781 }
2782 setRecoveryMode(cachedRecoveryMode);
2783 }
2784
2785 /**
2786 * {@inheritDoc}
2787 */
2788 @Override
2789 public void rebootIntoRecovery() throws DeviceNotAvailableException {
2790 if (TestDeviceState.FASTBOOT == getDeviceState()) {
2791 CLog.w("device %s in fastboot when requesting boot to recovery. " +
2792 "Rebooting to userspace first.", getSerialNumber());
2793 rebootUntilOnline();
2794 }
2795 doAdbReboot("recovery");
2796 if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) {
2797 recoverDeviceInRecovery();
2798 }
2799 }
2800
2801 /**
2802 * {@inheritDoc}
2803 */
2804 @Override
2805 public void nonBlockingReboot() throws DeviceNotAvailableException {
2806 doReboot();
2807 }
2808
Julien Desprez78344aa2018-09-04 16:06:05 -07002809 /**
2810 * Trigger a reboot of the device, offers no guarantee of the device state after the call.
2811 *
2812 * @throws DeviceNotAvailableException
2813 * @throws UnsupportedOperationException
2814 */
Jeffrey Lu279122f2018-01-29 17:25:08 -08002815 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00002816 void doReboot() throws DeviceNotAvailableException, UnsupportedOperationException {
2817 if (TestDeviceState.FASTBOOT == getDeviceState()) {
2818 CLog.i("device %s in fastboot. Rebooting to userspace.", getSerialNumber());
2819 executeFastbootCommand("reboot");
2820 } else {
Guang Zhu120ed1c2016-02-24 23:31:49 -08002821 if (mOptions.shouldDisableReboot()) {
2822 CLog.i("Device reboot disabled by options, skipped.");
2823 return;
2824 }
Julien Desprez6961b272016-02-01 09:58:23 +00002825 CLog.i("Rebooting device %s", getSerialNumber());
2826 doAdbReboot(null);
Julien Desprez78344aa2018-09-04 16:06:05 -07002827 // Check if device shows as unavailable (as expected after reboot).
2828 boolean notAvailable = waitForDeviceNotAvailable(DEFAULT_UNAVAILABLE_TIMEOUT);
2829 if (notAvailable) {
2830 postAdbReboot();
2831 } else {
2832 CLog.w(
2833 "Did not detect device %s becoming unavailable after reboot",
2834 getSerialNumber());
2835 }
Julien Desprez6961b272016-02-01 09:58:23 +00002836 }
2837 }
2838
2839 /**
Julien Desprez78344aa2018-09-04 16:06:05 -07002840 * Possible extra actions that can be taken after a reboot.
2841 *
2842 * @throws DeviceNotAvailableException
2843 */
2844 protected void postAdbReboot() throws DeviceNotAvailableException {
2845 // Default implementation empty on purpose.
2846 }
2847
2848 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002849 * Perform a adb reboot.
2850 *
2851 * @param into the bootloader name to reboot into, or <code>null</code> to just reboot the
2852 * device.
2853 * @throws DeviceNotAvailableException
2854 */
Julien Desprezc8474552016-02-17 10:59:27 +00002855 protected void doAdbReboot(final String into) throws DeviceNotAvailableException {
Julien Desprez78344aa2018-09-04 16:06:05 -07002856 DeviceAction rebootAction = createRebootDeviceAction(into);
Julien Desprezc8474552016-02-17 10:59:27 +00002857 performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS);
Julien Desprez78344aa2018-09-04 16:06:05 -07002858 }
Julien Desprezc8474552016-02-17 10:59:27 +00002859
Julien Desprez78344aa2018-09-04 16:06:05 -07002860 /**
2861 * Create a {@link RebootDeviceAction} to be used when performing a reboot action.
2862 *
2863 * @param into the bootloader name to reboot into, or <code>null</code> to just reboot the
2864 * device.
2865 * @return the created {@link RebootDeviceAction}.
2866 */
2867 protected RebootDeviceAction createRebootDeviceAction(final String into) {
2868 return new RebootDeviceAction(into);
Julien Desprez6961b272016-02-01 09:58:23 +00002869 }
2870
Julien Desprezc8474552016-02-17 10:59:27 +00002871 protected void waitForDeviceNotAvailable(String operationDesc, long time) {
Julien Desprez6961b272016-02-01 09:58:23 +00002872 // TODO: a bit of a race condition here. Would be better to start a
2873 // before the operation
2874 if (!mStateMonitor.waitForDeviceNotAvailable(time)) {
2875 // above check is flaky, ignore till better solution is found
2876 CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(),
2877 operationDesc);
2878 }
2879 }
2880
2881 /**
2882 * {@inheritDoc}
2883 */
2884 @Override
2885 public boolean enableAdbRoot() throws DeviceNotAvailableException {
2886 // adb root is a relatively intensive command, so do a brief check first to see
2887 // if its necessary or not
2888 if (isAdbRoot()) {
2889 CLog.i("adb is already running as root on %s", getSerialNumber());
jdesprezdcc60fc2017-08-07 14:51:00 -07002890 // Still check for online, in some case we could see the root, but device could be
2891 // very early in its cycle.
2892 waitForDeviceOnline();
Julien Desprez6961b272016-02-01 09:58:23 +00002893 return true;
2894 }
2895 // Don't enable root if user requested no root
2896 if (!isEnableAdbRoot()) {
2897 CLog.i("\"enable-root\" set to false; ignoring 'adb root' request");
2898 return false;
2899 }
2900 CLog.i("adb root on device %s", getSerialNumber());
2901 int attempts = MAX_RETRY_ATTEMPTS + 1;
2902 for (int i=1; i <= attempts; i++) {
2903 String output = executeAdbCommand("root");
2904 // wait for device to disappear from adb
2905 waitForDeviceNotAvailable("root", 20 * 1000);
Julien Desprez8539c082016-03-04 13:39:19 +00002906
2907 postAdbRootAction();
2908
Julien Desprez6961b272016-02-01 09:58:23 +00002909 // wait for device to be back online
2910 waitForDeviceOnline();
2911
2912 if (isAdbRoot()) {
2913 return true;
2914 }
2915 CLog.w("'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'",
2916 getSerialNumber(), i, attempts, output);
2917 }
2918 return false;
2919 }
2920
2921 /**
Julien Desprez22d0c3a2016-04-15 10:38:08 +01002922 * {@inheritDoc}
2923 */
2924 @Override
2925 public boolean disableAdbRoot() throws DeviceNotAvailableException {
2926 if (!isAdbRoot()) {
2927 CLog.i("adb is already unroot on %s", getSerialNumber());
2928 return true;
2929 }
2930
2931 CLog.i("adb unroot on device %s", getSerialNumber());
2932 int attempts = MAX_RETRY_ATTEMPTS + 1;
2933 for (int i=1; i <= attempts; i++) {
2934 String output = executeAdbCommand("unroot");
2935 // wait for device to disappear from adb
2936 waitForDeviceNotAvailable("unroot", 5 * 1000);
2937
2938 postAdbUnrootAction();
2939
2940 // wait for device to be back online
2941 waitForDeviceOnline();
2942
2943 if (!isAdbRoot()) {
2944 return true;
2945 }
2946 CLog.w("'adb unroot' on %s unsuccessful on attempt %d of %d. Output: '%s'",
2947 getSerialNumber(), i, attempts, output);
2948 }
2949 return false;
2950 }
2951
2952 /**
Julien Desprez8539c082016-03-04 13:39:19 +00002953 * Override if the device needs some specific actions to be taken after adb root and before the
2954 * device is back online.
2955 * Default implementation doesn't include any addition actions.
2956 * adb root is not guaranteed to be enabled at this stage.
Julien Desprez021af1d2016-11-29 14:54:07 +00002957 * @throws DeviceNotAvailableException
Julien Desprez8539c082016-03-04 13:39:19 +00002958 */
Julien Desprez9dca62e2016-04-08 14:47:57 +01002959 public void postAdbRootAction() throws DeviceNotAvailableException {
Julien Desprez8539c082016-03-04 13:39:19 +00002960 // Empty on purpose.
2961 }
2962
2963 /**
Julien Desprez22d0c3a2016-04-15 10:38:08 +01002964 * Override if the device needs some specific actions to be taken after adb unroot and before
2965 * the device is back online.
2966 * Default implementation doesn't include any additional actions.
2967 * adb root is not guaranteed to be disabled at this stage.
Julien Desprez021af1d2016-11-29 14:54:07 +00002968 * @throws DeviceNotAvailableException
Julien Desprez22d0c3a2016-04-15 10:38:08 +01002969 */
Julien Desprez5e4fc6b2016-06-06 10:08:46 +01002970 public void postAdbUnrootAction() throws DeviceNotAvailableException {
Julien Desprez22d0c3a2016-04-15 10:38:08 +01002971 // Empty on purpose.
2972 }
2973
2974 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002975 * {@inheritDoc}
2976 */
2977 @Override
2978 public boolean isAdbRoot() throws DeviceNotAvailableException {
2979 String output = executeShellCommand("id");
2980 return output.contains("uid=0(root)");
2981 }
2982
2983 /**
2984 * {@inheritDoc}
2985 */
2986 @Override
2987 public boolean encryptDevice(boolean inplace) throws DeviceNotAvailableException,
2988 UnsupportedOperationException {
2989 if (!isEncryptionSupported()) {
2990 throw new UnsupportedOperationException(String.format("Can't encrypt device %s: "
2991 + "encryption not supported", getSerialNumber()));
2992 }
2993
2994 if (isDeviceEncrypted()) {
2995 CLog.d("Device %s is already encrypted, skipping", getSerialNumber());
2996 return true;
2997 }
2998
2999 enableAdbRoot();
3000
3001 String encryptMethod;
3002 long timeout;
3003 if (inplace) {
3004 encryptMethod = "inplace";
3005 timeout = ENCRYPTION_INPLACE_TIMEOUT_MIN;
3006 } else {
3007 encryptMethod = "wipe";
3008 timeout = ENCRYPTION_WIPE_TIMEOUT_MIN;
3009 }
3010
3011 CLog.i("Encrypting device %s via %s", getSerialNumber(), encryptMethod);
3012
3013 // enable crypto takes one of the following formats:
3014 // cryptfs enablecrypto <wipe|inplace> <passwd>
3015 // cryptfs enablecrypto <wipe|inplace> default|password|pin|pattern [passwd]
3016 // Try the first one first, if it outputs "500 0 Usage: ...", try the second.
3017 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
3018 String command = String.format("vdc cryptfs enablecrypto %s \"%s\"", encryptMethod,
3019 ENCRYPTION_PASSWORD);
3020 executeShellCommand(command, receiver, timeout, TimeUnit.MINUTES, 1);
Julien Desprezac96c812016-08-10 14:57:45 +01003021 if (receiver.getOutput().split(":")[0].matches("500 \\d+ Usage")) {
Julien Desprez6961b272016-02-01 09:58:23 +00003022 command = String.format("vdc cryptfs enablecrypto %s default", encryptMethod);
3023 executeShellCommand(command, new NullOutputReceiver(), timeout, TimeUnit.MINUTES, 1);
3024 }
3025
3026 waitForDeviceNotAvailable("reboot", getCommandTimeout());
3027 waitForDeviceOnline(); // Device will not become available until the user data is unlocked.
3028
3029 return isDeviceEncrypted();
3030 }
3031
3032 /**
3033 * {@inheritDoc}
3034 */
3035 @Override
3036 public boolean unencryptDevice() throws DeviceNotAvailableException,
3037 UnsupportedOperationException {
3038 if (!isEncryptionSupported()) {
3039 throw new UnsupportedOperationException(String.format("Can't unencrypt device %s: "
3040 + "encryption not supported", getSerialNumber()));
3041 }
3042
3043 if (!isDeviceEncrypted()) {
3044 CLog.d("Device %s is already unencrypted, skipping", getSerialNumber());
3045 return true;
3046 }
3047
3048 CLog.i("Unencrypting device %s", getSerialNumber());
3049
3050 // If the device supports fastboot format, then we're done.
3051 if (!mOptions.getUseFastbootErase()) {
3052 rebootIntoBootloader();
3053 fastbootWipePartition("userdata");
3054 rebootUntilOnline();
3055 waitForDeviceAvailable(ENCRYPTION_WIPE_TIMEOUT_MIN * 60 * 1000);
3056 return true;
3057 }
3058
3059 // Determine if we need to format partition instead of wipe.
3060 boolean format = false;
3061 String output = executeShellCommand("vdc volume list");
3062 String[] splitOutput;
3063 if (output != null) {
3064 splitOutput = output.split("\r?\n");
3065 for (String line : splitOutput) {
3066 if (line.startsWith("110 ") && line.contains("sdcard /mnt/sdcard") &&
3067 !line.endsWith("0")) {
3068 format = true;
3069 }
3070 }
3071 }
3072
3073 rebootIntoBootloader();
3074 fastbootWipePartition("userdata");
3075
3076 // If the device requires time to format the filesystem after fastboot erase userdata, wait
3077 // for the device to reboot a second time.
3078 if (mOptions.getUnencryptRebootTimeout() > 0) {
3079 rebootUntilOnline();
3080 if (waitForDeviceNotAvailable(mOptions.getUnencryptRebootTimeout())) {
3081 waitForDeviceOnline();
3082 }
3083 }
3084
3085 if (format) {
3086 CLog.d("Need to format sdcard for device %s", getSerialNumber());
3087
3088 RecoveryMode cachedRecoveryMode = getRecoveryMode();
3089 setRecoveryMode(RecoveryMode.ONLINE);
3090
3091 output = executeShellCommand("vdc volume format sdcard");
3092 if (output == null) {
3093 CLog.e("Command vdc volume format sdcard failed will no output for device %s:\n%s",
3094 getSerialNumber());
3095 setRecoveryMode(cachedRecoveryMode);
3096 return false;
3097 }
3098 splitOutput = output.split("\r?\n");
3099 if (!splitOutput[splitOutput.length - 1].startsWith("200 ")) {
3100 CLog.e("Command vdc volume format sdcard failed for device %s:\n%s",
3101 getSerialNumber(), output);
3102 setRecoveryMode(cachedRecoveryMode);
3103 return false;
3104 }
3105
3106 setRecoveryMode(cachedRecoveryMode);
3107 }
3108
3109 reboot();
3110
3111 return true;
3112 }
3113
3114 /**
3115 * {@inheritDoc}
3116 */
3117 @Override
3118 public boolean unlockDevice() throws DeviceNotAvailableException,
3119 UnsupportedOperationException {
3120 if (!isEncryptionSupported()) {
3121 throw new UnsupportedOperationException(String.format("Can't unlock device %s: "
3122 + "encryption not supported", getSerialNumber()));
3123 }
3124
3125 if (!isDeviceEncrypted()) {
3126 CLog.d("Device %s is not encrypted, skipping", getSerialNumber());
3127 return true;
3128 }
3129
3130 CLog.i("Unlocking device %s", getSerialNumber());
3131
3132 enableAdbRoot();
3133
3134 // FIXME: currently, vcd checkpw can return an empty string when it never should. Try 3
3135 // times.
3136 String output;
3137 int i = 0;
3138 do {
3139 // Enter the password. Output will be:
3140 // "200 [X] -1" if the password has already been entered correctly,
3141 // "200 [X] 0" if the password is entered correctly,
3142 // "200 [X] N" where N is any positive number if the password is incorrect,
3143 // any other string if there is an error.
3144 output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"",
3145 ENCRYPTION_PASSWORD)).trim();
3146
3147 if (output.startsWith("200 ") && output.endsWith(" -1")) {
3148 return true;
3149 }
3150
3151 if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) {
3152 CLog.e("checkpw gave output '%s' while trying to unlock device %s",
3153 output, getSerialNumber());
3154 return false;
3155 }
3156
3157 getRunUtil().sleep(500);
3158 } while (output.isEmpty() && ++i < 3);
3159
3160 if (output.isEmpty()) {
3161 CLog.e("checkpw gave no output while trying to unlock device %s");
3162 }
3163
3164 // Restart the framework. Output will be:
3165 // "200 [X] 0" if the user data partition can be mounted,
3166 // "200 [X] -1" if the user data partition can not be mounted (no correct password given),
3167 // any other string if there is an error.
3168 output = executeShellCommand("vdc cryptfs restart").trim();
3169
3170 if (!(output.startsWith("200 ") && output.endsWith(" 0"))) {
3171 CLog.e("restart gave output '%s' while trying to unlock device %s", output,
3172 getSerialNumber());
3173 return false;
3174 }
3175
3176 waitForDeviceAvailable();
3177
3178 return true;
3179 }
3180
3181 /**
3182 * {@inheritDoc}
3183 */
3184 @Override
3185 public boolean isDeviceEncrypted() throws DeviceNotAvailableException {
Julien Desprezaf166b32016-08-08 11:58:47 +01003186 String output = getProperty("ro.crypto.state");
Julien Desprez6961b272016-02-01 09:58:23 +00003187
3188 if (output == null && isEncryptionSupported()) {
3189 CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber());
3190 }
3191
3192 return "encrypted".equals(output);
3193 }
3194
3195 /**
3196 * {@inheritDoc}
3197 */
3198 @Override
3199 public boolean isEncryptionSupported() throws DeviceNotAvailableException {
3200 if (!isEnableAdbRoot()) {
3201 CLog.i("root is required for encryption");
3202 mIsEncryptionSupported = false;
3203 return mIsEncryptionSupported;
3204 }
3205 if (mIsEncryptionSupported != null) {
3206 return mIsEncryptionSupported.booleanValue();
3207 }
3208 enableAdbRoot();
3209 String output = executeShellCommand("vdc cryptfs enablecrypto").trim();
jdesprezdd9d9e92017-07-13 03:34:19 -07003210
3211 mIsEncryptionSupported =
3212 (output != null
3213 && Pattern.matches("(500)(\\s+)(\\d+)(\\s+)(Usage)(.*)(:)(.*)", output));
Julien Desprez6961b272016-02-01 09:58:23 +00003214 return mIsEncryptionSupported;
3215 }
3216
3217 /**
3218 * {@inheritDoc}
3219 */
3220 @Override
3221 public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException {
3222 if (mStateMonitor.waitForDeviceOnline(waitTime) == null) {
3223 recoverDevice();
3224 }
3225 }
3226
3227 /**
3228 * {@inheritDoc}
3229 */
3230 @Override
3231 public void waitForDeviceOnline() throws DeviceNotAvailableException {
3232 if (mStateMonitor.waitForDeviceOnline() == null) {
3233 recoverDevice();
3234 }
3235 }
3236
3237 /**
3238 * {@inheritDoc}
3239 */
3240 @Override
3241 public void waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException {
3242 if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) {
3243 recoverDevice();
3244 }
3245 }
3246
3247 /**
3248 * {@inheritDoc}
3249 */
3250 @Override
3251 public void waitForDeviceAvailable() throws DeviceNotAvailableException {
3252 if (mStateMonitor.waitForDeviceAvailable() == null) {
3253 recoverDevice();
3254 }
3255 }
3256
3257 /**
3258 * {@inheritDoc}
3259 */
3260 @Override
3261 public boolean waitForDeviceNotAvailable(long waitTime) {
3262 return mStateMonitor.waitForDeviceNotAvailable(waitTime);
3263 }
3264
3265 /**
3266 * {@inheritDoc}
3267 */
3268 @Override
3269 public boolean waitForDeviceInRecovery(long waitTime) {
3270 return mStateMonitor.waitForDeviceInRecovery(waitTime);
3271 }
3272
3273 /**
3274 * Small helper function to throw an NPE if the passed arg is null. This should be used when
3275 * some value will be stored and used later, in which case it'll avoid hard-to-trace
3276 * asynchronous NullPointerExceptions by throwing the exception synchronously. This is not
3277 * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care
3278 * of it in that case.
3279 */
3280 private void throwIfNull(Object obj) {
3281 if (obj == null) throw new NullPointerException();
3282 }
3283
Jeffrey Lu279122f2018-01-29 17:25:08 -08003284 /** Retrieve this device's recovery mechanism. */
3285 @VisibleForTesting
Julien Desprez6961b272016-02-01 09:58:23 +00003286 IDeviceRecovery getRecovery() {
3287 return mRecovery;
3288 }
3289
3290 /**
3291 * {@inheritDoc}
3292 */
3293 @Override
3294 public void setRecovery(IDeviceRecovery recovery) {
3295 throwIfNull(recovery);
3296 mRecovery = recovery;
3297 }
3298
3299 /**
3300 * {@inheritDoc}
3301 */
3302 @Override
3303 public void setRecoveryMode(RecoveryMode mode) {
3304 throwIfNull(mRecoveryMode);
3305 mRecoveryMode = mode;
3306 }
3307
3308 /**
3309 * {@inheritDoc}
3310 */
3311 @Override
3312 public RecoveryMode getRecoveryMode() {
3313 return mRecoveryMode;
3314 }
3315
3316 /**
3317 * {@inheritDoc}
3318 */
3319 @Override
3320 public void setFastbootEnabled(boolean fastbootEnabled) {
3321 mFastbootEnabled = fastbootEnabled;
3322 }
3323
3324 /**
3325 * {@inheritDoc}
3326 */
3327 @Override
Julien Desprez3a503442016-04-05 15:00:41 +01003328 public boolean isFastbootEnabled() {
3329 return mFastbootEnabled;
3330 }
3331
3332 /**
3333 * {@inheritDoc}
3334 */
3335 @Override
Julien Desprez0a7d67d2016-07-21 16:05:57 +01003336 public void setFastbootPath(String fastbootPath) {
3337 mFastbootPath = fastbootPath;
3338 // ensure the device and its associated recovery use the same fastboot version.
3339 mRecovery.setFastbootPath(fastbootPath);
3340 }
3341
3342 /**
3343 * {@inheritDoc}
3344 */
3345 @Override
3346 public String getFastbootPath() {
3347 return mFastbootPath;
3348 }
3349
3350 /**
3351 * {@inheritDoc}
3352 */
3353 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003354 public void setDeviceState(final TestDeviceState deviceState) {
3355 if (!deviceState.equals(getDeviceState())) {
3356 // disable state changes while fastboot lock is held, because issuing fastboot command
3357 // will disrupt state
3358 if (getDeviceState().equals(TestDeviceState.FASTBOOT) && mFastbootLock.isLocked()) {
3359 return;
3360 }
3361 mState = deviceState;
3362 CLog.d("Device %s state is now %s", getSerialNumber(), deviceState);
3363 mStateMonitor.setState(deviceState);
3364 }
3365 }
3366
3367 /**
3368 * {@inheritDoc}
3369 */
3370 @Override
3371 public TestDeviceState getDeviceState() {
3372 return mState;
3373 }
3374
3375 @Override
3376 public boolean isAdbTcp() {
3377 return mStateMonitor.isAdbTcp();
3378 }
3379
3380 /**
3381 * {@inheritDoc}
3382 */
3383 @Override
3384 public String switchToAdbTcp() throws DeviceNotAvailableException {
3385 String ipAddress = getIpAddress();
3386 if (ipAddress == null) {
3387 CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber());
3388 return null;
3389 }
3390 String port = "5555";
3391 executeAdbCommand("tcpip", port);
3392 // TODO: analyze result? wait for device offline?
3393 return String.format("%s:%s", ipAddress, port);
3394 }
3395
3396 /**
3397 * {@inheritDoc}
3398 */
3399 @Override
3400 public boolean switchToAdbUsb() throws DeviceNotAvailableException {
3401 executeAdbCommand("usb");
3402 // TODO: analyze result? wait for device offline?
3403 return true;
3404 }
3405
3406 /**
3407 * {@inheritDoc}
3408 */
3409 @Override
3410 public void setEmulatorProcess(Process p) {
3411 mEmulatorProcess = p;
3412
3413 }
3414
3415 /**
3416 * For emulator set {@link SizeLimitedOutputStream} to log output
3417 * @param output to log the output
3418 */
3419 public void setEmulatorOutputStream(SizeLimitedOutputStream output) {
3420 mEmulatorOutput = output;
3421 }
3422
3423 /**
3424 * {@inheritDoc}
3425 */
3426 @Override
3427 public void stopEmulatorOutput() {
3428 if (mEmulatorOutput != null) {
3429 mEmulatorOutput.delete();
3430 mEmulatorOutput = null;
3431 }
3432 }
3433
3434 /**
3435 * {@inheritDoc}
3436 */
3437 @Override
3438 public InputStreamSource getEmulatorOutput() {
3439 if (getIDevice().isEmulator()) {
3440 if (mEmulatorOutput == null) {
3441 CLog.w("Emulator output for %s was not captured in background",
3442 getSerialNumber());
3443 } else {
3444 try {
jdesprez14084fb2017-07-26 14:36:39 -07003445 return new SnapshotInputStreamSource(
3446 "getEmulatorOutput", mEmulatorOutput.getData());
Julien Desprez6961b272016-02-01 09:58:23 +00003447 } catch (IOException e) {
3448 CLog.e("Failed to get %s data.", getSerialNumber());
3449 CLog.e(e);
3450 }
3451 }
3452 }
3453 return new ByteArrayInputStreamSource(new byte[0]);
3454 }
3455
3456 /**
3457 * {@inheritDoc}
3458 */
3459 @Override
3460 public Process getEmulatorProcess() {
3461 return mEmulatorProcess;
3462 }
3463
3464 /**
3465 * @return <code>true</code> if adb root should be enabled on device
3466 */
3467 public boolean isEnableAdbRoot() {
3468 return mOptions.isEnableAdbRoot();
3469 }
3470
3471 /**
3472 * {@inheritDoc}
3473 */
3474 @Override
3475 public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003476 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003477 }
3478
3479 /**
3480 * {@inheritDoc}
3481 */
3482 @Override
3483 public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003484 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003485 }
3486
3487 /**
3488 * {@inheritDoc}
3489 */
3490 @Override
3491 public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003492 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003493 }
3494
3495 /**
3496 * {@inheritDoc}
3497 */
3498 @Override
3499 public TestDeviceOptions getOptions() {
3500 return mOptions;
3501 }
3502
3503 /**
3504 * {@inheritDoc}
3505 */
3506 @Override
3507 public int getApiLevel() throws DeviceNotAvailableException {
3508 int apiLevel = UNKNOWN_API_LEVEL;
3509 try {
3510 String prop = getProperty("ro.build.version.sdk");
3511 apiLevel = Integer.parseInt(prop);
3512 } catch (NumberFormatException nfe) {
3513 // ignore, return unknown instead
3514 }
3515 return apiLevel;
3516 }
3517
jdesprezc3537f72017-09-05 17:27:14 -07003518 private int getApiLevelSafe() {
3519 try {
3520 return getApiLevel();
3521 } catch (DeviceNotAvailableException e) {
3522 CLog.e(e);
3523 return UNKNOWN_API_LEVEL;
3524 }
3525 }
3526
Julien Desprez6961b272016-02-01 09:58:23 +00003527 @Override
3528 public IDeviceStateMonitor getMonitor() {
3529 return mStateMonitor;
3530 }
3531
3532 /**
3533 * {@inheritDoc}
3534 */
3535 @Override
3536 public boolean waitForDeviceShell(long waitTime) {
3537 return mStateMonitor.waitForDeviceShell(waitTime);
3538 }
3539
3540 @Override
3541 public DeviceAllocationState getAllocationState() {
3542 return mAllocationState;
3543 }
3544
3545 /**
3546 * {@inheritDoc}
3547 * <p>
3548 * Process the DeviceEvent, which may or may not transition this device to a new allocation
3549 * state.
3550 * </p>
3551 */
3552 @Override
3553 public DeviceEventResponse handleAllocationEvent(DeviceEvent event) {
3554
3555 // keep track of whether state has actually changed or not
3556 boolean stateChanged = false;
3557 DeviceAllocationState newState;
3558 DeviceAllocationState oldState = mAllocationState;
3559 mAllocationStateLock.lock();
3560 try {
3561 // update oldState here, just in case in changed before we got lock
3562 oldState = mAllocationState;
3563 newState = mAllocationState.handleDeviceEvent(event);
3564 if (oldState != newState) {
3565 // state has changed! record this fact, and store the new state
3566 stateChanged = true;
3567 mAllocationState = newState;
3568 }
3569 } finally {
3570 mAllocationStateLock.unlock();
3571 }
3572 if (stateChanged && mAllocationMonitor != null) {
3573 // state has changed! Lets inform the allocation monitor listener
3574 mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState);
3575 }
3576 return new DeviceEventResponse(newState, stateChanged);
3577 }
3578
jdespreze2d5ed72018-03-07 14:48:16 -08003579 /** {@inheritDoc} */
3580 @Override
3581 public long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException {
Julien Desprezdcb19d52016-06-20 12:23:12 +01003582 Long deviceTime = getDeviceDate();
Julien Desprez6961b272016-02-01 09:58:23 +00003583 long offset = 0;
3584
Julien Desprez6961b272016-02-01 09:58:23 +00003585 if (date == null) {
3586 date = new Date();
3587 }
3588
jdesprez0b9d69e2017-12-11 03:40:39 -08003589 offset = date.getTime() - deviceTime;
Julien Desprez1fadf1a2016-10-20 15:50:46 +01003590 CLog.d("Time offset = %d ms", offset);
Julien Desprez6961b272016-02-01 09:58:23 +00003591 return offset;
3592 }
3593
3594 /**
3595 * {@inheritDoc}
3596 */
3597 @Override
3598 public void setDate(Date date) throws DeviceNotAvailableException {
3599 if (date == null) {
3600 date = new Date();
3601 }
3602 long timeOffset = getDeviceTimeOffset(date);
3603 // no need to set date
3604 if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) {
3605 return;
3606 }
3607 String dateString = null;
3608 if (getApiLevel() < 23) {
3609 // set date in epoch format
3610 dateString = Long.toString(date.getTime() / 1000); //ms to s
3611 } else {
3612 // set date with POSIX like params
3613 SimpleDateFormat sdf = new java.text.SimpleDateFormat(
3614 "MMddHHmmyyyy.ss");
3615 sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
3616 dateString = sdf.format(date);
3617 }
3618 // best effort, no verification
3619 executeShellCommand("date -u " + dateString);
3620 }
3621
3622 /**
3623 * {@inheritDoc}
3624 */
3625 @Override
Julien Desprezdcb19d52016-06-20 12:23:12 +01003626 public long getDeviceDate() throws DeviceNotAvailableException {
3627 String deviceTimeString = executeShellCommand("date +%s");
3628 Long deviceTime = null;
3629 try {
3630 deviceTime = Long.valueOf(deviceTimeString.trim());
3631 } catch (NumberFormatException nfe) {
3632 CLog.i("Invalid device time: \"%s\", ignored.", nfe);
3633 return 0;
3634 }
jdesprez0b9d69e2017-12-11 03:40:39 -08003635 // Convert from seconds to milliseconds
3636 return deviceTime * 1000L;
Julien Desprezdcb19d52016-06-20 12:23:12 +01003637 }
3638
3639 /**
3640 * {@inheritDoc}
3641 */
3642 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003643 public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException {
3644 return mStateMonitor.waitForBootComplete(timeOut);
3645 }
3646
3647 /**
3648 * {@inheritDoc}
3649 */
3650 @Override
3651 public ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003652 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003653 }
3654
3655 /**
3656 * {@inheritDoc}
3657 */
3658 @Override
3659 public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003660 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003661 }
3662
Alex Chau93617352018-01-16 15:22:25 +00003663 @Override
3664 public int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException {
3665 throw new UnsupportedOperationException("No support for user's feature.");
3666 }
3667
Julien Desprez6961b272016-02-01 09:58:23 +00003668 /**
3669 * {@inheritDoc}
3670 */
3671 @Override
3672 public boolean isMultiUserSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003673 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003674 }
3675
3676 /**
3677 * {@inheritDoc}
3678 */
3679 @Override
3680 public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException {
Julien Desprezc8474552016-02-17 10:59:27 +00003681 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003682 }
3683
3684 /**
3685 * {@inheritDoc}
3686 */
3687 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003688 public int createUser(String name, boolean guest, boolean ephemeral)
3689 throws DeviceNotAvailableException, IllegalStateException {
3690 throw new UnsupportedOperationException("No support for user's feature.");
3691 }
3692
3693 /**
3694 * {@inheritDoc}
3695 */
3696 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003697 public boolean removeUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003698 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003699 }
3700
3701 /**
3702 * {@inheritDoc}
3703 */
3704 @Override
3705 public boolean startUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003706 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003707 }
3708
3709 /**
3710 * {@inheritDoc}
3711 */
3712 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003713 public boolean stopUser(int userId) throws DeviceNotAvailableException {
3714 throw new UnsupportedOperationException("No support for user's feature.");
3715 }
3716
3717 /**
3718 * {@inheritDoc}
3719 */
3720 @Override
3721 public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
3722 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003723 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003724 }
3725
3726 /**
3727 * {@inheritDoc}
3728 */
3729 @Override
3730 public void remountSystemWritable() throws DeviceNotAvailableException {
3731 String verity = getProperty("partition.system.verified");
3732 // have the property set (regardless state) implies verity is enabled, so we send adb
3733 // command to disable verity
3734 if (verity != null && !verity.isEmpty()) {
3735 executeAdbCommand("disable-verity");
3736 reboot();
3737 }
3738 executeAdbCommand("remount");
3739 waitForDeviceAvailable();
3740 }
3741
3742 /**
3743 * {@inheritDoc}
3744 */
3745 @Override
3746 public Integer getPrimaryUserId() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003747 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003748 }
3749
3750 /**
3751 * {@inheritDoc}
3752 */
3753 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003754 public int getCurrentUser() throws DeviceNotAvailableException {
3755 throw new UnsupportedOperationException("No support for user's feature.");
3756 }
3757
3758 /**
3759 * {@inheritDoc}
3760 */
3761 @Override
3762 public int getUserFlags(int userId) throws DeviceNotAvailableException {
3763 throw new UnsupportedOperationException("No support for user's feature.");
3764 }
3765
3766 /**
3767 * {@inheritDoc}
3768 */
3769 @Override
3770 public int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
3771 throw new UnsupportedOperationException("No support for user's feature.");
3772 }
3773
3774 /**
3775 * {@inheritDoc}
3776 */
3777 @Override
3778 public boolean switchUser(int userId) throws DeviceNotAvailableException {
3779 throw new UnsupportedOperationException("No support for user's feature.");
3780 }
3781
3782 /**
3783 * {@inheritDoc}
3784 */
3785 @Override
3786 public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException {
3787 throw new UnsupportedOperationException("No support for user's feature.");
3788 }
3789
3790 /**
3791 * {@inheritDoc}
3792 */
3793 @Override
3794 public boolean isUserRunning(int userId) throws DeviceNotAvailableException {
3795 throw new UnsupportedOperationException("No support for user's feature.");
3796 }
3797
3798 /**
3799 * {@inheritDoc}
3800 */
3801 @Override
3802 public boolean hasFeature(String feature) throws DeviceNotAvailableException {
3803 throw new UnsupportedOperationException("No support pm's features.");
3804 }
3805
3806 /**
3807 * {@inheritDoc}
3808 */
3809 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00003810 public String getSetting(String namespace, String key)
3811 throws DeviceNotAvailableException {
3812 throw new UnsupportedOperationException("No support for setting's feature.");
3813 }
3814
3815 /**
3816 * {@inheritDoc}
3817 */
3818 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003819 public String getSetting(int userId, String namespace, String key)
3820 throws DeviceNotAvailableException {
3821 throw new UnsupportedOperationException("No support for setting's feature.");
3822 }
3823
Yichun Lib1900022018-05-11 09:54:48 -07003824 /** {@inheritDoc} */
3825 @Override
3826 public Map<String, String> getAllSettings(String namespace) throws DeviceNotAvailableException {
3827 throw new UnsupportedOperationException("No support for setting's feature.");
3828 }
3829
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003830 /**
3831 * {@inheritDoc}
3832 */
3833 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00003834 public void setSetting(String namespace, String key, String value)
3835 throws DeviceNotAvailableException {
3836 throw new UnsupportedOperationException("No support for setting's feature.");
3837 }
3838
3839 /**
3840 * {@inheritDoc}
3841 */
3842 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003843 public void setSetting(int userId, String namespace, String key, String value)
3844 throws DeviceNotAvailableException {
3845 throw new UnsupportedOperationException("No support for setting's feature.");
3846 }
3847
3848 /**
3849 * {@inheritDoc}
3850 */
3851 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003852 public String getBuildSigningKeys() throws DeviceNotAvailableException {
3853 String buildTags = getProperty(BUILD_TAGS);
3854 if (buildTags != null) {
3855 String[] tags = buildTags.split(",");
3856 for (String tag : tags) {
3857 Matcher m = KEYS_PATTERN.matcher(tag);
3858 if (m.matches()) {
3859 return tag;
3860 }
3861 }
3862 }
3863 return null;
3864 }
3865
3866 /**
3867 * {@inheritDoc}
3868 */
3869 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003870 public String getAndroidId(int userId) throws DeviceNotAvailableException {
3871 throw new UnsupportedOperationException("No support for user's feature.");
3872 }
3873
3874 /**
3875 * {@inheritDoc}
3876 */
3877 @Override
3878 public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
3879 throw new UnsupportedOperationException("No support for user's feature.");
3880 }
3881
Tony Mak14e4dee2017-04-12 14:37:34 +01003882 /** {@inheritDoc} */
3883 @Override
3884 public boolean setDeviceOwner(String componentName, int userId)
3885 throws DeviceNotAvailableException {
3886 throw new UnsupportedOperationException("No support for user's feature.");
3887 }
3888
3889 /** {@inheritDoc} */
3890 @Override
3891 public boolean removeAdmin(String componentName, int userId)
3892 throws DeviceNotAvailableException {
3893 throw new UnsupportedOperationException("No support for user's feature.");
3894 }
3895
3896 /** {@inheritDoc} */
3897 @Override
3898 public void removeOwners() throws DeviceNotAvailableException {
3899 throw new UnsupportedOperationException("No support for user's feature.");
3900 }
3901
Guang Zhu03c985e2017-04-28 14:53:55 -07003902 /**
3903 * {@inheritDoc}
3904 */
3905 @Override
3906 public void disableKeyguard() throws DeviceNotAvailableException {
3907 throw new UnsupportedOperationException("No support for Window Manager's features");
3908 }
3909
Tony Mak14e4dee2017-04-12 14:37:34 +01003910 /** {@inheritDoc} */
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003911 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003912 public String getDeviceClass() {
3913 IDevice device = getIDevice();
3914 if (device == null) {
3915 CLog.w("No IDevice instance, cannot determine device class.");
3916 return "";
3917 }
3918 return device.getClass().getSimpleName();
3919 }
Julien Desprez26bee8d2016-03-29 12:09:48 +01003920
3921 /**
3922 * {@inheritDoc}
3923 */
3924 @Override
3925 public void preInvocationSetup(IBuildInfo info)
3926 throws TargetSetupError, DeviceNotAvailableException {
3927 // Default implementation empty on purpose
3928 }
3929
3930 /**
3931 * {@inheritDoc}
3932 */
3933 @Override
3934 public void postInvocationTearDown() {
3935 // Default implementation empty on purpose
3936 }
Julien Despreze824e8b2016-06-08 17:21:58 +01003937
3938 /**
3939 * {@inheritDoc}
3940 */
3941 @Override
3942 public boolean isHeadless() throws DeviceNotAvailableException {
3943 if (getProperty(HEADLESS_PROP) != null) {
3944 return true;
3945 }
3946 return false;
3947 }
Julien Desprez16184162016-06-10 08:56:17 +01003948
3949 protected void checkApiLevelAgainst(String feature, int strictMinLevel) {
3950 try {
3951 if (getApiLevel() < strictMinLevel){
3952 throw new IllegalArgumentException(String.format("%s not supported on %s. "
3953 + "Must be API %d.", feature, getSerialNumber(), strictMinLevel));
3954 }
3955 } catch (DeviceNotAvailableException e) {
3956 throw new RuntimeException("Device became unavailable while checking API level", e);
3957 }
3958 }
Julien Desprez07fe1532016-08-12 11:48:32 +01003959
3960 /**
3961 * {@inheritDoc}
3962 */
3963 @Override
3964 public DeviceDescriptor getDeviceDescriptor() {
3965 IDeviceSelection selector = new DeviceSelectionOptions();
3966 IDevice idevice = getIDevice();
jdesprezbc580f92017-06-02 11:41:40 -07003967 return new DeviceDescriptor(
3968 idevice.getSerialNumber(),
Julien Desprez07fe1532016-08-12 11:48:32 +01003969 idevice instanceof StubDevice,
3970 getAllocationState(),
3971 getDisplayString(selector.getDeviceProductType(idevice)),
3972 getDisplayString(selector.getDeviceProductVariant(idevice)),
3973 getDisplayString(idevice.getProperty("ro.build.version.sdk")),
3974 getDisplayString(idevice.getProperty("ro.build.id")),
3975 getDisplayString(selector.getBatteryLevel(idevice)),
Kevin Lau Fang805161d2016-12-07 21:06:38 -08003976 getDeviceClass(),
jdesprezbc580f92017-06-02 11:41:40 -07003977 getDisplayString(getMacAddress()),
3978 getDisplayString(getSimState()),
3979 getDisplayString(getSimOperator()));
Julien Desprez07fe1532016-08-12 11:48:32 +01003980 }
3981
3982 /**
3983 * Return the displayable string for given object
3984 */
3985 private String getDisplayString(Object o) {
3986 return o == null ? "unknown" : o.toString();
3987 }
Gopinath65e837a2016-10-06 17:55:12 -07003988
3989 /**
3990 * {@inheritDoc}
3991 */
3992 @Override
3993 public List<ProcessInfo> getProcesses() throws DeviceNotAvailableException {
3994 return PsParser.getProcesses(executeShellCommand(PS_COMMAND));
3995 }
3996
3997 /**
3998 * {@inheritDoc}
3999 */
4000 @Override
4001 public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException {
4002 List<ProcessInfo> processList = getProcesses();
4003 for (ProcessInfo processInfo : processList) {
4004 if (processName.equals(processInfo.getName())) {
4005 return processInfo;
4006 }
4007 }
4008 return null;
4009 }
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004010
4011 /**
4012 * Validates that the given input is a valid MAC address
4013 *
4014 * @param address input to validate
4015 * @return true if the input is a valid MAC address
4016 */
4017 boolean isMacAddress(String address) {
4018 Pattern macPattern = Pattern.compile(MAC_ADDRESS_PATTERN);
4019 Matcher macMatcher = macPattern.matcher(address);
4020 return macMatcher.find();
4021 }
4022
4023 /**
4024 * {@inheritDoc}
4025 */
4026 @Override
4027 public String getMacAddress() {
Julien Desprez30715d62018-10-12 12:01:53 -07004028 if (getIDevice() instanceof StubDevice) {
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004029 // Do not query MAC addresses from stub devices.
4030 return null;
4031 }
Kevin Lau Fangfeff6d62016-12-14 17:29:18 -08004032 if (!TestDeviceState.ONLINE.equals(mState)) {
4033 // Only query MAC addresses from online devices.
4034 return null;
4035 }
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004036 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
4037 try {
4038 mIDevice.executeShellCommand(MAC_ADDRESS_COMMAND, receiver);
4039 } catch (IOException | TimeoutException | AdbCommandRejectedException |
4040 ShellCommandUnresponsiveException e) {
4041 CLog.w("Failed to query MAC address for %s", mIDevice.getSerialNumber());
Kevin Lau Fange94d8972017-03-13 15:11:30 -07004042 CLog.w(e);
Kevin Lau Fang805161d2016-12-07 21:06:38 -08004043 }
4044 String output = receiver.getOutput().trim();
4045 if (isMacAddress(output)) {
4046 return output;
4047 }
4048 CLog.d("No valid MAC address queried from device %s", mIDevice.getSerialNumber());
4049 return null;
4050 }
jdesprezbc580f92017-06-02 11:41:40 -07004051
4052 /** {@inheritDoc} */
4053 @Override
4054 public String getSimState() {
4055 try {
4056 return getProperty(SIM_STATE_PROP);
4057 } catch (DeviceNotAvailableException e) {
4058 CLog.w("Failed to query SIM state for %s", mIDevice.getSerialNumber());
4059 CLog.w(e);
4060 return null;
4061 }
4062 }
4063
4064 /** {@inheritDoc} */
4065 @Override
4066 public String getSimOperator() {
4067 try {
4068 return getProperty(SIM_OPERATOR_PROP);
4069 } catch (DeviceNotAvailableException e) {
4070 CLog.w("Failed to query SIM operator for %s", mIDevice.getSerialNumber());
4071 CLog.w(e);
4072 return null;
4073 }
4074 }
jdesprezd7670322017-08-30 14:12:28 -07004075
jdesprez1fbb2472018-03-07 11:15:38 -08004076 /** {@inheritDoc} */
jdesprezd7670322017-08-30 14:12:28 -07004077 @Override
4078 public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException {
4079 throw new UnsupportedOperationException("dumpHeap is not supported.");
4080 }
4081
jdesprez1fbb2472018-03-07 11:15:38 -08004082 /** {@inheritDoc} */
jdesprezd7670322017-08-30 14:12:28 -07004083 @Override
4084 public String getProcessPid(String process) throws DeviceNotAvailableException {
4085 String output = executeShellCommand(String.format("pidof %s", process)).trim();
4086 if (checkValidPid(output)) {
4087 return output;
4088 }
4089 CLog.e("Failed to find a valid pid for process.");
4090 return null;
4091 }
4092
jdesprez1fbb2472018-03-07 11:15:38 -08004093 /** {@inheritDoc} */
4094 @Override
4095 public void logOnDevice(String tag, LogLevel level, String format, Object... args) {
4096 String message = String.format(format, args);
4097 try {
4098 String levelLetter = logLevelToLogcatLevel(level);
4099 String command = String.format("log -t %s -p %s '%s'", tag, levelLetter, message);
4100 executeShellCommand(command);
4101 } catch (DeviceNotAvailableException e) {
4102 CLog.e("Device went not available when attempting to log '%s'", message);
4103 CLog.e(e);
4104 }
4105 }
4106
4107 /** Convert the {@link LogLevel} to the letter used in log (see 'adb shell log --help'). */
4108 private String logLevelToLogcatLevel(LogLevel level) {
4109 switch (level) {
4110 case DEBUG:
4111 return "d";
4112 case ERROR:
4113 return "e";
4114 case INFO:
4115 return "i";
4116 case VERBOSE:
4117 return "v";
4118 case WARN:
4119 return "w";
4120 default:
4121 return "i";
4122 }
4123 }
4124
Yuji Hachiya7bcf9ca2018-07-16 18:27:49 -07004125 /** {@inheritDoc} */
4126 @Override
4127 public long getTotalMemory() {
4128 // "/proc/meminfo" always returns value in kilobytes.
4129 long totalMemory = 0;
4130 String output = null;
4131 try {
4132 output = executeShellCommand("cat /proc/meminfo | grep MemTotal");
4133 } catch (DeviceNotAvailableException e) {
4134 CLog.e(e);
4135 return -1;
4136 }
4137 if (output.isEmpty()) {
4138 return -1;
4139 }
4140 String[] results = output.split("\\s+");
4141 try {
4142 totalMemory = Long.parseLong(results[1].replaceAll("\\D+", ""));
4143 } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) {
4144 CLog.e(e);
4145 return -1;
4146 }
4147 return totalMemory * 1024;
4148 }
4149
jdesprezd7670322017-08-30 14:12:28 -07004150 /** Validate that pid is an integer and not empty. */
4151 private boolean checkValidPid(String output) {
4152 if (output.isEmpty()) {
4153 return false;
4154 }
4155 try {
4156 Integer.parseInt(output);
4157 } catch (NumberFormatException e) {
4158 CLog.e(e);
4159 return false;
4160 }
4161 return true;
4162 }
Guang Zhu26cca482017-11-01 18:18:08 -07004163
Jeffrey Lu279122f2018-01-29 17:25:08 -08004164 /** Gets the {@link IHostOptions} instance to use. */
4165 @VisibleForTesting
Guang Zhu26cca482017-11-01 18:18:08 -07004166 IHostOptions getHostOptions() {
4167 return GlobalConfiguration.getInstance().getHostOptions();
4168 }
Julien Desprez6961b272016-02-01 09:58:23 +00004169}