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