blob: 42c4b44eb63e6e3b2a361f16ed1b0d2c6246c397 [file] [log] [blame]
Julien Desprez6961b272016-02-01 09:58:23 +00001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Julien Desprez6961b272016-02-01 09:58:23 +000016package com.android.tradefed.device;
17
18import com.android.ddmlib.AdbCommandRejectedException;
19import com.android.ddmlib.FileListingService;
20import com.android.ddmlib.FileListingService.FileEntry;
21import com.android.ddmlib.IDevice;
22import com.android.ddmlib.IShellOutputReceiver;
23import com.android.ddmlib.InstallException;
24import com.android.ddmlib.NullOutputReceiver;
Julien Desprez6961b272016-02-01 09:58:23 +000025import com.android.ddmlib.ShellCommandUnresponsiveException;
26import com.android.ddmlib.SyncException;
27import com.android.ddmlib.SyncException.SyncError;
28import com.android.ddmlib.SyncService;
29import com.android.ddmlib.TimeoutException;
30import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner;
31import com.android.ddmlib.testrunner.ITestRunListener;
32import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
33import com.android.tradefed.build.IBuildInfo;
34import com.android.tradefed.log.LogUtil.CLog;
35import com.android.tradefed.result.ByteArrayInputStreamSource;
36import com.android.tradefed.result.InputStreamSource;
37import com.android.tradefed.result.SnapshotInputStreamSource;
38import com.android.tradefed.result.StubTestRunListener;
Julien Desprez26bee8d2016-03-29 12:09:48 +010039import com.android.tradefed.targetprep.TargetSetupError;
Julien Desprez6961b272016-02-01 09:58:23 +000040import com.android.tradefed.util.ArrayUtil;
41import com.android.tradefed.util.CommandResult;
42import com.android.tradefed.util.CommandStatus;
43import com.android.tradefed.util.FileUtil;
44import com.android.tradefed.util.IRunUtil;
45import com.android.tradefed.util.RunUtil;
46import com.android.tradefed.util.SizeLimitedOutputStream;
Julien Desprez6961b272016-02-01 09:58:23 +000047
Julien Desprez6961b272016-02-01 09:58:23 +000048import java.io.File;
49import java.io.FilenameFilter;
50import java.io.IOException;
51import java.text.ParseException;
52import java.text.SimpleDateFormat;
53import java.util.ArrayList;
54import java.util.Arrays;
55import java.util.Collection;
56import java.util.Date;
Julien Desprez6961b272016-02-01 09:58:23 +000057import java.util.List;
58import java.util.Map;
59import java.util.Random;
60import java.util.Set;
61import java.util.concurrent.ExecutionException;
62import java.util.concurrent.TimeUnit;
63import java.util.concurrent.locks.ReentrantLock;
64import java.util.regex.Matcher;
65import java.util.regex.Pattern;
66
67import javax.annotation.concurrent.GuardedBy;
Julien Desprez6961b272016-02-01 09:58:23 +000068
69/**
70 * Default implementation of a {@link ITestDevice}
71 * Non-full stack android devices.
72 */
73public class AndroidNativeDevice implements IManagedTestDevice {
74
75 /** the default number of command retry attempts to perform */
Julien Desprezc8474552016-02-17 10:59:27 +000076 protected static final int MAX_RETRY_ATTEMPTS = 2;
77
Julien Desprez4c8aabc2016-03-14 15:53:48 +000078 /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value **/
79 protected static final int INVALID_USER_ID = -10000;
80
Julien Desprez6961b272016-02-01 09:58:23 +000081 /** regex to match input dispatch readiness line **/
82 static final Pattern INPUT_DISPATCH_STATE_REGEX =
83 Pattern.compile("DispatchEnabled:\\s?([01])");
84 /** regex to match build signing key type */
85 private static final Pattern KEYS_PATTERN = Pattern.compile("^.*-keys$");
86 private static final Pattern DF_PATTERN = Pattern.compile(
87 //Fs 1K-blks Used Available Use% Mounted on
88 "^[/a-z]+\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+\\d+%\\s+[/a-z]+$", Pattern.MULTILINE);
89
Julien Desprez6961b272016-02-01 09:58:23 +000090 private static final long MAX_HOST_DEVICE_TIME_OFFSET = 5 * 1000;
91
92 /** The password for encrypting and decrypting the device. */
93 private static final String ENCRYPTION_PASSWORD = "android";
94 /** Encrypting with inplace can take up to 2 hours. */
95 private static final int ENCRYPTION_INPLACE_TIMEOUT_MIN = 2 * 60;
96 /** Encrypting with wipe can take up to 20 minutes. */
97 private static final long ENCRYPTION_WIPE_TIMEOUT_MIN = 20;
Julien Desprez6961b272016-02-01 09:58:23 +000098 /** Beginning of the string returned by vdc for "vdc cryptfs enablecrypto". */
99 private static final String ENCRYPTION_SUPPORTED_CODE = "500";
100 /** Message in the string returned by vdc for "vdc cryptfs enablecrypto". */
101 private static final String ENCRYPTION_SUPPORTED_USAGE = "Usage: ";
102
103 /** The time in ms to wait before starting logcat for a device */
104 private int mLogStartDelay = 5*1000;
105
106 /** The time in ms to wait for a device to become unavailable. Should usually be short */
107 private static final int DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000;
108 /** The time in ms to wait for a recovery that we skip because of the NONE mode */
109 static final int NONE_RECOVERY_MODE_DELAY = 1000;
Julien Desprez6961b272016-02-01 09:58:23 +0000110
111 static final String BUILD_ID_PROP = "ro.build.version.incremental";
112 private static final String PRODUCT_NAME_PROP = "ro.product.name";
113 private static final String BUILD_TYPE_PROP = "ro.build.type";
114 private static final String BUILD_ALIAS_PROP = "ro.build.id";
115 private static final String BUILD_FLAVOR = "ro.build.flavor";
116 static final String BUILD_CODENAME_PROP = "ro.build.version.codename";
117 static final String BUILD_TAGS = "ro.build.tags";
118
119
120 /** The network monitoring interval in ms. */
121 private static final int NETWORK_MONITOR_INTERVAL = 10 * 1000;
122
123 /** Wifi reconnect check interval in ms. */
124 private static final int WIFI_RECONNECT_CHECK_INTERVAL = 1 * 1000;
125
126 /** Wifi reconnect timeout in ms. */
127 private static final int WIFI_RECONNECT_TIMEOUT = 60 * 1000;
128
129 /** The time in ms to wait for a command to complete. */
130 private int mCmdTimeout = 2 * 60 * 1000;
131 /** The time in ms to wait for a 'long' command to complete. */
132 private long mLongCmdTimeout = 25 * 60 * 1000;
133
Julien Desprez6961b272016-02-01 09:58:23 +0000134 private IDevice mIDevice;
135 private IDeviceRecovery mRecovery = new WaitDeviceRecovery();
Julien Desprezc8474552016-02-17 10:59:27 +0000136 protected final IDeviceStateMonitor mStateMonitor;
Julien Desprez6961b272016-02-01 09:58:23 +0000137 private TestDeviceState mState = TestDeviceState.ONLINE;
138 private final ReentrantLock mFastbootLock = new ReentrantLock();
139 private LogcatReceiver mLogcatReceiver;
140 private boolean mFastbootEnabled = true;
141
Julien Desprezc8474552016-02-17 10:59:27 +0000142 protected TestDeviceOptions mOptions = new TestDeviceOptions();
Julien Desprez6961b272016-02-01 09:58:23 +0000143 private Process mEmulatorProcess;
144 private SizeLimitedOutputStream mEmulatorOutput;
145
146 private RecoveryMode mRecoveryMode = RecoveryMode.AVAILABLE;
147
148 private Boolean mIsEncryptionSupported = null;
149 private ReentrantLock mAllocationStateLock = new ReentrantLock();
150 @GuardedBy("mAllocationStateLock")
151 private DeviceAllocationState mAllocationState = DeviceAllocationState.Unknown;
152 private IDeviceMonitor mAllocationMonitor = null;
153
154 private String mLastConnectedWifiSsid = null;
155 private String mLastConnectedWifiPsk = null;
156 private boolean mNetworkMonitorEnabled = false;
157
158 /**
159 * Interface for a generic device communication attempt.
160 */
Julien Desprezc8474552016-02-17 10:59:27 +0000161 abstract interface DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000162
163 /**
164 * Execute the device operation.
165 *
166 * @return <code>true</code> if operation is performed successfully, <code>false</code>
167 * otherwise
Julien Desprezc8474552016-02-17 10:59:27 +0000168 * @throws IOException, TimeoutException, AdbCommandRejectedException,
169 * ShellCommandUnresponsiveException, InstallException,
170 * SyncException if operation terminated abnormally
Julien Desprez6961b272016-02-01 09:58:23 +0000171 */
172 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
173 ShellCommandUnresponsiveException, InstallException, SyncException;
174 }
175
176 /**
177 * A {@link DeviceAction} for running a OS 'adb ....' command.
178 */
Julien Desprezc8474552016-02-17 10:59:27 +0000179 protected class AdbAction implements DeviceAction {
Julien Desprez6961b272016-02-01 09:58:23 +0000180 /** the output from the command */
181 String mOutput = null;
182 private String[] mCmd;
183
184 AdbAction(String[] cmd) {
185 mCmd = cmd;
186 }
187
188 @Override
189 public boolean run() throws TimeoutException, IOException {
190 CommandResult result = getRunUtil().runTimedCmd(getCommandTimeout(), mCmd);
191 // TODO: how to determine device not present with command failing for other reasons
192 if (result.getStatus() == CommandStatus.EXCEPTION) {
193 throw new IOException();
194 } else if (result.getStatus() == CommandStatus.TIMED_OUT) {
195 throw new TimeoutException();
196 } else if (result.getStatus() == CommandStatus.FAILED) {
197 // interpret as communication failure
198 throw new IOException();
199 }
200 mOutput = result.getStdout();
201 return true;
202 }
203 }
204
205 /**
206 * Creates a {@link TestDevice}.
207 *
208 * @param device the associated {@link IDevice}
209 * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use
210 * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes.
211 * Can be null
212 */
Julien Desprezc8474552016-02-17 10:59:27 +0000213 public AndroidNativeDevice(IDevice device, IDeviceStateMonitor stateMonitor,
214 IDeviceMonitor allocationMonitor) {
Julien Desprez6961b272016-02-01 09:58:23 +0000215 throwIfNull(device);
216 throwIfNull(stateMonitor);
217 mIDevice = device;
218 mStateMonitor = stateMonitor;
219 mAllocationMonitor = allocationMonitor;
220 }
221
222 /**
223 * Get the {@link RunUtil} instance to use.
224 * <p/>
225 * Exposed for unit testing.
226 */
Julien Desprezc8474552016-02-17 10:59:27 +0000227 protected IRunUtil getRunUtil() {
Julien Desprez6961b272016-02-01 09:58:23 +0000228 return RunUtil.getDefault();
229 }
230
231 /**
232 * {@inheritDoc}
233 */
234 @Override
235 public void setOptions(TestDeviceOptions options) {
236 throwIfNull(options);
237 mOptions = options;
238 mStateMonitor.setDefaultOnlineTimeout(options.getOnlineTimeout());
239 mStateMonitor.setDefaultAvailableTimeout(options.getAvailableTimeout());
240 }
241
242 /**
243 * Sets the max size of a tmp logcat file.
244 *
245 * @param size max byte size of tmp file
246 */
247 void setTmpLogcatSize(long size) {
248 mOptions.setMaxLogcatDataSize(size);
249 }
250
251 /**
252 * Sets the time in ms to wait before starting logcat capture for a online device.
253 *
254 * @param delay the delay in ms
255 */
256 void setLogStartDelay(int delay) {
257 mLogStartDelay = delay;
258 }
259
260 /**
261 * {@inheritDoc}
262 */
263 @Override
264 public IDevice getIDevice() {
265 synchronized (mIDevice) {
266 return mIDevice;
267 }
268 }
269
270 /**
271 * {@inheritDoc}
272 */
273 @Override
274 public void setIDevice(IDevice newDevice) {
275 throwIfNull(newDevice);
276 IDevice currentDevice = mIDevice;
277 if (!getIDevice().equals(newDevice)) {
278 synchronized (currentDevice) {
279 mIDevice = newDevice;
280 }
281 mStateMonitor.setIDevice(mIDevice);
282 }
283 }
284
285 /**
286 * {@inheritDoc}
287 */
288 @Override
289 public String getSerialNumber() {
290 return getIDevice().getSerialNumber();
291 }
292
293 private boolean nullOrEmpty(String string) {
294 return string == null || string.isEmpty();
295 }
296
297 /**
298 * Fetch a device property, from the ddmlib cache by default, and falling back to either
299 * `adb shell getprop` or `fastboot getvar` depending on whether the device is in Fastboot or
300 * not.
301 *
302 * @param propName The name of the device property as returned by `adb shell getprop`
303 * @param fastbootVar The name of the equivalent fastboot variable to query. if {@code null},
304 * fastboot query will not be attempted
305 * @param description A simple description of the variable. First letter should be capitalized.
306 * @return A string, possibly {@code null} or empty, containing the value of the given property
307 */
308 private String internalGetProperty(String propName, String fastbootVar, String description)
309 throws DeviceNotAvailableException, UnsupportedOperationException {
310 String propValue = getIDevice().getProperty(propName);
311 if (propValue != null) {
312 return propValue;
313 } else if (TestDeviceState.FASTBOOT.equals(getDeviceState()) &&
314 fastbootVar != null) {
315 CLog.i("%s for device %s is null, re-querying in fastboot", description,
316 getSerialNumber());
317 return getFastbootVariable(fastbootVar);
318 } else {
319 CLog.d("property collection for device %s is null, re-querying for prop %s",
320 getSerialNumber(), description);
321 return getProperty(propName);
322 }
323 }
324
325 /**
326 * {@inheritDoc}
327 */
328 @Override
329 public String getProperty(final String name) throws DeviceNotAvailableException {
330 final String[] result = new String[1];
331 DeviceAction propAction = new DeviceAction() {
332
333 @Override
334 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
335 ShellCommandUnresponsiveException, InstallException, SyncException {
336 try {
337 result[0] = getIDevice().getSystemProperty(name).get();
338 } catch (InterruptedException | ExecutionException e) {
339 // getProperty will stash the original exception inside
340 // ExecutionException.getCause
341 // throw the specific original exception if available in case TF ever does
342 // specific handling for different exceptions
343 if (e.getCause() instanceof IOException) {
344 throw (IOException)e.getCause();
345 } else if (e.getCause() instanceof TimeoutException) {
346 throw (TimeoutException)e.getCause();
347 } else if (e.getCause() instanceof AdbCommandRejectedException) {
348 throw (AdbCommandRejectedException)e.getCause();
349 } else if (e.getCause() instanceof ShellCommandUnresponsiveException) {
350 throw (ShellCommandUnresponsiveException)e.getCause();
351 }
352 else {
353 throw new IOException(e);
354 }
355 }
356 return true;
357 }
358
359 };
360 performDeviceAction("getprop", propAction, MAX_RETRY_ATTEMPTS);
361 return result[0];
362 }
363
364 /**
365 * {@inheritDoc}
366 */
367 @Deprecated
368 @Override
369 public String getPropertySync(final String name) throws DeviceNotAvailableException {
370 return getProperty(name);
371 }
372
373 /**
374 * {@inheritDoc}
375 */
376 @Override
377 public String getBootloaderVersion() throws UnsupportedOperationException,
378 DeviceNotAvailableException {
379 return internalGetProperty("ro.bootloader", "version-bootloader", "Bootloader");
380 }
381
382 @Override
383 public String getBasebandVersion() throws DeviceNotAvailableException {
384 return internalGetProperty("gsm.version.baseband", "version-baseband", "Baseband");
385 }
386
387 /**
388 * {@inheritDoc}
389 */
390 @Override
391 public String getProductType() throws DeviceNotAvailableException {
392 return internalGetProductType(MAX_RETRY_ATTEMPTS);
393 }
394
395 /**
Julien Desprezc8474552016-02-17 10:59:27 +0000396 * {@link #getProductType()}
Julien Desprez6961b272016-02-01 09:58:23 +0000397 *
Julien Desprezc8474552016-02-17 10:59:27 +0000398 * @param retryAttempts The number of times to try calling {@link #recoverDevice()} if the
Julien Desprez6961b272016-02-01 09:58:23 +0000399 * device's product type cannot be found.
400 */
401 private String internalGetProductType(int retryAttempts) throws DeviceNotAvailableException {
402 String productType = internalGetProperty("ro.hardware", "product", "Product type");
403
404 // Things will likely break if we don't have a valid product type. Try recovery (in case
405 // the device is only partially booted for some reason), and if that doesn't help, bail.
406 if (nullOrEmpty(productType)) {
407 if (retryAttempts > 0) {
408 recoverDevice();
409 productType = internalGetProductType(retryAttempts - 1);
410 }
411
412 if (nullOrEmpty(productType)) {
413 throw new DeviceNotAvailableException(String.format(
414 "Could not determine product type for device %s.", getSerialNumber()));
415 }
416 }
417
418 return productType;
419 }
420
421 /**
422 * {@inheritDoc}
423 */
424 @Override
425 public String getFastbootProductType()
426 throws DeviceNotAvailableException, UnsupportedOperationException {
427 return getFastbootVariable("product");
428 }
429
430 /**
431 * {@inheritDoc}
432 */
433 @Override
434 public String getProductVariant() throws DeviceNotAvailableException {
435 return internalGetProperty("ro.product.device", "variant", "Product variant");
436 }
437
438 /**
439 * {@inheritDoc}
440 */
441 @Override
442 public String getFastbootProductVariant()
443 throws DeviceNotAvailableException, UnsupportedOperationException {
444 return getFastbootVariable("variant");
445 }
446
447 private String getFastbootVariable(String variableName)
448 throws DeviceNotAvailableException, UnsupportedOperationException {
449 CommandResult result = executeFastbootCommand("getvar", variableName);
450 if (result.getStatus() == CommandStatus.SUCCESS) {
451 Pattern fastbootProductPattern = Pattern.compile(variableName + ":\\s(.*)\\s");
452 // fastboot is weird, and may dump the output on stderr instead of stdout
453 String resultText = result.getStdout();
454 if (resultText == null || resultText.length() < 1) {
455 resultText = result.getStderr();
456 }
457 Matcher matcher = fastbootProductPattern.matcher(resultText);
458 if (matcher.find()) {
459 return matcher.group(1);
460 }
461 }
462 return null;
463 }
464
465 /**
466 * {@inheritDoc}
467 */
468 @Override
469 public String getBuildAlias() throws DeviceNotAvailableException {
470 String alias = getProperty(BUILD_ALIAS_PROP);
471 if (alias == null || alias.isEmpty()) {
472 return getBuildId();
473 }
474 return alias;
475 }
476
477 /**
478 * {@inheritDoc}
479 */
480 @Override
481 public String getBuildId() throws DeviceNotAvailableException {
482 String bid = getProperty(BUILD_ID_PROP);
483 if (bid == null) {
484 CLog.w("Could not get device %s build id.", getSerialNumber());
485 return IBuildInfo.UNKNOWN_BUILD_ID;
486 }
487 return bid;
488 }
489
490 /**
491 * {@inheritDoc}
492 */
493 @Override
494 public String getBuildFlavor() throws DeviceNotAvailableException {
495 String buildFlavor = getProperty(BUILD_FLAVOR);
496 if (buildFlavor != null && !buildFlavor.isEmpty()) {
497 return buildFlavor;
498 }
499 String productName = getProperty(PRODUCT_NAME_PROP);
500 String buildType = getProperty(BUILD_TYPE_PROP);
501 if (productName == null || buildType == null) {
502 CLog.w("Could not get device %s build flavor.", getSerialNumber());
503 return null;
504 }
505 return String.format("%s-%s", productName, buildType);
506 }
507
508 /**
509 * {@inheritDoc}
510 */
511 @Override
512 public void executeShellCommand(final String command, final IShellOutputReceiver receiver)
513 throws DeviceNotAvailableException {
514 DeviceAction action = new DeviceAction() {
515 @Override
516 public boolean run() throws TimeoutException, IOException,
517 AdbCommandRejectedException, ShellCommandUnresponsiveException {
518 getIDevice().executeShellCommand(command, receiver,
519 mCmdTimeout, TimeUnit.MILLISECONDS);
520 return true;
521 }
522 };
523 performDeviceAction(String.format("shell %s", command), action, MAX_RETRY_ATTEMPTS);
524 }
525
526 /**
527 * {@inheritDoc}
528 */
529 @Deprecated
530 @Override
531 public void executeShellCommand(final String command, final IShellOutputReceiver receiver,
532 final int maxTimeToOutputShellResponse, int retryAttempts)
533 throws DeviceNotAvailableException {
534 executeShellCommand(command, receiver,
535 maxTimeToOutputShellResponse, TimeUnit.MILLISECONDS, retryAttempts);
536 }
537
538 /**
539 * {@inheritDoc}
540 */
541 @Override
542 public void executeShellCommand(final String command, final IShellOutputReceiver receiver,
543 final long maxTimeToOutputShellResponse, final TimeUnit timeUnit,
544 final int retryAttempts) throws DeviceNotAvailableException {
545 DeviceAction action = new DeviceAction() {
546 @Override
547 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
548 ShellCommandUnresponsiveException {
549 getIDevice().executeShellCommand(command, receiver,
550 maxTimeToOutputShellResponse, timeUnit);
551 return true;
552 }
553 };
554 performDeviceAction(String.format("shell %s", command), action, retryAttempts);
555 }
556
557 /**
558 * {@inheritDoc}
559 */
560 @Override
561 public String executeShellCommand(String command) throws DeviceNotAvailableException {
562 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
563 executeShellCommand(command, receiver);
564 String output = receiver.getOutput();
565 CLog.v("%s on %s returned %s", command, getSerialNumber(), output);
566 return output;
567 }
568
569 /**
570 * {@inheritDoc}
571 */
572 @Override
573 public boolean runInstrumentationTests(final IRemoteAndroidTestRunner runner,
574 final Collection<ITestRunListener> listeners) throws DeviceNotAvailableException {
575 RunFailureListener failureListener = new RunFailureListener();
576 listeners.add(failureListener);
577 DeviceAction runTestsAction = new DeviceAction() {
578 @Override
579 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
580 ShellCommandUnresponsiveException, InstallException, SyncException {
581 runner.run(listeners);
582 return true;
583 }
584
585 };
586 boolean result = performDeviceAction(String.format("run %s instrumentation tests",
587 runner.getPackageName()), runTestsAction, 0);
588 if (failureListener.isRunFailure()) {
589 // run failed, might be system crash. Ensure device is up
590 if (mStateMonitor.waitForDeviceAvailable(5 * 1000) == null) {
591 // device isn't up, recover
592 recoverDevice();
593 }
594 }
595 return result;
596 }
597
598 /**
599 * {@inheritDoc}
600 */
601 @Override
602 public boolean runInstrumentationTestsAsUser(final IRemoteAndroidTestRunner runner,
603 int userId, final Collection<ITestRunListener> listeners)
604 throws DeviceNotAvailableException {
605 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
606 boolean result = runInstrumentationTests(runner, listeners);
607 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
608 return result;
609 }
610
611 /**
612 * Helper method to add user run time option to {@link RemoteAndroidTestRunner}
613 *
614 * @param runner {@link IRemoteAndroidTestRunner}
615 * @param userId the integer of the user id to run as.
616 * @return original run time options.
617 */
618 private String appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId) {
619 if (runner instanceof RemoteAndroidTestRunner) {
620 String original = ((RemoteAndroidTestRunner) runner).getRunOptions();
621 String userRunTimeOption = String.format("--user %s", Integer.toString(userId));
622 ((RemoteAndroidTestRunner) runner).setRunOptions(userRunTimeOption);
623 return original;
624 } else {
625 throw new IllegalStateException(String.format("%s runner does not support multi-user",
626 runner.getClass().getName()));
627 }
628 }
629
630 /**
631 * Helper method to reset the run time options to {@link RemoteAndroidTestRunner}
632 *
633 * @param runner {@link IRemoteAndroidTestRunner}
634 * @param oldRunTimeOptions
635 */
636 private void resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner,
637 String oldRunTimeOptions) {
638 if (runner instanceof RemoteAndroidTestRunner) {
639 if (oldRunTimeOptions != null) {
640 ((RemoteAndroidTestRunner) runner).setRunOptions(oldRunTimeOptions);
641 }
642 } else {
643 throw new IllegalStateException(String.format("%s runner does not support multi-user",
644 runner.getClass().getName()));
645 }
646 }
647
648 private static class RunFailureListener extends StubTestRunListener {
649 private boolean mIsRunFailure = false;
650
651 @Override
652 public void testRunFailed(String message) {
653 mIsRunFailure = true;
654 }
655
656 public boolean isRunFailure() {
657 return mIsRunFailure;
658 }
659 }
660
661 /**
662 * {@inheritDoc}
663 */
664 @Override
665 public boolean runInstrumentationTests(IRemoteAndroidTestRunner runner,
666 ITestRunListener... listeners) throws DeviceNotAvailableException {
667 List<ITestRunListener> listenerList = new ArrayList<ITestRunListener>();
668 listenerList.addAll(Arrays.asList(listeners));
669 return runInstrumentationTests(runner, listenerList);
670 }
671
672 /**
673 * {@inheritDoc}
674 */
675 @Override
676 public boolean runInstrumentationTestsAsUser(IRemoteAndroidTestRunner runner, int userId,
677 ITestRunListener... listeners) throws DeviceNotAvailableException {
678 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId);
679 boolean result = runInstrumentationTests(runner, listeners);
680 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions);
681 return result;
682 }
683
684 /**
685 * {@inheritDoc}
686 */
687 @Override
688 public boolean isRuntimePermissionSupported() throws DeviceNotAvailableException {
Guang Zhu1fb39eb2016-03-27 15:57:28 -0700689 return getApiLevel() > 22;
Julien Desprez6961b272016-02-01 09:58:23 +0000690 }
691
692 /**
693 * helper method to throw exception if runtime permission isn't supported
694 * @throws DeviceNotAvailableException
695 */
Julien Desprezc8474552016-02-17 10:59:27 +0000696 protected void ensureRuntimePermissionSupported() throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +0000697 boolean runtimePermissionSupported = isRuntimePermissionSupported();
698 if (!runtimePermissionSupported) {
699 throw new UnsupportedOperationException(
700 "platform on device does not support runtime permission granting!");
701 }
702 }
703
704 /**
Julien Desprez6961b272016-02-01 09:58:23 +0000705 * {@inheritDoc}
706 */
707 @Override
708 public String installPackage(final File packageFile, final boolean reinstall,
709 final String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000710 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000711 }
712
713 /**
714 * {@inheritDoc}
715 */
716 @Override
717 public String installPackage(File packageFile, boolean reinstall, boolean grantPermissions,
718 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000719 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000720 }
721
722 /**
723 * {@inheritDoc}
724 */
725 @Override
726 public String installPackageForUser(File packageFile, boolean reinstall, int userId,
727 String... extraArgs) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000728 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000729 }
730
731 /**
732 * {@inheritDoc}
733 */
734 @Override
735 public String installPackageForUser(File packageFile, boolean reinstall,
736 boolean grantPermissions, int userId, String... extraArgs)
737 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000738 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000739 }
740
741 /**
742 * {@inheritDoc}
743 */
744 @Override
745 public String uninstallPackage(final String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +0000746 throw new UnsupportedOperationException("No support for Package Manager's features");
Julien Desprez6961b272016-02-01 09:58:23 +0000747 }
748
749 /**
750 * {@inheritDoc}
751 */
752 @Override
753 public boolean pullFile(final String remoteFilePath, final File localFile)
754 throws DeviceNotAvailableException {
755
756 DeviceAction pullAction = new DeviceAction() {
757 @Override
758 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
759 SyncException {
760 SyncService syncService = null;
761 boolean status = false;
762 try {
763 syncService = getIDevice().getSyncService();
764 syncService.pullFile(interpolatePathVariables(remoteFilePath),
765 localFile.getAbsolutePath(), SyncService.getNullProgressMonitor());
766 status = true;
767 } catch (SyncException e) {
768 CLog.w("Failed to pull %s from %s to %s. Message %s", remoteFilePath,
769 getSerialNumber(), localFile.getAbsolutePath(), e.getMessage());
770 throw e;
771 } finally {
772 if (syncService != null) {
773 syncService.close();
774 }
775 }
776 return status;
777 }
778 };
779 return performDeviceAction(String.format("pull %s to %s", remoteFilePath,
780 localFile.getAbsolutePath()), pullAction, MAX_RETRY_ATTEMPTS);
781 }
782
783 /**
784 * {@inheritDoc}
785 */
786 @Override
787 public File pullFile(String remoteFilePath) throws DeviceNotAvailableException {
788 File localFile = null;
789 boolean success = false;
790 try {
791 localFile = FileUtil.createTempFileForRemote(remoteFilePath, null);
792 if (pullFile(remoteFilePath, localFile)) {
793 success = true;
794 return localFile;
795 }
796 } catch (IOException e) {
797 CLog.w("Encountered IOException while trying to pull '%s': %s", remoteFilePath, e);
798 } finally {
799 if (!success) {
800 FileUtil.deleteFile(localFile);
801 }
802 }
803 return null;
804 }
805
806 /**
807 * {@inheritDoc}
808 */
809 @Override
810 public File pullFileFromExternal(String remoteFilePath) throws DeviceNotAvailableException {
811 String externalPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
812 String fullPath = (new File(externalPath, remoteFilePath)).getPath();
813 return pullFile(fullPath);
814 }
815
816 /**
817 * Helper function that watches for the string "${EXTERNAL_STORAGE}" and replaces it with the
818 * pathname of the EXTERNAL_STORAGE mountpoint. Specifically intended to be used for pathnames
819 * that are being passed to SyncService, which does not support variables inside of filenames.
820 */
821 String interpolatePathVariables(String path) {
822 final String esString = "${EXTERNAL_STORAGE}";
823 if (path.contains(esString)) {
824 final String esPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
825 path = path.replace(esString, esPath);
826 }
827 return path;
828 }
829
830 /**
831 * {@inheritDoc}
832 */
833 @Override
834 public boolean pushFile(final File localFile, final String remoteFilePath)
835 throws DeviceNotAvailableException {
836 DeviceAction pushAction = new DeviceAction() {
837 @Override
838 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
839 SyncException {
840 SyncService syncService = null;
841 boolean status = false;
842 try {
843 syncService = getIDevice().getSyncService();
844 syncService.pushFile(localFile.getAbsolutePath(),
845 interpolatePathVariables(remoteFilePath),
846 SyncService.getNullProgressMonitor());
847 status = true;
848 } catch (SyncException e) {
849 CLog.w("Failed to push %s to %s on device %s. Message %s",
850 localFile.getAbsolutePath(), remoteFilePath, getSerialNumber(),
851 e.getMessage());
852 throw e;
853 } finally {
854 if (syncService != null) {
855 syncService.close();
856 }
857 }
858 return status;
859 }
860 };
861 return performDeviceAction(String.format("push %s to %s", localFile.getAbsolutePath(),
862 remoteFilePath), pushAction, MAX_RETRY_ATTEMPTS);
863 }
864
865 /**
866 * {@inheritDoc}
867 */
868 @Override
869 public boolean pushString(final String contents, final String remoteFilePath)
870 throws DeviceNotAvailableException {
871 File tmpFile = null;
872 try {
873 tmpFile = FileUtil.createTempFile("temp", ".txt");
874 FileUtil.writeToFile(contents, tmpFile);
875 return pushFile(tmpFile, remoteFilePath);
876 } catch (IOException e) {
877 CLog.e(e);
878 return false;
879 } finally {
880 if (tmpFile != null) {
881 tmpFile.delete();
882 }
883 }
884 }
885
886 /**
887 * {@inheritDoc}
888 */
889 @Override
890 public boolean doesFileExist(String destPath) throws DeviceNotAvailableException {
891 String lsGrep = executeShellCommand(String.format("ls \"%s\"", destPath));
892 return !lsGrep.contains("No such file or directory");
893 }
894
895 /**
896 * {@inheritDoc}
897 */
898 @Override
899 public long getExternalStoreFreeSpace() throws DeviceNotAvailableException {
900 CLog.i("Checking free space for %s", getSerialNumber());
901 String externalStorePath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
902 String output = getDfOutput(externalStorePath);
903 // Try coreutils/toybox style output first.
904 Long available = parseFreeSpaceFromModernOutput(output);
905 if (available != null) {
906 return available;
907 }
908 // Then the two legacy toolbox formats.
909 available = parseFreeSpaceFromAvailable(output);
910 if (available != null) {
911 return available;
912 }
913 available = parseFreeSpaceFromFree(externalStorePath, output);
914 if (available != null) {
915 return available;
916 }
917
918 CLog.e("free space command output \"%s\" did not match expected patterns", output);
919 return 0;
920 }
921
922 /**
923 * Run the 'df' shell command and return output, making multiple attempts if necessary.
924 *
925 * @param externalStorePath the path to check
926 * @return the output from 'shell df path'
927 * @throws DeviceNotAvailableException
928 */
929 private String getDfOutput(String externalStorePath) throws DeviceNotAvailableException {
930 for (int i=0; i < MAX_RETRY_ATTEMPTS; i++) {
931 String output = executeShellCommand(String.format("df %s", externalStorePath));
932 if (output.trim().length() > 0) {
933 return output;
934 }
935 }
936 throw new DeviceUnresponsiveException(String.format(
937 "Device %s not returning output from df command after %d attempts",
938 getSerialNumber(), MAX_RETRY_ATTEMPTS));
939 }
940
941 /**
942 * Parses a partition's available space from the legacy output of a 'df' command, used
943 * pre-gingerbread.
944 * <p/>
945 * Assumes output format of:
946 * <br>/
947 * <code>
948 * [partition]: 15659168K total, 51584K used, 15607584K available (block size 32768)
949 * </code>
950 * @param dfOutput the output of df command to parse
951 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
952 */
953 private Long parseFreeSpaceFromAvailable(String dfOutput) {
954 final Pattern freeSpacePattern = Pattern.compile("(\\d+)K available");
955 Matcher patternMatcher = freeSpacePattern.matcher(dfOutput);
956 if (patternMatcher.find()) {
957 String freeSpaceString = patternMatcher.group(1);
958 try {
959 return Long.parseLong(freeSpaceString);
960 } catch (NumberFormatException e) {
961 // fall through
962 }
963 }
964 return null;
965 }
966
967 /**
968 * Parses a partition's available space from the 'table-formatted' output of a toolbox 'df'
969 * command, used from gingerbread to lollipop.
970 * <p/>
971 * Assumes output format of:
972 * <br/>
973 * <code>
974 * Filesystem Size Used Free Blksize
975 * <br/>
976 * [partition]: 3G 790M 2G 4096
977 * </code>
978 * @param dfOutput the output of df command to parse
979 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
980 */
981 Long parseFreeSpaceFromFree(String externalStorePath, String dfOutput) {
982 Long freeSpace = null;
983 final Pattern freeSpaceTablePattern = Pattern.compile(String.format(
984 //fs Size Used Free
985 "%s\\s+[\\w\\d\\.]+\\s+[\\w\\d\\.]+\\s+([\\d\\.]+)(\\w)", externalStorePath));
986 Matcher tablePatternMatcher = freeSpaceTablePattern.matcher(dfOutput);
987 if (tablePatternMatcher.find()) {
988 String numericValueString = tablePatternMatcher.group(1);
989 String unitType = tablePatternMatcher.group(2);
990 try {
991 Float freeSpaceFloat = Float.parseFloat(numericValueString);
992 if (unitType.equals("M")) {
993 freeSpaceFloat = freeSpaceFloat * 1024;
994 } else if (unitType.equals("G")) {
995 freeSpaceFloat = freeSpaceFloat * 1024 * 1024;
996 }
997 freeSpace = freeSpaceFloat.longValue();
998 } catch (NumberFormatException e) {
999 // fall through
1000 }
1001 }
1002 return freeSpace;
1003 }
1004
1005 /**
1006 * Parses a partition's available space from the modern coreutils/toybox 'df' output, used
1007 * after lollipop.
1008 * <p/>
1009 * Assumes output format of:
1010 * <br/>
1011 * <code>
1012 * Filesystem 1K-blocks Used Available Use% Mounted on
1013 * <br/>
1014 * /dev/fuse 11585536 1316348 10269188 12% /mnt/shell/emulated
1015 * </code>
1016 * @param dfOutput the output of df command to parse
1017 * @return the available space in kilobytes or <code>null</code> if output could not be parsed
1018 */
1019 Long parseFreeSpaceFromModernOutput(String dfOutput) {
1020 Matcher matcher = DF_PATTERN.matcher(dfOutput);
1021 if (matcher.find()) {
1022 try {
1023 return Long.parseLong(matcher.group(1));
1024 } catch (NumberFormatException e) {
1025 // fall through
1026 }
1027 }
1028 return null;
1029 }
1030
1031 /**
1032 * {@inheritDoc}
1033 */
1034 @Override
1035 public String getMountPoint(String mountName) {
1036 return mStateMonitor.getMountPoint(mountName);
1037 }
1038
1039 /**
1040 * {@inheritDoc}
1041 */
1042 @Override
1043 public List<MountPointInfo> getMountPointInfo() throws DeviceNotAvailableException {
1044 final String mountInfo = executeShellCommand("cat /proc/mounts");
1045 final String[] mountInfoLines = mountInfo.split("\r?\n");
1046 List<MountPointInfo> list = new ArrayList<MountPointInfo>(mountInfoLines.length);
1047
1048 for (String line : mountInfoLines) {
1049 // We ignore the last two fields
1050 // /dev/block/mtdblock4 /cache yaffs2 rw,nosuid,nodev,relatime 0 0
1051 final String[] parts = line.split("\\s+", 5);
1052 list.add(new MountPointInfo(parts[0], parts[1], parts[2], parts[3]));
1053 }
1054
1055 return list;
1056 }
1057
1058 /**
1059 * {@inheritDoc}
1060 */
1061 @Override
1062 public MountPointInfo getMountPointInfo(String mountpoint) throws DeviceNotAvailableException {
1063 // The overhead of parsing all of the lines should be minimal
1064 List<MountPointInfo> mountpoints = getMountPointInfo();
1065 for (MountPointInfo info : mountpoints) {
1066 if (mountpoint.equals(info.mountpoint)) return info;
1067 }
1068 return null;
1069 }
1070
1071 /**
1072 * {@inheritDoc}
1073 */
1074 @Override
1075 public IFileEntry getFileEntry(String path) throws DeviceNotAvailableException {
1076 path = interpolatePathVariables(path);
1077 String[] pathComponents = path.split(FileListingService.FILE_SEPARATOR);
1078 FileListingService service = getFileListingService();
1079 IFileEntry rootFile = new FileEntryWrapper(this, service.getRoot());
1080 return FileEntryWrapper.getDescendant(rootFile, Arrays.asList(pathComponents));
1081 }
1082
1083 /**
Julien Desprez56f18e02016-03-11 14:40:18 +00001084 * {@inheritDoc}
1085 */
1086 @Override
1087 public boolean isDirectory(String path) throws DeviceNotAvailableException {
1088 return executeShellCommand(String.format("ls -ld %s", path)).charAt(0) == 'd';
1089 }
1090
1091 /**
1092 * {@inheritDoc}
1093 */
1094 @Override
1095 public String[] getChildren(String path) throws DeviceNotAvailableException {
1096 String lsOutput = executeShellCommand(String.format("ls -A1 %s", path));
1097 if (lsOutput.trim().isEmpty()) {
1098 return new String[0];
1099 }
1100 return lsOutput.split("\r?\n");
1101 }
1102
1103 /**
Julien Desprez6961b272016-02-01 09:58:23 +00001104 * Retrieve the {@link FileListingService} for the {@link IDevice}, making multiple attempts
1105 * and recovery operations if necessary.
1106 * <p/>
1107 * This is necessary because {@link IDevice#getFileListingService()} can return
1108 * <code>null</code> if device is in fastboot. The symptom of this condition is that the
1109 * current {@link #getIDevice()} is a {@link StubDevice}.
1110 *
1111 * @return the {@link FileListingService}
1112 * @throws DeviceNotAvailableException if device communication is lost.
1113 */
1114 private FileListingService getFileListingService() throws DeviceNotAvailableException {
1115 final FileListingService[] service = new FileListingService[1];
1116 DeviceAction serviceAction = new DeviceAction() {
1117 @Override
1118 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException,
1119 ShellCommandUnresponsiveException, InstallException, SyncException {
1120 service[0] = getIDevice().getFileListingService();
1121 if (service[0] == null) {
1122 // could not get file listing service - must be a stub device - enter recovery
1123 throw new IOException("Could not get file listing service");
1124 }
1125 return true;
1126 }
1127 };
1128 performDeviceAction("getFileListingService", serviceAction, MAX_RETRY_ATTEMPTS);
1129 return service[0];
1130 }
1131
1132 /**
1133 * {@inheritDoc}
1134 */
1135 @Override
1136 public boolean pushDir(File localFileDir, String deviceFilePath)
1137 throws DeviceNotAvailableException {
1138 if (!localFileDir.isDirectory()) {
1139 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1140 return false;
1141 }
1142 File[] childFiles = localFileDir.listFiles();
1143 if (childFiles == null) {
1144 CLog.e("Could not read files in %s", localFileDir.getAbsolutePath());
1145 return false;
1146 }
1147 for (File childFile : childFiles) {
1148 String remotePath = String.format("%s/%s", deviceFilePath, childFile.getName());
1149 if (childFile.isDirectory()) {
1150 executeShellCommand(String.format("mkdir %s", remotePath));
1151 if (!pushDir(childFile, remotePath)) {
1152 return false;
1153 }
1154 } else if (childFile.isFile()) {
1155 if (!pushFile(childFile, remotePath)) {
1156 return false;
1157 }
1158 }
1159 }
1160 return true;
1161 }
1162
1163 /**
1164 * {@inheritDoc}
1165 */
1166 @Override
1167 public boolean syncFiles(File localFileDir, String deviceFilePath)
1168 throws DeviceNotAvailableException {
1169 if (localFileDir == null || deviceFilePath == null) {
1170 throw new IllegalArgumentException("syncFiles does not take null arguments");
1171 }
1172 CLog.i("Syncing %s to %s on device %s",
1173 localFileDir.getAbsolutePath(), deviceFilePath, getSerialNumber());
1174 if (!localFileDir.isDirectory()) {
1175 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath());
1176 return false;
1177 }
1178 // get the real destination path. This is done because underlying syncService.push
1179 // implementation will add localFileDir.getName() to destination path
1180 deviceFilePath = String.format("%s/%s", interpolatePathVariables(deviceFilePath),
1181 localFileDir.getName());
1182 if (!doesFileExist(deviceFilePath)) {
1183 executeShellCommand(String.format("mkdir -p %s", deviceFilePath));
1184 }
1185 IFileEntry remoteFileEntry = getFileEntry(deviceFilePath);
1186 if (remoteFileEntry == null) {
1187 CLog.e("Could not find remote file entry %s ", deviceFilePath);
1188 return false;
1189 }
1190
1191 return syncFiles(localFileDir, remoteFileEntry);
1192 }
1193
1194 /**
1195 * Recursively sync newer files.
1196 *
1197 * @param localFileDir the local {@link File} directory to sync
1198 * @param remoteFileEntry the remote destination {@link IFileEntry}
1199 * @return <code>true</code> if files were synced successfully
1200 * @throws DeviceNotAvailableException
1201 */
1202 private boolean syncFiles(File localFileDir, final IFileEntry remoteFileEntry)
1203 throws DeviceNotAvailableException {
1204 CLog.d("Syncing %s to %s on %s", localFileDir.getAbsolutePath(),
1205 remoteFileEntry.getFullPath(), getSerialNumber());
1206 // find newer files to sync
1207 File[] localFiles = localFileDir.listFiles(new NoHiddenFilesFilter());
1208 ArrayList<String> filePathsToSync = new ArrayList<String>();
1209 for (File localFile : localFiles) {
1210 IFileEntry entry = remoteFileEntry.findChild(localFile.getName());
1211 if (entry == null) {
1212 CLog.d("Detected missing file path %s", localFile.getAbsolutePath());
1213 filePathsToSync.add(localFile.getAbsolutePath());
1214 } else if (localFile.isDirectory()) {
1215 // This directory exists remotely. recursively sync it to sync only its newer files
1216 // contents
1217 if (!syncFiles(localFile, entry)) {
1218 return false;
1219 }
1220 } else if (isNewer(localFile, entry)) {
1221 CLog.d("Detected newer file %s", localFile.getAbsolutePath());
1222 filePathsToSync.add(localFile.getAbsolutePath());
1223 }
1224 }
1225
1226 if (filePathsToSync.size() == 0) {
1227 CLog.d("No files to sync");
1228 return true;
1229 }
1230 final String files[] = filePathsToSync.toArray(new String[filePathsToSync.size()]);
1231 DeviceAction syncAction = new DeviceAction() {
1232 @Override
1233 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1234 SyncException {
1235 SyncService syncService = null;
1236 boolean status = false;
1237 try {
1238 syncService = getIDevice().getSyncService();
1239 syncService.push(files, remoteFileEntry.getFileEntry(),
1240 SyncService.getNullProgressMonitor());
1241 status = true;
1242 } catch (SyncException e) {
1243 CLog.w("Failed to sync files to %s on device %s. Message %s",
1244 remoteFileEntry.getFullPath(), getSerialNumber(), e.getMessage());
1245 throw e;
1246 } finally {
1247 if (syncService != null) {
1248 syncService.close();
1249 }
1250 }
1251 return status;
1252 }
1253 };
1254 return performDeviceAction(String.format("sync files %s", remoteFileEntry.getFullPath()),
1255 syncAction, MAX_RETRY_ATTEMPTS);
1256 }
1257
1258 /**
1259 * Queries the file listing service for a given directory
1260 *
1261 * @param remoteFileEntry
1262 * @throws DeviceNotAvailableException
1263 */
1264 FileEntry[] getFileChildren(final FileEntry remoteFileEntry)
1265 throws DeviceNotAvailableException {
1266 // time this operation because its known to hang
1267 FileQueryAction action = new FileQueryAction(remoteFileEntry,
1268 getIDevice().getFileListingService());
1269 performDeviceAction("buildFileCache", action, MAX_RETRY_ATTEMPTS);
1270 return action.mFileContents;
1271 }
1272
1273 private class FileQueryAction implements DeviceAction {
1274
1275 FileEntry[] mFileContents = null;
1276 private final FileEntry mRemoteFileEntry;
1277 private final FileListingService mService;
1278
1279 FileQueryAction(FileEntry remoteFileEntry, FileListingService service) {
1280 throwIfNull(remoteFileEntry);
1281 throwIfNull(service);
1282 mRemoteFileEntry = remoteFileEntry;
1283 mService = service;
1284 }
1285
1286 @Override
1287 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException,
1288 ShellCommandUnresponsiveException {
1289 mFileContents = mService.getChildrenSync(mRemoteFileEntry);
1290 return true;
1291 }
1292 }
1293
1294 /**
1295 * A {@link FilenameFilter} that rejects hidden (ie starts with ".") files.
1296 */
1297 private static class NoHiddenFilesFilter implements FilenameFilter {
1298 /**
1299 * {@inheritDoc}
1300 */
1301 @Override
1302 public boolean accept(File dir, String name) {
1303 return !name.startsWith(".");
1304 }
1305 }
1306
1307 /**
1308 * Return <code>true</code> if local file is newer than remote file.
1309 */
1310 private boolean isNewer(File localFile, IFileEntry entry) {
1311 // remote times are in GMT timezone
1312 final String entryTimeString = String.format("%s %s GMT", entry.getDate(), entry.getTime());
1313 try {
1314 // expected format of a FileEntry's date and time
1315 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm zzz");
1316 Date remoteDate = format.parse(entryTimeString);
1317 // localFile.lastModified has granularity of ms, but remoteDate.getTime only has
1318 // granularity of minutes. Shift remoteDate.getTime() backward by one minute so newly
1319 // modified files get synced
1320 return localFile.lastModified() > (remoteDate.getTime() - 60 * 1000);
1321 } catch (ParseException e) {
1322 CLog.e("Error converting remote time stamp %s for %s on device %s", entryTimeString,
1323 entry.getFullPath(), getSerialNumber());
1324 }
1325 // sync file by default
1326 return true;
1327 }
1328
1329 /**
1330 * {@inheritDoc}
1331 */
1332 @Override
1333 public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException {
1334 final String[] fullCmd = buildAdbCommand(cmdArgs);
1335 AdbAction adbAction = new AdbAction(fullCmd);
1336 performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
1337 return adbAction.mOutput;
1338 }
1339
1340 /**
1341 * {@inheritDoc}
1342 */
1343 @Override
1344 public CommandResult executeFastbootCommand(String... cmdArgs)
1345 throws DeviceNotAvailableException, UnsupportedOperationException {
1346 return doFastbootCommand(getCommandTimeout(), cmdArgs);
1347 }
1348
1349 /**
1350 * {@inheritDoc}
1351 */
1352 @Override
1353 public CommandResult executeLongFastbootCommand(String... cmdArgs)
1354 throws DeviceNotAvailableException, UnsupportedOperationException {
1355 return doFastbootCommand(getLongCommandTimeout(), cmdArgs);
1356 }
1357
1358 /**
1359 * @param cmdArgs
1360 * @throws DeviceNotAvailableException
1361 */
1362 private CommandResult doFastbootCommand(final long timeout, String... cmdArgs)
1363 throws DeviceNotAvailableException, UnsupportedOperationException {
1364 if (!mFastbootEnabled) {
1365 throw new UnsupportedOperationException(String.format(
1366 "Attempted to fastboot on device %s , but fastboot is not available. Aborting.",
1367 getSerialNumber()));
1368 }
1369 final String[] fullCmd = buildFastbootCommand(cmdArgs);
1370 for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) {
1371 CommandResult result = new CommandResult(CommandStatus.EXCEPTION);
1372 // block state changes while executing a fastboot command, since
1373 // device will disappear from fastboot devices while command is being executed
1374 mFastbootLock.lock();
1375 try {
1376 result = getRunUtil().runTimedCmd(timeout, fullCmd);
1377 } finally {
1378 mFastbootLock.unlock();
1379 }
1380 if (!isRecoveryNeeded(result)) {
1381 return result;
1382 }
1383 CLog.w("Recovery needed after executing fastboot command");
1384 if (result != null) {
1385 CLog.v("fastboot command output:\nstdout: %s\nstderr:%s",
1386 result.getStdout(), result.getStderr());
1387 }
1388 recoverDeviceFromBootloader();
1389 }
1390 throw new DeviceUnresponsiveException(String.format("Attempted fastboot %s multiple "
1391 + "times on device %s without communication success. Aborting.", cmdArgs[0],
1392 getSerialNumber()));
1393 }
1394
1395 /**
1396 * {@inheritDoc}
1397 */
1398 @Override
1399 public boolean getUseFastbootErase() {
1400 return mOptions.getUseFastbootErase();
1401 }
1402
1403 /**
1404 * {@inheritDoc}
1405 */
1406 @Override
1407 public void setUseFastbootErase(boolean useFastbootErase) {
1408 mOptions.setUseFastbootErase(useFastbootErase);
1409 }
1410
1411 /**
1412 * {@inheritDoc}
1413 */
1414 @Override
1415 public CommandResult fastbootWipePartition(String partition)
1416 throws DeviceNotAvailableException {
1417 if (mOptions.getUseFastbootErase()) {
1418 return executeLongFastbootCommand("erase", partition);
1419 } else {
1420 return executeLongFastbootCommand("format", partition);
1421 }
1422 }
1423
1424 /**
1425 * Evaluate the given fastboot result to determine if recovery mode needs to be entered
1426 *
1427 * @param fastbootResult the {@link CommandResult} from a fastboot command
1428 * @return <code>true</code> if recovery mode should be entered, <code>false</code> otherwise.
1429 */
1430 private boolean isRecoveryNeeded(CommandResult fastbootResult) {
1431 if (fastbootResult.getStatus().equals(CommandStatus.TIMED_OUT)) {
1432 // fastboot commands always time out if devices is not present
1433 return true;
1434 } else {
1435 // check for specific error messages in result that indicate bad device communication
1436 // and recovery mode is needed
1437 if (fastbootResult.getStderr() == null ||
1438 fastbootResult.getStderr().contains("data transfer failure (Protocol error)") ||
1439 fastbootResult.getStderr().contains("status read failed (No such device)")) {
1440 CLog.w("Bad fastboot response from device %s. stderr: %s. Entering recovery",
1441 getSerialNumber(), fastbootResult.getStderr());
1442 return true;
1443 }
1444 }
1445 return false;
1446 }
1447
1448 /**
1449 * Get the max time allowed in ms for commands.
1450 */
1451 int getCommandTimeout() {
1452 return mCmdTimeout;
1453 }
1454
1455 /**
1456 * Set the max time allowed in ms for commands.
1457 */
1458 void setLongCommandTimeout(long timeout) {
1459 mLongCmdTimeout = timeout;
1460 }
1461
1462 /**
1463 * Get the max time allowed in ms for commands.
1464 */
1465 long getLongCommandTimeout() {
1466 return mLongCmdTimeout;
1467 }
1468
1469 /**
1470 * Set the max time allowed in ms for commands.
1471 */
1472 void setCommandTimeout(int timeout) {
1473 mCmdTimeout = timeout;
1474 }
1475
1476 /**
1477 * Builds the OS command for the given adb command and args
1478 */
1479 private String[] buildAdbCommand(String... commandArgs) {
1480 return ArrayUtil.buildArray(new String[] {"adb", "-s", getSerialNumber()},
1481 commandArgs);
1482 }
1483
1484 /**
1485 * Builds the OS command for the given fastboot command and args
1486 */
1487 private String[] buildFastbootCommand(String... commandArgs) {
1488 return ArrayUtil.buildArray(new String[] {"fastboot", "-s", getSerialNumber()},
1489 commandArgs);
1490 }
1491
1492 /**
1493 * Performs an action on this device. Attempts to recover device and optionally retry command
1494 * if action fails.
1495 *
1496 * @param actionDescription a short description of action to be performed. Used for logging
1497 * purposes only.
1498 * @param action the action to be performed
1499 * @param retryAttempts the retry attempts to make for action if it fails but
1500 * recovery succeeds
1501 * @returns <code>true</code> if action was performed successfully
1502 * @throws DeviceNotAvailableException if recovery attempt fails or max attempts done without
1503 * success
1504 */
Julien Desprezc8474552016-02-17 10:59:27 +00001505 protected boolean performDeviceAction(String actionDescription, final DeviceAction action,
Julien Desprez6961b272016-02-01 09:58:23 +00001506 int retryAttempts) throws DeviceNotAvailableException {
1507
1508 for (int i = 0; i < retryAttempts + 1; i++) {
1509 try {
1510 return action.run();
1511 } catch (TimeoutException e) {
1512 logDeviceActionException(actionDescription, e);
1513 } catch (IOException e) {
1514 logDeviceActionException(actionDescription, e);
1515 } catch (InstallException e) {
1516 logDeviceActionException(actionDescription, e);
1517 } catch (SyncException e) {
1518 logDeviceActionException(actionDescription, e);
1519 // a SyncException is not necessarily a device communication problem
1520 // do additional diagnosis
1521 if (!e.getErrorCode().equals(SyncError.BUFFER_OVERRUN) &&
1522 !e.getErrorCode().equals(SyncError.TRANSFER_PROTOCOL_ERROR)) {
1523 // this is a logic problem, doesn't need recovery or to be retried
1524 return false;
1525 }
1526 } catch (AdbCommandRejectedException e) {
1527 logDeviceActionException(actionDescription, e);
1528 } catch (ShellCommandUnresponsiveException e) {
1529 CLog.w("Device %s stopped responding when attempting %s", getSerialNumber(),
1530 actionDescription);
1531 }
1532 // TODO: currently treat all exceptions the same. In future consider different recovery
1533 // mechanisms for time out's vs IOExceptions
1534 recoverDevice();
1535 }
1536 if (retryAttempts > 0) {
1537 throw new DeviceUnresponsiveException(String.format("Attempted %s multiple times "
1538 + "on device %s without communication success. Aborting.", actionDescription,
1539 getSerialNumber()));
1540 }
1541 return false;
1542 }
1543
1544 /**
1545 * Log an entry for given exception
1546 *
1547 * @param actionDescription the action's description
1548 * @param e the exception
1549 */
1550 private void logDeviceActionException(String actionDescription, Exception e) {
1551 CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(),
1552 getExceptionMessage(e), actionDescription, getSerialNumber());
1553 }
1554
1555 /**
1556 * Make a best effort attempt to retrieve a meaningful short descriptive message for given
1557 * {@link Exception}
1558 *
1559 * @param e the {@link Exception}
1560 * @return a short message
1561 */
1562 private String getExceptionMessage(Exception e) {
1563 StringBuilder msgBuilder = new StringBuilder();
1564 if (e.getMessage() != null) {
1565 msgBuilder.append(e.getMessage());
1566 }
1567 if (e.getCause() != null) {
1568 msgBuilder.append(" cause: ");
1569 msgBuilder.append(e.getCause().getClass().getSimpleName());
1570 if (e.getCause().getMessage() != null) {
1571 msgBuilder.append(" (");
1572 msgBuilder.append(e.getCause().getMessage());
1573 msgBuilder.append(")");
1574 }
1575 }
1576 return msgBuilder.toString();
1577 }
1578
1579 /**
1580 * Attempts to recover device communication.
1581 *
1582 * @throws DeviceNotAvailableException if device is not longer available
1583 */
1584 @Override
1585 public void recoverDevice() throws DeviceNotAvailableException {
1586 if (mRecoveryMode.equals(RecoveryMode.NONE)) {
1587 CLog.i("Skipping recovery on %s", getSerialNumber());
1588 getRunUtil().sleep(NONE_RECOVERY_MODE_DELAY);
1589 return;
1590 }
1591 CLog.i("Attempting recovery on %s", getSerialNumber());
Julien Desprez7a7d97e2016-02-05 12:27:49 +00001592 try {
1593 mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE));
1594 } catch (DeviceUnresponsiveException due) {
1595 RecoveryMode previousRecoveryMode = mRecoveryMode;
1596 mRecoveryMode = RecoveryMode.NONE;
1597 boolean enabled = enableAdbRoot();
1598 CLog.d("Device Unresponsive during recovery, is root still enabled: %s", enabled);
1599 mRecoveryMode = previousRecoveryMode;
1600 throw due;
1601 }
Julien Desprez6961b272016-02-01 09:58:23 +00001602 if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) {
1603 // turn off recovery mode to prevent reentrant recovery
1604 // TODO: look for a better way to handle this, such as doing postBootUp steps in
1605 // recovery itself
1606 mRecoveryMode = RecoveryMode.NONE;
1607 // this might be a runtime reset - still need to run post boot setup steps
1608 if (isEncryptionSupported() && isDeviceEncrypted()) {
1609 unlockDevice();
1610 }
1611 postBootSetup();
1612 mRecoveryMode = RecoveryMode.AVAILABLE;
1613 } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) {
1614 // turn off recovery mode to prevent reentrant recovery
1615 // TODO: look for a better way to handle this, such as doing postBootUp steps in
1616 // recovery itself
1617 mRecoveryMode = RecoveryMode.NONE;
1618 enableAdbRoot();
1619 mRecoveryMode = RecoveryMode.ONLINE;
1620 }
1621 CLog.i("Recovery successful for %s", getSerialNumber());
1622 }
1623
1624 /**
1625 * Attempts to recover device fastboot communication.
1626 *
1627 * @throws DeviceNotAvailableException if device is not longer available
1628 */
1629 private void recoverDeviceFromBootloader() throws DeviceNotAvailableException {
1630 CLog.i("Attempting recovery on %s in bootloader", getSerialNumber());
1631 mRecovery.recoverDeviceBootloader(mStateMonitor);
1632 CLog.i("Bootloader recovery successful for %s", getSerialNumber());
1633 }
1634
1635 private void recoverDeviceInRecovery() throws DeviceNotAvailableException {
1636 CLog.i("Attempting recovery on %s in recovery", getSerialNumber());
1637 mRecovery.recoverDeviceRecovery(mStateMonitor);
1638 CLog.i("Recovery mode recovery successful for %s", getSerialNumber());
1639 }
1640
1641 /**
1642 * {@inheritDoc}
1643 */
1644 @Override
1645 public void startLogcat() {
1646 if (mLogcatReceiver != null) {
1647 CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber());
1648 return;
1649 }
1650 mLogcatReceiver = createLogcatReceiver();
1651 mLogcatReceiver.start();
1652 }
1653
1654 /**
1655 * {@inheritDoc}
1656 */
1657 @Override
1658 public void clearLogcat() {
1659 if (mLogcatReceiver != null) {
1660 mLogcatReceiver.clear();
1661 }
1662 }
1663
1664 /**
1665 * {@inheritDoc}
1666 */
1667 @Override
1668 public InputStreamSource getLogcat() {
1669 if (mLogcatReceiver == null) {
1670 CLog.w("Not capturing logcat for %s in background, returning a logcat dump",
1671 getSerialNumber());
1672 return getLogcatDump();
1673 } else {
1674 return mLogcatReceiver.getLogcatData();
1675 }
1676 }
1677
1678 /**
1679 * {@inheritDoc}
1680 */
1681 @Override
1682 public InputStreamSource getLogcat(int maxBytes) {
1683 if (mLogcatReceiver == null) {
1684 CLog.w("Not capturing logcat for %s in background, returning a logcat dump "
1685 + "ignoring size", getSerialNumber());
1686 return getLogcatDump();
1687 } else {
1688 return mLogcatReceiver.getLogcatData(maxBytes);
1689 }
1690 }
1691
1692 /**
1693 * {@inheritDoc}
1694 */
1695 @Override
1696 public InputStreamSource getLogcatDump() {
1697 byte[] output = new byte[0];
1698 try {
1699 // use IDevice directly because we don't want callers to handle
1700 // DeviceNotAvailableException for this method
1701 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
1702 // add -d parameter to make this a non blocking call
1703 getIDevice().executeShellCommand(LogcatReceiver.LOGCAT_CMD + " -d", receiver);
1704 output = receiver.getOutput();
1705 } catch (IOException e) {
1706 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
1707 } catch (TimeoutException e) {
1708 CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber());
1709 } catch (AdbCommandRejectedException e) {
1710 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
1711 } catch (ShellCommandUnresponsiveException e) {
1712 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage());
1713 }
1714 return new ByteArrayInputStreamSource(output);
1715 }
1716
1717 /**
1718 * {@inheritDoc}
1719 */
1720 @Override
1721 public void stopLogcat() {
1722 if (mLogcatReceiver != null) {
1723 mLogcatReceiver.stop();
1724 mLogcatReceiver = null;
1725 } else {
1726 CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber());
1727 }
1728 }
1729
1730 /**
1731 * Factory method to create a {@link LogcatReceiver}.
1732 * <p/>
1733 * Exposed for unit testing.
1734 */
1735 LogcatReceiver createLogcatReceiver() {
1736 String logcatOptions = mOptions.getLogcatOptions();
1737 if (logcatOptions == null) {
1738 return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay);
1739 } else {
1740 return new LogcatReceiver(this,
1741 String.format("%s %s", LogcatReceiver.LOGCAT_CMD, logcatOptions),
1742 mOptions.getMaxLogcatDataSize(), mLogStartDelay);
1743 }
1744 }
1745
1746 /**
1747 * {@inheritDoc}
1748 */
1749 @Override
1750 public InputStreamSource getBugreport() {
Julien Desprezc8474552016-02-17 10:59:27 +00001751 throw new UnsupportedOperationException("No support for Bugreport");
Julien Desprez6961b272016-02-01 09:58:23 +00001752 }
1753
1754 /**
1755 * {@inheritDoc}
1756 */
1757 @Override
1758 public InputStreamSource getScreenshot() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001759 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00001760 }
1761
1762 /**
1763 * {@inheritDoc}
1764 */
1765 @Override
1766 public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001767 throw new UnsupportedOperationException("No support for Screenshot");
Julien Desprez6961b272016-02-01 09:58:23 +00001768 }
1769
1770 /**
1771 * {@inheritDoc}
1772 */
1773 @Override
1774 public void clearLastConnectedWifiNetwork() {
1775 mLastConnectedWifiSsid = null;
1776 mLastConnectedWifiPsk = null;
1777 }
1778
1779 /**
1780 * {@inheritDoc}
1781 */
1782 @Override
1783 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk)
1784 throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00001785 // Clears the last connected wifi network.
1786 mLastConnectedWifiSsid = null;
1787 mLastConnectedWifiPsk = null;
1788
1789 // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()}
Julien Desprez8539c082016-03-04 13:39:19 +00001790 // times
Julien Desprez6961b272016-02-01 09:58:23 +00001791 Random rnd = new Random();
1792 int backoffSlotCount = 2;
Julien Desprez8539c082016-03-04 13:39:19 +00001793 int waitTime = mOptions.getWifiRetryWaitTime();
Julien Desprez6961b272016-02-01 09:58:23 +00001794 IWifiHelper wifi = createWifiHelper();
1795 for (int i = 1; i <= mOptions.getWifiAttempts(); i++) {
1796 CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber());
1797 boolean success = wifi.connectToNetwork(wifiSsid, wifiPsk,
1798 mOptions.getConnCheckUrl());
1799 final Map<String, String> wifiInfo = wifi.getWifiInfo();
1800 if (success) {
1801 CLog.i("Successfully connected to wifi network %s(%s) on %s",
1802 wifiSsid, wifiInfo.get("bssid"), getSerialNumber());
1803
1804 mLastConnectedWifiSsid = wifiSsid;
1805 mLastConnectedWifiPsk = wifiPsk;
1806
1807 return true;
1808 } else {
1809 CLog.w("Failed to connect to wifi network %s(%s) on %s on attempt %d of %d",
1810 wifiSsid, wifiInfo.get("bssid"), getSerialNumber(), i,
1811 mOptions.getWifiAttempts());
1812 }
Julien Desprez6961b272016-02-01 09:58:23 +00001813 if (i < mOptions.getWifiAttempts()) {
Julien Desprez8539c082016-03-04 13:39:19 +00001814 if (mOptions.isWifiExpoRetryEnabled()) {
1815 // use binary exponential back-offs when retrying.
1816 waitTime *= rnd.nextInt(backoffSlotCount) ;
1817 backoffSlotCount *= 2;
1818 }
1819 CLog.e("Waiting for %d ms before reconnecting to %s...", waitTime, wifiSsid);
Julien Desprez6961b272016-02-01 09:58:23 +00001820 getRunUtil().sleep(waitTime);
1821 }
1822 }
1823 return false;
1824 }
1825
1826 /**
1827 * {@inheritDoc}
1828 */
1829 @Override
1830 public boolean checkConnectivity() throws DeviceNotAvailableException {
1831 IWifiHelper wifi = createWifiHelper();
1832 return wifi.checkConnectivity(mOptions.getConnCheckUrl());
1833 }
1834
1835 /**
1836 * {@inheritDoc}
1837 */
1838 @Override
1839 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)
1840 throws DeviceNotAvailableException {
1841 if (!checkConnectivity()) {
1842 return connectToWifiNetwork(wifiSsid, wifiPsk);
1843 }
1844 return true;
1845 }
1846
1847 /**
1848 * {@inheritDoc}
1849 */
1850 @Override
1851 public boolean isWifiEnabled() throws DeviceNotAvailableException {
1852 try {
1853 final IWifiHelper wifi = createWifiHelper();
1854 return wifi.isWifiEnabled();
1855 } catch (RuntimeException e) {
1856 CLog.w("Failed to create WifiHelper: %s", e.getMessage());
1857 return false;
1858 }
1859 }
1860
1861 /**
1862 * Checks that the device is currently successfully connected to given wifi SSID.
1863 *
1864 * @param wifiSSID the wifi ssid
1865 * @return <code>true</code> if device is currently connected to wifiSSID and has network
1866 * connectivity. <code>false</code> otherwise
1867 * @throws DeviceNotAvailableException if connection with device was lost
1868 */
1869 boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException {
1870 CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber());
1871 final IWifiHelper wifi = createWifiHelper();
1872 // getSSID returns SSID as "SSID"
1873 final String quotedSSID = String.format("\"%s\"", wifiSSID);
1874
1875 boolean test = wifi.isWifiEnabled();
1876 CLog.v("%s: wifi enabled? %b", getSerialNumber(), test);
1877
1878 if (test) {
1879 final String actualSSID = wifi.getSSID();
1880 test = quotedSSID.equals(actualSSID);
1881 CLog.v("%s: SSID match (%s, %s, %b)", getSerialNumber(),
1882 quotedSSID, actualSSID, test);
1883 }
1884 if (test) {
1885 test = wifi.hasValidIp();
1886 CLog.v("%s: validIP? %b", getSerialNumber(), test);
1887 }
1888 if (test) {
1889 test = checkConnectivity();
1890 CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test);
1891 }
1892 return test;
1893 }
1894
1895 /**
1896 * {@inheritDoc}
1897 */
1898 @Override
1899 public boolean disconnectFromWifi() throws DeviceNotAvailableException {
1900 CLog.i("Disconnecting from wifi on %s", getSerialNumber());
1901 // Clears the last connected wifi network.
1902 mLastConnectedWifiSsid = null;
1903 mLastConnectedWifiPsk = null;
1904
1905 IWifiHelper wifi = createWifiHelper();
1906 return wifi.disconnectFromNetwork();
1907 }
1908
1909 /**
1910 * {@inheritDoc}
1911 */
1912 @Override
1913 public String getIpAddress() throws DeviceNotAvailableException {
1914 IWifiHelper wifi = createWifiHelper();
1915 return wifi.getIpAddress();
1916 }
1917
1918 /**
1919 * {@inheritDoc}
1920 */
1921 @Override
1922 public boolean enableNetworkMonitor() throws DeviceNotAvailableException {
1923 mNetworkMonitorEnabled = false;
1924
1925 IWifiHelper wifi = createWifiHelper();
1926 wifi.stopMonitor();
1927 if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) {
1928 mNetworkMonitorEnabled = true;
1929 return true;
1930 }
1931 return false;
1932 }
1933
1934 /**
1935 * {@inheritDoc}
1936 */
1937 @Override
1938 public boolean disableNetworkMonitor() throws DeviceNotAvailableException {
1939 mNetworkMonitorEnabled = false;
1940
1941 IWifiHelper wifi = createWifiHelper();
1942 List<Long> samples = wifi.stopMonitor();
1943 if (!samples.isEmpty()) {
1944 int failures = 0;
1945 long totalLatency = 0;
1946 for (Long sample : samples) {
1947 if (sample < 0) {
1948 failures += 1;
1949 } else {
1950 totalLatency += sample;
1951 }
1952 }
1953 double failureRate = failures * 100.0 / samples.size();
1954 double avgLatency = 0.0;
1955 if (failures < samples.size()) {
1956 avgLatency = totalLatency / (samples.size() - failures);
1957 }
1958 CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f",
1959 mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000,
1960 failureRate, avgLatency);
1961 }
1962 return true;
1963 }
1964
1965 /**
1966 * Create a {@link WifiHelper} to use
1967 * <p/>
1968 * Exposed so unit tests can mock
1969 */
1970 IWifiHelper createWifiHelper() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001971 // current wifi helper won't work on AndroidNativeDevice
1972 // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when
1973 // we learn what is available.
1974 throw new UnsupportedOperationException("Wifi helper is not supported.");
Julien Desprez6961b272016-02-01 09:58:23 +00001975 }
1976
1977 /**
1978 * {@inheritDoc}
1979 */
1980 @Override
1981 public boolean clearErrorDialogs() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00001982 throw new UnsupportedOperationException("No support for Screen's features");
Julien Desprez6961b272016-02-01 09:58:23 +00001983 }
1984
1985 IDeviceStateMonitor getDeviceStateMonitor() {
1986 return mStateMonitor;
1987 }
1988
1989 /**
1990 * {@inheritDoc}
1991 */
1992 @Override
1993 public void postBootSetup() throws DeviceNotAvailableException {
1994 enableAdbRoot();
Julien Desprezc8474552016-02-17 10:59:27 +00001995 prePostBootSetup();
Julien Desprez6961b272016-02-01 09:58:23 +00001996 for (String command : mOptions.getPostBootCommands()) {
1997 executeShellCommand(command);
1998 }
1999 }
2000
2001 /**
Julien Desprezc8474552016-02-17 10:59:27 +00002002 * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for
2003 * specific post boot setup.
2004 * @throws DeviceNotAvailableException
2005 */
2006 protected void prePostBootSetup() throws DeviceNotAvailableException {
2007 // Empty on purpose.
2008 }
2009
2010 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002011 * Ensure wifi connection is re-established after boot. This is intended to be called after TF
2012 * initiated reboots(ones triggered by {@link #reboot()}) only.
2013 *
2014 * @throws DeviceNotAvailableException
2015 */
2016 void postBootWifiSetup() throws DeviceNotAvailableException {
2017 if (mLastConnectedWifiSsid != null) {
2018 reconnectToWifiNetwork();
2019 }
2020 if (mNetworkMonitorEnabled) {
2021 if (!enableNetworkMonitor()) {
2022 CLog.w("Failed to enable network monitor on %s after reboot", getSerialNumber());
2023 }
2024 }
2025 }
2026
2027 void reconnectToWifiNetwork() throws DeviceNotAvailableException {
2028 // First, wait for wifi to re-connect automatically.
2029 long startTime = System.currentTimeMillis();
2030 boolean isConnected = checkConnectivity();
2031 while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) {
2032 getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL);
2033 isConnected = checkConnectivity();
2034 }
2035
2036 if (isConnected) {
2037 return;
2038 }
2039
2040 // If wifi is still not connected, try to re-connect on our own.
2041 final String wifiSsid = mLastConnectedWifiSsid;
2042 if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) {
2043 throw new NetworkNotAvailableException(
2044 String.format("Failed to connect to wifi network %s on %s after reboot",
2045 wifiSsid, getSerialNumber()));
2046 }
2047 }
2048
2049 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002050 * {@inheritDoc}
2051 */
2052 @Override
2053 public void rebootIntoBootloader()
2054 throws DeviceNotAvailableException, UnsupportedOperationException {
2055 if (!mFastbootEnabled) {
2056 throw new UnsupportedOperationException(
2057 "Fastboot is not available and cannot reboot into bootloader");
2058 }
2059 CLog.i("Rebooting device %s in state %s into bootloader", getSerialNumber(),
2060 getDeviceState());
2061 if (TestDeviceState.FASTBOOT.equals(getDeviceState())) {
2062 CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
2063 executeFastbootCommand("reboot-bootloader");
2064 } else {
2065 CLog.i("Booting device %s into bootloader", getSerialNumber());
2066 doAdbRebootBootloader();
2067 }
2068 if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
2069 recoverDeviceFromBootloader();
2070 }
2071 }
2072
2073 private void doAdbRebootBootloader() throws DeviceNotAvailableException {
2074 doAdbReboot("bootloader");
2075 }
2076
2077 /**
2078 * {@inheritDoc}
2079 */
2080 @Override
2081 public void reboot() throws DeviceNotAvailableException {
2082 rebootUntilOnline();
2083
2084 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2085 setRecoveryMode(RecoveryMode.ONLINE);
2086
2087 if (isEncryptionSupported() && isDeviceEncrypted()) {
2088 unlockDevice();
2089 }
2090
2091 setRecoveryMode(cachedRecoveryMode);
2092
2093 if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) {
2094 postBootSetup();
2095 postBootWifiSetup();
2096 return;
2097 } else {
2098 recoverDevice();
2099 }
2100 }
2101
2102 /**
2103 * {@inheritDoc}
2104 */
2105 @Override
2106 public void rebootUntilOnline() throws DeviceNotAvailableException {
2107 doReboot();
2108 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2109 setRecoveryMode(RecoveryMode.ONLINE);
2110 if (mStateMonitor.waitForDeviceOnline() != null) {
2111 enableAdbRoot();
2112 } else {
2113 recoverDevice();
2114 }
2115 setRecoveryMode(cachedRecoveryMode);
2116 }
2117
2118 /**
2119 * {@inheritDoc}
2120 */
2121 @Override
2122 public void rebootIntoRecovery() throws DeviceNotAvailableException {
2123 if (TestDeviceState.FASTBOOT == getDeviceState()) {
2124 CLog.w("device %s in fastboot when requesting boot to recovery. " +
2125 "Rebooting to userspace first.", getSerialNumber());
2126 rebootUntilOnline();
2127 }
2128 doAdbReboot("recovery");
2129 if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) {
2130 recoverDeviceInRecovery();
2131 }
2132 }
2133
2134 /**
2135 * {@inheritDoc}
2136 */
2137 @Override
2138 public void nonBlockingReboot() throws DeviceNotAvailableException {
2139 doReboot();
2140 }
2141
2142 /**
2143 * Exposed for unit testing.
2144 *
2145 * @throws DeviceNotAvailableException
2146 */
2147 void doReboot() throws DeviceNotAvailableException, UnsupportedOperationException {
2148 if (TestDeviceState.FASTBOOT == getDeviceState()) {
2149 CLog.i("device %s in fastboot. Rebooting to userspace.", getSerialNumber());
2150 executeFastbootCommand("reboot");
2151 } else {
Guang Zhu120ed1c2016-02-24 23:31:49 -08002152 if (mOptions.shouldDisableReboot()) {
2153 CLog.i("Device reboot disabled by options, skipped.");
2154 return;
2155 }
Julien Desprez6961b272016-02-01 09:58:23 +00002156 CLog.i("Rebooting device %s", getSerialNumber());
2157 doAdbReboot(null);
2158 waitForDeviceNotAvailable("reboot", DEFAULT_UNAVAILABLE_TIMEOUT);
2159 }
2160 }
2161
2162 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002163 * Perform a adb reboot.
2164 *
2165 * @param into the bootloader name to reboot into, or <code>null</code> to just reboot the
2166 * device.
2167 * @throws DeviceNotAvailableException
2168 */
Julien Desprezc8474552016-02-17 10:59:27 +00002169 protected void doAdbReboot(final String into) throws DeviceNotAvailableException {
Julien Desprez6961b272016-02-01 09:58:23 +00002170 // emulator doesn't support reboot, try just resetting framework and hoping for the best
2171 if (getIDevice().isEmulator()) {
2172 CLog.i("since emulator, performing shell stop & start instead of reboot");
2173 executeShellCommand("stop");
2174 executeShellCommand(String.format("setprop %s 0",
2175 DeviceStateMonitor.BOOTCOMPLETE_PROP));
2176 executeShellCommand("start");
2177 return;
2178 }
Julien Desprezc8474552016-02-17 10:59:27 +00002179 DeviceAction rebootAction = new DeviceAction() {
2180 @Override
2181 public boolean run() throws TimeoutException, IOException,
2182 AdbCommandRejectedException {
2183 getIDevice().reboot(into);
2184 return true;
2185 }
2186 };
2187 performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS);
2188
Julien Desprez6961b272016-02-01 09:58:23 +00002189 }
2190
Julien Desprezc8474552016-02-17 10:59:27 +00002191 protected void waitForDeviceNotAvailable(String operationDesc, long time) {
Julien Desprez6961b272016-02-01 09:58:23 +00002192 // TODO: a bit of a race condition here. Would be better to start a
2193 // before the operation
2194 if (!mStateMonitor.waitForDeviceNotAvailable(time)) {
2195 // above check is flaky, ignore till better solution is found
2196 CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(),
2197 operationDesc);
2198 }
2199 }
2200
2201 /**
2202 * {@inheritDoc}
2203 */
2204 @Override
2205 public boolean enableAdbRoot() throws DeviceNotAvailableException {
2206 // adb root is a relatively intensive command, so do a brief check first to see
2207 // if its necessary or not
2208 if (isAdbRoot()) {
2209 CLog.i("adb is already running as root on %s", getSerialNumber());
2210 return true;
2211 }
2212 // Don't enable root if user requested no root
2213 if (!isEnableAdbRoot()) {
2214 CLog.i("\"enable-root\" set to false; ignoring 'adb root' request");
2215 return false;
2216 }
2217 CLog.i("adb root on device %s", getSerialNumber());
2218 int attempts = MAX_RETRY_ATTEMPTS + 1;
2219 for (int i=1; i <= attempts; i++) {
2220 String output = executeAdbCommand("root");
2221 // wait for device to disappear from adb
2222 waitForDeviceNotAvailable("root", 20 * 1000);
Julien Desprez8539c082016-03-04 13:39:19 +00002223
2224 postAdbRootAction();
2225
Julien Desprez6961b272016-02-01 09:58:23 +00002226 // wait for device to be back online
2227 waitForDeviceOnline();
2228
2229 if (isAdbRoot()) {
2230 return true;
2231 }
2232 CLog.w("'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'",
2233 getSerialNumber(), i, attempts, output);
2234 }
2235 return false;
2236 }
2237
2238 /**
Julien Desprez8539c082016-03-04 13:39:19 +00002239 * Override if the device needs some specific actions to be taken after adb root and before the
2240 * device is back online.
2241 * Default implementation doesn't include any addition actions.
2242 * adb root is not guaranteed to be enabled at this stage.
2243 */
Julien Desprez9dca62e2016-04-08 14:47:57 +01002244 public void postAdbRootAction() throws DeviceNotAvailableException {
Julien Desprez8539c082016-03-04 13:39:19 +00002245 // Empty on purpose.
2246 }
2247
2248 /**
Julien Desprez6961b272016-02-01 09:58:23 +00002249 * {@inheritDoc}
2250 */
2251 @Override
2252 public boolean isAdbRoot() throws DeviceNotAvailableException {
2253 String output = executeShellCommand("id");
2254 return output.contains("uid=0(root)");
2255 }
2256
2257 /**
2258 * {@inheritDoc}
2259 */
2260 @Override
2261 public boolean encryptDevice(boolean inplace) throws DeviceNotAvailableException,
2262 UnsupportedOperationException {
2263 if (!isEncryptionSupported()) {
2264 throw new UnsupportedOperationException(String.format("Can't encrypt device %s: "
2265 + "encryption not supported", getSerialNumber()));
2266 }
2267
2268 if (isDeviceEncrypted()) {
2269 CLog.d("Device %s is already encrypted, skipping", getSerialNumber());
2270 return true;
2271 }
2272
2273 enableAdbRoot();
2274
2275 String encryptMethod;
2276 long timeout;
2277 if (inplace) {
2278 encryptMethod = "inplace";
2279 timeout = ENCRYPTION_INPLACE_TIMEOUT_MIN;
2280 } else {
2281 encryptMethod = "wipe";
2282 timeout = ENCRYPTION_WIPE_TIMEOUT_MIN;
2283 }
2284
2285 CLog.i("Encrypting device %s via %s", getSerialNumber(), encryptMethod);
2286
2287 // enable crypto takes one of the following formats:
2288 // cryptfs enablecrypto <wipe|inplace> <passwd>
2289 // cryptfs enablecrypto <wipe|inplace> default|password|pin|pattern [passwd]
2290 // Try the first one first, if it outputs "500 0 Usage: ...", try the second.
2291 CollectingOutputReceiver receiver = new CollectingOutputReceiver();
2292 String command = String.format("vdc cryptfs enablecrypto %s \"%s\"", encryptMethod,
2293 ENCRYPTION_PASSWORD);
2294 executeShellCommand(command, receiver, timeout, TimeUnit.MINUTES, 1);
2295 if (receiver.getOutput().startsWith("500 0 Usage:")) {
2296 command = String.format("vdc cryptfs enablecrypto %s default", encryptMethod);
2297 executeShellCommand(command, new NullOutputReceiver(), timeout, TimeUnit.MINUTES, 1);
2298 }
2299
2300 waitForDeviceNotAvailable("reboot", getCommandTimeout());
2301 waitForDeviceOnline(); // Device will not become available until the user data is unlocked.
2302
2303 return isDeviceEncrypted();
2304 }
2305
2306 /**
2307 * {@inheritDoc}
2308 */
2309 @Override
2310 public boolean unencryptDevice() throws DeviceNotAvailableException,
2311 UnsupportedOperationException {
2312 if (!isEncryptionSupported()) {
2313 throw new UnsupportedOperationException(String.format("Can't unencrypt device %s: "
2314 + "encryption not supported", getSerialNumber()));
2315 }
2316
2317 if (!isDeviceEncrypted()) {
2318 CLog.d("Device %s is already unencrypted, skipping", getSerialNumber());
2319 return true;
2320 }
2321
2322 CLog.i("Unencrypting device %s", getSerialNumber());
2323
2324 // If the device supports fastboot format, then we're done.
2325 if (!mOptions.getUseFastbootErase()) {
2326 rebootIntoBootloader();
2327 fastbootWipePartition("userdata");
2328 rebootUntilOnline();
2329 waitForDeviceAvailable(ENCRYPTION_WIPE_TIMEOUT_MIN * 60 * 1000);
2330 return true;
2331 }
2332
2333 // Determine if we need to format partition instead of wipe.
2334 boolean format = false;
2335 String output = executeShellCommand("vdc volume list");
2336 String[] splitOutput;
2337 if (output != null) {
2338 splitOutput = output.split("\r?\n");
2339 for (String line : splitOutput) {
2340 if (line.startsWith("110 ") && line.contains("sdcard /mnt/sdcard") &&
2341 !line.endsWith("0")) {
2342 format = true;
2343 }
2344 }
2345 }
2346
2347 rebootIntoBootloader();
2348 fastbootWipePartition("userdata");
2349
2350 // If the device requires time to format the filesystem after fastboot erase userdata, wait
2351 // for the device to reboot a second time.
2352 if (mOptions.getUnencryptRebootTimeout() > 0) {
2353 rebootUntilOnline();
2354 if (waitForDeviceNotAvailable(mOptions.getUnencryptRebootTimeout())) {
2355 waitForDeviceOnline();
2356 }
2357 }
2358
2359 if (format) {
2360 CLog.d("Need to format sdcard for device %s", getSerialNumber());
2361
2362 RecoveryMode cachedRecoveryMode = getRecoveryMode();
2363 setRecoveryMode(RecoveryMode.ONLINE);
2364
2365 output = executeShellCommand("vdc volume format sdcard");
2366 if (output == null) {
2367 CLog.e("Command vdc volume format sdcard failed will no output for device %s:\n%s",
2368 getSerialNumber());
2369 setRecoveryMode(cachedRecoveryMode);
2370 return false;
2371 }
2372 splitOutput = output.split("\r?\n");
2373 if (!splitOutput[splitOutput.length - 1].startsWith("200 ")) {
2374 CLog.e("Command vdc volume format sdcard failed for device %s:\n%s",
2375 getSerialNumber(), output);
2376 setRecoveryMode(cachedRecoveryMode);
2377 return false;
2378 }
2379
2380 setRecoveryMode(cachedRecoveryMode);
2381 }
2382
2383 reboot();
2384
2385 return true;
2386 }
2387
2388 /**
2389 * {@inheritDoc}
2390 */
2391 @Override
2392 public boolean unlockDevice() throws DeviceNotAvailableException,
2393 UnsupportedOperationException {
2394 if (!isEncryptionSupported()) {
2395 throw new UnsupportedOperationException(String.format("Can't unlock device %s: "
2396 + "encryption not supported", getSerialNumber()));
2397 }
2398
2399 if (!isDeviceEncrypted()) {
2400 CLog.d("Device %s is not encrypted, skipping", getSerialNumber());
2401 return true;
2402 }
2403
2404 CLog.i("Unlocking device %s", getSerialNumber());
2405
2406 enableAdbRoot();
2407
2408 // FIXME: currently, vcd checkpw can return an empty string when it never should. Try 3
2409 // times.
2410 String output;
2411 int i = 0;
2412 do {
2413 // Enter the password. Output will be:
2414 // "200 [X] -1" if the password has already been entered correctly,
2415 // "200 [X] 0" if the password is entered correctly,
2416 // "200 [X] N" where N is any positive number if the password is incorrect,
2417 // any other string if there is an error.
2418 output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"",
2419 ENCRYPTION_PASSWORD)).trim();
2420
2421 if (output.startsWith("200 ") && output.endsWith(" -1")) {
2422 return true;
2423 }
2424
2425 if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) {
2426 CLog.e("checkpw gave output '%s' while trying to unlock device %s",
2427 output, getSerialNumber());
2428 return false;
2429 }
2430
2431 getRunUtil().sleep(500);
2432 } while (output.isEmpty() && ++i < 3);
2433
2434 if (output.isEmpty()) {
2435 CLog.e("checkpw gave no output while trying to unlock device %s");
2436 }
2437
2438 // Restart the framework. Output will be:
2439 // "200 [X] 0" if the user data partition can be mounted,
2440 // "200 [X] -1" if the user data partition can not be mounted (no correct password given),
2441 // any other string if there is an error.
2442 output = executeShellCommand("vdc cryptfs restart").trim();
2443
2444 if (!(output.startsWith("200 ") && output.endsWith(" 0"))) {
2445 CLog.e("restart gave output '%s' while trying to unlock device %s", output,
2446 getSerialNumber());
2447 return false;
2448 }
2449
2450 waitForDeviceAvailable();
2451
2452 return true;
2453 }
2454
2455 /**
2456 * {@inheritDoc}
2457 */
2458 @Override
2459 public boolean isDeviceEncrypted() throws DeviceNotAvailableException {
2460 String output = getPropertySync("ro.crypto.state");
2461
2462 if (output == null && isEncryptionSupported()) {
2463 CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber());
2464 }
2465
2466 return "encrypted".equals(output);
2467 }
2468
2469 /**
2470 * {@inheritDoc}
2471 */
2472 @Override
2473 public boolean isEncryptionSupported() throws DeviceNotAvailableException {
2474 if (!isEnableAdbRoot()) {
2475 CLog.i("root is required for encryption");
2476 mIsEncryptionSupported = false;
2477 return mIsEncryptionSupported;
2478 }
2479 if (mIsEncryptionSupported != null) {
2480 return mIsEncryptionSupported.booleanValue();
2481 }
2482 enableAdbRoot();
2483 String output = executeShellCommand("vdc cryptfs enablecrypto").trim();
2484 mIsEncryptionSupported = (output != null && output.startsWith(ENCRYPTION_SUPPORTED_CODE) &&
2485 output.contains(ENCRYPTION_SUPPORTED_USAGE));
2486 return mIsEncryptionSupported;
2487 }
2488
2489 /**
2490 * {@inheritDoc}
2491 */
2492 @Override
2493 public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException {
2494 if (mStateMonitor.waitForDeviceOnline(waitTime) == null) {
2495 recoverDevice();
2496 }
2497 }
2498
2499 /**
2500 * {@inheritDoc}
2501 */
2502 @Override
2503 public void waitForDeviceOnline() throws DeviceNotAvailableException {
2504 if (mStateMonitor.waitForDeviceOnline() == null) {
2505 recoverDevice();
2506 }
2507 }
2508
2509 /**
2510 * {@inheritDoc}
2511 */
2512 @Override
2513 public void waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException {
2514 if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) {
2515 recoverDevice();
2516 }
2517 }
2518
2519 /**
2520 * {@inheritDoc}
2521 */
2522 @Override
2523 public void waitForDeviceAvailable() throws DeviceNotAvailableException {
2524 if (mStateMonitor.waitForDeviceAvailable() == null) {
2525 recoverDevice();
2526 }
2527 }
2528
2529 /**
2530 * {@inheritDoc}
2531 */
2532 @Override
2533 public boolean waitForDeviceNotAvailable(long waitTime) {
2534 return mStateMonitor.waitForDeviceNotAvailable(waitTime);
2535 }
2536
2537 /**
2538 * {@inheritDoc}
2539 */
2540 @Override
2541 public boolean waitForDeviceInRecovery(long waitTime) {
2542 return mStateMonitor.waitForDeviceInRecovery(waitTime);
2543 }
2544
2545 /**
2546 * Small helper function to throw an NPE if the passed arg is null. This should be used when
2547 * some value will be stored and used later, in which case it'll avoid hard-to-trace
2548 * asynchronous NullPointerExceptions by throwing the exception synchronously. This is not
2549 * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care
2550 * of it in that case.
2551 */
2552 private void throwIfNull(Object obj) {
2553 if (obj == null) throw new NullPointerException();
2554 }
2555
2556 /**
2557 * Retrieve this device's recovery mechanism.
2558 * <p/>
2559 * Exposed for unit testing.
2560 */
2561 IDeviceRecovery getRecovery() {
2562 return mRecovery;
2563 }
2564
2565 /**
2566 * {@inheritDoc}
2567 */
2568 @Override
2569 public void setRecovery(IDeviceRecovery recovery) {
2570 throwIfNull(recovery);
2571 mRecovery = recovery;
2572 }
2573
2574 /**
2575 * {@inheritDoc}
2576 */
2577 @Override
2578 public void setRecoveryMode(RecoveryMode mode) {
2579 throwIfNull(mRecoveryMode);
2580 mRecoveryMode = mode;
2581 }
2582
2583 /**
2584 * {@inheritDoc}
2585 */
2586 @Override
2587 public RecoveryMode getRecoveryMode() {
2588 return mRecoveryMode;
2589 }
2590
2591 /**
2592 * {@inheritDoc}
2593 */
2594 @Override
2595 public void setFastbootEnabled(boolean fastbootEnabled) {
2596 mFastbootEnabled = fastbootEnabled;
2597 }
2598
2599 /**
2600 * {@inheritDoc}
2601 */
2602 @Override
Julien Desprez3a503442016-04-05 15:00:41 +01002603 public boolean isFastbootEnabled() {
2604 return mFastbootEnabled;
2605 }
2606
2607 /**
2608 * {@inheritDoc}
2609 */
2610 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00002611 public void setDeviceState(final TestDeviceState deviceState) {
2612 if (!deviceState.equals(getDeviceState())) {
2613 // disable state changes while fastboot lock is held, because issuing fastboot command
2614 // will disrupt state
2615 if (getDeviceState().equals(TestDeviceState.FASTBOOT) && mFastbootLock.isLocked()) {
2616 return;
2617 }
2618 mState = deviceState;
2619 CLog.d("Device %s state is now %s", getSerialNumber(), deviceState);
2620 mStateMonitor.setState(deviceState);
2621 }
2622 }
2623
2624 /**
2625 * {@inheritDoc}
2626 */
2627 @Override
2628 public TestDeviceState getDeviceState() {
2629 return mState;
2630 }
2631
2632 @Override
2633 public boolean isAdbTcp() {
2634 return mStateMonitor.isAdbTcp();
2635 }
2636
2637 /**
2638 * {@inheritDoc}
2639 */
2640 @Override
2641 public String switchToAdbTcp() throws DeviceNotAvailableException {
2642 String ipAddress = getIpAddress();
2643 if (ipAddress == null) {
2644 CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber());
2645 return null;
2646 }
2647 String port = "5555";
2648 executeAdbCommand("tcpip", port);
2649 // TODO: analyze result? wait for device offline?
2650 return String.format("%s:%s", ipAddress, port);
2651 }
2652
2653 /**
2654 * {@inheritDoc}
2655 */
2656 @Override
2657 public boolean switchToAdbUsb() throws DeviceNotAvailableException {
2658 executeAdbCommand("usb");
2659 // TODO: analyze result? wait for device offline?
2660 return true;
2661 }
2662
2663 /**
2664 * {@inheritDoc}
2665 */
2666 @Override
2667 public void setEmulatorProcess(Process p) {
2668 mEmulatorProcess = p;
2669
2670 }
2671
2672 /**
2673 * For emulator set {@link SizeLimitedOutputStream} to log output
2674 * @param output to log the output
2675 */
2676 public void setEmulatorOutputStream(SizeLimitedOutputStream output) {
2677 mEmulatorOutput = output;
2678 }
2679
2680 /**
2681 * {@inheritDoc}
2682 */
2683 @Override
2684 public void stopEmulatorOutput() {
2685 if (mEmulatorOutput != null) {
2686 mEmulatorOutput.delete();
2687 mEmulatorOutput = null;
2688 }
2689 }
2690
2691 /**
2692 * {@inheritDoc}
2693 */
2694 @Override
2695 public InputStreamSource getEmulatorOutput() {
2696 if (getIDevice().isEmulator()) {
2697 if (mEmulatorOutput == null) {
2698 CLog.w("Emulator output for %s was not captured in background",
2699 getSerialNumber());
2700 } else {
2701 try {
2702 return new SnapshotInputStreamSource(mEmulatorOutput.getData());
2703 } catch (IOException e) {
2704 CLog.e("Failed to get %s data.", getSerialNumber());
2705 CLog.e(e);
2706 }
2707 }
2708 }
2709 return new ByteArrayInputStreamSource(new byte[0]);
2710 }
2711
2712 /**
2713 * {@inheritDoc}
2714 */
2715 @Override
2716 public Process getEmulatorProcess() {
2717 return mEmulatorProcess;
2718 }
2719
2720 /**
2721 * @return <code>true</code> if adb root should be enabled on device
2722 */
2723 public boolean isEnableAdbRoot() {
2724 return mOptions.isEnableAdbRoot();
2725 }
2726
2727 /**
2728 * {@inheritDoc}
2729 */
2730 @Override
2731 public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002732 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00002733 }
2734
2735 /**
2736 * {@inheritDoc}
2737 */
2738 @Override
2739 public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002740 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00002741 }
2742
2743 /**
2744 * {@inheritDoc}
2745 */
2746 @Override
2747 public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002748 throw new UnsupportedOperationException("No support for Package's feature");
Julien Desprez6961b272016-02-01 09:58:23 +00002749 }
2750
2751 /**
2752 * {@inheritDoc}
2753 */
2754 @Override
2755 public TestDeviceOptions getOptions() {
2756 return mOptions;
2757 }
2758
2759 /**
2760 * {@inheritDoc}
2761 */
2762 @Override
2763 public int getApiLevel() throws DeviceNotAvailableException {
2764 int apiLevel = UNKNOWN_API_LEVEL;
2765 try {
2766 String prop = getProperty("ro.build.version.sdk");
2767 apiLevel = Integer.parseInt(prop);
2768 } catch (NumberFormatException nfe) {
2769 // ignore, return unknown instead
2770 }
2771 return apiLevel;
2772 }
2773
2774 @Override
2775 public IDeviceStateMonitor getMonitor() {
2776 return mStateMonitor;
2777 }
2778
2779 /**
2780 * {@inheritDoc}
2781 */
2782 @Override
2783 public boolean waitForDeviceShell(long waitTime) {
2784 return mStateMonitor.waitForDeviceShell(waitTime);
2785 }
2786
2787 @Override
2788 public DeviceAllocationState getAllocationState() {
2789 return mAllocationState;
2790 }
2791
2792 /**
2793 * {@inheritDoc}
2794 * <p>
2795 * Process the DeviceEvent, which may or may not transition this device to a new allocation
2796 * state.
2797 * </p>
2798 */
2799 @Override
2800 public DeviceEventResponse handleAllocationEvent(DeviceEvent event) {
2801
2802 // keep track of whether state has actually changed or not
2803 boolean stateChanged = false;
2804 DeviceAllocationState newState;
2805 DeviceAllocationState oldState = mAllocationState;
2806 mAllocationStateLock.lock();
2807 try {
2808 // update oldState here, just in case in changed before we got lock
2809 oldState = mAllocationState;
2810 newState = mAllocationState.handleDeviceEvent(event);
2811 if (oldState != newState) {
2812 // state has changed! record this fact, and store the new state
2813 stateChanged = true;
2814 mAllocationState = newState;
2815 }
2816 } finally {
2817 mAllocationStateLock.unlock();
2818 }
2819 if (stateChanged && mAllocationMonitor != null) {
2820 // state has changed! Lets inform the allocation monitor listener
2821 mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState);
2822 }
2823 return new DeviceEventResponse(newState, stateChanged);
2824 }
2825
2826 private long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException {
2827 String deviceTimeString = executeShellCommand("date +%s");
2828 Long deviceTime = null;
2829 long offset = 0;
2830
2831 try {
2832 deviceTime = Long.valueOf(deviceTimeString.trim());
2833 } catch (NumberFormatException nfe) {
2834 CLog.i("Invalid device time: \"%s\", ignored.");
2835 return 0;
2836 }
2837 if (date == null) {
2838 date = new Date();
2839 }
2840
2841 offset = date.getTime() - deviceTime * 1000;
2842 CLog.d("Time offset = " + offset);
2843 return offset;
2844 }
2845
2846 /**
2847 * {@inheritDoc}
2848 */
2849 @Override
2850 public void setDate(Date date) throws DeviceNotAvailableException {
2851 if (date == null) {
2852 date = new Date();
2853 }
2854 long timeOffset = getDeviceTimeOffset(date);
2855 // no need to set date
2856 if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) {
2857 return;
2858 }
2859 String dateString = null;
2860 if (getApiLevel() < 23) {
2861 // set date in epoch format
2862 dateString = Long.toString(date.getTime() / 1000); //ms to s
2863 } else {
2864 // set date with POSIX like params
2865 SimpleDateFormat sdf = new java.text.SimpleDateFormat(
2866 "MMddHHmmyyyy.ss");
2867 sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC"));
2868 dateString = sdf.format(date);
2869 }
2870 // best effort, no verification
2871 executeShellCommand("date -u " + dateString);
2872 }
2873
2874 /**
2875 * {@inheritDoc}
2876 */
2877 @Override
2878 public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException {
2879 return mStateMonitor.waitForBootComplete(timeOut);
2880 }
2881
2882 /**
2883 * {@inheritDoc}
2884 */
2885 @Override
2886 public ArrayList<Integer> listUsers() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002887 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00002888 }
2889
2890 /**
2891 * {@inheritDoc}
2892 */
2893 @Override
2894 public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002895 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00002896 }
2897
2898 /**
2899 * {@inheritDoc}
2900 */
2901 @Override
2902 public boolean isMultiUserSupported() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002903 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00002904 }
2905
2906 /**
2907 * {@inheritDoc}
2908 */
2909 @Override
2910 public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException {
Julien Desprezc8474552016-02-17 10:59:27 +00002911 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00002912 }
2913
2914 /**
2915 * {@inheritDoc}
2916 */
2917 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00002918 public int createUser(String name, boolean guest, boolean ephemeral)
2919 throws DeviceNotAvailableException, IllegalStateException {
2920 throw new UnsupportedOperationException("No support for user's feature.");
2921 }
2922
2923 /**
2924 * {@inheritDoc}
2925 */
2926 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00002927 public boolean removeUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002928 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00002929 }
2930
2931 /**
2932 * {@inheritDoc}
2933 */
2934 @Override
2935 public boolean startUser(int userId) throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002936 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00002937 }
2938
2939 /**
2940 * {@inheritDoc}
2941 */
2942 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00002943 public boolean stopUser(int userId) throws DeviceNotAvailableException {
2944 throw new UnsupportedOperationException("No support for user's feature.");
2945 }
2946
2947 /**
2948 * {@inheritDoc}
2949 */
2950 @Override
2951 public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag)
2952 throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002953 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00002954 }
2955
2956 /**
2957 * {@inheritDoc}
2958 */
2959 @Override
2960 public void remountSystemWritable() throws DeviceNotAvailableException {
2961 String verity = getProperty("partition.system.verified");
2962 // have the property set (regardless state) implies verity is enabled, so we send adb
2963 // command to disable verity
2964 if (verity != null && !verity.isEmpty()) {
2965 executeAdbCommand("disable-verity");
2966 reboot();
2967 }
2968 executeAdbCommand("remount");
2969 waitForDeviceAvailable();
2970 }
2971
2972 /**
2973 * {@inheritDoc}
2974 */
2975 @Override
2976 public Integer getPrimaryUserId() throws DeviceNotAvailableException {
Julien Desprezc8474552016-02-17 10:59:27 +00002977 throw new UnsupportedOperationException("No support for user's feature.");
Julien Desprez6961b272016-02-01 09:58:23 +00002978 }
2979
2980 /**
2981 * {@inheritDoc}
2982 */
2983 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00002984 public int getCurrentUser() throws DeviceNotAvailableException {
2985 throw new UnsupportedOperationException("No support for user's feature.");
2986 }
2987
2988 /**
2989 * {@inheritDoc}
2990 */
2991 @Override
2992 public int getUserFlags(int userId) throws DeviceNotAvailableException {
2993 throw new UnsupportedOperationException("No support for user's feature.");
2994 }
2995
2996 /**
2997 * {@inheritDoc}
2998 */
2999 @Override
3000 public int getUserSerialNumber(int userId) throws DeviceNotAvailableException {
3001 throw new UnsupportedOperationException("No support for user's feature.");
3002 }
3003
3004 /**
3005 * {@inheritDoc}
3006 */
3007 @Override
3008 public boolean switchUser(int userId) throws DeviceNotAvailableException {
3009 throw new UnsupportedOperationException("No support for user's feature.");
3010 }
3011
3012 /**
3013 * {@inheritDoc}
3014 */
3015 @Override
3016 public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException {
3017 throw new UnsupportedOperationException("No support for user's feature.");
3018 }
3019
3020 /**
3021 * {@inheritDoc}
3022 */
3023 @Override
3024 public boolean isUserRunning(int userId) throws DeviceNotAvailableException {
3025 throw new UnsupportedOperationException("No support for user's feature.");
3026 }
3027
3028 /**
3029 * {@inheritDoc}
3030 */
3031 @Override
3032 public boolean hasFeature(String feature) throws DeviceNotAvailableException {
3033 throw new UnsupportedOperationException("No support pm's features.");
3034 }
3035
3036 /**
3037 * {@inheritDoc}
3038 */
3039 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00003040 public String getSetting(String namespace, String key)
3041 throws DeviceNotAvailableException {
3042 throw new UnsupportedOperationException("No support for setting's feature.");
3043 }
3044
3045 /**
3046 * {@inheritDoc}
3047 */
3048 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003049 public String getSetting(int userId, String namespace, String key)
3050 throws DeviceNotAvailableException {
3051 throw new UnsupportedOperationException("No support for setting's feature.");
3052 }
3053
3054 /**
3055 * {@inheritDoc}
3056 */
3057 @Override
Julien Despreza3d9c0f2016-03-21 12:18:22 +00003058 public void setSetting(String namespace, String key, String value)
3059 throws DeviceNotAvailableException {
3060 throw new UnsupportedOperationException("No support for setting's feature.");
3061 }
3062
3063 /**
3064 * {@inheritDoc}
3065 */
3066 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003067 public void setSetting(int userId, String namespace, String key, String value)
3068 throws DeviceNotAvailableException {
3069 throw new UnsupportedOperationException("No support for setting's feature.");
3070 }
3071
3072 /**
3073 * {@inheritDoc}
3074 */
3075 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003076 public String getBuildSigningKeys() throws DeviceNotAvailableException {
3077 String buildTags = getProperty(BUILD_TAGS);
3078 if (buildTags != null) {
3079 String[] tags = buildTags.split(",");
3080 for (String tag : tags) {
3081 Matcher m = KEYS_PATTERN.matcher(tag);
3082 if (m.matches()) {
3083 return tag;
3084 }
3085 }
3086 }
3087 return null;
3088 }
3089
3090 /**
3091 * {@inheritDoc}
3092 */
3093 @Override
Julien Desprez4c8aabc2016-03-14 15:53:48 +00003094 public String getAndroidId(int userId) throws DeviceNotAvailableException {
3095 throw new UnsupportedOperationException("No support for user's feature.");
3096 }
3097
3098 /**
3099 * {@inheritDoc}
3100 */
3101 @Override
3102 public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException {
3103 throw new UnsupportedOperationException("No support for user's feature.");
3104 }
3105
3106 /**
3107 * {@inheritDoc}
3108 */
3109 @Override
Julien Desprez6961b272016-02-01 09:58:23 +00003110 public String getDeviceClass() {
3111 IDevice device = getIDevice();
3112 if (device == null) {
3113 CLog.w("No IDevice instance, cannot determine device class.");
3114 return "";
3115 }
3116 return device.getClass().getSimpleName();
3117 }
Julien Desprez26bee8d2016-03-29 12:09:48 +01003118
3119 /**
3120 * {@inheritDoc}
3121 */
3122 @Override
3123 public void preInvocationSetup(IBuildInfo info)
3124 throws TargetSetupError, DeviceNotAvailableException {
3125 // Default implementation empty on purpose
3126 }
3127
3128 /**
3129 * {@inheritDoc}
3130 */
3131 @Override
3132 public void postInvocationTearDown() {
3133 // Default implementation empty on purpose
3134 }
Julien Desprez6961b272016-02-01 09:58:23 +00003135}