blob: b31601a109d7affce92a1f20f57fcf3747b7b7d6 [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;
jdesprezbc580f92017-06-02 11:41:40 -070022import com.android.ddmlib.IDevice.DeviceState;
Julien Desprez6961b272016-02-01 09:58:23 +000023import com.android.ddmlib.IShellOutputReceiver;
24import com.android.ddmlib.InstallException;
25import 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;
Julien Desprez0c836c92016-08-10 14:40:41 +010036import com.android.tradefed.log.ITestLogger;
Julien Desprez6961b272016-02-01 09:58:23 +000037import com.android.tradefed.log.LogUtil.CLog;
38import com.android.tradefed.result.ByteArrayInputStreamSource;
Julien Desprez16184162016-06-10 08:56:17 +010039import com.android.tradefed.result.FileInputStreamSource;
Julien Desprez6961b272016-02-01 09:58:23 +000040import com.android.tradefed.result.InputStreamSource;
Julien Desprez0c836c92016-08-10 14:40:41 +010041import com.android.tradefed.result.LogDataType;
Julien Desprez6961b272016-02-01 09:58:23 +000042import com.android.tradefed.result.SnapshotInputStreamSource;
43import com.android.tradefed.result.StubTestRunListener;
Julien Desprez26bee8d2016-03-29 12:09:48 +010044import com.android.tradefed.targetprep.TargetSetupError;
Julien Desprez6961b272016-02-01 09:58:23 +000045import com.android.tradefed.util.ArrayUtil;
Julien Desprez0c836c92016-08-10 14:40:41 +010046import com.android.tradefed.util.Bugreport;
Julien Desprez6961b272016-02-01 09:58:23 +000047import com.android.tradefed.util.CommandResult;
48import com.android.tradefed.util.CommandStatus;
49import com.android.tradefed.util.FileUtil;
50import com.android.tradefed.util.IRunUtil;
Julien Desprez14e96692017-01-12 12:31:29 +000051import com.android.tradefed.util.KeyguardControllerState;
Gopinath65e837a2016-10-06 17:55:12 -070052import com.android.tradefed.util.ProcessInfo;
53import com.android.tradefed.util.PsParser;
Julien Desprez6961b272016-02-01 09:58:23 +000054import com.android.tradefed.util.RunUtil;
55import com.android.tradefed.util.SizeLimitedOutputStream;
Julien Desprez0c836c92016-08-10 14:40:41 +010056import com.android.tradefed.util.StreamUtil;
Guang Zhu91fdf442017-01-03 17:59:55 -080057import com.android.tradefed.util.ZipUtil2;
Julien Desprez6961b272016-02-01 09:58:23 +000058
Guang Zhu91fdf442017-01-03 17:59:55 -080059import org.apache.commons.compress.archivers.zip.ZipFile;
60
Julien Desprez0c836c92016-08-10 14:40:41 +010061import java.io.ByteArrayInputStream;
Julien Desprez6961b272016-02-01 09:58:23 +000062import java.io.File;
63import java.io.FilenameFilter;
64import java.io.IOException;
65import java.text.ParseException;
66import java.text.SimpleDateFormat;
67import java.util.ArrayList;
68import java.util.Arrays;
69import java.util.Collection;
70import java.util.Date;
Julien Desprez6961b272016-02-01 09:58:23 +000071import java.util.List;
72import java.util.Map;
73import java.util.Random;
74import java.util.Set;
Julien Desprez7c15ac42016-08-10 16:45:44 +010075import java.util.TimeZone;
Julien Desprez6961b272016-02-01 09:58:23 +000076import java.util.concurrent.ExecutionException;
77import java.util.concurrent.TimeUnit;
78import java.util.concurrent.locks.ReentrantLock;
79import java.util.regex.Matcher;
80import java.util.regex.Pattern;
81
82import javax.annotation.concurrent.GuardedBy;
Julien Desprez6961b272016-02-01 09:58:23 +000083
84/**
85 * Default implementation of a {@link ITestDevice}
86 * Non-full stack android devices.
87 */
Julien Desprez2f34e382016-06-21 12:30:39 +010088public class NativeDevice implements IManagedTestDevice {
Julien Desprez6961b272016-02-01 09:58:23 +000089
Julien Desprez16184162016-06-10 08:56:17 +010090 /**
91 * Allow pauses of up to 2 minutes while receiving bugreport.
92 * <p/>
93 * Note that dumpsys may pause up to a minute while waiting for unresponsive components.
94 * It still should bail after that minute, if it will ever terminate on its own.
95 */
96 private static final int BUGREPORT_TIMEOUT = 2 * 60 * 1000;
Julien Desprezf54735c2016-08-16 09:17:07 +010097 /**
98 * Allow a little more time for bugreportz because there are extra steps.
99 */
100 private static final int BUGREPORTZ_TIMEOUT = 5 * 60 * 1000;
Julien Desprez16184162016-06-10 08:56:17 +0100101 private static final String BUGREPORT_CMD = "bugreport";
102 private static final String BUGREPORTZ_CMD = "bugreportz";
Nick Kralevich5487ad22016-11-19 12:16:15 -0800103 private static final String BUGREPORTZ_TMP_PATH = "/bugreports/";
Julien Desprez16184162016-06-10 08:56:17 +0100104
Julien Desprez73c55bf2016-09-01 09:27:37 +0100105 /**
106 * Allow up to 2 minutes to receives the full logcat dump.
107 */
108 private static final int LOGCAT_DUMP_TIMEOUT = 2 * 60 * 1000;
109
Julien Desprez6961b272016-02-01 09:58:23 +0000110 /** the default number of command retry attempts to perform */
Julien Desprezc8474552016-02-17 10:59:27 +0000111 protected static final int MAX_RETRY_ATTEMPTS = 2;
112
Julien Desprez4c8aabc2016-03-14 15:53:48 +0000113 /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value **/
114 protected static final int INVALID_USER_ID = -10000;
115
Julien Desprez6961b272016-02-01 09:58:23 +0000116 /** regex to match input dispatch readiness line **/
117 static final Pattern INPUT_DISPATCH_STATE_REGEX =
118 Pattern.compile("DispatchEnabled:\\s?([01])");
119 /** regex to match build signing key type */
120 private static final Pattern KEYS_PATTERN = Pattern.compile("^.*-keys$");
121 private static final Pattern DF_PATTERN = Pattern.compile(
122 //Fs 1K-blks Used Available Use% Mounted on
Julien Desprez3d8c1472016-09-19 11:18:09 +0100123 "^/\\S+\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+\\d+%\\s+/\\S*$", Pattern.MULTILINE);
Julien Desprez16184162016-06-10 08:56:17 +0100124 private static final Pattern BUGREPORTZ_RESPONSE_PATTERN = Pattern.compile("(OK:)(.*)");
Julien Desprez6961b272016-02-01 09:58:23 +0000125
Julien Desprez1fadf1a2016-10-20 15:50:46 +0100126 protected static final long MAX_HOST_DEVICE_TIME_OFFSET = 5 * 1000;
Julien Desprez6961b272016-02-01 09:58:23 +0000127
128 /** The password for encrypting and decrypting the device. */
129 private static final String ENCRYPTION_PASSWORD = "android";
130 /** Encrypting with inplace can take up to 2 hours. */
131 private static final int ENCRYPTION_INPLACE_TIMEOUT_MIN = 2 * 60;
132 /** Encrypting with wipe can take up to 20 minutes. */
133 private static final long ENCRYPTION_WIPE_TIMEOUT_MIN = 20;
Julien Desprez6961b272016-02-01 09:58:23 +0000134
135 /** The time in ms to wait before starting logcat for a device */
136 private int mLogStartDelay = 5*1000;
137
138 /** The time in ms to wait for a device to become unavailable. Should usually be short */
139 private static final int DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000;
140 /** The time in ms to wait for a recovery that we skip because of the NONE mode */
141 static final int NONE_RECOVERY_MODE_DELAY = 1000;
Julien Desprez6961b272016-02-01 09:58:23 +0000142
143 static final String BUILD_ID_PROP = "ro.build.version.incremental";
144 private static final String PRODUCT_NAME_PROP = "ro.product.name";
145 private static final String BUILD_TYPE_PROP = "ro.build.type";
146 private static final String BUILD_ALIAS_PROP = "ro.build.id";
147 private static final String BUILD_FLAVOR = "ro.build.flavor";
Julien Despreze824e8b2016-06-08 17:21:58 +0100148 private static final String HEADLESS_PROP = "ro.build.headless";
Julien Desprez6961b272016-02-01 09:58:23 +0000149 static final String BUILD_CODENAME_PROP = "ro.build.version.codename";
150 static final String BUILD_TAGS = "ro.build.tags";
Gopinath65e837a2016-10-06 17:55:12 -0700151 private static final String PS_COMMAND = "ps -A || ps";
Julien Desprez6961b272016-02-01 09:58:23 +0000152
jdesprezbc580f92017-06-02 11:41:40 -0700153 private static final String SIM_STATE_PROP = "gsm.sim.state";
154 private static final String SIM_OPERATOR_PROP = "gsm.operator.alpha";
155
Kevin Lau Fang805161d2016-12-07 21:06:38 -0800156 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 -0700157 static final String MAC_ADDRESS_COMMAND = "su root cat /sys/class/net/wlan0/address";
Kevin Lau Fang805161d2016-12-07 21:06:38 -0800158
Julien Desprez6961b272016-02-01 09:58:23 +0000159
160 /** The network monitoring interval in ms. */
161 private static final int NETWORK_MONITOR_INTERVAL = 10 * 1000;
162
163 /** Wifi reconnect check interval in ms. */
164 private static final int WIFI_RECONNECT_CHECK_INTERVAL = 1 * 1000;
165
166 /** Wifi reconnect timeout in ms. */
167 private static final int WIFI_RECONNECT_TIMEOUT = 60 * 1000;
168
169 /** The time in ms to wait for a command to complete. */
170 private int mCmdTimeout = 2 * 60 * 1000;
171 /** The time in ms to wait for a 'long' command to complete. */
172 private long mLongCmdTimeout = 25 * 60 * 1000;
173
Julien Desprez6961b272016-02-01 09:58:23 +0000174 private IDevice mIDevice;
175 private IDeviceRecovery mRecovery = new WaitDeviceRecovery();
Julien Desprezc8474552016-02-17 10:59:27 +0000176 protected final IDeviceStateMonitor mStateMonitor;
Julien Desprez6961b272016-02-01 09:58:23 +0000177 private TestDeviceState mState = TestDeviceState.ONLINE;
178 private final ReentrantLock mFastbootLock = new ReentrantLock();
179 private LogcatReceiver mLogcatReceiver;
180 private boolean mFastbootEnabled = true;
Julien Desprez0a7d67d2016-07-21 16:05:57 +0100181 private String mFastbootPath = "fastboot";
Julien Desprez6961b272016-02-01 09:58:23 +0000182
Julien Desprezc8474552016-02-17 10:59:27 +0000183 protected TestDeviceOptions mOptions = new TestDeviceOptions();
Julien Desprez6961b272016-02-01 09:58:23 +0000184 private Process mEmulatorProcess;
185 private SizeLimitedOutputStream mEmulatorOutput;
186
187 private RecoveryMode mRecoveryMode = RecoveryMode.AVAILABLE;
188
189 private Boolean mIsEncryptionSupported = null;
190 private ReentrantLock mAllocationStateLock = new ReentrantLock();
191 @GuardedBy("mAllocationStateLock")
192 private DeviceAllocationState mAllocationState = DeviceAllocationState.Unknown;
193 private IDeviceMonitor mAllocationMonitor = null;
194
195 private String mLastConnectedWifiSsid = null;
196 private String mLastConnectedWifiPsk = null;
197 private boolean mNetworkMonitorEnabled = false;
198
199 /**
200 * Interface for a generic device communication attempt.
201 */
Julien Desprezc8474552016-02-17 10:59:27 +0000202 abstract interface DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000203
204 /**
205 * Execute the device operation.
206 *
207 * @return <code>true</code> if operation is performed successfully, <code>false</code>
208 * otherwise
Julien Desprezc8474552016-02-17 10:59:27 +0000209 * @throws IOException, TimeoutException, AdbCommandRejectedException,
210 * ShellCommandUnresponsiveException, InstallException,
211 * SyncException if operation terminated abnormally
Julien Desprez6961b272016-02-01 09:58:23 +0000212 */
213 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
214 ShellCommandUnresponsiveException, InstallException, SyncException;
215 }
216
217 /**
218 * A {@link DeviceAction} for running a OS 'adb ....' command.
219 */
Julien Desprezc8474552016-02-17 10:59:27 +0000220 protected class AdbAction implements DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000221 /** the output from the command */
222 String mOutput = null;
223 private String[] mCmd;
224
225 AdbAction(String[] cmd) {
226 mCmd = cmd;
227 }
228
229 @Override
230 public boolean run() throws TimeoutException, IOException {
231 CommandResult result = getRunUtil().runTimedCmd(getCommandTimeout(), mCmd);
232 // TODO: how to determine device not present with command failing for other reasons
233 if (result.getStatus() == CommandStatus.EXCEPTION) {
234 throw new IOException();
235 } else if (result.getStatus() == CommandStatus.TIMED_OUT) {
236 throw new TimeoutException();
237 } else if (result.getStatus() == CommandStatus.FAILED) {
238 // interpret as communication failure
239 throw new IOException();
240 }
241 mOutput = result.getStdout();
242 return true;
243 }
244 }
245
246 /**
247 * Creates a {@link TestDevice}.
248 *
249 * @param device the associated {@link IDevice}
250 * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use
251 * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes.
252 * Can be null
253 */
Julien Desprez2f34e382016-06-21 12:30:39 +0100254 public NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor,
Julien Desprezc8474552016-02-17 10:59:27 +0000255 IDeviceMonitor allocationMonitor) {
Julien Desprez6961b272016-02-01 09:58:23 +0000256 throwIfNull(device);
257 throwIfNull(stateMonitor);
258 mIDevice = device;
259 mStateMonitor = stateMonitor;
260 mAllocationMonitor = allocationMonitor;
261 }
262
263 /**
264 * Get the {@link RunUtil} instance to use.
265 * <p/>
266 * Exposed for unit testing.
267 */
Julien Desprezc8474552016-02-17 10:59:27 +0000268 protected IRunUtil getRunUtil() {
Julien Desprez6961b272016-02-01 09:58:23 +0000269 return RunUtil.getDefault();
270 }
271
272 /**
273 * {@inheritDoc}
274 */
275 @Override
276 public void setOptions(TestDeviceOptions options) {
277 throwIfNull(options);
278 mOptions = options;
279 mStateMonitor.setDefaultOnlineTimeout(options.getOnlineTimeout());
280 mStateMonitor.setDefaultAvailableTimeout(options.getAvailableTimeout());
281 }
282
283 /**
284 * Sets the max size of a tmp logcat file.
285 *
286 * @param size max byte size of tmp file
287 */
288 void setTmpLogcatSize(long size) {
289 mOptions.setMaxLogcatDataSize(size);
290 }
291
292 /**
293 * Sets the time in ms to wait before starting logcat capture for a online device.
294 *
295 * @param delay the delay in ms
296 */
Julien Desprezbca52e02017-01-23 09:50:07 +0000297 protected void setLogStartDelay(int delay) {
Julien Desprez6961b272016-02-01 09:58:23 +0000298 mLogStartDelay = delay;
299 }
300
301 /**
302 * {@inheritDoc}
303 */
304 @Override
305 public IDevice getIDevice() {
306 synchronized (mIDevice) {
307 return mIDevice;
308 }
309 }
310
311 /**
312 * {@inheritDoc}
313 */
314 @Override
315 public void setIDevice(IDevice newDevice) {
316 throwIfNull(newDevice);
317 IDevice currentDevice = mIDevice;
318 if (!getIDevice().equals(newDevice)) {
319 synchronized (currentDevice) {
320 mIDevice = newDevice;
321 }
322 mStateMonitor.setIDevice(mIDevice);
323 }
324 }
325
326 /**
327 * {@inheritDoc}
328 */
329 @Override
330 public String getSerialNumber() {
331 return getIDevice().getSerialNumber();
332 }
333
334 private boolean nullOrEmpty(String string) {
335 return string == null || string.isEmpty();
336 }
337
338 /**
339 * Fetch a device property, from the ddmlib cache by default, and falling back to either
340 * `adb shell getprop` or `fastboot getvar` depending on whether the device is in Fastboot or
341 * not.
342 *
343 * @param propName The name of the device property as returned by `adb shell getprop`
344 * @param fastbootVar The name of the equivalent fastboot variable to query. if {@code null},
345 * fastboot query will not be attempted
346 * @param description A simple description of the variable. First letter should be capitalized.
347 * @return A string, possibly {@code null} or empty, containing the value of the given property
348 */
349 private String internalGetProperty(String propName, String fastbootVar, String description)
350 throws DeviceNotAvailableException, UnsupportedOperationException {
351 String propValue = getIDevice().getProperty(propName);
352 if (propValue != null) {
353 return propValue;
354 } else if (TestDeviceState.FASTBOOT.equals(getDeviceState()) &&
355 fastbootVar != null) {
356 CLog.i("%s for device %s is null, re-querying in fastboot", description,
357 getSerialNumber());
358 return getFastbootVariable(fastbootVar);
359 } else {
360 CLog.d("property collection for device %s is null, re-querying for prop %s",
361 getSerialNumber(), description);
362 return getProperty(propName);
363 }
364 }
365
366 /**
367 * {@inheritDoc}
368 */
369 @Override
370 public String getProperty(final String name) throws DeviceNotAvailableException {
jdesprezbc580f92017-06-02 11:41:40 -0700371 if (!DeviceState.ONLINE.equals(getIDevice().getState())) {
372 CLog.d("Device %s is not online cannot get property %s.", getSerialNumber(), name);
373 return null;
374 }
Julien Desprez6961b272016-02-01 09:58:23 +0000375 final String[] result = new String[1];
376 DeviceAction propAction = new DeviceAction() {
377
378 @Override
379 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
380 ShellCommandUnresponsiveException, InstallException, SyncException {
381 try {
382 result[0] = getIDevice().getSystemProperty(name).get();
383 } catch (InterruptedException | ExecutionException e) {
384 // getProperty will stash the original exception inside
385 // ExecutionException.getCause
386 // throw the specific original exception if available in case TF ever does
387 // specific handling for different exceptions
388 if (e.getCause() instanceof IOException) {
389 throw (IOException)e.getCause();
390 } else if (e.getCause() instanceof TimeoutException) {
391 throw (TimeoutException)e.getCause();
392 } else if (e.getCause() instanceof AdbCommandRejectedException) {
393 throw (AdbCommandRejectedException)e.getCause();
394 } else if (e.getCause() instanceof ShellCommandUnresponsiveException) {
395 throw (ShellCommandUnresponsiveException)e.getCause();
396 }
397 else {
398 throw new IOException(e);
399 }
400 }
401 return true;
402 }
403
404 };
405 performDeviceAction("getprop", propAction, MAX_RETRY_ATTEMPTS);
406 return result[0];
407 }
408
409 /**
410 * {@inheritDoc}
411 */
Julien Desprez6961b272016-02-01 09:58:23 +0000412 @Override
413 public String getBootloaderVersion() throws UnsupportedOperationException,
414 DeviceNotAvailableException {
415 return internalGetProperty("ro.bootloader", "version-bootloader", "Bootloader");
416 }
417
418 @Override
419 public String getBasebandVersion() throws DeviceNotAvailableException {
420 return internalGetProperty("gsm.version.baseband", "version-baseband", "Baseband");
421 }
422
423 /**
424 * {@inheritDoc}
425 */
426 @Override
427 public String getProductType() throws DeviceNotAvailableException {
428 return internalGetProductType(MAX_RETRY_ATTEMPTS);
429 }
430
431 /**
Julien Desprezc8474552016-02-17 10:59:27 +0000432 * {@link #getProductType()}
Julien Desprez6961b272016-02-01 09:58:23 +0000433 *
Julien Desprezc8474552016-02-17 10:59:27 +0000434 * @param retryAttempts The number of times to try calling {@link #recoverDevice()} if the
Julien Desprez6961b272016-02-01 09:58:23 +0000435 * device's product type cannot be found.
436 */
437 private String internalGetProductType(int retryAttempts) throws DeviceNotAvailableException {
438 String productType = internalGetProperty("ro.hardware", "product", "Product type");
439
440 // Things will likely break if we don't have a valid product type. Try recovery (in case
441 // the device is only partially booted for some reason), and if that doesn't help, bail.
442 if (nullOrEmpty(productType)) {
443 if (retryAttempts > 0) {
444 recoverDevice();
445 productType = internalGetProductType(retryAttempts - 1);
446 }
447
448 if (nullOrEmpty(productType)) {
449 throw new DeviceNotAvailableException(String.format(
Julien Desprez0c6c77c2016-05-31 16:35:57 +0100450 "Could not determine product type for device %s.", getSerialNumber()),
451 getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +0000452 }
453 }
454
455 return productType;
456 }
457
458 /**
459 * {@inheritDoc}
460 */
461 @Override
462 public String getFastbootProductType()
463 throws DeviceNotAvailableException, UnsupportedOperationException {
464 return getFastbootVariable("product");
465 }
466
467 /**
468 * {@inheritDoc}
469 */
470 @Override
471 public String getProductVariant() throws DeviceNotAvailableException {
472 return internalGetProperty("ro.product.device", "variant", "Product variant");
473 }
474
475 /**
476 * {@inheritDoc}
477 */
478 @Override
479 public String getFastbootProductVariant()
480 throws DeviceNotAvailableException, UnsupportedOperationException {
481 return getFastbootVariable("variant");
482 }
483
484 private String getFastbootVariable(String variableName)
485 throws DeviceNotAvailableException, UnsupportedOperationException {
486 CommandResult result = executeFastbootCommand("getvar", variableName);
487 if (result.getStatus() == CommandStatus.SUCCESS) {
488 Pattern fastbootProductPattern = Pattern.compile(variableName + ":\\s(.*)\\s");
489 // fastboot is weird, and may dump the output on stderr instead of stdout
490 String resultText = result.getStdout();
491 if (resultText == null || resultText.length() < 1) {
492 resultText = result.getStderr();
493 }
494 Matcher matcher = fastbootProductPattern.matcher(resultText);
495 if (matcher.find()) {
496 return matcher.group(1);
497 }
498 }
499 return null;
500 }
501
502 /**
503 * {@inheritDoc}
504 */
505 @Override
506 public String getBuildAlias() throws DeviceNotAvailableException {
507 String alias = getProperty(BUILD_ALIAS_PROP);
508 if (alias == null || alias.isEmpty()) {
509 return getBuildId();
510 }
511 return alias;
512 }
513
514 /**
515 * {@inheritDoc}
516 */
517 @Override
518 public String getBuildId() throws DeviceNotAvailableException {
519 String bid = getProperty(BUILD_ID_PROP);
520 if (bid == null) {
521 CLog.w("Could not get device %s build id.", getSerialNumber());
522 return IBuildInfo.UNKNOWN_BUILD_ID;
523 }
524 return bid;
525 }
526
527 /**
528 * {@inheritDoc}
529 */
530 @Override
531 public String getBuildFlavor() throws DeviceNotAvailableException {
532 String buildFlavor = getProperty(BUILD_FLAVOR);
533 if (buildFlavor != null && !buildFlavor.isEmpty()) {
534 return buildFlavor;
535 }
536 String productName = getProperty(PRODUCT_NAME_PROP);
537 String buildType = getProperty(BUILD_TYPE_PROP);
538 if (productName == null || buildType == null) {
539 CLog.w("Could not get device %s build flavor.", getSerialNumber());
540 return null;
541 }
542 return String.format("%s-%s", productName, buildType);
543 }
544
545 /**
546 * {@inheritDoc}
547 */
548 @Override
549 public void executeShellCommand(final String command, final IShellOutputReceiver receiver)
550 throws DeviceNotAvailableException {
551 DeviceAction action = new DeviceAction() {
552 @Override
553 public boolean run() throws TimeoutException, IOException,
554 AdbCommandRejectedException, ShellCommandUnresponsiveException {
555 getIDevice().executeShellCommand(command, receiver,
556 mCmdTimeout, TimeUnit.MILLISECONDS);
557 return true;
558 }
559 };
560 performDeviceAction(String.format("shell %s", command), action, MAX_RETRY_ATTEMPTS);
561 }
562
563 /**
564 * {@inheritDoc}
565 */
Julien Desprez6961b272016-02-01 09:58:23 +0000566 @Override
567 public void executeShellCommand(final String command, final IShellOutputReceiver receiver,
568 final long maxTimeToOutputShellResponse, final TimeUnit timeUnit,
569 final int retryAttempts) throws DeviceNotAvailableException {
570 DeviceAction action = new DeviceAction() {
571 @Override
572 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
573 ShellCommandUnresponsiveException {
574 getIDevice().executeShellCommand(command, receiver,
575 maxTimeToOutputShellResponse, timeUnit);
576 return true;
577 }
578 };
579 performDeviceAction(String.format("shell %s", command), action, retryAttempts);
580 }
581
jdesprez2c37cec2017-08-25 12:03:55 -0700582 /** {@inheritDoc} */
583 @Override
584 public void executeShellCommand(
585 final String command,
586 final IShellOutputReceiver receiver,
587 final long maxTimeoutForCommand,
588 final long maxTimeToOutputShellResponse,
589 final TimeUnit timeUnit,
590 final int retryAttempts)
591 throws DeviceNotAvailableException {
592 DeviceAction action =
593 new DeviceAction() {
594 @Override
595 public boolean run()
596 throws TimeoutException, IOException, AdbCommandRejectedException,
597 ShellCommandUnresponsiveException {
598 getIDevice()
599 .executeShellCommand(
600 command,
601 receiver,
602 maxTimeoutForCommand,
603 maxTimeToOutputShellResponse,
604 timeUnit);
605 return true;
606 }
607 };
608 performDeviceAction(String.format("shell %s", command), action, retryAttempts);
609 }
610
Julien Desprez6961b272016-02-01 09:58:23 +0000611 /**
612 * {@inheritDoc}
613 */
614 @Override
615 public String executeShellCommand(String command) throws DeviceNotAvailableException {
616 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
617 executeShellCommand(command, receiver);
618 String output = receiver.getOutput();
619 CLog.v("%s on %s returned %s", command, getSerialNumber(), output);
620 return output;
621 }
622
623 /**
624 * {@inheritDoc}
625 */
626 @Override
627 public boolean runInstrumentationTests(final IRemoteAndroidTestRunner runner,
628 final Collection<ITestRunListener> listeners) throws DeviceNotAvailableException {
629 RunFailureListener failureListener = new RunFailureListener();
630 listeners.add(failureListener);
631 DeviceAction runTestsAction = new DeviceAction() {
632 @Override
633 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
634 ShellCommandUnresponsiveException, InstallException, SyncException {
635 runner.run(listeners);
636 return true;
637 }
638
639 };
640 boolean result = performDeviceAction(String.format("run %s instrumentation tests",
641 runner.getPackageName()), runTestsAction, 0);
642 if (failureListener.isRunFailure()) {
643 // run failed, might be system crash. Ensure device is up
644 if (mStateMonitor.waitForDeviceAvailable(5 * 1000) == null) {
645 // device isn't up, recover
646 recoverDevice();
647 }
648 }
649 return result;
650 }
651
652 /**
653 * {@inheritDoc}
654 */
655 @Override
656 public boolean runInstrumentationTestsAsUser(final IRemoteAndroidTestRunner runner,
657 int userId, final Collection<ITestRunListener> listeners)
658 throws DeviceNotAvailableException {
659 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
660 boolean result = runInstrumentationTests(runner, listeners);
661 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
662 return result;
663 }
664
665 /**
666 * Helper method to add user run time option to {@link RemoteAndroidTestRunner}
667 *
668 * @param runner {@link IRemoteAndroidTestRunner}
669 * @param userId the integer of the user id to run as.
670 * @return original run time options.
671 */
672 private String appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId) {
673 if (runner instanceof RemoteAndroidTestRunner) {
674 String original = ((RemoteAndroidTestRunner) runner).getRunOptions();
675 String userRunTimeOption = String.format("--user %s", Integer.toString(userId));
676 ((RemoteAndroidTestRunner) runner).setRunOptions(userRunTimeOption);
677 return original;
678 } else {
679 throw new IllegalStateException(String.format("%s runner does not support multi-user",
680 runner.getClass().getName()));
681 }
682 }
683
684 /**
685 * Helper method to reset the run time options to {@link RemoteAndroidTestRunner}
686 *
687 * @param runner {@link IRemoteAndroidTestRunner}
688 * @param oldRunTimeOptions
689 */
690 private void resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner,
691 String oldRunTimeOptions) {
692 if (runner instanceof RemoteAndroidTestRunner) {
693 if (oldRunTimeOptions != null) {
694 ((RemoteAndroidTestRunner) runner).setRunOptions(oldRunTimeOptions);
695 }
696 } else {
697 throw new IllegalStateException(String.format("%s runner does not support multi-user",
698 runner.getClass().getName()));
699 }
700 }
701
702 private static class RunFailureListener extends StubTestRunListener {
703 private boolean mIsRunFailure = false;
704
705 @Override
706 public void testRunFailed(String message) {
707 mIsRunFailure = true;
708 }
709
710 public boolean isRunFailure() {
711 return mIsRunFailure;
712 }
713 }
714
715 /**
716 * {@inheritDoc}
717 */
718 @Override
719 public boolean runInstrumentationTests(IRemoteAndroidTestRunner runner,
720 ITestRunListener... listeners) throws DeviceNotAvailableException {
Eric Rowe1abf2c02017-03-20 17:12:33 -0700721 List<ITestRunListener> listenerList = new ArrayList<>();
Julien Desprez6961b272016-02-01 09:58:23 +0000722 listenerList.addAll(Arrays.asList(listeners));
723 return runInstrumentationTests(runner, listenerList);
724 }
725
726 /**
727 * {@inheritDoc}
728 */
729 @Override
730 public boolean runInstrumentationTestsAsUser(IRemoteAndroidTestRunner runner, int userId,
731 ITestRunListener... listeners) throws DeviceNotAvailableException {
732 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
733 boolean result = runInstrumentationTests(runner, listeners);
734 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
735 return result;
736 }
737
738 /**
739 * {@inheritDoc}
740 */
741 @Override
742 public boolean isRuntimePermissionSupported() throws DeviceNotAvailableException {
Guang Zhu1fb39eb2016-03-27 15:57:28 -0700743 return getApiLevel() > 22;
Julien Desprez6961b272016-02-01 09:58:23 +0000744 }
745
746 /**
747 * helper method to throw exception if runtime permission isn't supported
748 * @throws DeviceNotAvailableException
749 */
Julien Desprezc8474552016-02-17 10:59:27 +0000750 protected void ensureRuntimePermissionSupported() throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000751 boolean runtimePermissionSupported = isRuntimePermissionSupported();
752 if (!runtimePermissionSupported) {
753 throw new UnsupportedOperationException(
754 "platform on device does not support runtime permission granting!");
755 }
756 }
757
758 /**
Julien Desprez6961b272016-02-01 09:58:23 +0000759 * {@inheritDoc}
760 */
761 @Override
762 public String installPackage(final File packageFile, final boolean reinstall,
763 final String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000764 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000765 }
766
767 /**
768 * {@inheritDoc}
769 */
770 @Override
771 public String installPackage(File packageFile, boolean reinstall, boolean grantPermissions,
772 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000773 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000774 }
775
776 /**
777 * {@inheritDoc}
778 */
779 @Override
780 public String installPackageForUser(File packageFile, boolean reinstall, int userId,
781 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000782 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000783 }
784
785 /**
786 * {@inheritDoc}
787 */
788 @Override
789 public String installPackageForUser(File packageFile, boolean reinstall,
790 boolean grantPermissions, int userId, String... extraArgs)
791 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000792 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000793 }
794
795 /**
796 * {@inheritDoc}
797 */
798 @Override
799 public String uninstallPackage(final String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000800 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000801 }
802
803 /**
804 * {@inheritDoc}
805 */
806 @Override
807 public boolean pullFile(final String remoteFilePath, final File localFile)
808 throws DeviceNotAvailableException {
809
810 DeviceAction pullAction = new DeviceAction() {
811 @Override
812 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
813 SyncException {
814 SyncService syncService = null;
815 boolean status = false;
816 try {
817 syncService = getIDevice().getSyncService();
818 syncService.pullFile(interpolatePathVariables(remoteFilePath),
819 localFile.getAbsolutePath(), SyncService.getNullProgressMonitor());
820 status = true;
821 } catch (SyncException e) {
822 CLog.w("Failed to pull %s from %s to %s. Message %s", remoteFilePath,
823 getSerialNumber(), localFile.getAbsolutePath(), e.getMessage());
824 throw e;
825 } finally {
826 if (syncService != null) {
827 syncService.close();
828 }
829 }
830 return status;
831 }
832 };
833 return performDeviceAction(String.format("pull %s to %s", remoteFilePath,
834 localFile.getAbsolutePath()), pullAction, MAX_RETRY_ATTEMPTS);
835 }
836
837 /**
838 * {@inheritDoc}
839 */
840 @Override
841 public File pullFile(String remoteFilePath) throws DeviceNotAvailableException {
842 File localFile = null;
843 boolean success = false;
844 try {
845 localFile = FileUtil.createTempFileForRemote(remoteFilePath, null);
846 if (pullFile(remoteFilePath, localFile)) {
847 success = true;
848 return localFile;
849 }
850 } catch (IOException e) {
Julien Desprez9cd6ca72016-12-19 12:35:15 +0000851 CLog.w("Encountered IOException while trying to pull '%s':", remoteFilePath);
852 CLog.e(e);
Julien Desprez6961b272016-02-01 09:58:23 +0000853 } finally {
854 if (!success) {
855 FileUtil.deleteFile(localFile);
856 }
857 }
858 return null;
859 }
860
861 /**
862 * {@inheritDoc}
863 */
864 @Override
865 public File pullFileFromExternal(String remoteFilePath) throws DeviceNotAvailableException {
866 String externalPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
867 String fullPath = (new File(externalPath, remoteFilePath)).getPath();
868 return pullFile(fullPath);
869 }
870
871 /**
872 * Helper function that watches for the string "${EXTERNAL_STORAGE}" and replaces it with the
873 * pathname of the EXTERNAL_STORAGE mountpoint. Specifically intended to be used for pathnames
874 * that are being passed to SyncService, which does not support variables inside of filenames.
875 */
876 String interpolatePathVariables(String path) {
877 final String esString = "${EXTERNAL_STORAGE}";
878 if (path.contains(esString)) {
879 final String esPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
880 path = path.replace(esString, esPath);
881 }
882 return path;
883 }
884
885 /**
886 * {@inheritDoc}
887 */
888 @Override
889 public boolean pushFile(final File localFile, final String remoteFilePath)
890 throws DeviceNotAvailableException {
jdesprez3dff70b2017-04-18 10:39:13 -0700891 DeviceAction pushAction =
892 new DeviceAction() {
893 @Override
894 public boolean run()
895 throws TimeoutException, IOException, AdbCommandRejectedException,
896 SyncException {
897 SyncService syncService = null;
898 boolean status = false;
899 try {
900 syncService = getIDevice().getSyncService();
901 if (syncService == null) {
902 throw new IOException("SyncService returned null.");
903 }
904 syncService.pushFile(
905 localFile.getAbsolutePath(),
906 interpolatePathVariables(remoteFilePath),
907 SyncService.getNullProgressMonitor());
908 status = true;
909 } catch (SyncException e) {
910 CLog.w(
jdesprez791fc5d2017-08-02 11:25:48 -0700911 "Failed to push %s to %s on device %s. Message: '%s'. "
912 + "Error code: %s",
jdesprez3dff70b2017-04-18 10:39:13 -0700913 localFile.getAbsolutePath(),
914 remoteFilePath,
915 getSerialNumber(),
jdesprez791fc5d2017-08-02 11:25:48 -0700916 e.getMessage(),
917 e.getErrorCode());
918 // TODO: check if ddmlib can report a better error
919 if (SyncError.TRANSFER_PROTOCOL_ERROR.equals(e.getErrorCode())) {
920 if (e.getMessage().contains("Permission denied")) {
921 return false;
922 }
923 }
jdesprez3dff70b2017-04-18 10:39:13 -0700924 throw e;
925 } finally {
926 if (syncService != null) {
927 syncService.close();
928 }
929 }
930 return status;
Julien Desprez6961b272016-02-01 09:58:23 +0000931 }
jdesprez3dff70b2017-04-18 10:39:13 -0700932 };
Julien Desprez6961b272016-02-01 09:58:23 +0000933 return performDeviceAction(String.format("push %s to %s", localFile.getAbsolutePath(),
934 remoteFilePath), pushAction, MAX_RETRY_ATTEMPTS);
935 }
936
937 /**
938 * {@inheritDoc}
939 */
940 @Override
941 public boolean pushString(final String contents, final String remoteFilePath)
942 throws DeviceNotAvailableException {
943 File tmpFile = null;
944 try {
945 tmpFile = FileUtil.createTempFile("temp", ".txt");
946 FileUtil.writeToFile(contents, tmpFile);
947 return pushFile(tmpFile, remoteFilePath);
948 } catch (IOException e) {
949 CLog.e(e);
950 return false;
951 } finally {
Julien Desprez1320e592016-12-06 09:51:53 +0000952 FileUtil.deleteFile(tmpFile);
Julien Desprez6961b272016-02-01 09:58:23 +0000953 }
954 }
955
956 /**
957 * {@inheritDoc}
958 */
959 @Override
960 public boolean doesFileExist(String destPath) throws DeviceNotAvailableException {
961 String lsGrep = executeShellCommand(String.format("ls \"%s\"", destPath));
962 return !lsGrep.contains("No such file or directory");
963 }
964
965 /**
966 * {@inheritDoc}
967 */
968 @Override
969 public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
970 CLog.i("Checking free space for %s", getSerialNumber());
971 String externalStorePath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
972 String output = getDfOutput(externalStorePath);
973 // Try coreutils/toybox style output first.
974 Long available = parseFreeSpaceFromModernOutput(output);
975 if (available != null) {
976 return available;
977 }
978 // Then the two legacy toolbox formats.
979 available = parseFreeSpaceFromAvailable(output);
980 if (available != null) {
981 return available;
982 }
983 available = parseFreeSpaceFromFree(externalStorePath, output);
984 if (available != null) {
985 return available;
986 }
987
988 CLog.e("free space command output \"%s\" did not match expected patterns", output);
989 return 0;
990 }
991
992 /**
993 * Run the 'df' shell command and return output, making multiple attempts if necessary.
994 *
995 * @param externalStorePath the path to check
996 * @return the output from 'shell df path'
997 * @throws DeviceNotAvailableException
998 */
999 private String getDfOutput(String externalStorePath) throws DeviceNotAvailableException {
1000 for (int i=0; i < MAX_RETRY_ATTEMPTS; i++) {
1001 String output = executeShellCommand(String.format("df %s", externalStorePath));
1002 if (output.trim().length() > 0) {
1003 return output;
1004 }
1005 }
1006 throw new DeviceUnresponsiveException(String.format(
1007 "Device %s not returning output from df command after %d attempts",
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001008 getSerialNumber(), MAX_RETRY_ATTEMPTS), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001009 }
1010
1011 /**
1012 * Parses a partition's available space from the legacy output of a 'df' command, used
1013 * pre-gingerbread.
1014 * <p/>
1015 * Assumes output format of:
1016 * <br>/
1017 * <code>
1018 * [partition]: 15659168K total, 51584K used, 15607584K available (block size 32768)
1019 * </code>
1020 * @param dfOutput the output of df command to parse
1021 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1022 */
1023 private Long parseFreeSpaceFromAvailable(String dfOutput) {
1024 final Pattern freeSpacePattern = Pattern.compile("(\\d+)K available");
1025 Matcher patternMatcher = freeSpacePattern.matcher(dfOutput);
1026 if (patternMatcher.find()) {
1027 String freeSpaceString = patternMatcher.group(1);
1028 try {
1029 return Long.parseLong(freeSpaceString);
1030 } catch (NumberFormatException e) {
1031 // fall through
1032 }
1033 }
1034 return null;
1035 }
1036
1037 /**
1038 * Parses a partition's available space from the 'table-formatted' output of a toolbox 'df'
1039 * command, used from gingerbread to lollipop.
1040 * <p/>
1041 * Assumes output format of:
1042 * <br/>
1043 * <code>
1044 * Filesystem Size Used Free Blksize
1045 * <br/>
1046 * [partition]: 3G 790M 2G 4096
1047 * </code>
1048 * @param dfOutput the output of df command to parse
1049 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1050 */
1051 Long parseFreeSpaceFromFree(String externalStorePath, String dfOutput) {
1052 Long freeSpace = null;
1053 final Pattern freeSpaceTablePattern = Pattern.compile(String.format(
1054 //fs Size Used Free
1055 "%s\\s+[\\w\\d\\.]+\\s+[\\w\\d\\.]+\\s+([\\d\\.]+)(\\w)", externalStorePath));
1056 Matcher tablePatternMatcher = freeSpaceTablePattern.matcher(dfOutput);
1057 if (tablePatternMatcher.find()) {
1058 String numericValueString = tablePatternMatcher.group(1);
1059 String unitType = tablePatternMatcher.group(2);
1060 try {
1061 Float freeSpaceFloat = Float.parseFloat(numericValueString);
1062 if (unitType.equals("M")) {
1063 freeSpaceFloat = freeSpaceFloat * 1024;
1064 } else if (unitType.equals("G")) {
1065 freeSpaceFloat = freeSpaceFloat * 1024 * 1024;
1066 }
1067 freeSpace = freeSpaceFloat.longValue();
1068 } catch (NumberFormatException e) {
1069 // fall through
1070 }
1071 }
1072 return freeSpace;
1073 }
1074
1075 /**
1076 * Parses a partition's available space from the modern coreutils/toybox 'df' output, used
1077 * after lollipop.
1078 * <p/>
1079 * Assumes output format of:
1080 * <br/>
1081 * <code>
1082 * Filesystem 1K-blocks Used Available Use% Mounted on
1083 * <br/>
1084 * /dev/fuse 11585536 1316348 10269188 12% /mnt/shell/emulated
1085 * </code>
1086 * @param dfOutput the output of df command to parse
1087 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1088 */
1089 Long parseFreeSpaceFromModernOutput(String dfOutput) {
1090 Matcher matcher = DF_PATTERN.matcher(dfOutput);
1091 if (matcher.find()) {
1092 try {
1093 return Long.parseLong(matcher.group(1));
1094 } catch (NumberFormatException e) {
1095 // fall through
1096 }
1097 }
1098 return null;
1099 }
1100
1101 /**
1102 * {@inheritDoc}
1103 */
1104 @Override
1105 public String getMountPoint(String mountName) {
1106 return mStateMonitor.getMountPoint(mountName);
1107 }
1108
1109 /**
1110 * {@inheritDoc}
1111 */
1112 @Override
1113 public List<MountPointInfo> getMountPointInfo() throws DeviceNotAvailableException {
1114 final String mountInfo = executeShellCommand("cat /proc/mounts");
1115 final String[] mountInfoLines = mountInfo.split("\r?\n");
Eric Rowe1abf2c02017-03-20 17:12:33 -07001116 List<MountPointInfo> list = new ArrayList<>(mountInfoLines.length);
Julien Desprez6961b272016-02-01 09:58:23 +00001117
1118 for (String line : mountInfoLines) {
1119 // We ignore the last two fields
1120 // /dev/block/mtdblock4 /cache yaffs2 rw,nosuid,nodev,relatime 0 0
1121 final String[] parts = line.split("\\s+", 5);
1122 list.add(new MountPointInfo(parts[0], parts[1], parts[2], parts[3]));
1123 }
1124
1125 return list;
1126 }
1127
1128 /**
1129 * {@inheritDoc}
1130 */
1131 @Override
1132 public MountPointInfo getMountPointInfo(String mountpoint) throws DeviceNotAvailableException {
1133 // The overhead of parsing all of the lines should be minimal
1134 List<MountPointInfo> mountpoints = getMountPointInfo();
1135 for (MountPointInfo info : mountpoints) {
1136 if (mountpoint.equals(info.mountpoint)) return info;
1137 }
1138 return null;
1139 }
1140
1141 /**
1142 * {@inheritDoc}
1143 */
1144 @Override
1145 public IFileEntry getFileEntry(String path) throws DeviceNotAvailableException {
1146 path = interpolatePathVariables(path);
1147 String[] pathComponents = path.split(FileListingService.FILE_SEPARATOR);
1148 FileListingService service = getFileListingService();
1149 IFileEntry rootFile = new FileEntryWrapper(this, service.getRoot());
1150 return FileEntryWrapper.getDescendant(rootFile, Arrays.asList(pathComponents));
1151 }
1152
1153 /**
Julien Desprez56f18e02016-03-11 14:40:18 +00001154 * {@inheritDoc}
1155 */
1156 @Override
1157 public boolean isDirectory(String path) throws DeviceNotAvailableException {
1158 return executeShellCommand(String.format("ls -ld %s", path)).charAt(0) == 'd';
1159 }
1160
1161 /**
1162 * {@inheritDoc}
1163 */
1164 @Override
1165 public String[] getChildren(String path) throws DeviceNotAvailableException {
1166 String lsOutput = executeShellCommand(String.format("ls -A1 %s", path));
1167 if (lsOutput.trim().isEmpty()) {
1168 return new String[0];
1169 }
1170 return lsOutput.split("\r?\n");
1171 }
1172
1173 /**
Julien Desprez6961b272016-02-01 09:58:23 +00001174 * Retrieve the {@link FileListingService} for the {@link IDevice}, making multiple attempts
1175 * and recovery operations if necessary.
1176 * <p/>
1177 * This is necessary because {@link IDevice#getFileListingService()} can return
1178 * <code>null</code> if device is in fastboot. The symptom of this condition is that the
1179 * current {@link #getIDevice()} is a {@link StubDevice}.
1180 *
1181 * @return the {@link FileListingService}
1182 * @throws DeviceNotAvailableException if device communication is lost.
1183 */
1184 private FileListingService getFileListingService() throws DeviceNotAvailableException {
1185 final FileListingService[] service = new FileListingService[1];
1186 DeviceAction serviceAction = new DeviceAction() {
1187 @Override
1188 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
1189 ShellCommandUnresponsiveException, InstallException, SyncException {
1190 service[0] = getIDevice().getFileListingService();
1191 if (service[0] == null) {
1192 // could not get file listing service - must be a stub device - enter recovery
1193 throw new IOException("Could not get file listing service");
1194 }
1195 return true;
1196 }
1197 };
1198 performDeviceAction("getFileListingService", serviceAction, MAX_RETRY_ATTEMPTS);
1199 return service[0];
1200 }
1201
1202 /**
1203 * {@inheritDoc}
1204 */
1205 @Override
1206 public boolean pushDir(File localFileDir, String deviceFilePath)
1207 throws DeviceNotAvailableException {
1208 if (!localFileDir.isDirectory()) {
1209 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1210 return false;
1211 }
1212 File[] childFiles = localFileDir.listFiles();
1213 if (childFiles == null) {
1214 CLog.e("Could not read files in %s", localFileDir.getAbsolutePath());
1215 return false;
1216 }
1217 for (File childFile : childFiles) {
1218 String remotePath = String.format("%s/%s", deviceFilePath, childFile.getName());
1219 if (childFile.isDirectory()) {
Hyungtae Tim Kim4cff14e2016-08-01 18:29:48 +09001220 executeShellCommand(String.format("mkdir -p \"%s\"", remotePath));
Julien Desprez6961b272016-02-01 09:58:23 +00001221 if (!pushDir(childFile, remotePath)) {
1222 return false;
1223 }
1224 } else if (childFile.isFile()) {
1225 if (!pushFile(childFile, remotePath)) {
1226 return false;
1227 }
1228 }
1229 }
1230 return true;
1231 }
1232
1233 /**
1234 * {@inheritDoc}
1235 */
1236 @Override
Guang Zhud7088362016-06-28 18:41:10 -07001237 public boolean pullDir(String deviceFilePath, File localDir)
1238 throws DeviceNotAvailableException {
1239 if (!localDir.isDirectory()) {
1240 CLog.e("Local path %s is not a directory", localDir.getAbsolutePath());
1241 return false;
1242 }
1243 if (!isDirectory(deviceFilePath)) {
1244 CLog.e("Device path %s is not a directory", deviceFilePath);
1245 return false;
1246 }
1247 String lsOutput = executeShellCommand(String.format("ls -Ap1 %s", deviceFilePath));
1248 if (lsOutput.trim().isEmpty()) {
1249 CLog.i("Device path is empty, nothing to do.");
1250 return true;
1251 }
1252 String[] items = lsOutput.split("\r?\n");
1253 for (String item : items) {
1254 if (item.isEmpty()) {
1255 // skip empty entries
1256 continue;
1257 }
1258 if (item.endsWith("/")) {
1259 // handle sub dir
1260 // prepare local path first
1261 item = item.substring(0, item.length() - 1);
1262 File subDir = new File(localDir, item);
1263 if (!subDir.mkdir()) {
1264 CLog.w("Failed to create sub directory %s, aborting.",
1265 subDir.getAbsolutePath());
1266 return false;
1267 }
1268 String deviceSubDir = String.format("%s/%s", deviceFilePath, item);
1269 if (!pullDir(deviceSubDir, subDir)) {
1270 CLog.w("Failed to pull sub directory %s from device, aborting", deviceSubDir);
1271 return false;
1272 }
1273 } else {
1274 // handle regular file
1275 String deviceFile = String.format("%s/%s", deviceFilePath, item);
1276 File localFile = new File(localDir, item);
1277 if (!pullFile(deviceFile, localFile)) {
1278 CLog.w("Failed to pull file %s from device, aborting", deviceFile);
1279 return false;
1280 }
1281 }
1282 }
1283 return true;
1284 }
1285
1286 /**
1287 * {@inheritDoc}
1288 */
1289 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001290 public boolean syncFiles(File localFileDir, String deviceFilePath)
1291 throws DeviceNotAvailableException {
1292 if (localFileDir == null || deviceFilePath == null) {
1293 throw new IllegalArgumentException("syncFiles does not take null arguments");
1294 }
1295 CLog.i("Syncing %s to %s on device %s",
1296 localFileDir.getAbsolutePath(), deviceFilePath, getSerialNumber());
1297 if (!localFileDir.isDirectory()) {
1298 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1299 return false;
1300 }
1301 // get the real destination path. This is done because underlying syncService.push
1302 // implementation will add localFileDir.getName() to destination path
1303 deviceFilePath = String.format("%s/%s", interpolatePathVariables(deviceFilePath),
1304 localFileDir.getName());
1305 if (!doesFileExist(deviceFilePath)) {
Hyungtae Tim Kim4cff14e2016-08-01 18:29:48 +09001306 executeShellCommand(String.format("mkdir -p \"%s\"", deviceFilePath));
Julien Desprez6961b272016-02-01 09:58:23 +00001307 }
1308 IFileEntry remoteFileEntry = getFileEntry(deviceFilePath);
1309 if (remoteFileEntry == null) {
1310 CLog.e("Could not find remote file entry %s ", deviceFilePath);
1311 return false;
1312 }
1313
1314 return syncFiles(localFileDir, remoteFileEntry);
1315 }
1316
1317 /**
1318 * Recursively sync newer files.
1319 *
1320 * @param localFileDir the local {@link File} directory to sync
1321 * @param remoteFileEntry the remote destination {@link IFileEntry}
1322 * @return <code>true</code> if files were synced successfully
1323 * @throws DeviceNotAvailableException
1324 */
1325 private boolean syncFiles(File localFileDir, final IFileEntry remoteFileEntry)
1326 throws DeviceNotAvailableException {
1327 CLog.d("Syncing %s to %s on %s", localFileDir.getAbsolutePath(),
1328 remoteFileEntry.getFullPath(), getSerialNumber());
1329 // find newer files to sync
1330 File[] localFiles = localFileDir.listFiles(new NoHiddenFilesFilter());
Eric Rowe1abf2c02017-03-20 17:12:33 -07001331 ArrayList<String> filePathsToSync = new ArrayList<>();
Julien Desprez6961b272016-02-01 09:58:23 +00001332 for (File localFile : localFiles) {
1333 IFileEntry entry = remoteFileEntry.findChild(localFile.getName());
1334 if (entry == null) {
1335 CLog.d("Detected missing file path %s", localFile.getAbsolutePath());
1336 filePathsToSync.add(localFile.getAbsolutePath());
1337 } else if (localFile.isDirectory()) {
1338 // This directory exists remotely. recursively sync it to sync only its newer files
1339 // contents
1340 if (!syncFiles(localFile, entry)) {
1341 return false;
1342 }
1343 } else if (isNewer(localFile, entry)) {
1344 CLog.d("Detected newer file %s", localFile.getAbsolutePath());
1345 filePathsToSync.add(localFile.getAbsolutePath());
1346 }
1347 }
1348
1349 if (filePathsToSync.size() == 0) {
1350 CLog.d("No files to sync");
1351 return true;
1352 }
1353 final String files[] = filePathsToSync.toArray(new String[filePathsToSync.size()]);
1354 DeviceAction syncAction = new DeviceAction() {
1355 @Override
1356 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1357 SyncException {
1358 SyncService syncService = null;
1359 boolean status = false;
1360 try {
1361 syncService = getIDevice().getSyncService();
1362 syncService.push(files, remoteFileEntry.getFileEntry(),
1363 SyncService.getNullProgressMonitor());
1364 status = true;
1365 } catch (SyncException e) {
1366 CLog.w("Failed to sync files to %s on device %s. Message %s",
1367 remoteFileEntry.getFullPath(), getSerialNumber(), e.getMessage());
1368 throw e;
1369 } finally {
1370 if (syncService != null) {
1371 syncService.close();
1372 }
1373 }
1374 return status;
1375 }
1376 };
1377 return performDeviceAction(String.format("sync files %s", remoteFileEntry.getFullPath()),
1378 syncAction, MAX_RETRY_ATTEMPTS);
1379 }
1380
1381 /**
1382 * Queries the file listing service for a given directory
1383 *
1384 * @param remoteFileEntry
1385 * @throws DeviceNotAvailableException
1386 */
1387 FileEntry[] getFileChildren(final FileEntry remoteFileEntry)
1388 throws DeviceNotAvailableException {
1389 // time this operation because its known to hang
1390 FileQueryAction action = new FileQueryAction(remoteFileEntry,
1391 getIDevice().getFileListingService());
1392 performDeviceAction("buildFileCache", action, MAX_RETRY_ATTEMPTS);
1393 return action.mFileContents;
1394 }
1395
1396 private class FileQueryAction implements DeviceAction {
1397
1398 FileEntry[] mFileContents = null;
1399 private final FileEntry mRemoteFileEntry;
1400 private final FileListingService mService;
1401
1402 FileQueryAction(FileEntry remoteFileEntry, FileListingService service) {
1403 throwIfNull(remoteFileEntry);
1404 throwIfNull(service);
1405 mRemoteFileEntry = remoteFileEntry;
1406 mService = service;
1407 }
1408
1409 @Override
1410 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1411 ShellCommandUnresponsiveException {
1412 mFileContents = mService.getChildrenSync(mRemoteFileEntry);
1413 return true;
1414 }
1415 }
1416
1417 /**
1418 * A {@link FilenameFilter} that rejects hidden (ie starts with ".") files.
1419 */
1420 private static class NoHiddenFilesFilter implements FilenameFilter {
1421 /**
1422 * {@inheritDoc}
1423 */
1424 @Override
1425 public boolean accept(File dir, String name) {
1426 return !name.startsWith(".");
1427 }
1428 }
1429
1430 /**
Julien Desprez7c15ac42016-08-10 16:45:44 +01001431 * helper to get the timezone from the device. Example: "Europe/London"
Julien Desprez6961b272016-02-01 09:58:23 +00001432 */
Julien Desprez7c15ac42016-08-10 16:45:44 +01001433 private String getDeviceTimezone() {
Julien Desprez6961b272016-02-01 09:58:23 +00001434 try {
Julien Desprez7c15ac42016-08-10 16:45:44 +01001435 // This may not be set at first, default to GMT in this case.
1436 String timezone = getProperty("persist.sys.timezone");
1437 if (timezone != null) {
1438 return timezone.trim();
1439 }
1440 } catch (DeviceNotAvailableException e) {
1441 // Fall through on purpose
1442 }
1443 return "GMT";
1444 }
1445
1446 /**
Julien Desprez75518d32016-11-18 09:54:34 +00001447 * Return <code>true</code> if local file is newer than remote file. {@link IFileEntry} being
1448 * accurate to the minute, in case of equal times, the file will be considered newer.
Julien Desprez7c15ac42016-08-10 16:45:44 +01001449 * Exposed for testing.
1450 */
1451 protected boolean isNewer(File localFile, IFileEntry entry) {
1452 final String entryTimeString = String.format("%s %s", entry.getDate(), entry.getTime());
1453 try {
1454 String timezone = getDeviceTimezone();
Julien Desprez6961b272016-02-01 09:58:23 +00001455 // expected format of a FileEntry's date and time
Julien Desprez7c15ac42016-08-10 16:45:44 +01001456 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm");
1457 format.setTimeZone(TimeZone.getTimeZone(timezone));
Julien Desprez6961b272016-02-01 09:58:23 +00001458 Date remoteDate = format.parse(entryTimeString);
Julien Desprez7c15ac42016-08-10 16:45:44 +01001459
1460 long offset = 0;
1461 try {
1462 offset = getDeviceTimeOffset(null);
1463 } catch (DeviceNotAvailableException e) {
1464 offset = 0;
1465 }
1466 CLog.i("Device offset time: %s", offset);
1467
Julien Desprez6961b272016-02-01 09:58:23 +00001468 // localFile.lastModified has granularity of ms, but remoteDate.getTime only has
1469 // granularity of minutes. Shift remoteDate.getTime() backward by one minute so newly
1470 // modified files get synced
Julien Desprez5aa8c6e2016-10-27 10:12:13 +01001471 return localFile.lastModified() > (remoteDate.getTime() - 60 * 1000 + offset);
Julien Desprez6961b272016-02-01 09:58:23 +00001472 } catch (ParseException e) {
1473 CLog.e("Error converting remote time stamp %s for %s on device %s", entryTimeString,
1474 entry.getFullPath(), getSerialNumber());
1475 }
1476 // sync file by default
1477 return true;
1478 }
1479
1480 /**
1481 * {@inheritDoc}
1482 */
1483 @Override
1484 public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
1485 final String[] fullCmd = buildAdbCommand(cmdArgs);
1486 AdbAction adbAction = new AdbAction(fullCmd);
1487 performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
1488 return adbAction.mOutput;
1489 }
1490
1491 /**
1492 * {@inheritDoc}
1493 */
1494 @Override
1495 public CommandResult executeFastbootCommand(String... cmdArgs)
1496 throws DeviceNotAvailableException, UnsupportedOperationException {
1497 return doFastbootCommand(getCommandTimeout(), cmdArgs);
1498 }
1499
1500 /**
1501 * {@inheritDoc}
1502 */
1503 @Override
Julien Desprezf7d1e0d2016-06-01 09:32:38 +01001504 public CommandResult executeFastbootCommand(long timeout, String... cmdArgs)
1505 throws DeviceNotAvailableException, UnsupportedOperationException {
1506 return doFastbootCommand(timeout, cmdArgs);
1507 }
1508
1509 /**
1510 * {@inheritDoc}
1511 */
1512 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001513 public CommandResult executeLongFastbootCommand(String... cmdArgs)
1514 throws DeviceNotAvailableException, UnsupportedOperationException {
1515 return doFastbootCommand(getLongCommandTimeout(), cmdArgs);
1516 }
1517
1518 /**
1519 * @param cmdArgs
1520 * @throws DeviceNotAvailableException
1521 */
1522 private CommandResult doFastbootCommand(final long timeout, String... cmdArgs)
1523 throws DeviceNotAvailableException, UnsupportedOperationException {
1524 if (!mFastbootEnabled) {
1525 throw new UnsupportedOperationException(String.format(
1526 "Attempted to fastboot on device %s , but fastboot is not available. Aborting.",
1527 getSerialNumber()));
1528 }
1529 final String[] fullCmd = buildFastbootCommand(cmdArgs);
1530 for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
1531 CommandResult result = new CommandResult(CommandStatus.EXCEPTION);
1532 // block state changes while executing a fastboot command, since
1533 // device will disappear from fastboot devices while command is being executed
1534 mFastbootLock.lock();
1535 try {
1536 result = getRunUtil().runTimedCmd(timeout, fullCmd);
1537 } finally {
1538 mFastbootLock.unlock();
1539 }
1540 if (!isRecoveryNeeded(result)) {
1541 return result;
1542 }
1543 CLog.w("Recovery needed after executing fastboot command");
1544 if (result != null) {
1545 CLog.v("fastboot command output:\nstdout: %s\nstderr:%s",
1546 result.getStdout(), result.getStderr());
1547 }
1548 recoverDeviceFromBootloader();
1549 }
1550 throw new DeviceUnresponsiveException(String.format("Attempted fastboot %s multiple "
1551 + "times on device %s without communication success. Aborting.", cmdArgs[0],
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001552 getSerialNumber()), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001553 }
1554
1555 /**
1556 * {@inheritDoc}
1557 */
1558 @Override
1559 public boolean getUseFastbootErase() {
1560 return mOptions.getUseFastbootErase();
1561 }
1562
1563 /**
1564 * {@inheritDoc}
1565 */
1566 @Override
1567 public void setUseFastbootErase(boolean useFastbootErase) {
1568 mOptions.setUseFastbootErase(useFastbootErase);
1569 }
1570
1571 /**
1572 * {@inheritDoc}
1573 */
1574 @Override
1575 public CommandResult fastbootWipePartition(String partition)
1576 throws DeviceNotAvailableException {
1577 if (mOptions.getUseFastbootErase()) {
1578 return executeLongFastbootCommand("erase", partition);
1579 } else {
1580 return executeLongFastbootCommand("format", partition);
1581 }
1582 }
1583
1584 /**
1585 * Evaluate the given fastboot result to determine if recovery mode needs to be entered
1586 *
1587 * @param fastbootResult the {@link CommandResult} from a fastboot command
1588 * @return <code>true</code> if recovery mode should be entered, <code>false</code> otherwise.
1589 */
1590 private boolean isRecoveryNeeded(CommandResult fastbootResult) {
1591 if (fastbootResult.getStatus().equals(CommandStatus.TIMED_OUT)) {
1592 // fastboot commands always time out if devices is not present
1593 return true;
1594 } else {
1595 // check for specific error messages in result that indicate bad device communication
1596 // and recovery mode is needed
1597 if (fastbootResult.getStderr() == null ||
1598 fastbootResult.getStderr().contains("data transfer failure (Protocol error)") ||
1599 fastbootResult.getStderr().contains("status read failed (No such device)")) {
1600 CLog.w("Bad fastboot response from device %s. stderr: %s. Entering recovery",
1601 getSerialNumber(), fastbootResult.getStderr());
1602 return true;
1603 }
1604 }
1605 return false;
1606 }
1607
1608 /**
1609 * Get the max time allowed in ms for commands.
1610 */
1611 int getCommandTimeout() {
1612 return mCmdTimeout;
1613 }
1614
1615 /**
1616 * Set the max time allowed in ms for commands.
1617 */
1618 void setLongCommandTimeout(long timeout) {
1619 mLongCmdTimeout = timeout;
1620 }
1621
1622 /**
1623 * Get the max time allowed in ms for commands.
1624 */
1625 long getLongCommandTimeout() {
1626 return mLongCmdTimeout;
1627 }
1628
1629 /**
1630 * Set the max time allowed in ms for commands.
1631 */
1632 void setCommandTimeout(int timeout) {
1633 mCmdTimeout = timeout;
1634 }
1635
1636 /**
1637 * Builds the OS command for the given adb command and args
1638 */
1639 private String[] buildAdbCommand(String... commandArgs) {
1640 return ArrayUtil.buildArray(new String[] {"adb", "-s", getSerialNumber()},
1641 commandArgs);
1642 }
1643
1644 /**
1645 * Builds the OS command for the given fastboot command and args
1646 */
1647 private String[] buildFastbootCommand(String... commandArgs) {
Julien Desprez0a7d67d2016-07-21 16:05:57 +01001648 return ArrayUtil.buildArray(new String[] {getFastbootPath(), "-s", getSerialNumber()},
Julien Desprez6961b272016-02-01 09:58:23 +00001649 commandArgs);
1650 }
1651
1652 /**
1653 * Performs an action on this device. Attempts to recover device and optionally retry command
1654 * if action fails.
1655 *
1656 * @param actionDescription a short description of action to be performed. Used for logging
1657 * purposes only.
1658 * @param action the action to be performed
1659 * @param retryAttempts the retry attempts to make for action if it fails but
1660 * recovery succeeds
Julien Desprezd0c379a2016-11-04 11:00:54 +00001661 * @return <code>true</code> if action was performed successfully
Julien Desprez6961b272016-02-01 09:58:23 +00001662 * @throws DeviceNotAvailableException if recovery attempt fails or max attempts done without
1663 * success
1664 */
Julien Desprezc8474552016-02-17 10:59:27 +00001665 protected boolean performDeviceAction(String actionDescription, final DeviceAction action,
Julien Desprez6961b272016-02-01 09:58:23 +00001666 int retryAttempts) throws DeviceNotAvailableException {
1667
1668 for (int i = 0; i < retryAttempts + 1; i++) {
1669 try {
1670 return action.run();
1671 } catch (TimeoutException e) {
1672 logDeviceActionException(actionDescription, e);
1673 } catch (IOException e) {
1674 logDeviceActionException(actionDescription, e);
1675 } catch (InstallException e) {
1676 logDeviceActionException(actionDescription, e);
1677 } catch (SyncException e) {
1678 logDeviceActionException(actionDescription, e);
1679 // a SyncException is not necessarily a device communication problem
1680 // do additional diagnosis
1681 if (!e.getErrorCode().equals(SyncError.BUFFER_OVERRUN) &&
1682 !e.getErrorCode().equals(SyncError.TRANSFER_PROTOCOL_ERROR)) {
1683 // this is a logic problem, doesn't need recovery or to be retried
1684 return false;
1685 }
1686 } catch (AdbCommandRejectedException e) {
1687 logDeviceActionException(actionDescription, e);
1688 } catch (ShellCommandUnresponsiveException e) {
1689 CLog.w("Device %s stopped responding when attempting %s", getSerialNumber(),
1690 actionDescription);
1691 }
1692 // TODO: currently treat all exceptions the same. In future consider different recovery
1693 // mechanisms for time out's vs IOExceptions
1694 recoverDevice();
1695 }
1696 if (retryAttempts > 0) {
1697 throw new DeviceUnresponsiveException(String.format("Attempted %s multiple times "
1698 + "on device %s without communication success. Aborting.", actionDescription,
Julien Desprez0c6c77c2016-05-31 16:35:57 +01001699 getSerialNumber()), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00001700 }
1701 return false;
1702 }
1703
1704 /**
1705 * Log an entry for given exception
1706 *
1707 * @param actionDescription the action's description
1708 * @param e the exception
1709 */
1710 private void logDeviceActionException(String actionDescription, Exception e) {
1711 CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(),
1712 getExceptionMessage(e), actionDescription, getSerialNumber());
1713 }
1714
1715 /**
1716 * Make a best effort attempt to retrieve a meaningful short descriptive message for given
1717 * {@link Exception}
1718 *
1719 * @param e the {@link Exception}
1720 * @return a short message
1721 */
1722 private String getExceptionMessage(Exception e) {
1723 StringBuilder msgBuilder = new StringBuilder();
1724 if (e.getMessage() != null) {
1725 msgBuilder.append(e.getMessage());
1726 }
1727 if (e.getCause() != null) {
1728 msgBuilder.append(" cause: ");
1729 msgBuilder.append(e.getCause().getClass().getSimpleName());
1730 if (e.getCause().getMessage() != null) {
1731 msgBuilder.append(" (");
1732 msgBuilder.append(e.getCause().getMessage());
1733 msgBuilder.append(")");
1734 }
1735 }
1736 return msgBuilder.toString();
1737 }
1738
1739 /**
1740 * Attempts to recover device communication.
1741 *
1742 * @throws DeviceNotAvailableException if device is not longer available
1743 */
1744 @Override
1745 public void recoverDevice() throws DeviceNotAvailableException {
1746 if (mRecoveryMode.equals(RecoveryMode.NONE)) {
1747 CLog.i("Skipping recovery on %s", getSerialNumber());
1748 getRunUtil().sleep(NONE_RECOVERY_MODE_DELAY);
1749 return;
1750 }
1751 CLog.i("Attempting recovery on %s", getSerialNumber());
Julien Desprez7a7d97e2016-02-05 12:27:49 +00001752 try {
1753 mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE));
1754 } catch (DeviceUnresponsiveException due) {
1755 RecoveryMode previousRecoveryMode = mRecoveryMode;
1756 mRecoveryMode = RecoveryMode.NONE;
1757 boolean enabled = enableAdbRoot();
1758 CLog.d("Device Unresponsive during recovery, is root still enabled: %s", enabled);
1759 mRecoveryMode = previousRecoveryMode;
1760 throw due;
1761 }
Julien Desprez6961b272016-02-01 09:58:23 +00001762 if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) {
1763 // turn off recovery mode to prevent reentrant recovery
1764 // TODO: look for a better way to handle this, such as doing postBootUp steps in
1765 // recovery itself
1766 mRecoveryMode = RecoveryMode.NONE;
1767 // this might be a runtime reset - still need to run post boot setup steps
1768 if (isEncryptionSupported() && isDeviceEncrypted()) {
1769 unlockDevice();
1770 }
1771 postBootSetup();
1772 mRecoveryMode = RecoveryMode.AVAILABLE;
1773 } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) {
1774 // turn off recovery mode to prevent reentrant recovery
1775 // TODO: look for a better way to handle this, such as doing postBootUp steps in
1776 // recovery itself
1777 mRecoveryMode = RecoveryMode.NONE;
1778 enableAdbRoot();
1779 mRecoveryMode = RecoveryMode.ONLINE;
1780 }
1781 CLog.i("Recovery successful for %s", getSerialNumber());
1782 }
1783
1784 /**
1785 * Attempts to recover device fastboot communication.
1786 *
1787 * @throws DeviceNotAvailableException if device is not longer available
1788 */
1789 private void recoverDeviceFromBootloader() throws DeviceNotAvailableException {
1790 CLog.i("Attempting recovery on %s in bootloader", getSerialNumber());
1791 mRecovery.recoverDeviceBootloader(mStateMonitor);
1792 CLog.i("Bootloader recovery successful for %s", getSerialNumber());
1793 }
1794
1795 private void recoverDeviceInRecovery() throws DeviceNotAvailableException {
1796 CLog.i("Attempting recovery on %s in recovery", getSerialNumber());
1797 mRecovery.recoverDeviceRecovery(mStateMonitor);
1798 CLog.i("Recovery mode recovery successful for %s", getSerialNumber());
1799 }
1800
1801 /**
1802 * {@inheritDoc}
1803 */
1804 @Override
1805 public void startLogcat() {
1806 if (mLogcatReceiver != null) {
1807 CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber());
1808 return;
1809 }
1810 mLogcatReceiver = createLogcatReceiver();
1811 mLogcatReceiver.start();
1812 }
1813
1814 /**
1815 * {@inheritDoc}
1816 */
1817 @Override
1818 public void clearLogcat() {
1819 if (mLogcatReceiver != null) {
1820 mLogcatReceiver.clear();
1821 }
1822 }
1823
1824 /**
1825 * {@inheritDoc}
1826 */
1827 @Override
1828 public InputStreamSource getLogcat() {
1829 if (mLogcatReceiver == null) {
1830 CLog.w("Not capturing logcat for %s in background, returning a logcat dump",
1831 getSerialNumber());
1832 return getLogcatDump();
1833 } else {
1834 return mLogcatReceiver.getLogcatData();
1835 }
1836 }
1837
1838 /**
1839 * {@inheritDoc}
1840 */
1841 @Override
1842 public InputStreamSource getLogcat(int maxBytes) {
1843 if (mLogcatReceiver == null) {
1844 CLog.w("Not capturing logcat for %s in background, returning a logcat dump "
1845 + "ignoring size", getSerialNumber());
1846 return getLogcatDump();
1847 } else {
1848 return mLogcatReceiver.getLogcatData(maxBytes);
1849 }
1850 }
1851
1852 /**
1853 * {@inheritDoc}
1854 */
1855 @Override
Julien Desprezdcb19d52016-06-20 12:23:12 +01001856 public InputStreamSource getLogcatSince(long date) {
1857 try {
1858 if (getApiLevel() <= 22) {
1859 CLog.i("Api level too low to use logcat -t 'time' reverting to dump");
1860 return getLogcatDump();
1861 }
1862 } catch (DeviceNotAvailableException e) {
1863 // For convenience of interface, we catch the DNAE here.
1864 CLog.e(e);
1865 return getLogcatDump();
1866 }
1867
1868 byte[] output = new byte[0];
1869 try {
1870 // use IDevice directly because we don't want callers to handle
1871 // DeviceNotAvailableException for this method
1872 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
1873 String command = String.format("%s -t '%s'", LogcatReceiver.LOGCAT_CMD, date);
1874 getIDevice().executeShellCommand(command, receiver);
1875 output = receiver.getOutput();
1876 } catch (IOException|AdbCommandRejectedException|
1877 ShellCommandUnresponsiveException|TimeoutException e) {
1878 CLog.w("Failed to get logcat dump from %s: %s", getSerialNumber(), e.getMessage());
1879 CLog.e(e);
1880 }
1881 return new ByteArrayInputStreamSource(output);
1882 }
1883
1884 /**
1885 * {@inheritDoc}
1886 */
1887 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00001888 public InputStreamSource getLogcatDump() {
1889 byte[] output = new byte[0];
1890 try {
1891 // use IDevice directly because we don't want callers to handle
1892 // DeviceNotAvailableException for this method
1893 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
1894 // add -d parameter to make this a non blocking call
Julien Desprez73c55bf2016-09-01 09:27:37 +01001895 getIDevice().executeShellCommand(LogcatReceiver.LOGCAT_CMD + " -d", receiver,
1896 LOGCAT_DUMP_TIMEOUT, TimeUnit.MILLISECONDS);
Julien Desprez6961b272016-02-01 09:58:23 +00001897 output = receiver.getOutput();
1898 } catch (IOException e) {
1899 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
1900 } catch (TimeoutException e) {
1901 CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber());
1902 } catch (AdbCommandRejectedException e) {
1903 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
1904 } catch (ShellCommandUnresponsiveException e) {
1905 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
1906 }
1907 return new ByteArrayInputStreamSource(output);
1908 }
1909
1910 /**
1911 * {@inheritDoc}
1912 */
1913 @Override
1914 public void stopLogcat() {
1915 if (mLogcatReceiver != null) {
1916 mLogcatReceiver.stop();
1917 mLogcatReceiver = null;
1918 } else {
1919 CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber());
1920 }
1921 }
1922
1923 /**
1924 * Factory method to create a {@link LogcatReceiver}.
1925 * <p/>
1926 * Exposed for unit testing.
1927 */
1928 LogcatReceiver createLogcatReceiver() {
1929 String logcatOptions = mOptions.getLogcatOptions();
1930 if (logcatOptions == null) {
1931 return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay);
1932 } else {
1933 return new LogcatReceiver(this,
1934 String.format("%s %s", LogcatReceiver.LOGCAT_CMD, logcatOptions),
1935 mOptions.getMaxLogcatDataSize(), mLogStartDelay);
1936 }
1937 }
1938
1939 /**
1940 * {@inheritDoc}
1941 */
1942 @Override
1943 public InputStreamSource getBugreport() {
Julien Desprez15de6812016-08-08 15:08:06 +01001944 int apiLevel;
Julien Desprez16184162016-06-10 08:56:17 +01001945 try {
Julien Desprez15de6812016-08-08 15:08:06 +01001946 apiLevel = getApiLevel();
Julien Desprez16184162016-06-10 08:56:17 +01001947 } catch (DeviceNotAvailableException e) {
Julien Desprez15de6812016-08-08 15:08:06 +01001948 CLog.e("Device became unavailable while checking API level.");
1949 CLog.e(e);
1950 return null;
Julien Desprez16184162016-06-10 08:56:17 +01001951 }
1952
Julien Desprez15de6812016-08-08 15:08:06 +01001953 if (apiLevel < 24) {
1954 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
1955 try {
1956 executeShellCommand(BUGREPORT_CMD, receiver,
1957 BUGREPORT_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
1958 } catch (DeviceNotAvailableException e) {
1959 // Log, but don't throw, so the caller can get the bugreport contents even
1960 // if the device goes away
1961 CLog.e("Device %s became unresponsive while retrieving bugreport",
1962 getSerialNumber());
1963 }
1964 return new ByteArrayInputStreamSource(receiver.getOutput());
1965 } else {
1966 CLog.d("Api level above 24, using bugreportz instead.");
Julien Desprez15de6812016-08-08 15:08:06 +01001967 File mainEntry = null;
1968 File bugreportzFile = null;
1969 try {
1970 bugreportzFile = getBugreportzInternal();
Julien Desprezf54735c2016-08-16 09:17:07 +01001971 if (bugreportzFile == null) {
1972 CLog.w("Fail to collect the bugreportz.");
Julien Desprez56d044d2016-08-17 12:28:10 +01001973 return bugreportzFallback();
Julien Desprezf54735c2016-08-16 09:17:07 +01001974 }
Julien Desprez35d4a902017-01-06 10:57:00 +00001975 try (ZipFile zip = new ZipFile(bugreportzFile)) {
1976 // We get the main_entry.txt that contains the bugreport name.
1977 mainEntry = ZipUtil2.extractFileFromZip(zip, "main_entry.txt");
1978 String bugreportName = FileUtil.readStringFromFile(mainEntry).trim();
1979 CLog.d("bugreport name: '%s'", bugreportName);
1980 File bugreport = ZipUtil2.extractFileFromZip(zip, bugreportName);
1981 return new FileInputStreamSource(bugreport, true);
1982 }
Julien Desprez15de6812016-08-08 15:08:06 +01001983 } catch (IOException e) {
1984 CLog.e("Error while unzipping bugreportz");
1985 CLog.e(e);
Julien Desprez56d044d2016-08-17 12:28:10 +01001986 return bugreportzFallback();
Julien Desprez15de6812016-08-08 15:08:06 +01001987 } finally {
1988 FileUtil.deleteFile(bugreportzFile);
1989 FileUtil.deleteFile(mainEntry);
1990 }
1991 }
Julien Desprez16184162016-06-10 08:56:17 +01001992 }
1993
1994 /**
Julien Desprez56d044d2016-08-17 12:28:10 +01001995 * If first bugreportz collection was interrupted for any reasons, the temporary file where
1996 * the dumpstate is redirected could exists if it started. We attempt to get it to have some
1997 * partial data.
1998 */
1999 private InputStreamSource bugreportzFallback() {
2000 try {
2001 IFileEntry entries = getFileEntry(BUGREPORTZ_TMP_PATH);
2002 if (entries != null) {
2003 for (IFileEntry f : entries.getChildren(false)) {
2004 String name = f.getName();
Julien Desprezf20644b2016-08-31 09:31:27 +01002005 CLog.d("bugreport entry: %s", name);
Julien Desprezd8501932016-12-06 10:54:45 +00002006 if (name.endsWith(".tmp") || name.endsWith(".zip")) {
Julien Desprez56d044d2016-08-17 12:28:10 +01002007 File tmpBugreport = pullFile(BUGREPORTZ_TMP_PATH + name);
2008 if (tmpBugreport != null) {
2009 return new FileInputStreamSource(tmpBugreport, true);
2010 }
2011 }
2012 }
Julien Desprezf20644b2016-08-31 09:31:27 +01002013 CLog.w("Could not find a tmp bugreport file in the directory.");
2014 } else {
2015 CLog.w("Could not find the file entry: '%s' on the device.", BUGREPORTZ_TMP_PATH);
Julien Desprez56d044d2016-08-17 12:28:10 +01002016 }
2017 } catch (DeviceNotAvailableException e) {
2018 CLog.e(e);
2019 }
Julien Desprezd8501932016-12-06 10:54:45 +00002020 return new ByteArrayInputStreamSource(new byte[] {});
Julien Desprez56d044d2016-08-17 12:28:10 +01002021 }
2022
2023 /**
Julien Desprez16184162016-06-10 08:56:17 +01002024 * {@inheritDoc}
2025 */
2026 @Override
Julien Desprez0c836c92016-08-10 14:40:41 +01002027 public boolean logBugreport(String dataName, ITestLogger listener) {
2028 InputStreamSource bugreport = getBugreportz();
2029 LogDataType type = LogDataType.BUGREPORTZ;
2030 try {
2031 if (bugreport == null) {
2032 bugreport = getBugreport();
2033 type = LogDataType.BUGREPORT;
2034 }
2035 if (bugreport != null) {
2036 listener.testLog(dataName, type, bugreport);
2037 return true;
2038 }
2039 } finally {
2040 StreamUtil.cancel(bugreport);
2041 }
2042 CLog.d("takeBugreport() was not successful in collecting and logging the bugreport "
2043 + "for device %s", getSerialNumber());
2044 return false;
2045 }
2046
2047 /**
2048 * {@inheritDoc}
2049 */
2050 @Override
2051 public Bugreport takeBugreport() {
2052 int apiLevel;
2053 try {
2054 apiLevel = getApiLevel();
2055 } catch (DeviceNotAvailableException e) {
2056 CLog.e("Device became unavailable while checking API level.");
2057 CLog.e(e);
2058 return null;
2059 }
2060 File bugreportFile = null;
2061 if (apiLevel < 24) {
2062 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
2063 try {
2064 executeShellCommand(BUGREPORT_CMD, receiver,
2065 BUGREPORT_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
2066 bugreportFile = FileUtil.createTempFile("bugreport", ".txt");
2067 FileUtil.writeToFile(new ByteArrayInputStream(receiver.getOutput()), bugreportFile);
2068 return new Bugreport(bugreportFile, false);
2069 } catch (DeviceNotAvailableException e) {
2070 // Log, but don't throw, so the caller can get the bugreport contents even
2071 // if the device goes away
2072 CLog.e("Device %s became unresponsive while retrieving bugreport",
2073 getSerialNumber());
2074 } catch (IOException e) {
2075 CLog.e("Error when writing the bugreport file");
2076 CLog.e(e);
2077 }
2078 return null;
2079 } else {
2080 CLog.d("Api level above 24, using bugreportz instead.");
2081 bugreportFile = getBugreportzInternal();
2082 if (bugreportFile != null) {
2083 return new Bugreport(bugreportFile, true);
2084 } else {
2085 CLog.w("Error when collecting the bugreportz.");
2086 return null;
2087 }
2088 }
2089 }
2090
2091 /**
2092 * {@inheritDoc}
2093 */
2094 @Override
Julien Desprez16184162016-06-10 08:56:17 +01002095 public InputStreamSource getBugreportz() {
Julien Desprez83922232016-11-07 09:56:04 +00002096 try {
2097 checkApiLevelAgainst("getBugreportz", 24);
2098 File bugreportZip = getBugreportzInternal();
2099 if (bugreportZip != null) {
2100 return new FileInputStreamSource(bugreportZip, true);
2101 }
2102 } catch (IllegalArgumentException e) {
2103 CLog.e("API level error when checking bugreportz support.");
2104 CLog.e(e);
Julien Desprez15de6812016-08-08 15:08:06 +01002105 }
2106 return null;
2107 }
2108
2109 /**
2110 * Internal Helper method to get the bugreportz zip file as a {@link File}.
Julien Desprez0c836c92016-08-10 14:40:41 +01002111 * Exposed for testing.
Julien Desprez15de6812016-08-08 15:08:06 +01002112 */
Julien Desprez0c836c92016-08-10 14:40:41 +01002113 protected File getBugreportzInternal() {
Julien Desprez15de6812016-08-08 15:08:06 +01002114 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
Julien Desprez16184162016-06-10 08:56:17 +01002115 // Does not rely on {@link ITestDevice#executeAdbCommand(String...)} because it does not
2116 // provide a timeout.
Julien Desprez16184162016-06-10 08:56:17 +01002117 try {
2118 executeShellCommand(BUGREPORTZ_CMD, receiver,
Julien Desprezf54735c2016-08-16 09:17:07 +01002119 BUGREPORTZ_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */);
Julien Desprez16184162016-06-10 08:56:17 +01002120 String output = receiver.getOutput().trim();
2121 Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output);
2122 if (!match.find()) {
2123 CLog.e("Something went went wrong during bugreportz collection: '%s'", output);
2124 return null;
2125 } else {
2126 String remoteFilePath = match.group(2);
2127 File zipFile = null;
2128 try {
2129 if (!doesFileExist(remoteFilePath)) {
2130 CLog.e("Did not find bugreportz at: %s", remoteFilePath);
2131 return null;
2132 }
2133 // Create a placeholder to replace the file
2134 zipFile = FileUtil.createTempFile("bugreportz", ".zip");
2135 pullFile(remoteFilePath, zipFile);
2136 String bugreportDir =
2137 remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/'));
2138 if (!bugreportDir.isEmpty()) {
2139 // clean bugreport files directory on device
2140 executeShellCommand(String.format("rm %s/*", bugreportDir));
2141 }
2142
Julien Desprez15de6812016-08-08 15:08:06 +01002143 return zipFile;
Julien Desprez16184162016-06-10 08:56:17 +01002144 } catch (IOException e) {
2145 CLog.e("Failed to create the temporary file.");
2146 return null;
2147 }
2148 }
2149 } catch (DeviceNotAvailableException e) {
2150 CLog.e("Device %s became unresponsive while retrieving bugreportz", getSerialNumber());
2151 CLog.e(e);
2152 }
2153 return null;
Julien Desprez6961b272016-02-01 09:58:23 +00002154 }
2155
2156 /**
2157 * {@inheritDoc}
2158 */
2159 @Override
2160 public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002161 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00002162 }
2163
2164 /**
2165 * {@inheritDoc}
2166 */
2167 @Override
2168 public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002169 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00002170 }
2171
Insaf Latypov73f7aeb2017-03-03 11:05:08 +00002172 /** {@inheritDoc} */
2173 @Override
2174 public InputStreamSource getScreenshot(String format, boolean rescale)
2175 throws DeviceNotAvailableException {
2176 throw new UnsupportedOperationException("No support for Screenshot");
2177 }
2178
2179 /** {@inheritDoc} */
Julien Desprez6961b272016-02-01 09:58:23 +00002180 @Override
2181 public void clearLastConnectedWifiNetwork() {
2182 mLastConnectedWifiSsid = null;
2183 mLastConnectedWifiPsk = null;
2184 }
2185
2186 /**
2187 * {@inheritDoc}
2188 */
2189 @Override
2190 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk)
2191 throws DeviceNotAvailableException {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002192 return connectToWifiNetwork(wifiSsid, wifiPsk, false);
2193 }
2194
2195 /**
2196 * {@inheritDoc}
2197 */
2198 @Override
2199 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)
2200 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00002201 // Clears the last connected wifi network.
2202 mLastConnectedWifiSsid = null;
2203 mLastConnectedWifiPsk = null;
2204
2205 // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()}
Julien Desprez8539c082016-03-04 13:39:19 +00002206 // times
Julien Desprez6961b272016-02-01 09:58:23 +00002207 Random rnd = new Random();
2208 int backoffSlotCount = 2;
Julien Desprez8539c082016-03-04 13:39:19 +00002209 int waitTime = mOptions.getWifiRetryWaitTime();
Julien Desprez6961b272016-02-01 09:58:23 +00002210 IWifiHelper wifi = createWifiHelper();
Julien Desprezac5f37f2017-02-13 10:41:22 +00002211 try {
2212 for (int i = 1; i <= mOptions.getWifiAttempts(); i++) {
2213 CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber());
2214 boolean success =
2215 wifi.connectToNetwork(
2216 wifiSsid, wifiPsk, mOptions.getConnCheckUrl(), scanSsid);
2217 final Map<String, String> wifiInfo = wifi.getWifiInfo();
2218 if (success) {
2219 CLog.i(
2220 "Successfully connected to wifi network %s(%s) on %s",
2221 wifiSsid, wifiInfo.get("bssid"), getSerialNumber());
Julien Desprez6961b272016-02-01 09:58:23 +00002222
Julien Desprezac5f37f2017-02-13 10:41:22 +00002223 mLastConnectedWifiSsid = wifiSsid;
2224 mLastConnectedWifiPsk = wifiPsk;
Julien Desprez6961b272016-02-01 09:58:23 +00002225
Julien Desprezac5f37f2017-02-13 10:41:22 +00002226 return true;
2227 } else {
2228 CLog.w(
2229 "Failed to connect to wifi network %s(%s) on %s on attempt %d of %d",
2230 wifiSsid,
2231 wifiInfo.get("bssid"),
2232 getSerialNumber(),
2233 i,
2234 mOptions.getWifiAttempts());
Julien Desprez8539c082016-03-04 13:39:19 +00002235 }
Julien Desprezac5f37f2017-02-13 10:41:22 +00002236 if (i < mOptions.getWifiAttempts()) {
2237 if (mOptions.isWifiExpoRetryEnabled()) {
2238 // use binary exponential back-offs when retrying.
2239 waitTime *= rnd.nextInt(backoffSlotCount);
2240 backoffSlotCount *= 2;
2241 }
2242 CLog.e("Waiting for %d ms before reconnecting to %s...", waitTime, wifiSsid);
2243 getRunUtil().sleep(waitTime);
2244 }
Julien Desprez6961b272016-02-01 09:58:23 +00002245 }
Julien Desprezac5f37f2017-02-13 10:41:22 +00002246 } finally {
2247 wifi.cleanUp();
Julien Desprez6961b272016-02-01 09:58:23 +00002248 }
2249 return false;
2250 }
2251
2252 /**
2253 * {@inheritDoc}
2254 */
2255 @Override
2256 public boolean checkConnectivity() throws DeviceNotAvailableException {
2257 IWifiHelper wifi = createWifiHelper();
2258 return wifi.checkConnectivity(mOptions.getConnCheckUrl());
2259 }
2260
2261 /**
2262 * {@inheritDoc}
2263 */
2264 @Override
2265 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)
2266 throws DeviceNotAvailableException {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002267 return connectToWifiNetworkIfNeeded(wifiSsid, wifiPsk, false);
2268 }
2269
2270 /**
2271 * {@inheritDoc}
2272 */
2273 @Override
2274 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)
2275 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00002276 if (!checkConnectivity()) {
Trevor Radcliffe321a1972016-11-04 10:16:01 -07002277 return connectToWifiNetwork(wifiSsid, wifiPsk, scanSsid);
Julien Desprez6961b272016-02-01 09:58:23 +00002278 }
2279 return true;
2280 }
2281
2282 /**
2283 * {@inheritDoc}
2284 */
2285 @Override
2286 public boolean isWifiEnabled() throws DeviceNotAvailableException {
Julien Desprezac5f37f2017-02-13 10:41:22 +00002287 final IWifiHelper wifi = createWifiHelper();
Julien Desprez6961b272016-02-01 09:58:23 +00002288 try {
Julien Desprez6961b272016-02-01 09:58:23 +00002289 return wifi.isWifiEnabled();
2290 } catch (RuntimeException e) {
2291 CLog.w("Failed to create WifiHelper: %s", e.getMessage());
2292 return false;
Julien Desprezac5f37f2017-02-13 10:41:22 +00002293 } finally {
2294 wifi.cleanUp();
Julien Desprez6961b272016-02-01 09:58:23 +00002295 }
2296 }
2297
2298 /**
2299 * Checks that the device is currently successfully connected to given wifi SSID.
2300 *
2301 * @param wifiSSID the wifi ssid
2302 * @return <code>true</code> if device is currently connected to wifiSSID and has network
2303 * connectivity. <code>false</code> otherwise
2304 * @throws DeviceNotAvailableException if connection with device was lost
2305 */
2306 boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException {
2307 CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber());
2308 final IWifiHelper wifi = createWifiHelper();
Julien Desprezac5f37f2017-02-13 10:41:22 +00002309 try {
2310 // getSSID returns SSID as "SSID"
2311 final String quotedSSID = String.format("\"%s\"", wifiSSID);
Julien Desprez6961b272016-02-01 09:58:23 +00002312
Julien Desprezac5f37f2017-02-13 10:41:22 +00002313 boolean test = wifi.isWifiEnabled();
2314 CLog.v("%s: wifi enabled? %b", getSerialNumber(), test);
Julien Desprez6961b272016-02-01 09:58:23 +00002315
Julien Desprezac5f37f2017-02-13 10:41:22 +00002316 if (test) {
2317 final String actualSSID = wifi.getSSID();
2318 test = quotedSSID.equals(actualSSID);
2319 CLog.v(
2320 "%s: SSID match (%s, %s, %b)",
2321 getSerialNumber(), quotedSSID, actualSSID, test);
2322 }
2323 if (test) {
2324 test = wifi.hasValidIp();
2325 CLog.v("%s: validIP? %b", getSerialNumber(), test);
2326 }
2327 if (test) {
2328 test = checkConnectivity();
2329 CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test);
2330 }
2331 return test;
2332 } finally {
2333 wifi.cleanUp();
Julien Desprez6961b272016-02-01 09:58:23 +00002334 }
Julien Desprez6961b272016-02-01 09:58:23 +00002335 }
2336
2337 /**
2338 * {@inheritDoc}
2339 */
2340 @Override
2341 public boolean disconnectFromWifi() throws DeviceNotAvailableException {
2342 CLog.i("Disconnecting from wifi on %s", getSerialNumber());
2343 // Clears the last connected wifi network.
2344 mLastConnectedWifiSsid = null;
2345 mLastConnectedWifiPsk = null;
2346
2347 IWifiHelper wifi = createWifiHelper();
Julien Desprezac5f37f2017-02-13 10:41:22 +00002348 try {
2349 return wifi.disconnectFromNetwork();
2350 } finally {
2351 wifi.cleanUp();
2352 }
Julien Desprez6961b272016-02-01 09:58:23 +00002353 }
2354
2355 /**
2356 * {@inheritDoc}
2357 */
2358 @Override
2359 public String getIpAddress() throws DeviceNotAvailableException {
2360 IWifiHelper wifi = createWifiHelper();
Julien Desprezac5f37f2017-02-13 10:41:22 +00002361 try {
2362 return wifi.getIpAddress();
2363 } finally {
2364 wifi.cleanUp();
2365 }
Julien Desprez6961b272016-02-01 09:58:23 +00002366 }
2367
2368 /**
2369 * {@inheritDoc}
2370 */
2371 @Override
2372 public boolean enableNetworkMonitor() throws DeviceNotAvailableException {
2373 mNetworkMonitorEnabled = false;
2374
2375 IWifiHelper wifi = createWifiHelper();
Julien Desprezac5f37f2017-02-13 10:41:22 +00002376 try {
2377 wifi.stopMonitor();
2378 if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) {
2379 mNetworkMonitorEnabled = true;
2380 return true;
2381 }
2382 } finally {
2383 wifi.cleanUp();
Julien Desprez6961b272016-02-01 09:58:23 +00002384 }
2385 return false;
2386 }
2387
2388 /**
2389 * {@inheritDoc}
2390 */
2391 @Override
2392 public boolean disableNetworkMonitor() throws DeviceNotAvailableException {
2393 mNetworkMonitorEnabled = false;
2394
2395 IWifiHelper wifi = createWifiHelper();
2396 List<Long> samples = wifi.stopMonitor();
2397 if (!samples.isEmpty()) {
2398 int failures = 0;
2399 long totalLatency = 0;
2400 for (Long sample : samples) {
2401 if (sample < 0) {
2402 failures += 1;
2403 } else {
2404 totalLatency += sample;
2405 }
2406 }
2407 double failureRate = failures * 100.0 / samples.size();
2408 double avgLatency = 0.0;
2409 if (failures < samples.size()) {
2410 avgLatency = totalLatency / (samples.size() - failures);
2411 }
2412 CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f",
2413 mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000,
2414 failureRate, avgLatency);
2415 }
2416 return true;
2417 }
2418
2419 /**
2420 * Create a {@link WifiHelper} to use
2421 * <p/>
2422 * Exposed so unit tests can mock
Julien Desprez021af1d2016-11-29 14:54:07 +00002423 * @throws DeviceNotAvailableException
Julien Desprez6961b272016-02-01 09:58:23 +00002424 */
2425 IWifiHelper createWifiHelper() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002426 // current wifi helper won't work on AndroidNativeDevice
2427 // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when
2428 // we learn what is available.
2429 throw new UnsupportedOperationException("Wifi helper is not supported.");
Julien Desprez6961b272016-02-01 09:58:23 +00002430 }
2431
2432 /**
2433 * {@inheritDoc}
2434 */
2435 @Override
2436 public boolean clearErrorDialogs() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002437 throw new UnsupportedOperationException("No support for Screen's features");
Julien Desprez6961b272016-02-01 09:58:23 +00002438 }
2439
Julien Desprez14e96692017-01-12 12:31:29 +00002440 /** {@inheritDoc} */
2441 @Override
2442 public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException {
2443 throw new UnsupportedOperationException("No support for keyguard querying.");
2444 }
2445
Julien Desprez6961b272016-02-01 09:58:23 +00002446 IDeviceStateMonitor getDeviceStateMonitor() {
2447 return mStateMonitor;
2448 }
2449
2450 /**
2451 * {@inheritDoc}
2452 */
2453 @Override
2454 public void postBootSetup() throws DeviceNotAvailableException {
2455 enableAdbRoot();
Julien Desprezc8474552016-02-17 10:59:27 +00002456 prePostBootSetup();
Julien Desprez6961b272016-02-01 09:58:23 +00002457 for (String command : mOptions.getPostBootCommands()) {
2458 executeShellCommand(command);
2459 }
2460 }
2461
2462 /**
Julien Desprezc8474552016-02-17 10:59:27 +00002463 * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for
2464 * specific post boot setup.
2465 * @throws DeviceNotAvailableException
2466 */
2467 protected void prePostBootSetup() throws DeviceNotAvailableException {
2468 // Empty on purpose.
2469 }
2470
2471 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002472 * Ensure wifi connection is re-established after boot. This is intended to be called after TF
2473 * initiated reboots(ones triggered by {@link #reboot()}) only.
2474 *
2475 * @throws DeviceNotAvailableException
2476 */
2477 void postBootWifiSetup() throws DeviceNotAvailableException {
2478 if (mLastConnectedWifiSsid != null) {
2479 reconnectToWifiNetwork();
2480 }
2481 if (mNetworkMonitorEnabled) {
2482 if (!enableNetworkMonitor()) {
2483 CLog.w("Failed to enable network monitor on %s after reboot", getSerialNumber());
2484 }
2485 }
2486 }
2487
2488 void reconnectToWifiNetwork() throws DeviceNotAvailableException {
2489 // First, wait for wifi to re-connect automatically.
2490 long startTime = System.currentTimeMillis();
2491 boolean isConnected = checkConnectivity();
2492 while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) {
2493 getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL);
2494 isConnected = checkConnectivity();
2495 }
2496
2497 if (isConnected) {
2498 return;
2499 }
2500
2501 // If wifi is still not connected, try to re-connect on our own.
2502 final String wifiSsid = mLastConnectedWifiSsid;
2503 if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) {
2504 throw new NetworkNotAvailableException(
2505 String.format("Failed to connect to wifi network %s on %s after reboot",
2506 wifiSsid, getSerialNumber()));
2507 }
2508 }
2509
2510 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002511 * {@inheritDoc}
2512 */
2513 @Override
2514 public void rebootIntoBootloader()
2515 throws DeviceNotAvailableException, UnsupportedOperationException {
2516 if (!mFastbootEnabled) {
2517 throw new UnsupportedOperationException(
2518 "Fastboot is not available and cannot reboot into bootloader");
2519 }
2520 CLog.i("Rebooting device %s in state %s into bootloader", getSerialNumber(),
2521 getDeviceState());
2522 if (TestDeviceState.FASTBOOT.equals(getDeviceState())) {
2523 CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
2524 executeFastbootCommand("reboot-bootloader");
2525 } else {
2526 CLog.i("Booting device %s into bootloader", getSerialNumber());
2527 doAdbRebootBootloader();
2528 }
2529 if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
2530 recoverDeviceFromBootloader();
2531 }
2532 }
2533
2534 private void doAdbRebootBootloader() throws DeviceNotAvailableException {
2535 doAdbReboot("bootloader");
2536 }
2537
2538 /**
2539 * {@inheritDoc}
2540 */
2541 @Override
2542 public void reboot() throws DeviceNotAvailableException {
2543 rebootUntilOnline();
2544
2545 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2546 setRecoveryMode(RecoveryMode.ONLINE);
2547
2548 if (isEncryptionSupported() && isDeviceEncrypted()) {
2549 unlockDevice();
2550 }
2551
2552 setRecoveryMode(cachedRecoveryMode);
2553
2554 if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) {
2555 postBootSetup();
2556 postBootWifiSetup();
2557 return;
2558 } else {
2559 recoverDevice();
2560 }
2561 }
2562
2563 /**
2564 * {@inheritDoc}
2565 */
2566 @Override
2567 public void rebootUntilOnline() throws DeviceNotAvailableException {
2568 doReboot();
2569 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2570 setRecoveryMode(RecoveryMode.ONLINE);
2571 if (mStateMonitor.waitForDeviceOnline() != null) {
2572 enableAdbRoot();
2573 } else {
2574 recoverDevice();
2575 }
2576 setRecoveryMode(cachedRecoveryMode);
2577 }
2578
2579 /**
2580 * {@inheritDoc}
2581 */
2582 @Override
2583 public void rebootIntoRecovery() throws DeviceNotAvailableException {
2584 if (TestDeviceState.FASTBOOT == getDeviceState()) {
2585 CLog.w("device %s in fastboot when requesting boot to recovery. " +
2586 "Rebooting to userspace first.", getSerialNumber());
2587 rebootUntilOnline();
2588 }
2589 doAdbReboot("recovery");
2590 if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) {
2591 recoverDeviceInRecovery();
2592 }
2593 }
2594
2595 /**
2596 * {@inheritDoc}
2597 */
2598 @Override
2599 public void nonBlockingReboot() throws DeviceNotAvailableException {
2600 doReboot();
2601 }
2602
2603 /**
2604 * Exposed for unit testing.
2605 *
2606 * @throws DeviceNotAvailableException
2607 */
2608 void doReboot() throws DeviceNotAvailableException, UnsupportedOperationException {
2609 if (TestDeviceState.FASTBOOT == getDeviceState()) {
2610 CLog.i("device %s in fastboot. Rebooting to userspace.", getSerialNumber());
2611 executeFastbootCommand("reboot");
2612 } else {
Guang Zhu120ed1c2016-02-24 23:31:49 -08002613 if (mOptions.shouldDisableReboot()) {
2614 CLog.i("Device reboot disabled by options, skipped.");
2615 return;
2616 }
Julien Desprez6961b272016-02-01 09:58:23 +00002617 CLog.i("Rebooting device %s", getSerialNumber());
2618 doAdbReboot(null);
2619 waitForDeviceNotAvailable("reboot", DEFAULT_UNAVAILABLE_TIMEOUT);
2620 }
2621 }
2622
2623 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002624 * Perform a adb reboot.
2625 *
2626 * @param into the bootloader name to reboot into, or <code>null</code> to just reboot the
2627 * device.
2628 * @throws DeviceNotAvailableException
2629 */
Julien Desprezc8474552016-02-17 10:59:27 +00002630 protected void doAdbReboot(final String into) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002631 DeviceAction rebootAction = new DeviceAction() {
2632 @Override
2633 public boolean run() throws TimeoutException, IOException,
2634 AdbCommandRejectedException {
2635 getIDevice().reboot(into);
2636 return true;
2637 }
2638 };
2639 performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS);
2640
Julien Desprez6961b272016-02-01 09:58:23 +00002641 }
2642
Julien Desprezc8474552016-02-17 10:59:27 +00002643 protected void waitForDeviceNotAvailable(String operationDesc, long time) {
Julien Desprez6961b272016-02-01 09:58:23 +00002644 // TODO: a bit of a race condition here. Would be better to start a
2645 // before the operation
2646 if (!mStateMonitor.waitForDeviceNotAvailable(time)) {
2647 // above check is flaky, ignore till better solution is found
2648 CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(),
2649 operationDesc);
2650 }
2651 }
2652
2653 /**
2654 * {@inheritDoc}
2655 */
2656 @Override
2657 public boolean enableAdbRoot() throws DeviceNotAvailableException {
2658 // adb root is a relatively intensive command, so do a brief check first to see
2659 // if its necessary or not
2660 if (isAdbRoot()) {
2661 CLog.i("adb is already running as root on %s", getSerialNumber());
jdesprezdcc60fc2017-08-07 14:51:00 -07002662 // Still check for online, in some case we could see the root, but device could be
2663 // very early in its cycle.
2664 waitForDeviceOnline();
Julien Desprez6961b272016-02-01 09:58:23 +00002665 return true;
2666 }
2667 // Don't enable root if user requested no root
2668 if (!isEnableAdbRoot()) {
2669 CLog.i("\"enable-root\" set to false; ignoring 'adb root' request");
2670 return false;
2671 }
2672 CLog.i("adb root on device %s", getSerialNumber());
2673 int attempts = MAX_RETRY_ATTEMPTS + 1;
2674 for (int i=1; i <= attempts; i++) {
2675 String output = executeAdbCommand("root");
2676 // wait for device to disappear from adb
2677 waitForDeviceNotAvailable("root", 20 * 1000);
Julien Desprez8539c082016-03-04 13:39:19 +00002678
2679 postAdbRootAction();
2680
Julien Desprez6961b272016-02-01 09:58:23 +00002681 // wait for device to be back online
2682 waitForDeviceOnline();
2683
2684 if (isAdbRoot()) {
2685 return true;
2686 }
2687 CLog.w("'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'",
2688 getSerialNumber(), i, attempts, output);
2689 }
2690 return false;
2691 }
2692
2693 /**
Julien Desprez22d0c3a2016-04-15 10:38:08 +01002694 * {@inheritDoc}
2695 */
2696 @Override
2697 public boolean disableAdbRoot() throws DeviceNotAvailableException {
2698 if (!isAdbRoot()) {
2699 CLog.i("adb is already unroot on %s", getSerialNumber());
2700 return true;
2701 }
2702
2703 CLog.i("adb unroot on device %s", getSerialNumber());
2704 int attempts = MAX_RETRY_ATTEMPTS + 1;
2705 for (int i=1; i <= attempts; i++) {
2706 String output = executeAdbCommand("unroot");
2707 // wait for device to disappear from adb
2708 waitForDeviceNotAvailable("unroot", 5 * 1000);
2709
2710 postAdbUnrootAction();
2711
2712 // wait for device to be back online
2713 waitForDeviceOnline();
2714
2715 if (!isAdbRoot()) {
2716 return true;
2717 }
2718 CLog.w("'adb unroot' on %s unsuccessful on attempt %d of %d. Output: '%s'",
2719 getSerialNumber(), i, attempts, output);
2720 }
2721 return false;
2722 }
2723
2724 /**
Julien Desprez8539c082016-03-04 13:39:19 +00002725 * Override if the device needs some specific actions to be taken after adb root and before the
2726 * device is back online.
2727 * Default implementation doesn't include any addition actions.
2728 * adb root is not guaranteed to be enabled at this stage.
Julien Desprez021af1d2016-11-29 14:54:07 +00002729 * @throws DeviceNotAvailableException
Julien Desprez8539c082016-03-04 13:39:19 +00002730 */
Julien Desprez9dca62e2016-04-08 14:47:57 +01002731 public void postAdbRootAction() throws DeviceNotAvailableException {
Julien Desprez8539c082016-03-04 13:39:19 +00002732 // Empty on purpose.
2733 }
2734
2735 /**
Julien Desprez22d0c3a2016-04-15 10:38:08 +01002736 * Override if the device needs some specific actions to be taken after adb unroot and before
2737 * the device is back online.
2738 * Default implementation doesn't include any additional actions.
2739 * adb root is not guaranteed to be disabled at this stage.
Julien Desprez021af1d2016-11-29 14:54:07 +00002740 * @throws DeviceNotAvailableException
Julien Desprez22d0c3a2016-04-15 10:38:08 +01002741 */
Julien Desprez5e4fc6b2016-06-06 10:08:46 +01002742 public void postAdbUnrootAction() throws DeviceNotAvailableException {
Julien Desprez22d0c3a2016-04-15 10:38:08 +01002743 // Empty on purpose.
2744 }
2745
2746 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002747 * {@inheritDoc}
2748 */
2749 @Override
2750 public boolean isAdbRoot() throws DeviceNotAvailableException {
2751 String output = executeShellCommand("id");
2752 return output.contains("uid=0(root)");
2753 }
2754
2755 /**
2756 * {@inheritDoc}
2757 */
2758 @Override
2759 public boolean encryptDevice(boolean inplace) throws DeviceNotAvailableException,
2760 UnsupportedOperationException {
2761 if (!isEncryptionSupported()) {
2762 throw new UnsupportedOperationException(String.format("Can't encrypt device %s: "
2763 + "encryption not supported", getSerialNumber()));
2764 }
2765
2766 if (isDeviceEncrypted()) {
2767 CLog.d("Device %s is already encrypted, skipping", getSerialNumber());
2768 return true;
2769 }
2770
2771 enableAdbRoot();
2772
2773 String encryptMethod;
2774 long timeout;
2775 if (inplace) {
2776 encryptMethod = "inplace";
2777 timeout = ENCRYPTION_INPLACE_TIMEOUT_MIN;
2778 } else {
2779 encryptMethod = "wipe";
2780 timeout = ENCRYPTION_WIPE_TIMEOUT_MIN;
2781 }
2782
2783 CLog.i("Encrypting device %s via %s", getSerialNumber(), encryptMethod);
2784
2785 // enable crypto takes one of the following formats:
2786 // cryptfs enablecrypto <wipe|inplace> <passwd>
2787 // cryptfs enablecrypto <wipe|inplace> default|password|pin|pattern [passwd]
2788 // Try the first one first, if it outputs "500 0 Usage: ...", try the second.
2789 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
2790 String command = String.format("vdc cryptfs enablecrypto %s \"%s\"", encryptMethod,
2791 ENCRYPTION_PASSWORD);
2792 executeShellCommand(command, receiver, timeout, TimeUnit.MINUTES, 1);
Julien Desprezac96c812016-08-10 14:57:45 +01002793 if (receiver.getOutput().split(":")[0].matches("500 \\d+ Usage")) {
Julien Desprez6961b272016-02-01 09:58:23 +00002794 command = String.format("vdc cryptfs enablecrypto %s default", encryptMethod);
2795 executeShellCommand(command, new NullOutputReceiver(), timeout, TimeUnit.MINUTES, 1);
2796 }
2797
2798 waitForDeviceNotAvailable("reboot", getCommandTimeout());
2799 waitForDeviceOnline(); // Device will not become available until the user data is unlocked.
2800
2801 return isDeviceEncrypted();
2802 }
2803
2804 /**
2805 * {@inheritDoc}
2806 */
2807 @Override
2808 public boolean unencryptDevice() throws DeviceNotAvailableException,
2809 UnsupportedOperationException {
2810 if (!isEncryptionSupported()) {
2811 throw new UnsupportedOperationException(String.format("Can't unencrypt device %s: "
2812 + "encryption not supported", getSerialNumber()));
2813 }
2814
2815 if (!isDeviceEncrypted()) {
2816 CLog.d("Device %s is already unencrypted, skipping", getSerialNumber());
2817 return true;
2818 }
2819
2820 CLog.i("Unencrypting device %s", getSerialNumber());
2821
2822 // If the device supports fastboot format, then we're done.
2823 if (!mOptions.getUseFastbootErase()) {
2824 rebootIntoBootloader();
2825 fastbootWipePartition("userdata");
2826 rebootUntilOnline();
2827 waitForDeviceAvailable(ENCRYPTION_WIPE_TIMEOUT_MIN * 60 * 1000);
2828 return true;
2829 }
2830
2831 // Determine if we need to format partition instead of wipe.
2832 boolean format = false;
2833 String output = executeShellCommand("vdc volume list");
2834 String[] splitOutput;
2835 if (output != null) {
2836 splitOutput = output.split("\r?\n");
2837 for (String line : splitOutput) {
2838 if (line.startsWith("110 ") && line.contains("sdcard /mnt/sdcard") &&
2839 !line.endsWith("0")) {
2840 format = true;
2841 }
2842 }
2843 }
2844
2845 rebootIntoBootloader();
2846 fastbootWipePartition("userdata");
2847
2848 // If the device requires time to format the filesystem after fastboot erase userdata, wait
2849 // for the device to reboot a second time.
2850 if (mOptions.getUnencryptRebootTimeout() > 0) {
2851 rebootUntilOnline();
2852 if (waitForDeviceNotAvailable(mOptions.getUnencryptRebootTimeout())) {
2853 waitForDeviceOnline();
2854 }
2855 }
2856
2857 if (format) {
2858 CLog.d("Need to format sdcard for device %s", getSerialNumber());
2859
2860 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2861 setRecoveryMode(RecoveryMode.ONLINE);
2862
2863 output = executeShellCommand("vdc volume format sdcard");
2864 if (output == null) {
2865 CLog.e("Command vdc volume format sdcard failed will no output for device %s:\n%s",
2866 getSerialNumber());
2867 setRecoveryMode(cachedRecoveryMode);
2868 return false;
2869 }
2870 splitOutput = output.split("\r?\n");
2871 if (!splitOutput[splitOutput.length - 1].startsWith("200 ")) {
2872 CLog.e("Command vdc volume format sdcard failed for device %s:\n%s",
2873 getSerialNumber(), output);
2874 setRecoveryMode(cachedRecoveryMode);
2875 return false;
2876 }
2877
2878 setRecoveryMode(cachedRecoveryMode);
2879 }
2880
2881 reboot();
2882
2883 return true;
2884 }
2885
2886 /**
2887 * {@inheritDoc}
2888 */
2889 @Override
2890 public boolean unlockDevice() throws DeviceNotAvailableException,
2891 UnsupportedOperationException {
2892 if (!isEncryptionSupported()) {
2893 throw new UnsupportedOperationException(String.format("Can't unlock device %s: "
2894 + "encryption not supported", getSerialNumber()));
2895 }
2896
2897 if (!isDeviceEncrypted()) {
2898 CLog.d("Device %s is not encrypted, skipping", getSerialNumber());
2899 return true;
2900 }
2901
2902 CLog.i("Unlocking device %s", getSerialNumber());
2903
2904 enableAdbRoot();
2905
2906 // FIXME: currently, vcd checkpw can return an empty string when it never should. Try 3
2907 // times.
2908 String output;
2909 int i = 0;
2910 do {
2911 // Enter the password. Output will be:
2912 // "200 [X] -1" if the password has already been entered correctly,
2913 // "200 [X] 0" if the password is entered correctly,
2914 // "200 [X] N" where N is any positive number if the password is incorrect,
2915 // any other string if there is an error.
2916 output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"",
2917 ENCRYPTION_PASSWORD)).trim();
2918
2919 if (output.startsWith("200 ") && output.endsWith(" -1")) {
2920 return true;
2921 }
2922
2923 if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) {
2924 CLog.e("checkpw gave output '%s' while trying to unlock device %s",
2925 output, getSerialNumber());
2926 return false;
2927 }
2928
2929 getRunUtil().sleep(500);
2930 } while (output.isEmpty() && ++i < 3);
2931
2932 if (output.isEmpty()) {
2933 CLog.e("checkpw gave no output while trying to unlock device %s");
2934 }
2935
2936 // Restart the framework. Output will be:
2937 // "200 [X] 0" if the user data partition can be mounted,
2938 // "200 [X] -1" if the user data partition can not be mounted (no correct password given),
2939 // any other string if there is an error.
2940 output = executeShellCommand("vdc cryptfs restart").trim();
2941
2942 if (!(output.startsWith("200 ") && output.endsWith(" 0"))) {
2943 CLog.e("restart gave output '%s' while trying to unlock device %s", output,
2944 getSerialNumber());
2945 return false;
2946 }
2947
2948 waitForDeviceAvailable();
2949
2950 return true;
2951 }
2952
2953 /**
2954 * {@inheritDoc}
2955 */
2956 @Override
2957 public boolean isDeviceEncrypted() throws DeviceNotAvailableException {
Julien Desprezaf166b32016-08-08 11:58:47 +01002958 String output = getProperty("ro.crypto.state");
Julien Desprez6961b272016-02-01 09:58:23 +00002959
2960 if (output == null && isEncryptionSupported()) {
2961 CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber());
2962 }
2963
2964 return "encrypted".equals(output);
2965 }
2966
2967 /**
2968 * {@inheritDoc}
2969 */
2970 @Override
2971 public boolean isEncryptionSupported() throws DeviceNotAvailableException {
2972 if (!isEnableAdbRoot()) {
2973 CLog.i("root is required for encryption");
2974 mIsEncryptionSupported = false;
2975 return mIsEncryptionSupported;
2976 }
2977 if (mIsEncryptionSupported != null) {
2978 return mIsEncryptionSupported.booleanValue();
2979 }
2980 enableAdbRoot();
2981 String output = executeShellCommand("vdc cryptfs enablecrypto").trim();
jdesprezdd9d9e92017-07-13 03:34:19 -07002982
2983 mIsEncryptionSupported =
2984 (output != null
2985 && Pattern.matches("(500)(\\s+)(\\d+)(\\s+)(Usage)(.*)(:)(.*)", output));
Julien Desprez6961b272016-02-01 09:58:23 +00002986 return mIsEncryptionSupported;
2987 }
2988
2989 /**
2990 * {@inheritDoc}
2991 */
2992 @Override
2993 public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException {
2994 if (mStateMonitor.waitForDeviceOnline(waitTime) == null) {
2995 recoverDevice();
2996 }
2997 }
2998
2999 /**
3000 * {@inheritDoc}
3001 */
3002 @Override
3003 public void waitForDeviceOnline() throws DeviceNotAvailableException {
3004 if (mStateMonitor.waitForDeviceOnline() == null) {
3005 recoverDevice();
3006 }
3007 }
3008
3009 /**
3010 * {@inheritDoc}
3011 */
3012 @Override
3013 public void waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException {
3014 if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) {
3015 recoverDevice();
3016 }
3017 }
3018
3019 /**
3020 * {@inheritDoc}
3021 */
3022 @Override
3023 public void waitForDeviceAvailable() throws DeviceNotAvailableException {
3024 if (mStateMonitor.waitForDeviceAvailable() == null) {
3025 recoverDevice();
3026 }
3027 }
3028
3029 /**
3030 * {@inheritDoc}
3031 */
3032 @Override
3033 public boolean waitForDeviceNotAvailable(long waitTime) {
3034 return mStateMonitor.waitForDeviceNotAvailable(waitTime);
3035 }
3036
3037 /**
3038 * {@inheritDoc}
3039 */
3040 @Override
3041 public boolean waitForDeviceInRecovery(long waitTime) {
3042 return mStateMonitor.waitForDeviceInRecovery(waitTime);
3043 }
3044
3045 /**
3046 * Small helper function to throw an NPE if the passed arg is null. This should be used when
3047 * some value will be stored and used later, in which case it'll avoid hard-to-trace
3048 * asynchronous NullPointerExceptions by throwing the exception synchronously. This is not
3049 * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care
3050 * of it in that case.
3051 */
3052 private void throwIfNull(Object obj) {
3053 if (obj == null) throw new NullPointerException();
3054 }
3055
3056 /**
3057 * Retrieve this device's recovery mechanism.
3058 * <p/>
3059 * Exposed for unit testing.
3060 */
3061 IDeviceRecovery getRecovery() {
3062 return mRecovery;
3063 }
3064
3065 /**
3066 * {@inheritDoc}
3067 */
3068 @Override
3069 public void setRecovery(IDeviceRecovery recovery) {
3070 throwIfNull(recovery);
3071 mRecovery = recovery;
3072 }
3073
3074 /**
3075 * {@inheritDoc}
3076 */
3077 @Override
3078 public void setRecoveryMode(RecoveryMode mode) {
3079 throwIfNull(mRecoveryMode);
3080 mRecoveryMode = mode;
3081 }
3082
3083 /**
3084 * {@inheritDoc}
3085 */
3086 @Override
3087 public RecoveryMode getRecoveryMode() {
3088 return mRecoveryMode;
3089 }
3090
3091 /**
3092 * {@inheritDoc}
3093 */
3094 @Override
3095 public void setFastbootEnabled(boolean fastbootEnabled) {
3096 mFastbootEnabled = fastbootEnabled;
3097 }
3098
3099 /**
3100 * {@inheritDoc}
3101 */
3102 @Override
Julien Desprez3a503442016-04-05 15:00:41 +01003103 public boolean isFastbootEnabled() {
3104 return mFastbootEnabled;
3105 }
3106
3107 /**
3108 * {@inheritDoc}
3109 */
3110 @Override
Julien Desprez0a7d67d2016-07-21 16:05:57 +01003111 public void setFastbootPath(String fastbootPath) {
3112 mFastbootPath = fastbootPath;
3113 // ensure the device and its associated recovery use the same fastboot version.
3114 mRecovery.setFastbootPath(fastbootPath);
3115 }
3116
3117 /**
3118 * {@inheritDoc}
3119 */
3120 @Override
3121 public String getFastbootPath() {
3122 return mFastbootPath;
3123 }
3124
3125 /**
3126 * {@inheritDoc}
3127 */
3128 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003129 public void setDeviceState(final TestDeviceState deviceState) {
3130 if (!deviceState.equals(getDeviceState())) {
3131 // disable state changes while fastboot lock is held, because issuing fastboot command
3132 // will disrupt state
3133 if (getDeviceState().equals(TestDeviceState.FASTBOOT) && mFastbootLock.isLocked()) {
3134 return;
3135 }
3136 mState = deviceState;
3137 CLog.d("Device %s state is now %s", getSerialNumber(), deviceState);
3138 mStateMonitor.setState(deviceState);
3139 }
3140 }
3141
3142 /**
3143 * {@inheritDoc}
3144 */
3145 @Override
3146 public TestDeviceState getDeviceState() {
3147 return mState;
3148 }
3149
3150 @Override
3151 public boolean isAdbTcp() {
3152 return mStateMonitor.isAdbTcp();
3153 }
3154
3155 /**
3156 * {@inheritDoc}
3157 */
3158 @Override
3159 public String switchToAdbTcp() throws DeviceNotAvailableException {
3160 String ipAddress = getIpAddress();
3161 if (ipAddress == null) {
3162 CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber());
3163 return null;
3164 }
3165 String port = "5555";
3166 executeAdbCommand("tcpip", port);
3167 // TODO: analyze result? wait for device offline?
3168 return String.format("%s:%s", ipAddress, port);
3169 }
3170
3171 /**
3172 * {@inheritDoc}
3173 */
3174 @Override
3175 public boolean switchToAdbUsb() throws DeviceNotAvailableException {
3176 executeAdbCommand("usb");
3177 // TODO: analyze result? wait for device offline?
3178 return true;
3179 }
3180
3181 /**
3182 * {@inheritDoc}
3183 */
3184 @Override
3185 public void setEmulatorProcess(Process p) {
3186 mEmulatorProcess = p;
3187
3188 }
3189
3190 /**
3191 * For emulator set {@link SizeLimitedOutputStream} to log output
3192 * @param output to log the output
3193 */
3194 public void setEmulatorOutputStream(SizeLimitedOutputStream output) {
3195 mEmulatorOutput = output;
3196 }
3197
3198 /**
3199 * {@inheritDoc}
3200 */
3201 @Override
3202 public void stopEmulatorOutput() {
3203 if (mEmulatorOutput != null) {
3204 mEmulatorOutput.delete();
3205 mEmulatorOutput = null;
3206 }
3207 }
3208
3209 /**
3210 * {@inheritDoc}
3211 */
3212 @Override
3213 public InputStreamSource getEmulatorOutput() {
3214 if (getIDevice().isEmulator()) {
3215 if (mEmulatorOutput == null) {
3216 CLog.w("Emulator output for %s was not captured in background",
3217 getSerialNumber());
3218 } else {
3219 try {
jdesprez14084fb2017-07-26 14:36:39 -07003220 return new SnapshotInputStreamSource(
3221 "getEmulatorOutput", mEmulatorOutput.getData());
Julien Desprez6961b272016-02-01 09:58:23 +00003222 } catch (IOException e) {
3223 CLog.e("Failed to get %s data.", getSerialNumber());
3224 CLog.e(e);
3225 }
3226 }
3227 }
3228 return new ByteArrayInputStreamSource(new byte[0]);
3229 }
3230
3231 /**
3232 * {@inheritDoc}
3233 */
3234 @Override
3235 public Process getEmulatorProcess() {
3236 return mEmulatorProcess;
3237 }
3238
3239 /**
3240 * @return <code>true</code> if adb root should be enabled on device
3241 */
3242 public boolean isEnableAdbRoot() {
3243 return mOptions.isEnableAdbRoot();
3244 }
3245
3246 /**
3247 * {@inheritDoc}
3248 */
3249 @Override
3250 public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003251 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003252 }
3253
3254 /**
3255 * {@inheritDoc}
3256 */
3257 @Override
3258 public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003259 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003260 }
3261
3262 /**
3263 * {@inheritDoc}
3264 */
3265 @Override
3266 public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003267 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00003268 }
3269
3270 /**
3271 * {@inheritDoc}
3272 */
3273 @Override
3274 public TestDeviceOptions getOptions() {
3275 return mOptions;
3276 }
3277
3278 /**
3279 * {@inheritDoc}
3280 */
3281 @Override
3282 public int getApiLevel() throws DeviceNotAvailableException {
3283 int apiLevel = UNKNOWN_API_LEVEL;
3284 try {
3285 String prop = getProperty("ro.build.version.sdk");
3286 apiLevel = Integer.parseInt(prop);
3287 } catch (NumberFormatException nfe) {
3288 // ignore, return unknown instead
3289 }
3290 return apiLevel;
3291 }
3292
3293 @Override
3294 public IDeviceStateMonitor getMonitor() {
3295 return mStateMonitor;
3296 }
3297
3298 /**
3299 * {@inheritDoc}
3300 */
3301 @Override
3302 public boolean waitForDeviceShell(long waitTime) {
3303 return mStateMonitor.waitForDeviceShell(waitTime);
3304 }
3305
3306 @Override
3307 public DeviceAllocationState getAllocationState() {
3308 return mAllocationState;
3309 }
3310
3311 /**
3312 * {@inheritDoc}
3313 * <p>
3314 * Process the DeviceEvent, which may or may not transition this device to a new allocation
3315 * state.
3316 * </p>
3317 */
3318 @Override
3319 public DeviceEventResponse handleAllocationEvent(DeviceEvent event) {
3320
3321 // keep track of whether state has actually changed or not
3322 boolean stateChanged = false;
3323 DeviceAllocationState newState;
3324 DeviceAllocationState oldState = mAllocationState;
3325 mAllocationStateLock.lock();
3326 try {
3327 // update oldState here, just in case in changed before we got lock
3328 oldState = mAllocationState;
3329 newState = mAllocationState.handleDeviceEvent(event);
3330 if (oldState != newState) {
3331 // state has changed! record this fact, and store the new state
3332 stateChanged = true;
3333 mAllocationState = newState;
3334 }
3335 } finally {
3336 mAllocationStateLock.unlock();
3337 }
3338 if (stateChanged && mAllocationMonitor != null) {
3339 // state has changed! Lets inform the allocation monitor listener
3340 mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState);
3341 }
3342 return new DeviceEventResponse(newState, stateChanged);
3343 }
3344
Julien Desprez7c15ac42016-08-10 16:45:44 +01003345 /**
3346 * Helper to get the time difference between the device and the host. Use Epoch time.
3347 * Exposed for testing.
3348 */
3349 protected long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException {
Julien Desprezdcb19d52016-06-20 12:23:12 +01003350 Long deviceTime = getDeviceDate();
Julien Desprez6961b272016-02-01 09:58:23 +00003351 long offset = 0;
3352
Julien Desprez6961b272016-02-01 09:58:23 +00003353 if (date == null) {
3354 date = new Date();
3355 }
3356
3357 offset = date.getTime() - deviceTime * 1000;
Julien Desprez1fadf1a2016-10-20 15:50:46 +01003358 CLog.d("Time offset = %d ms", offset);
Julien Desprez6961b272016-02-01 09:58:23 +00003359 return offset;
3360 }
3361
3362 /**
3363 * {@inheritDoc}
3364 */
3365 @Override
3366 public void setDate(Date date) throws DeviceNotAvailableException {
3367 if (date == null) {
3368 date = new Date();
3369 }
3370 long timeOffset = getDeviceTimeOffset(date);
3371 // no need to set date
3372 if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) {
3373 return;
3374 }
3375 String dateString = null;
3376 if (getApiLevel() < 23) {
3377 // set date in epoch format
3378 dateString = Long.toString(date.getTime() / 1000); //ms to s
3379 } else {
3380 // set date with POSIX like params
3381 SimpleDateFormat sdf = new java.text.SimpleDateFormat(
3382 "MMddHHmmyyyy.ss");
3383 sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
3384 dateString = sdf.format(date);
3385 }
3386 // best effort, no verification
3387 executeShellCommand("date -u " + dateString);
3388 }
3389
3390 /**
3391 * {@inheritDoc}
3392 */
3393 @Override
Julien Desprezdcb19d52016-06-20 12:23:12 +01003394 public long getDeviceDate() throws DeviceNotAvailableException {
3395 String deviceTimeString = executeShellCommand("date +%s");
3396 Long deviceTime = null;
3397 try {
3398 deviceTime = Long.valueOf(deviceTimeString.trim());
3399 } catch (NumberFormatException nfe) {
3400 CLog.i("Invalid device time: \"%s\", ignored.", nfe);
3401 return 0;
3402 }
3403 return deviceTime;
3404 }
3405
3406 /**
3407 * {@inheritDoc}
3408 */
3409 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003410 public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException {
3411 return mStateMonitor.waitForBootComplete(timeOut);
3412 }
3413
3414 /**
3415 * {@inheritDoc}
3416 */
3417 @Override
3418 public ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003419 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003420 }
3421
3422 /**
3423 * {@inheritDoc}
3424 */
3425 @Override
3426 public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003427 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003428 }
3429
3430 /**
3431 * {@inheritDoc}
3432 */
3433 @Override
3434 public boolean isMultiUserSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003435 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003436 }
3437
3438 /**
3439 * {@inheritDoc}
3440 */
3441 @Override
3442 public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException {
Julien Desprezc8474552016-02-17 10:59:27 +00003443 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003444 }
3445
3446 /**
3447 * {@inheritDoc}
3448 */
3449 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003450 public int createUser(String name, boolean guest, boolean ephemeral)
3451 throws DeviceNotAvailableException, IllegalStateException {
3452 throw new UnsupportedOperationException("No support for user's feature.");
3453 }
3454
3455 /**
3456 * {@inheritDoc}
3457 */
3458 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003459 public boolean removeUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003460 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003461 }
3462
3463 /**
3464 * {@inheritDoc}
3465 */
3466 @Override
3467 public boolean startUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003468 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003469 }
3470
3471 /**
3472 * {@inheritDoc}
3473 */
3474 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003475 public boolean stopUser(int userId) throws DeviceNotAvailableException {
3476 throw new UnsupportedOperationException("No support for user's feature.");
3477 }
3478
3479 /**
3480 * {@inheritDoc}
3481 */
3482 @Override
3483 public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
3484 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003485 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003486 }
3487
3488 /**
3489 * {@inheritDoc}
3490 */
3491 @Override
3492 public void remountSystemWritable() throws DeviceNotAvailableException {
3493 String verity = getProperty("partition.system.verified");
3494 // have the property set (regardless state) implies verity is enabled, so we send adb
3495 // command to disable verity
3496 if (verity != null && !verity.isEmpty()) {
3497 executeAdbCommand("disable-verity");
3498 reboot();
3499 }
3500 executeAdbCommand("remount");
3501 waitForDeviceAvailable();
3502 }
3503
3504 /**
3505 * {@inheritDoc}
3506 */
3507 @Override
3508 public Integer getPrimaryUserId() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00003509 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00003510 }
3511
3512 /**
3513 * {@inheritDoc}
3514 */
3515 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003516 public int getCurrentUser() throws DeviceNotAvailableException {
3517 throw new UnsupportedOperationException("No support for user's feature.");
3518 }
3519
3520 /**
3521 * {@inheritDoc}
3522 */
3523 @Override
3524 public int getUserFlags(int userId) throws DeviceNotAvailableException {
3525 throw new UnsupportedOperationException("No support for user's feature.");
3526 }
3527
3528 /**
3529 * {@inheritDoc}
3530 */
3531 @Override
3532 public int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
3533 throw new UnsupportedOperationException("No support for user's feature.");
3534 }
3535
3536 /**
3537 * {@inheritDoc}
3538 */
3539 @Override
3540 public boolean switchUser(int userId) throws DeviceNotAvailableException {
3541 throw new UnsupportedOperationException("No support for user's feature.");
3542 }
3543
3544 /**
3545 * {@inheritDoc}
3546 */
3547 @Override
3548 public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException {
3549 throw new UnsupportedOperationException("No support for user's feature.");
3550 }
3551
3552 /**
3553 * {@inheritDoc}
3554 */
3555 @Override
3556 public boolean isUserRunning(int userId) throws DeviceNotAvailableException {
3557 throw new UnsupportedOperationException("No support for user's feature.");
3558 }
3559
3560 /**
3561 * {@inheritDoc}
3562 */
3563 @Override
3564 public boolean hasFeature(String feature) throws DeviceNotAvailableException {
3565 throw new UnsupportedOperationException("No support pm's features.");
3566 }
3567
3568 /**
3569 * {@inheritDoc}
3570 */
3571 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00003572 public String getSetting(String namespace, String key)
3573 throws DeviceNotAvailableException {
3574 throw new UnsupportedOperationException("No support for setting's feature.");
3575 }
3576
3577 /**
3578 * {@inheritDoc}
3579 */
3580 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003581 public String getSetting(int userId, String namespace, String key)
3582 throws DeviceNotAvailableException {
3583 throw new UnsupportedOperationException("No support for setting's feature.");
3584 }
3585
3586 /**
3587 * {@inheritDoc}
3588 */
3589 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00003590 public void setSetting(String namespace, String key, String value)
3591 throws DeviceNotAvailableException {
3592 throw new UnsupportedOperationException("No support for setting's feature.");
3593 }
3594
3595 /**
3596 * {@inheritDoc}
3597 */
3598 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003599 public void setSetting(int userId, String namespace, String key, String value)
3600 throws DeviceNotAvailableException {
3601 throw new UnsupportedOperationException("No support for setting's feature.");
3602 }
3603
3604 /**
3605 * {@inheritDoc}
3606 */
3607 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003608 public String getBuildSigningKeys() throws DeviceNotAvailableException {
3609 String buildTags = getProperty(BUILD_TAGS);
3610 if (buildTags != null) {
3611 String[] tags = buildTags.split(",");
3612 for (String tag : tags) {
3613 Matcher m = KEYS_PATTERN.matcher(tag);
3614 if (m.matches()) {
3615 return tag;
3616 }
3617 }
3618 }
3619 return null;
3620 }
3621
3622 /**
3623 * {@inheritDoc}
3624 */
3625 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003626 public String getAndroidId(int userId) throws DeviceNotAvailableException {
3627 throw new UnsupportedOperationException("No support for user's feature.");
3628 }
3629
3630 /**
3631 * {@inheritDoc}
3632 */
3633 @Override
3634 public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
3635 throw new UnsupportedOperationException("No support for user's feature.");
3636 }
3637
Tony Mak14e4dee2017-04-12 14:37:34 +01003638 /** {@inheritDoc} */
3639 @Override
3640 public boolean setDeviceOwner(String componentName, int userId)
3641 throws DeviceNotAvailableException {
3642 throw new UnsupportedOperationException("No support for user's feature.");
3643 }
3644
3645 /** {@inheritDoc} */
3646 @Override
3647 public boolean removeAdmin(String componentName, int userId)
3648 throws DeviceNotAvailableException {
3649 throw new UnsupportedOperationException("No support for user's feature.");
3650 }
3651
3652 /** {@inheritDoc} */
3653 @Override
3654 public void removeOwners() throws DeviceNotAvailableException {
3655 throw new UnsupportedOperationException("No support for user's feature.");
3656 }
3657
Guang Zhu03c985e2017-04-28 14:53:55 -07003658 /**
3659 * {@inheritDoc}
3660 */
3661 @Override
3662 public void disableKeyguard() throws DeviceNotAvailableException {
3663 throw new UnsupportedOperationException("No support for Window Manager's features");
3664 }
3665
Tony Mak14e4dee2017-04-12 14:37:34 +01003666 /** {@inheritDoc} */
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003667 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003668 public String getDeviceClass() {
3669 IDevice device = getIDevice();
3670 if (device == null) {
3671 CLog.w("No IDevice instance, cannot determine device class.");
3672 return "";
3673 }
3674 return device.getClass().getSimpleName();
3675 }
Julien Desprez26bee8d2016-03-29 12:09:48 +01003676
3677 /**
3678 * {@inheritDoc}
3679 */
3680 @Override
3681 public void preInvocationSetup(IBuildInfo info)
3682 throws TargetSetupError, DeviceNotAvailableException {
3683 // Default implementation empty on purpose
3684 }
3685
3686 /**
3687 * {@inheritDoc}
3688 */
3689 @Override
3690 public void postInvocationTearDown() {
3691 // Default implementation empty on purpose
3692 }
Julien Despreze824e8b2016-06-08 17:21:58 +01003693
3694 /**
3695 * {@inheritDoc}
3696 */
3697 @Override
3698 public boolean isHeadless() throws DeviceNotAvailableException {
3699 if (getProperty(HEADLESS_PROP) != null) {
3700 return true;
3701 }
3702 return false;
3703 }
Julien Desprez16184162016-06-10 08:56:17 +01003704
3705 protected void checkApiLevelAgainst(String feature, int strictMinLevel) {
3706 try {
3707 if (getApiLevel() < strictMinLevel){
3708 throw new IllegalArgumentException(String.format("%s not supported on %s. "
3709 + "Must be API %d.", feature, getSerialNumber(), strictMinLevel));
3710 }
3711 } catch (DeviceNotAvailableException e) {
3712 throw new RuntimeException("Device became unavailable while checking API level", e);
3713 }
3714 }
Julien Desprez07fe1532016-08-12 11:48:32 +01003715
3716 /**
3717 * {@inheritDoc}
3718 */
3719 @Override
3720 public DeviceDescriptor getDeviceDescriptor() {
3721 IDeviceSelection selector = new DeviceSelectionOptions();
3722 IDevice idevice = getIDevice();
jdesprezbc580f92017-06-02 11:41:40 -07003723 return new DeviceDescriptor(
3724 idevice.getSerialNumber(),
Julien Desprez07fe1532016-08-12 11:48:32 +01003725 idevice instanceof StubDevice,
3726 getAllocationState(),
3727 getDisplayString(selector.getDeviceProductType(idevice)),
3728 getDisplayString(selector.getDeviceProductVariant(idevice)),
3729 getDisplayString(idevice.getProperty("ro.build.version.sdk")),
3730 getDisplayString(idevice.getProperty("ro.build.id")),
3731 getDisplayString(selector.getBatteryLevel(idevice)),
Kevin Lau Fang805161d2016-12-07 21:06:38 -08003732 getDeviceClass(),
jdesprezbc580f92017-06-02 11:41:40 -07003733 getDisplayString(getMacAddress()),
3734 getDisplayString(getSimState()),
3735 getDisplayString(getSimOperator()));
Julien Desprez07fe1532016-08-12 11:48:32 +01003736 }
3737
3738 /**
3739 * Return the displayable string for given object
3740 */
3741 private String getDisplayString(Object o) {
3742 return o == null ? "unknown" : o.toString();
3743 }
Gopinath65e837a2016-10-06 17:55:12 -07003744
3745 /**
3746 * {@inheritDoc}
3747 */
3748 @Override
3749 public List<ProcessInfo> getProcesses() throws DeviceNotAvailableException {
3750 return PsParser.getProcesses(executeShellCommand(PS_COMMAND));
3751 }
3752
3753 /**
3754 * {@inheritDoc}
3755 */
3756 @Override
3757 public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException {
3758 List<ProcessInfo> processList = getProcesses();
3759 for (ProcessInfo processInfo : processList) {
3760 if (processName.equals(processInfo.getName())) {
3761 return processInfo;
3762 }
3763 }
3764 return null;
3765 }
Kevin Lau Fang805161d2016-12-07 21:06:38 -08003766
3767 /**
3768 * Validates that the given input is a valid MAC address
3769 *
3770 * @param address input to validate
3771 * @return true if the input is a valid MAC address
3772 */
3773 boolean isMacAddress(String address) {
3774 Pattern macPattern = Pattern.compile(MAC_ADDRESS_PATTERN);
3775 Matcher macMatcher = macPattern.matcher(address);
3776 return macMatcher.find();
3777 }
3778
3779 /**
3780 * {@inheritDoc}
3781 */
3782 @Override
3783 public String getMacAddress() {
3784 if (mIDevice instanceof StubDevice) {
3785 // Do not query MAC addresses from stub devices.
3786 return null;
3787 }
Kevin Lau Fangfeff6d62016-12-14 17:29:18 -08003788 if (!TestDeviceState.ONLINE.equals(mState)) {
3789 // Only query MAC addresses from online devices.
3790 return null;
3791 }
Kevin Lau Fang805161d2016-12-07 21:06:38 -08003792 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
3793 try {
3794 mIDevice.executeShellCommand(MAC_ADDRESS_COMMAND, receiver);
3795 } catch (IOException | TimeoutException | AdbCommandRejectedException |
3796 ShellCommandUnresponsiveException e) {
3797 CLog.w("Failed to query MAC address for %s", mIDevice.getSerialNumber());
Kevin Lau Fange94d8972017-03-13 15:11:30 -07003798 CLog.w(e);
Kevin Lau Fang805161d2016-12-07 21:06:38 -08003799 }
3800 String output = receiver.getOutput().trim();
3801 if (isMacAddress(output)) {
3802 return output;
3803 }
3804 CLog.d("No valid MAC address queried from device %s", mIDevice.getSerialNumber());
3805 return null;
3806 }
jdesprezbc580f92017-06-02 11:41:40 -07003807
3808 /** {@inheritDoc} */
3809 @Override
3810 public String getSimState() {
3811 try {
3812 return getProperty(SIM_STATE_PROP);
3813 } catch (DeviceNotAvailableException e) {
3814 CLog.w("Failed to query SIM state for %s", mIDevice.getSerialNumber());
3815 CLog.w(e);
3816 return null;
3817 }
3818 }
3819
3820 /** {@inheritDoc} */
3821 @Override
3822 public String getSimOperator() {
3823 try {
3824 return getProperty(SIM_OPERATOR_PROP);
3825 } catch (DeviceNotAvailableException e) {
3826 CLog.w("Failed to query SIM operator for %s", mIDevice.getSerialNumber());
3827 CLog.w(e);
3828 return null;
3829 }
3830 }
Julien Desprez6961b272016-02-01 09:58:23 +00003831}