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