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