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