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(String.format("Attempted %s multiple times " 2103 + "on device %s without communication success. Aborting.", actionDescription, 2104 getSerialNumber()), getSerialNumber()); 2105 } 2106 return false; 2107 } 2108 2109 /** 2110 * Log an entry for given exception 2111 * 2112 * @param actionDescription the action's description 2113 * @param e the exception 2114 */ logDeviceActionException(String actionDescription, Exception e)2115 private void logDeviceActionException(String actionDescription, Exception e) { 2116 CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(), 2117 getExceptionMessage(e), actionDescription, getSerialNumber()); 2118 } 2119 2120 /** 2121 * Make a best effort attempt to retrieve a meaningful short descriptive message for given 2122 * {@link Exception} 2123 * 2124 * @param e the {@link Exception} 2125 * @return a short message 2126 */ getExceptionMessage(Exception e)2127 private String getExceptionMessage(Exception e) { 2128 StringBuilder msgBuilder = new StringBuilder(); 2129 if (e.getMessage() != null) { 2130 msgBuilder.append(e.getMessage()); 2131 } 2132 if (e.getCause() != null) { 2133 msgBuilder.append(" cause: "); 2134 msgBuilder.append(e.getCause().getClass().getSimpleName()); 2135 if (e.getCause().getMessage() != null) { 2136 msgBuilder.append(" ("); 2137 msgBuilder.append(e.getCause().getMessage()); 2138 msgBuilder.append(")"); 2139 } 2140 } 2141 return msgBuilder.toString(); 2142 } 2143 2144 /** 2145 * Attempts to recover device communication. 2146 * 2147 * @throws DeviceNotAvailableException if device is not longer available 2148 */ 2149 @Override recoverDevice()2150 public void recoverDevice() throws DeviceNotAvailableException { 2151 if (mRecoveryMode.equals(RecoveryMode.NONE)) { 2152 CLog.i("Skipping recovery on %s", getSerialNumber()); 2153 getRunUtil().sleep(NONE_RECOVERY_MODE_DELAY); 2154 return; 2155 } 2156 CLog.i("Attempting recovery on %s", getSerialNumber()); 2157 try { 2158 mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE)); 2159 } catch (DeviceUnresponsiveException due) { 2160 RecoveryMode previousRecoveryMode = mRecoveryMode; 2161 mRecoveryMode = RecoveryMode.NONE; 2162 try { 2163 boolean enabled = enableAdbRoot(); 2164 CLog.d("Device Unresponsive during recovery, is root still enabled: %s", enabled); 2165 } catch (DeviceUnresponsiveException e) { 2166 // Ignore exception thrown here to rethrow original exception. 2167 CLog.e("Exception occurred during recovery adb root:"); 2168 CLog.e(e); 2169 } 2170 mRecoveryMode = previousRecoveryMode; 2171 throw due; 2172 } 2173 if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) { 2174 // turn off recovery mode to prevent reentrant recovery 2175 // TODO: look for a better way to handle this, such as doing postBootUp steps in 2176 // recovery itself 2177 mRecoveryMode = RecoveryMode.NONE; 2178 // this might be a runtime reset - still need to run post boot setup steps 2179 if (isEncryptionSupported() && isDeviceEncrypted()) { 2180 unlockDevice(); 2181 } 2182 postBootSetup(); 2183 mRecoveryMode = RecoveryMode.AVAILABLE; 2184 } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) { 2185 // turn off recovery mode to prevent reentrant recovery 2186 // TODO: look for a better way to handle this, such as doing postBootUp steps in 2187 // recovery itself 2188 mRecoveryMode = RecoveryMode.NONE; 2189 enableAdbRoot(); 2190 mRecoveryMode = RecoveryMode.ONLINE; 2191 } 2192 CLog.i("Recovery successful for %s", getSerialNumber()); 2193 } 2194 2195 /** 2196 * Attempts to recover device fastboot communication. 2197 * 2198 * @throws DeviceNotAvailableException if device is not longer available 2199 */ recoverDeviceFromBootloader()2200 private void recoverDeviceFromBootloader() throws DeviceNotAvailableException { 2201 CLog.i("Attempting recovery on %s in bootloader", getSerialNumber()); 2202 mRecovery.recoverDeviceBootloader(mStateMonitor); 2203 CLog.i("Bootloader recovery successful for %s", getSerialNumber()); 2204 } 2205 recoverDeviceFromFastbootd()2206 private void recoverDeviceFromFastbootd() throws DeviceNotAvailableException { 2207 CLog.i("Attempting recovery on %s in fastbootd", getSerialNumber()); 2208 mRecovery.recoverDeviceFastbootd(mStateMonitor); 2209 CLog.i("Fastbootd recovery successful for %s", getSerialNumber()); 2210 } 2211 recoverDeviceInRecovery()2212 private void recoverDeviceInRecovery() throws DeviceNotAvailableException { 2213 CLog.i("Attempting recovery on %s in recovery", getSerialNumber()); 2214 mRecovery.recoverDeviceRecovery(mStateMonitor); 2215 CLog.i("Recovery mode recovery successful for %s", getSerialNumber()); 2216 } 2217 2218 /** 2219 * {@inheritDoc} 2220 */ 2221 @Override startLogcat()2222 public void startLogcat() { 2223 if (mLogcatReceiver != null) { 2224 CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber()); 2225 return; 2226 } 2227 mLogcatReceiver = createLogcatReceiver(); 2228 mLogcatReceiver.start(); 2229 } 2230 2231 /** 2232 * {@inheritDoc} 2233 */ 2234 @Override clearLogcat()2235 public void clearLogcat() { 2236 if (mLogcatReceiver != null) { 2237 mLogcatReceiver.clear(); 2238 } 2239 } 2240 2241 /** {@inheritDoc} */ 2242 @Override 2243 @SuppressWarnings("MustBeClosedChecker") getLogcat()2244 public InputStreamSource getLogcat() { 2245 if (mLogcatReceiver == null) { 2246 CLog.w("Not capturing logcat for %s in background, returning a logcat dump", 2247 getSerialNumber()); 2248 return getLogcatDump(); 2249 } else { 2250 return mLogcatReceiver.getLogcatData(); 2251 } 2252 } 2253 2254 /** {@inheritDoc} */ 2255 @Override 2256 @SuppressWarnings("MustBeClosedChecker") getLogcat(int maxBytes)2257 public InputStreamSource getLogcat(int maxBytes) { 2258 if (mLogcatReceiver == null) { 2259 CLog.w("Not capturing logcat for %s in background, returning a logcat dump " 2260 + "ignoring size", getSerialNumber()); 2261 return getLogcatDump(); 2262 } else { 2263 return mLogcatReceiver.getLogcatData(maxBytes); 2264 } 2265 } 2266 2267 /** 2268 * {@inheritDoc} 2269 */ 2270 @Override getLogcatSince(long date)2271 public InputStreamSource getLogcatSince(long date) { 2272 try { 2273 if (getApiLevel() <= 22) { 2274 CLog.i("Api level too low to use logcat -t 'time' reverting to dump"); 2275 return getLogcatDump(); 2276 } 2277 } catch (DeviceNotAvailableException e) { 2278 // For convenience of interface, we catch the DNAE here. 2279 CLog.e(e); 2280 return getLogcatDump(); 2281 } 2282 2283 // Convert date to format needed by the command: 2284 // 'MM-DD HH:mm:ss.mmm' or 'YYYY-MM-DD HH:mm:ss.mmm' 2285 SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss.mmm"); 2286 String dateFormatted = format.format(new Date(date)); 2287 2288 byte[] output = new byte[0]; 2289 try { 2290 // use IDevice directly because we don't want callers to handle 2291 // DeviceNotAvailableException for this method 2292 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver(); 2293 String command = String.format("%s -t '%s'", LogcatReceiver.LOGCAT_CMD, dateFormatted); 2294 getIDevice().executeShellCommand(command, receiver); 2295 output = receiver.getOutput(); 2296 } catch (IOException|AdbCommandRejectedException| 2297 ShellCommandUnresponsiveException|TimeoutException e) { 2298 CLog.w("Failed to get logcat dump from %s: %s", getSerialNumber(), e.getMessage()); 2299 CLog.e(e); 2300 } 2301 return new ByteArrayInputStreamSource(output); 2302 } 2303 2304 /** 2305 * {@inheritDoc} 2306 */ 2307 @Override getLogcatDump()2308 public InputStreamSource getLogcatDump() { 2309 byte[] output = new byte[0]; 2310 try { 2311 // use IDevice directly because we don't want callers to handle 2312 // DeviceNotAvailableException for this method 2313 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver(); 2314 // add -d parameter to make this a non blocking call 2315 getIDevice().executeShellCommand(LogcatReceiver.LOGCAT_CMD + " -d", receiver, 2316 LOGCAT_DUMP_TIMEOUT, TimeUnit.MILLISECONDS); 2317 output = receiver.getOutput(); 2318 } catch (IOException e) { 2319 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage()); 2320 } catch (TimeoutException e) { 2321 CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber()); 2322 } catch (AdbCommandRejectedException e) { 2323 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage()); 2324 } catch (ShellCommandUnresponsiveException e) { 2325 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage()); 2326 } 2327 return new ByteArrayInputStreamSource(output); 2328 } 2329 2330 /** 2331 * {@inheritDoc} 2332 */ 2333 @Override stopLogcat()2334 public void stopLogcat() { 2335 if (mLogcatReceiver != null) { 2336 mLogcatReceiver.stop(); 2337 mLogcatReceiver = null; 2338 } else { 2339 CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber()); 2340 } 2341 } 2342 2343 /** Factory method to create a {@link LogcatReceiver}. */ 2344 @VisibleForTesting createLogcatReceiver()2345 LogcatReceiver createLogcatReceiver() { 2346 String logcatOptions = mOptions.getLogcatOptions(); 2347 if (logcatOptions == null) { 2348 return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay); 2349 } else { 2350 return new LogcatReceiver(this, 2351 String.format("%s %s", LogcatReceiver.LOGCAT_CMD, logcatOptions), 2352 mOptions.getMaxLogcatDataSize(), mLogStartDelay); 2353 } 2354 } 2355 2356 /** 2357 * {@inheritDoc} 2358 */ 2359 @Override getBugreport()2360 public InputStreamSource getBugreport() { 2361 if (getApiLevelSafe() < 24) { 2362 InputStreamSource bugreport = getBugreportInternal(); 2363 if (bugreport == null) { 2364 // Safe call so we don't return null but an empty resource. 2365 return new ByteArrayInputStreamSource("".getBytes()); 2366 } 2367 return bugreport; 2368 } 2369 CLog.d("Api level above 24, using bugreportz instead."); 2370 File mainEntry = null; 2371 File bugreportzFile = null; 2372 try { 2373 bugreportzFile = getBugreportzInternal(); 2374 if (bugreportzFile == null) { 2375 bugreportzFile = bugreportzFallback(); 2376 } 2377 if (bugreportzFile == null) { 2378 // return empty buffer 2379 return new ByteArrayInputStreamSource("".getBytes()); 2380 } 2381 try (ZipFile zip = new ZipFile(bugreportzFile)) { 2382 // We get the main_entry.txt that contains the bugreport name. 2383 mainEntry = ZipUtil2.extractFileFromZip(zip, "main_entry.txt"); 2384 String bugreportName = FileUtil.readStringFromFile(mainEntry).trim(); 2385 CLog.d("bugreport name: '%s'", bugreportName); 2386 File bugreport = ZipUtil2.extractFileFromZip(zip, bugreportName); 2387 return new FileInputStreamSource(bugreport, true); 2388 } 2389 } catch (IOException e) { 2390 CLog.e("Error while unzipping bugreportz"); 2391 CLog.e(e); 2392 return new ByteArrayInputStreamSource("corrupted bugreport.".getBytes()); 2393 } finally { 2394 FileUtil.deleteFile(bugreportzFile); 2395 FileUtil.deleteFile(mainEntry); 2396 } 2397 } 2398 2399 /** 2400 * If first bugreportz collection was interrupted for any reasons, the temporary file where the 2401 * dumpstate is redirected could exists if it started. We attempt to get it to have some partial 2402 * data. 2403 */ bugreportzFallback()2404 private File bugreportzFallback() { 2405 try { 2406 IFileEntry entries = getFileEntry(BUGREPORTZ_TMP_PATH); 2407 if (entries != null) { 2408 for (IFileEntry f : entries.getChildren(false)) { 2409 String name = f.getName(); 2410 CLog.d("bugreport entry: %s", name); 2411 // Only get left-over zipped data to avoid confusing data types. 2412 if (name.endsWith(".zip")) { 2413 File pulledZip = pullFile(BUGREPORTZ_TMP_PATH + name); 2414 try { 2415 // Validate the zip before returning it. 2416 if (ZipUtil.isZipFileValid(pulledZip, false)) { 2417 return pulledZip; 2418 } 2419 } catch (IOException e) { 2420 CLog.e(e); 2421 } 2422 CLog.w("Failed to get a valid bugreportz."); 2423 // if zip validation failed, delete it and return null. 2424 FileUtil.deleteFile(pulledZip); 2425 return null; 2426 2427 } 2428 } 2429 CLog.w("Could not find a tmp bugreport file in the directory."); 2430 } else { 2431 CLog.w("Could not find the file entry: '%s' on the device.", BUGREPORTZ_TMP_PATH); 2432 } 2433 } catch (DeviceNotAvailableException e) { 2434 CLog.e(e); 2435 } 2436 return null; 2437 } 2438 2439 /** 2440 * {@inheritDoc} 2441 */ 2442 @Override logBugreport(String dataName, ITestLogger listener)2443 public boolean logBugreport(String dataName, ITestLogger listener) { 2444 InputStreamSource bugreport = null; 2445 LogDataType type = null; 2446 try { 2447 bugreport = getBugreportz(); 2448 type = LogDataType.BUGREPORTZ; 2449 2450 if (bugreport == null) { 2451 CLog.d("Bugreportz failed, attempting bugreport collection instead."); 2452 bugreport = getBugreportInternal(); 2453 type = LogDataType.BUGREPORT; 2454 } 2455 // log what we managed to capture. 2456 if (bugreport != null) { 2457 listener.testLog(dataName, type, bugreport); 2458 return true; 2459 } 2460 } finally { 2461 StreamUtil.cancel(bugreport); 2462 } 2463 CLog.d( 2464 "logBugreport() was not successful in collecting and logging the bugreport " 2465 + "for device %s", 2466 getSerialNumber()); 2467 return false; 2468 } 2469 2470 /** 2471 * {@inheritDoc} 2472 */ 2473 @Override takeBugreport()2474 public Bugreport takeBugreport() { 2475 File bugreportFile = null; 2476 int apiLevel = getApiLevelSafe(); 2477 if (apiLevel == UNKNOWN_API_LEVEL) { 2478 return null; 2479 } 2480 if (apiLevel >= 24) { 2481 CLog.d("Api level above 24, using bugreportz."); 2482 bugreportFile = getBugreportzInternal(); 2483 if (bugreportFile != null) { 2484 return new Bugreport(bugreportFile, true); 2485 } 2486 return null; 2487 } 2488 // fall back to regular bugreport 2489 InputStreamSource bugreport = getBugreportInternal(); 2490 if (bugreport == null) { 2491 CLog.e("Error when collecting the bugreport."); 2492 return null; 2493 } 2494 try { 2495 bugreportFile = FileUtil.createTempFile("bugreport", ".txt"); 2496 FileUtil.writeToFile(bugreport.createInputStream(), bugreportFile); 2497 return new Bugreport(bugreportFile, false); 2498 } catch (IOException e) { 2499 CLog.e("Error when writing the bugreport file"); 2500 CLog.e(e); 2501 } 2502 return null; 2503 } 2504 2505 /** 2506 * {@inheritDoc} 2507 */ 2508 @Override getBugreportz()2509 public InputStreamSource getBugreportz() { 2510 if (getApiLevelSafe() < 24) { 2511 return null; 2512 } 2513 File bugreportZip = getBugreportzInternal(); 2514 if (bugreportZip == null) { 2515 bugreportZip = bugreportzFallback(); 2516 } 2517 if (bugreportZip != null) { 2518 return new FileInputStreamSource(bugreportZip, true); 2519 } 2520 return null; 2521 } 2522 2523 /** Internal Helper method to get the bugreportz zip file as a {@link File}. */ 2524 @VisibleForTesting getBugreportzInternal()2525 protected File getBugreportzInternal() { 2526 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 2527 // Does not rely on {@link ITestDevice#executeAdbCommand(String...)} because it does not 2528 // provide a timeout. 2529 try { 2530 executeShellCommand(BUGREPORTZ_CMD, receiver, 2531 BUGREPORTZ_TIMEOUT, TimeUnit.MILLISECONDS, 0 /* don't retry */); 2532 String output = receiver.getOutput().trim(); 2533 Matcher match = BUGREPORTZ_RESPONSE_PATTERN.matcher(output); 2534 if (!match.find()) { 2535 CLog.e("Something went went wrong during bugreportz collection: '%s'", output); 2536 return null; 2537 } else { 2538 String remoteFilePath = match.group(2); 2539 if (Strings.isNullOrEmpty(remoteFilePath)) { 2540 CLog.e("Invalid bugreportz path found from output: %s", output); 2541 return null; 2542 } 2543 File zipFile = null; 2544 try { 2545 if (!doesFileExist(remoteFilePath)) { 2546 CLog.e("Did not find bugreportz at: '%s'", remoteFilePath); 2547 return null; 2548 } 2549 // Create a placeholder to replace the file 2550 zipFile = FileUtil.createTempFile("bugreportz", ".zip"); 2551 pullFile(remoteFilePath, zipFile); 2552 String bugreportDir = 2553 remoteFilePath.substring(0, remoteFilePath.lastIndexOf('/')); 2554 if (!bugreportDir.isEmpty()) { 2555 // clean bugreport files directory on device 2556 deleteFile(String.format("%s/*", bugreportDir)); 2557 } 2558 2559 return zipFile; 2560 } catch (IOException e) { 2561 CLog.e("Failed to create the temporary file."); 2562 return null; 2563 } 2564 } 2565 } catch (DeviceNotAvailableException e) { 2566 CLog.e("Device %s became unresponsive while retrieving bugreportz", getSerialNumber()); 2567 CLog.e(e); 2568 } 2569 return null; 2570 } 2571 getBugreportInternal()2572 protected InputStreamSource getBugreportInternal() { 2573 CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver(); 2574 try { 2575 executeShellCommand( 2576 BUGREPORT_CMD, 2577 receiver, 2578 BUGREPORT_TIMEOUT, 2579 TimeUnit.MILLISECONDS, 2580 0 /* don't retry */); 2581 } catch (DeviceNotAvailableException e) { 2582 // Log, but don't throw, so the caller can get the bugreport contents even 2583 // if the device goes away 2584 CLog.e("Device %s became unresponsive while retrieving bugreport", getSerialNumber()); 2585 return null; 2586 } 2587 return new ByteArrayInputStreamSource(receiver.getOutput()); 2588 } 2589 2590 /** 2591 * {@inheritDoc} 2592 */ 2593 @Override getScreenshot()2594 public InputStreamSource getScreenshot() throws DeviceNotAvailableException { 2595 throw new UnsupportedOperationException("No support for Screenshot"); 2596 } 2597 2598 /** 2599 * {@inheritDoc} 2600 */ 2601 @Override getScreenshot(String format)2602 public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException { 2603 throw new UnsupportedOperationException("No support for Screenshot"); 2604 } 2605 2606 /** {@inheritDoc} */ 2607 @Override getScreenshot(String format, boolean rescale)2608 public InputStreamSource getScreenshot(String format, boolean rescale) 2609 throws DeviceNotAvailableException { 2610 throw new UnsupportedOperationException("No support for Screenshot"); 2611 } 2612 2613 /** {@inheritDoc} */ 2614 @Override getScreenshot(long displayId)2615 public InputStreamSource getScreenshot(long displayId) throws DeviceNotAvailableException { 2616 throw new UnsupportedOperationException("No support for Screenshot"); 2617 } 2618 2619 /** {@inheritDoc} */ 2620 @Override clearLastConnectedWifiNetwork()2621 public void clearLastConnectedWifiNetwork() { 2622 mLastConnectedWifiSsid = null; 2623 mLastConnectedWifiPsk = null; 2624 } 2625 2626 /** 2627 * {@inheritDoc} 2628 */ 2629 @Override connectToWifiNetwork(String wifiSsid, String wifiPsk)2630 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk) 2631 throws DeviceNotAvailableException { 2632 return connectToWifiNetwork(wifiSsid, wifiPsk, false); 2633 } 2634 2635 /** 2636 * {@inheritDoc} 2637 */ 2638 @Override connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)2639 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid) 2640 throws DeviceNotAvailableException { 2641 // Clears the last connected wifi network. 2642 mLastConnectedWifiSsid = null; 2643 mLastConnectedWifiPsk = null; 2644 2645 // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()} 2646 // times 2647 Random rnd = new Random(); 2648 int backoffSlotCount = 2; 2649 int slotTime = mOptions.getWifiRetryWaitTime(); 2650 int waitTime = 0; 2651 IWifiHelper wifi = createWifiHelper(); 2652 long startTime = mClock.millis(); 2653 for (int i = 1; i <= mOptions.getWifiAttempts(); i++) { 2654 CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber()); 2655 boolean success = 2656 wifi.connectToNetwork(wifiSsid, wifiPsk, mOptions.getConnCheckUrl(), scanSsid); 2657 final Map<String, String> wifiInfo = wifi.getWifiInfo(); 2658 if (success) { 2659 CLog.i( 2660 "Successfully connected to wifi network %s(%s) on %s", 2661 wifiSsid, wifiInfo.get("bssid"), getSerialNumber()); 2662 2663 mLastConnectedWifiSsid = wifiSsid; 2664 mLastConnectedWifiPsk = wifiPsk; 2665 2666 return true; 2667 } else { 2668 CLog.w( 2669 "Failed to connect to wifi network %s(%s) on %s on attempt %d of %d", 2670 wifiSsid, 2671 wifiInfo.get("bssid"), 2672 getSerialNumber(), 2673 i, 2674 mOptions.getWifiAttempts()); 2675 } 2676 if (mClock.millis() - startTime >= mOptions.getMaxWifiConnectTime()) { 2677 CLog.e( 2678 "Failed to connect to wifi after %d ms. Aborting.", 2679 mOptions.getMaxWifiConnectTime()); 2680 break; 2681 } 2682 if (i < mOptions.getWifiAttempts()) { 2683 if (mOptions.isWifiExpoRetryEnabled()) { 2684 // use binary exponential back-offs when retrying. 2685 waitTime = rnd.nextInt(backoffSlotCount) * slotTime; 2686 backoffSlotCount *= 2; 2687 } 2688 CLog.e("Waiting for %d ms before reconnecting to %s...", waitTime, wifiSsid); 2689 getRunUtil().sleep(waitTime); 2690 } 2691 } 2692 return false; 2693 } 2694 2695 /** 2696 * {@inheritDoc} 2697 */ 2698 @Override checkConnectivity()2699 public boolean checkConnectivity() throws DeviceNotAvailableException { 2700 IWifiHelper wifi = createWifiHelper(); 2701 return wifi.checkConnectivity(mOptions.getConnCheckUrl()); 2702 } 2703 2704 /** 2705 * {@inheritDoc} 2706 */ 2707 @Override connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)2708 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk) 2709 throws DeviceNotAvailableException { 2710 return connectToWifiNetworkIfNeeded(wifiSsid, wifiPsk, false); 2711 } 2712 2713 /** 2714 * {@inheritDoc} 2715 */ 2716 @Override connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)2717 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid) 2718 throws DeviceNotAvailableException { 2719 if (!checkConnectivity()) { 2720 return connectToWifiNetwork(wifiSsid, wifiPsk, scanSsid); 2721 } 2722 return true; 2723 } 2724 2725 /** 2726 * {@inheritDoc} 2727 */ 2728 @Override isWifiEnabled()2729 public boolean isWifiEnabled() throws DeviceNotAvailableException { 2730 final IWifiHelper wifi = createWifiHelper(); 2731 try { 2732 return wifi.isWifiEnabled(); 2733 } catch (RuntimeException e) { 2734 CLog.w("Failed to create WifiHelper: %s", e.getMessage()); 2735 return false; 2736 } 2737 } 2738 2739 /** 2740 * Checks that the device is currently successfully connected to given wifi SSID. 2741 * 2742 * @param wifiSSID the wifi ssid 2743 * @return <code>true</code> if device is currently connected to wifiSSID and has network 2744 * connectivity. <code>false</code> otherwise 2745 * @throws DeviceNotAvailableException if connection with device was lost 2746 */ checkWifiConnection(String wifiSSID)2747 boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException { 2748 CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber()); 2749 final IWifiHelper wifi = createWifiHelper(); 2750 // getSSID returns SSID as "SSID" 2751 final String quotedSSID = String.format("\"%s\"", wifiSSID); 2752 2753 boolean test = wifi.isWifiEnabled(); 2754 CLog.v("%s: wifi enabled? %b", getSerialNumber(), test); 2755 2756 if (test) { 2757 final String actualSSID = wifi.getSSID(); 2758 test = quotedSSID.equals(actualSSID); 2759 CLog.v("%s: SSID match (%s, %s, %b)", getSerialNumber(), quotedSSID, actualSSID, test); 2760 } 2761 if (test) { 2762 test = wifi.hasValidIp(); 2763 CLog.v("%s: validIP? %b", getSerialNumber(), test); 2764 } 2765 if (test) { 2766 test = checkConnectivity(); 2767 CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test); 2768 } 2769 return test; 2770 } 2771 2772 /** 2773 * {@inheritDoc} 2774 */ 2775 @Override disconnectFromWifi()2776 public boolean disconnectFromWifi() throws DeviceNotAvailableException { 2777 CLog.i("Disconnecting from wifi on %s", getSerialNumber()); 2778 // Clears the last connected wifi network. 2779 mLastConnectedWifiSsid = null; 2780 mLastConnectedWifiPsk = null; 2781 2782 IWifiHelper wifi = createWifiHelper(); 2783 return wifi.disconnectFromNetwork(); 2784 } 2785 2786 /** 2787 * {@inheritDoc} 2788 */ 2789 @Override getIpAddress()2790 public String getIpAddress() throws DeviceNotAvailableException { 2791 IWifiHelper wifi = createWifiHelper(); 2792 return wifi.getIpAddress(); 2793 } 2794 2795 /** 2796 * {@inheritDoc} 2797 */ 2798 @Override enableNetworkMonitor()2799 public boolean enableNetworkMonitor() throws DeviceNotAvailableException { 2800 mNetworkMonitorEnabled = false; 2801 2802 IWifiHelper wifi = createWifiHelper(); 2803 wifi.stopMonitor(); 2804 if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) { 2805 mNetworkMonitorEnabled = true; 2806 return true; 2807 } 2808 return false; 2809 } 2810 2811 /** 2812 * {@inheritDoc} 2813 */ 2814 @Override disableNetworkMonitor()2815 public boolean disableNetworkMonitor() throws DeviceNotAvailableException { 2816 mNetworkMonitorEnabled = false; 2817 2818 IWifiHelper wifi = createWifiHelper(); 2819 List<Long> samples = wifi.stopMonitor(); 2820 if (!samples.isEmpty()) { 2821 int failures = 0; 2822 long totalLatency = 0; 2823 for (Long sample : samples) { 2824 if (sample < 0) { 2825 failures += 1; 2826 } else { 2827 totalLatency += sample; 2828 } 2829 } 2830 double failureRate = failures * 100.0 / samples.size(); 2831 double avgLatency = 0.0; 2832 if (failures < samples.size()) { 2833 avgLatency = totalLatency / (samples.size() - failures); 2834 } 2835 CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f", 2836 mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000, 2837 failureRate, avgLatency); 2838 } 2839 return true; 2840 } 2841 2842 /** 2843 * Create a {@link WifiHelper} to use 2844 * 2845 * <p> 2846 * 2847 * @throws DeviceNotAvailableException 2848 */ 2849 @VisibleForTesting createWifiHelper()2850 IWifiHelper createWifiHelper() throws DeviceNotAvailableException { 2851 // current wifi helper won't work on AndroidNativeDevice 2852 // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when 2853 // we learn what is available. 2854 throw new UnsupportedOperationException("Wifi helper is not supported."); 2855 } 2856 2857 /** 2858 * {@inheritDoc} 2859 */ 2860 @Override clearErrorDialogs()2861 public boolean clearErrorDialogs() throws DeviceNotAvailableException { 2862 throw new UnsupportedOperationException("No support for Screen's features"); 2863 } 2864 2865 /** {@inheritDoc} */ 2866 @Override getKeyguardState()2867 public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException { 2868 throw new UnsupportedOperationException("No support for keyguard querying."); 2869 } 2870 getDeviceStateMonitor()2871 IDeviceStateMonitor getDeviceStateMonitor() { 2872 return mStateMonitor; 2873 } 2874 2875 /** 2876 * {@inheritDoc} 2877 */ 2878 @Override postBootSetup()2879 public void postBootSetup() throws DeviceNotAvailableException { 2880 enableAdbRoot(); 2881 prePostBootSetup(); 2882 for (String command : mOptions.getPostBootCommands()) { 2883 executeShellCommand(command); 2884 } 2885 } 2886 2887 /** 2888 * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for 2889 * specific post boot setup. 2890 * @throws DeviceNotAvailableException 2891 */ prePostBootSetup()2892 protected void prePostBootSetup() throws DeviceNotAvailableException { 2893 // Empty on purpose. 2894 } 2895 2896 /** 2897 * Ensure wifi connection is re-established after boot. This is intended to be called after TF 2898 * initiated reboots(ones triggered by {@link #reboot()}) only. 2899 * 2900 * @throws DeviceNotAvailableException 2901 */ postBootWifiSetup()2902 void postBootWifiSetup() throws DeviceNotAvailableException { 2903 if (mLastConnectedWifiSsid != null) { 2904 reconnectToWifiNetwork(); 2905 } 2906 if (mNetworkMonitorEnabled) { 2907 if (!enableNetworkMonitor()) { 2908 CLog.w("Failed to enable network monitor on %s after reboot", getSerialNumber()); 2909 } 2910 } 2911 } 2912 reconnectToWifiNetwork()2913 void reconnectToWifiNetwork() throws DeviceNotAvailableException { 2914 // First, wait for wifi to re-connect automatically. 2915 long startTime = System.currentTimeMillis(); 2916 boolean isConnected = checkConnectivity(); 2917 while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) { 2918 getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL); 2919 isConnected = checkConnectivity(); 2920 } 2921 2922 if (isConnected) { 2923 return; 2924 } 2925 2926 // If wifi is still not connected, try to re-connect on our own. 2927 final String wifiSsid = mLastConnectedWifiSsid; 2928 if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) { 2929 throw new NetworkNotAvailableException( 2930 String.format("Failed to connect to wifi network %s on %s after reboot", 2931 wifiSsid, getSerialNumber())); 2932 } 2933 } 2934 2935 /** 2936 * {@inheritDoc} 2937 */ 2938 @Override rebootIntoBootloader()2939 public void rebootIntoBootloader() 2940 throws DeviceNotAvailableException, UnsupportedOperationException { 2941 rebootIntoFastbootInternal(true); 2942 } 2943 2944 /** {@inheritDoc} */ 2945 @Override rebootIntoFastbootd()2946 public void rebootIntoFastbootd() 2947 throws DeviceNotAvailableException, UnsupportedOperationException { 2948 rebootIntoFastbootInternal(false); 2949 } 2950 2951 /** 2952 * Reboots the device into bootloader or fastbootd mode. 2953 * 2954 * @param isBootloader true to boot the device into bootloader mode, false to boot the device 2955 * into fastbootd mode. 2956 * @throws DeviceNotAvailableException if connection with device is lost and cannot be 2957 * recovered. 2958 */ rebootIntoFastbootInternal(boolean isBootloader)2959 private void rebootIntoFastbootInternal(boolean isBootloader) 2960 throws DeviceNotAvailableException { 2961 final RebootMode mode = 2962 isBootloader ? RebootMode.REBOOT_INTO_BOOTLOADER : RebootMode.REBOOT_INTO_FASTBOOTD; 2963 if (!mFastbootEnabled) { 2964 throw new UnsupportedOperationException( 2965 String.format("Fastboot is not available and cannot reboot into %s", mode)); 2966 } 2967 // If we go to bootloader, it's probably for flashing so ensure we re-check the provider 2968 mShouldSkipContentProviderSetup = false; 2969 CLog.i( 2970 "Rebooting device %s in state %s into %s", 2971 getSerialNumber(), getDeviceState(), mode); 2972 if (isStateBootloaderOrFastbootd()) { 2973 CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber()); 2974 executeFastbootCommand(String.format("reboot-%s", mode)); 2975 } else { 2976 CLog.i("Booting device %s into %s", getSerialNumber(), mode); 2977 doAdbReboot(mode, null); 2978 } 2979 2980 if (RebootMode.REBOOT_INTO_FASTBOOTD.equals(mode) && getHostOptions().isFastbootdEnable()) { 2981 if (!mStateMonitor.waitForDeviceFastbootd( 2982 getFastbootPath(), mOptions.getFastbootTimeout())) { 2983 recoverDeviceFromFastbootd(); 2984 } 2985 } else { 2986 if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) { 2987 recoverDeviceFromBootloader(); 2988 } 2989 } 2990 } 2991 2992 /** {@inheritDoc} */ 2993 @Override isStateBootloaderOrFastbootd()2994 public boolean isStateBootloaderOrFastbootd() { 2995 return TestDeviceState.FASTBOOT.equals(getDeviceState()) 2996 || TestDeviceState.FASTBOOTD.equals(getDeviceState()); 2997 } 2998 2999 /** 3000 * {@inheritDoc} 3001 */ 3002 @Override reboot()3003 public void reboot() throws DeviceNotAvailableException { 3004 reboot(null); 3005 } 3006 3007 /** {@inheritDoc} */ 3008 @Override reboot(@ullable String reason)3009 public void reboot(@Nullable String reason) throws DeviceNotAvailableException { 3010 rebootUntilOnline(reason); 3011 3012 RecoveryMode cachedRecoveryMode = getRecoveryMode(); 3013 setRecoveryMode(RecoveryMode.ONLINE); 3014 3015 if (isEncryptionSupported() && isDeviceEncrypted()) { 3016 unlockDevice(); 3017 } 3018 3019 setRecoveryMode(cachedRecoveryMode); 3020 3021 if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) { 3022 postBootSetup(); 3023 postBootWifiSetup(); 3024 return; 3025 } else { 3026 recoverDevice(); 3027 } 3028 } 3029 3030 @Override rebootUserspace()3031 public void rebootUserspace() throws DeviceNotAvailableException { 3032 rebootUserspaceUntilOnline(); 3033 3034 RecoveryMode cachedRecoveryMode = getRecoveryMode(); 3035 setRecoveryMode(RecoveryMode.ONLINE); 3036 3037 if (isEncryptionSupported()) { 3038 if (isDeviceEncrypted()) { 3039 CLog.e("Device is encrypted after userspace reboot!"); 3040 unlockDevice(); 3041 } 3042 } 3043 3044 setRecoveryMode(cachedRecoveryMode); 3045 3046 if (mStateMonitor.waitForDeviceAvailable(mOptions.getRebootTimeout()) != null) { 3047 postBootSetup(); 3048 postBootWifiSetup(); 3049 } else { 3050 recoverDevice(); 3051 } 3052 } 3053 3054 @Override rebootUntilOnline()3055 public void rebootUntilOnline() throws DeviceNotAvailableException { 3056 rebootUntilOnline(null); 3057 } 3058 3059 /** {@inheritDoc} */ 3060 @Override rebootUntilOnline(@ullable String reason)3061 public void rebootUntilOnline(@Nullable String reason) throws DeviceNotAvailableException { 3062 doReboot(RebootMode.REBOOT_FULL, reason); 3063 RecoveryMode cachedRecoveryMode = getRecoveryMode(); 3064 setRecoveryMode(RecoveryMode.ONLINE); 3065 waitForDeviceOnline(); 3066 enableAdbRoot(); 3067 setRecoveryMode(cachedRecoveryMode); 3068 } 3069 3070 @Override rebootUserspaceUntilOnline()3071 public void rebootUserspaceUntilOnline() throws DeviceNotAvailableException { 3072 doReboot(RebootMode.REBOOT_USERSPACE, null); 3073 RecoveryMode cachedRecoveryMode = getRecoveryMode(); 3074 setRecoveryMode(RecoveryMode.ONLINE); 3075 waitForDeviceOnline(); 3076 enableAdbRoot(); 3077 setRecoveryMode(cachedRecoveryMode); 3078 } 3079 3080 /** 3081 * {@inheritDoc} 3082 */ 3083 @Override rebootIntoRecovery()3084 public void rebootIntoRecovery() throws DeviceNotAvailableException { 3085 if (isStateBootloaderOrFastbootd()) { 3086 CLog.w("device %s in fastboot when requesting boot to recovery. " + 3087 "Rebooting to userspace first.", getSerialNumber()); 3088 rebootUntilOnline(); 3089 } 3090 doAdbReboot(RebootMode.REBOOT_INTO_RECOVERY, null); 3091 if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) { 3092 recoverDeviceInRecovery(); 3093 } 3094 } 3095 3096 3097 /** {@inheritDoc} */ 3098 @Override rebootIntoSideload()3099 public void rebootIntoSideload() throws DeviceNotAvailableException { 3100 rebootIntoSideload(false); 3101 } 3102 /** {@inheritDoc} */ 3103 @Override rebootIntoSideload(boolean autoReboot)3104 public void rebootIntoSideload(boolean autoReboot) throws DeviceNotAvailableException { 3105 if (isStateBootloaderOrFastbootd()) { 3106 CLog.w( 3107 "device %s in fastboot when requesting boot to sideload. " 3108 + "Rebooting to userspace first.", 3109 getSerialNumber()); 3110 rebootUntilOnline(); 3111 } 3112 final RebootMode rebootMode; 3113 if (autoReboot) { 3114 rebootMode = RebootMode.REBOOT_INTO_SIDELOAD_AUTO_REBOOT; 3115 } else { 3116 rebootMode = RebootMode.REBOOT_INTO_SIDELOAD; 3117 } 3118 doAdbReboot(rebootMode, null); 3119 if (!waitForDeviceInSideload(mOptions.getAdbRecoveryTimeout())) { 3120 // using recovery mode because sideload is a sub-mode under recovery 3121 recoverDeviceInRecovery(); 3122 } 3123 } 3124 3125 /** 3126 * {@inheritDoc} 3127 */ 3128 @Override nonBlockingReboot()3129 public void nonBlockingReboot() throws DeviceNotAvailableException { 3130 doReboot(RebootMode.REBOOT_FULL, null); 3131 } 3132 3133 /** 3134 * A mode of a reboot. 3135 * 3136 * <p>Source of truth for available modes is defined in init. 3137 */ 3138 @VisibleForTesting 3139 protected enum RebootMode { 3140 REBOOT_FULL(""), 3141 REBOOT_USERSPACE("userspace"), 3142 REBOOT_INTO_FASTBOOTD("fastboot"), 3143 REBOOT_INTO_BOOTLOADER("bootloader"), 3144 REBOOT_INTO_SIDELOAD("sideload"), 3145 REBOOT_INTO_SIDELOAD_AUTO_REBOOT("sideload-auto-reboot"), 3146 REBOOT_INTO_RECOVERY("recovery"); 3147 3148 private final String mRebootTarget; 3149 RebootMode(String rebootTarget)3150 RebootMode(String rebootTarget) { 3151 mRebootTarget = rebootTarget; 3152 } 3153 3154 @Nullable formatRebootCommand(@ullable String reason)3155 String formatRebootCommand(@Nullable String reason) { 3156 if (this == REBOOT_FULL) { 3157 return Strings.isNullOrEmpty(reason) ? null : reason; 3158 } else { 3159 return Strings.isNullOrEmpty(reason) ? mRebootTarget : mRebootTarget + "," + reason; 3160 } 3161 } 3162 3163 @Override toString()3164 public String toString() { 3165 return mRebootTarget; 3166 } 3167 } 3168 3169 /** 3170 * Trigger a reboot of the device, offers no guarantee of the device state after the call. 3171 * 3172 * @param rebootMode a mode of this reboot 3173 * @param reason reason for this reboot 3174 * @throws DeviceNotAvailableException 3175 * @throws UnsupportedOperationException 3176 */ 3177 @VisibleForTesting doReboot(RebootMode rebootMode, @Nullable final String reason)3178 void doReboot(RebootMode rebootMode, @Nullable final String reason) 3179 throws DeviceNotAvailableException, UnsupportedOperationException { 3180 // Track Tradefed reboot time 3181 mLastTradefedRebootTime = System.currentTimeMillis(); 3182 3183 if (isStateBootloaderOrFastbootd()) { 3184 CLog.i("device %s in %s. Rebooting to userspace.", getSerialNumber(), getDeviceState()); 3185 executeFastbootCommand("reboot"); 3186 } else { 3187 if (mOptions.shouldDisableReboot()) { 3188 CLog.i("Device reboot disabled by options, skipped."); 3189 return; 3190 } 3191 if (reason == null) { 3192 CLog.i("Rebooting device %s mode: %s", getSerialNumber(), rebootMode.name()); 3193 } else { 3194 CLog.i( 3195 "Rebooting device %s mode: %s reason: %s", 3196 getSerialNumber(), rebootMode.name(), reason); 3197 } 3198 doAdbReboot(rebootMode, reason); 3199 // Check if device shows as unavailable (as expected after reboot). 3200 boolean notAvailable = waitForDeviceNotAvailable(DEFAULT_UNAVAILABLE_TIMEOUT); 3201 if (notAvailable) { 3202 postAdbReboot(); 3203 } else { 3204 CLog.w( 3205 "Did not detect device %s becoming unavailable after reboot", 3206 getSerialNumber()); 3207 } 3208 } 3209 } 3210 3211 /** 3212 * Possible extra actions that can be taken after a reboot. 3213 * 3214 * @throws DeviceNotAvailableException 3215 */ postAdbReboot()3216 protected void postAdbReboot() throws DeviceNotAvailableException { 3217 // Default implementation empty on purpose. 3218 } 3219 3220 /** 3221 * Perform a adb reboot. 3222 * 3223 * @param rebootMode a mode of this reboot. 3224 * @param reason for this reboot. 3225 * @throws DeviceNotAvailableException 3226 */ doAdbReboot(RebootMode rebootMode, @Nullable final String reason)3227 protected void doAdbReboot(RebootMode rebootMode, @Nullable final String reason) 3228 throws DeviceNotAvailableException { 3229 DeviceAction rebootAction = createRebootDeviceAction(rebootMode, reason); 3230 performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS); 3231 } 3232 3233 /** 3234 * Create a {@link RebootDeviceAction} to be used when performing a reboot action. 3235 * 3236 * @param rebootMode a mode of this reboot. 3237 * @param reason for this reboot. 3238 * @return the created {@link RebootDeviceAction}. 3239 */ createRebootDeviceAction( RebootMode rebootMode, @Nullable final String reason)3240 protected RebootDeviceAction createRebootDeviceAction( 3241 RebootMode rebootMode, @Nullable final String reason) { 3242 return new RebootDeviceAction(rebootMode, reason); 3243 } 3244 waitForDeviceNotAvailable(String operationDesc, long time)3245 protected void waitForDeviceNotAvailable(String operationDesc, long time) { 3246 // TODO: a bit of a race condition here. Would be better to start a 3247 // before the operation 3248 if (!mStateMonitor.waitForDeviceNotAvailable(time)) { 3249 // above check is flaky, ignore till better solution is found 3250 CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(), 3251 operationDesc); 3252 } 3253 } 3254 3255 /** 3256 * {@inheritDoc} 3257 */ 3258 @Override enableAdbRoot()3259 public boolean enableAdbRoot() throws DeviceNotAvailableException { 3260 // adb root is a relatively intensive command, so do a brief check first to see 3261 // if its necessary or not 3262 if (isAdbRoot()) { 3263 CLog.i("adb is already running as root on %s", getSerialNumber()); 3264 // Still check for online, in some case we could see the root, but device could be 3265 // very early in its cycle. 3266 waitForDeviceOnline(); 3267 return true; 3268 } 3269 // Don't enable root if user requested no root 3270 if (!isEnableAdbRoot()) { 3271 CLog.i("\"enable-root\" set to false; ignoring 'adb root' request"); 3272 return false; 3273 } 3274 CLog.i("adb root on device %s", getSerialNumber()); 3275 int attempts = MAX_RETRY_ATTEMPTS + 1; 3276 for (int i=1; i <= attempts; i++) { 3277 String output = executeAdbCommand("root"); 3278 // wait for device to disappear from adb 3279 waitForDeviceNotAvailable("root", 20 * 1000); 3280 3281 postAdbRootAction(); 3282 3283 // wait for device to be back online 3284 waitForDeviceOnline(); 3285 3286 if (isAdbRoot()) { 3287 return true; 3288 } 3289 CLog.w("'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'", 3290 getSerialNumber(), i, attempts, output); 3291 } 3292 return false; 3293 } 3294 3295 /** 3296 * {@inheritDoc} 3297 */ 3298 @Override disableAdbRoot()3299 public boolean disableAdbRoot() throws DeviceNotAvailableException { 3300 if (!isAdbRoot()) { 3301 CLog.i("adb is already unroot on %s", getSerialNumber()); 3302 return true; 3303 } 3304 3305 CLog.i("adb unroot on device %s", getSerialNumber()); 3306 int attempts = MAX_RETRY_ATTEMPTS + 1; 3307 for (int i=1; i <= attempts; i++) { 3308 String output = executeAdbCommand("unroot"); 3309 // wait for device to disappear from adb 3310 waitForDeviceNotAvailable("unroot", 5 * 1000); 3311 3312 postAdbUnrootAction(); 3313 3314 // wait for device to be back online 3315 waitForDeviceOnline(); 3316 3317 if (!isAdbRoot()) { 3318 return true; 3319 } 3320 CLog.w("'adb unroot' on %s unsuccessful on attempt %d of %d. Output: '%s'", 3321 getSerialNumber(), i, attempts, output); 3322 } 3323 return false; 3324 } 3325 3326 /** 3327 * Override if the device needs some specific actions to be taken after adb root and before the 3328 * device is back online. 3329 * Default implementation doesn't include any addition actions. 3330 * adb root is not guaranteed to be enabled at this stage. 3331 * @throws DeviceNotAvailableException 3332 */ postAdbRootAction()3333 public void postAdbRootAction() throws DeviceNotAvailableException { 3334 // Empty on purpose. 3335 } 3336 3337 /** 3338 * Override if the device needs some specific actions to be taken after adb unroot and before 3339 * the device is back online. 3340 * Default implementation doesn't include any additional actions. 3341 * adb root is not guaranteed to be disabled at this stage. 3342 * @throws DeviceNotAvailableException 3343 */ postAdbUnrootAction()3344 public void postAdbUnrootAction() throws DeviceNotAvailableException { 3345 // Empty on purpose. 3346 } 3347 3348 /** 3349 * {@inheritDoc} 3350 */ 3351 @Override isAdbRoot()3352 public boolean isAdbRoot() throws DeviceNotAvailableException { 3353 String output = executeShellCommand("id"); 3354 return output.contains("uid=0(root)"); 3355 } 3356 3357 /** 3358 * {@inheritDoc} 3359 */ 3360 @Override encryptDevice(boolean inplace)3361 public boolean encryptDevice(boolean inplace) throws DeviceNotAvailableException, 3362 UnsupportedOperationException { 3363 if (!isEncryptionSupported()) { 3364 throw new UnsupportedOperationException(String.format("Can't encrypt device %s: " 3365 + "encryption not supported", getSerialNumber())); 3366 } 3367 3368 if (isDeviceEncrypted()) { 3369 CLog.d("Device %s is already encrypted, skipping", getSerialNumber()); 3370 return true; 3371 } 3372 3373 enableAdbRoot(); 3374 3375 String encryptMethod; 3376 long timeout; 3377 if (inplace) { 3378 encryptMethod = "inplace"; 3379 timeout = ENCRYPTION_INPLACE_TIMEOUT_MIN; 3380 } else { 3381 encryptMethod = "wipe"; 3382 timeout = ENCRYPTION_WIPE_TIMEOUT_MIN; 3383 } 3384 3385 CLog.i("Encrypting device %s via %s", getSerialNumber(), encryptMethod); 3386 3387 // enable crypto takes one of the following formats: 3388 // cryptfs enablecrypto <wipe|inplace> <passwd> 3389 // cryptfs enablecrypto <wipe|inplace> default|password|pin|pattern [passwd] 3390 // Try the first one first, if it outputs "500 0 Usage: ...", try the second. 3391 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 3392 String command = String.format("vdc cryptfs enablecrypto %s \"%s\"", encryptMethod, 3393 ENCRYPTION_PASSWORD); 3394 executeShellCommand(command, receiver, timeout, TimeUnit.MINUTES, 1); 3395 if (receiver.getOutput().split(":")[0].matches("500 \\d+ Usage")) { 3396 command = String.format("vdc cryptfs enablecrypto %s default", encryptMethod); 3397 executeShellCommand(command, new NullOutputReceiver(), timeout, TimeUnit.MINUTES, 1); 3398 } 3399 3400 waitForDeviceNotAvailable("reboot", getCommandTimeout()); 3401 waitForDeviceOnline(); // Device will not become available until the user data is unlocked. 3402 3403 return isDeviceEncrypted(); 3404 } 3405 3406 /** 3407 * {@inheritDoc} 3408 */ 3409 @Override unencryptDevice()3410 public boolean unencryptDevice() throws DeviceNotAvailableException, 3411 UnsupportedOperationException { 3412 if (!isEncryptionSupported()) { 3413 throw new UnsupportedOperationException(String.format("Can't unencrypt device %s: " 3414 + "encryption not supported", getSerialNumber())); 3415 } 3416 3417 if (!isDeviceEncrypted()) { 3418 CLog.d("Device %s is already unencrypted, skipping", getSerialNumber()); 3419 return true; 3420 } 3421 3422 CLog.i("Unencrypting device %s", getSerialNumber()); 3423 3424 // If the device supports fastboot format, then we're done. 3425 if (!mOptions.getUseFastbootErase()) { 3426 rebootIntoBootloader(); 3427 fastbootWipePartition("userdata"); 3428 rebootUntilOnline(); 3429 waitForDeviceAvailable(ENCRYPTION_WIPE_TIMEOUT_MIN * 60 * 1000); 3430 return true; 3431 } 3432 3433 // Determine if we need to format partition instead of wipe. 3434 boolean format = false; 3435 String output = executeShellCommand("vdc volume list"); 3436 String[] splitOutput; 3437 if (output != null) { 3438 splitOutput = output.split("\r?\n"); 3439 for (String line : splitOutput) { 3440 if (line.startsWith("110 ") && line.contains("sdcard /mnt/sdcard") && 3441 !line.endsWith("0")) { 3442 format = true; 3443 } 3444 } 3445 } 3446 3447 rebootIntoBootloader(); 3448 fastbootWipePartition("userdata"); 3449 3450 // If the device requires time to format the filesystem after fastboot erase userdata, wait 3451 // for the device to reboot a second time. 3452 if (mOptions.getUnencryptRebootTimeout() > 0) { 3453 rebootUntilOnline(); 3454 if (waitForDeviceNotAvailable(mOptions.getUnencryptRebootTimeout())) { 3455 waitForDeviceOnline(); 3456 } 3457 } 3458 3459 if (format) { 3460 CLog.d("Need to format sdcard for device %s", getSerialNumber()); 3461 3462 RecoveryMode cachedRecoveryMode = getRecoveryMode(); 3463 setRecoveryMode(RecoveryMode.ONLINE); 3464 3465 output = executeShellCommand("vdc volume format sdcard"); 3466 if (output == null) { 3467 CLog.e("Command vdc volume format sdcard failed will no output for device %s:\n%s", 3468 getSerialNumber()); 3469 setRecoveryMode(cachedRecoveryMode); 3470 return false; 3471 } 3472 splitOutput = output.split("\r?\n"); 3473 if (!splitOutput[splitOutput.length - 1].startsWith("200 ")) { 3474 CLog.e("Command vdc volume format sdcard failed for device %s:\n%s", 3475 getSerialNumber(), output); 3476 setRecoveryMode(cachedRecoveryMode); 3477 return false; 3478 } 3479 3480 setRecoveryMode(cachedRecoveryMode); 3481 } 3482 3483 reboot(); 3484 3485 return true; 3486 } 3487 3488 /** 3489 * {@inheritDoc} 3490 */ 3491 @Override unlockDevice()3492 public boolean unlockDevice() throws DeviceNotAvailableException, 3493 UnsupportedOperationException { 3494 if (!isEncryptionSupported()) { 3495 throw new UnsupportedOperationException(String.format("Can't unlock device %s: " 3496 + "encryption not supported", getSerialNumber())); 3497 } 3498 3499 if (!isDeviceEncrypted()) { 3500 CLog.d("Device %s is not encrypted, skipping", getSerialNumber()); 3501 return true; 3502 } 3503 3504 CLog.i("Unlocking device %s", getSerialNumber()); 3505 3506 enableAdbRoot(); 3507 3508 // FIXME: currently, vcd checkpw can return an empty string when it never should. Try 3 3509 // times. 3510 String output; 3511 int i = 0; 3512 do { 3513 // Enter the password. Output will be: 3514 // "200 [X] -1" if the password has already been entered correctly, 3515 // "200 [X] 0" if the password is entered correctly, 3516 // "200 [X] N" where N is any positive number if the password is incorrect, 3517 // any other string if there is an error. 3518 output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"", 3519 ENCRYPTION_PASSWORD)).trim(); 3520 3521 if (output.startsWith("200 ") && output.endsWith(" -1")) { 3522 return true; 3523 } 3524 3525 if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) { 3526 CLog.e("checkpw gave output '%s' while trying to unlock device %s", 3527 output, getSerialNumber()); 3528 return false; 3529 } 3530 3531 getRunUtil().sleep(500); 3532 } while (output.isEmpty() && ++i < 3); 3533 3534 if (output.isEmpty()) { 3535 CLog.e("checkpw gave no output while trying to unlock device %s"); 3536 } 3537 3538 // Restart the framework. Output will be: 3539 // "200 [X] 0" if the user data partition can be mounted, 3540 // "200 [X] -1" if the user data partition can not be mounted (no correct password given), 3541 // any other string if there is an error. 3542 output = executeShellCommand("vdc cryptfs restart").trim(); 3543 3544 if (!(output.startsWith("200 ") && output.endsWith(" 0"))) { 3545 CLog.e("restart gave output '%s' while trying to unlock device %s", output, 3546 getSerialNumber()); 3547 return false; 3548 } 3549 3550 waitForDeviceAvailable(); 3551 3552 return true; 3553 } 3554 3555 /** 3556 * {@inheritDoc} 3557 */ 3558 @Override isDeviceEncrypted()3559 public boolean isDeviceEncrypted() throws DeviceNotAvailableException { 3560 String output = getProperty("ro.crypto.state"); 3561 3562 if (output == null && isEncryptionSupported()) { 3563 CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber()); 3564 } 3565 if (output == null) { 3566 return false; 3567 } 3568 return "encrypted".equals(output.trim()); 3569 } 3570 3571 /** 3572 * {@inheritDoc} 3573 */ 3574 @Override isEncryptionSupported()3575 public boolean isEncryptionSupported() throws DeviceNotAvailableException { 3576 if (!isEnableAdbRoot()) { 3577 CLog.i("root is required for encryption"); 3578 mIsEncryptionSupported = false; 3579 return mIsEncryptionSupported; 3580 } 3581 if (mIsEncryptionSupported != null) { 3582 return mIsEncryptionSupported.booleanValue(); 3583 } 3584 enableAdbRoot(); 3585 3586 String output = getProperty("ro.crypto.state"); 3587 if (output == null || "unsupported".equals(output.trim())) { 3588 mIsEncryptionSupported = false; 3589 return mIsEncryptionSupported; 3590 } 3591 mIsEncryptionSupported = true; 3592 return mIsEncryptionSupported; 3593 } 3594 3595 /** 3596 * {@inheritDoc} 3597 */ 3598 @Override waitForDeviceOnline(long waitTime)3599 public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException { 3600 if (mStateMonitor.waitForDeviceOnline(waitTime) == null) { 3601 recoverDevice(); 3602 } 3603 } 3604 3605 /** 3606 * {@inheritDoc} 3607 */ 3608 @Override waitForDeviceOnline()3609 public void waitForDeviceOnline() throws DeviceNotAvailableException { 3610 if (mStateMonitor.waitForDeviceOnline() == null) { 3611 recoverDevice(); 3612 } 3613 } 3614 3615 /** 3616 * {@inheritDoc} 3617 */ 3618 @Override waitForDeviceAvailable(long waitTime)3619 public void waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException { 3620 if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) { 3621 recoverDevice(); 3622 } 3623 } 3624 3625 /** 3626 * {@inheritDoc} 3627 */ 3628 @Override waitForDeviceAvailable()3629 public void waitForDeviceAvailable() throws DeviceNotAvailableException { 3630 if (mStateMonitor.waitForDeviceAvailable() == null) { 3631 recoverDevice(); 3632 } 3633 } 3634 3635 /** 3636 * {@inheritDoc} 3637 */ 3638 @Override waitForDeviceNotAvailable(long waitTime)3639 public boolean waitForDeviceNotAvailable(long waitTime) { 3640 return mStateMonitor.waitForDeviceNotAvailable(waitTime); 3641 } 3642 3643 /** 3644 * {@inheritDoc} 3645 */ 3646 @Override waitForDeviceInRecovery(long waitTime)3647 public boolean waitForDeviceInRecovery(long waitTime) { 3648 return mStateMonitor.waitForDeviceInRecovery(waitTime); 3649 } 3650 3651 /** {@inheritDoc} */ 3652 @Override waitForDeviceInSideload(long waitTime)3653 public boolean waitForDeviceInSideload(long waitTime) { 3654 return mStateMonitor.waitForDeviceInSideload(waitTime); 3655 } 3656 3657 /** 3658 * Small helper function to throw an NPE if the passed arg is null. This should be used when 3659 * some value will be stored and used later, in which case it'll avoid hard-to-trace 3660 * asynchronous NullPointerExceptions by throwing the exception synchronously. This is not 3661 * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care 3662 * of it in that case. 3663 */ throwIfNull(Object obj)3664 private void throwIfNull(Object obj) { 3665 if (obj == null) throw new NullPointerException(); 3666 } 3667 3668 /** Retrieve this device's recovery mechanism. */ 3669 @VisibleForTesting getRecovery()3670 IDeviceRecovery getRecovery() { 3671 return mRecovery; 3672 } 3673 3674 /** 3675 * {@inheritDoc} 3676 */ 3677 @Override setRecovery(IDeviceRecovery recovery)3678 public void setRecovery(IDeviceRecovery recovery) { 3679 throwIfNull(recovery); 3680 mRecovery = recovery; 3681 } 3682 3683 /** 3684 * {@inheritDoc} 3685 */ 3686 @Override setRecoveryMode(RecoveryMode mode)3687 public void setRecoveryMode(RecoveryMode mode) { 3688 throwIfNull(mRecoveryMode); 3689 mRecoveryMode = mode; 3690 } 3691 3692 /** 3693 * {@inheritDoc} 3694 */ 3695 @Override getRecoveryMode()3696 public RecoveryMode getRecoveryMode() { 3697 return mRecoveryMode; 3698 } 3699 3700 /** 3701 * {@inheritDoc} 3702 */ 3703 @Override setFastbootEnabled(boolean fastbootEnabled)3704 public void setFastbootEnabled(boolean fastbootEnabled) { 3705 mFastbootEnabled = fastbootEnabled; 3706 } 3707 3708 /** 3709 * {@inheritDoc} 3710 */ 3711 @Override isFastbootEnabled()3712 public boolean isFastbootEnabled() { 3713 return mFastbootEnabled; 3714 } 3715 3716 /** 3717 * {@inheritDoc} 3718 */ 3719 @Override setFastbootPath(String fastbootPath)3720 public void setFastbootPath(String fastbootPath) { 3721 mFastbootPath = fastbootPath; 3722 // ensure the device and its associated recovery use the same fastboot version. 3723 mRecovery.setFastbootPath(fastbootPath); 3724 } 3725 3726 /** 3727 * {@inheritDoc} 3728 */ 3729 @Override getFastbootPath()3730 public String getFastbootPath() { 3731 return mFastbootPath; 3732 } 3733 3734 /** {@inheritDoc} */ 3735 @Override getFastbootVersion()3736 public String getFastbootVersion() { 3737 try { 3738 CommandResult res = executeFastbootCommand("--version"); 3739 return res.getStdout().trim(); 3740 } catch (DeviceNotAvailableException e) { 3741 // Ignored for host side request 3742 } 3743 return null; 3744 } 3745 3746 /** 3747 * {@inheritDoc} 3748 */ 3749 @Override setDeviceState(final TestDeviceState deviceState)3750 public void setDeviceState(final TestDeviceState deviceState) { 3751 if (!deviceState.equals(getDeviceState())) { 3752 // disable state changes while fastboot lock is held, because issuing fastboot command 3753 // will disrupt state 3754 if (isStateBootloaderOrFastbootd() && mFastbootLock.isLocked()) { 3755 return; 3756 } 3757 mState = deviceState; 3758 CLog.d("Device %s state is now %s", getSerialNumber(), deviceState); 3759 mStateMonitor.setState(deviceState); 3760 } 3761 } 3762 3763 /** 3764 * {@inheritDoc} 3765 */ 3766 @Override getDeviceState()3767 public TestDeviceState getDeviceState() { 3768 return mState; 3769 } 3770 3771 @Override isAdbTcp()3772 public boolean isAdbTcp() { 3773 return mStateMonitor.isAdbTcp(); 3774 } 3775 3776 /** 3777 * {@inheritDoc} 3778 */ 3779 @Override switchToAdbTcp()3780 public String switchToAdbTcp() throws DeviceNotAvailableException { 3781 String ipAddress = getIpAddress(); 3782 if (ipAddress == null) { 3783 CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber()); 3784 return null; 3785 } 3786 String port = "5555"; 3787 executeAdbCommand("tcpip", port); 3788 // TODO: analyze result? wait for device offline? 3789 return String.format("%s:%s", ipAddress, port); 3790 } 3791 3792 /** 3793 * {@inheritDoc} 3794 */ 3795 @Override switchToAdbUsb()3796 public boolean switchToAdbUsb() throws DeviceNotAvailableException { 3797 executeAdbCommand("usb"); 3798 // TODO: analyze result? wait for device offline? 3799 return true; 3800 } 3801 3802 /** 3803 * {@inheritDoc} 3804 */ 3805 @Override setEmulatorProcess(Process p)3806 public void setEmulatorProcess(Process p) { 3807 mEmulatorProcess = p; 3808 3809 } 3810 3811 /** 3812 * For emulator set {@link SizeLimitedOutputStream} to log output 3813 * @param output to log the output 3814 */ setEmulatorOutputStream(SizeLimitedOutputStream output)3815 public void setEmulatorOutputStream(SizeLimitedOutputStream output) { 3816 mEmulatorOutput = output; 3817 } 3818 3819 /** 3820 * {@inheritDoc} 3821 */ 3822 @Override stopEmulatorOutput()3823 public void stopEmulatorOutput() { 3824 if (mEmulatorOutput != null) { 3825 mEmulatorOutput.delete(); 3826 mEmulatorOutput = null; 3827 } 3828 } 3829 3830 /** 3831 * {@inheritDoc} 3832 */ 3833 @Override getEmulatorOutput()3834 public InputStreamSource getEmulatorOutput() { 3835 if (getIDevice().isEmulator()) { 3836 if (mEmulatorOutput == null) { 3837 CLog.w("Emulator output for %s was not captured in background", 3838 getSerialNumber()); 3839 } else { 3840 try { 3841 return new SnapshotInputStreamSource( 3842 "getEmulatorOutput", mEmulatorOutput.getData()); 3843 } catch (IOException e) { 3844 CLog.e("Failed to get %s data.", getSerialNumber()); 3845 CLog.e(e); 3846 } 3847 } 3848 } 3849 return new ByteArrayInputStreamSource(new byte[0]); 3850 } 3851 3852 /** 3853 * {@inheritDoc} 3854 */ 3855 @Override getEmulatorProcess()3856 public Process getEmulatorProcess() { 3857 return mEmulatorProcess; 3858 } 3859 3860 /** 3861 * @return <code>true</code> if adb root should be enabled on device 3862 */ isEnableAdbRoot()3863 public boolean isEnableAdbRoot() { 3864 return mOptions.isEnableAdbRoot(); 3865 } 3866 3867 /** 3868 * {@inheritDoc} 3869 */ 3870 @Override getInstalledPackageNames()3871 public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException { 3872 throw new UnsupportedOperationException("No support for Package's feature"); 3873 } 3874 3875 /** {@inheritDoc} */ 3876 @Override isPackageInstalled(String packageName)3877 public boolean isPackageInstalled(String packageName) throws DeviceNotAvailableException { 3878 throw new UnsupportedOperationException("No support for Package's feature"); 3879 } 3880 3881 /** {@inheritDoc} */ 3882 @Override isPackageInstalled(String packageName, String userId)3883 public boolean isPackageInstalled(String packageName, String userId) 3884 throws DeviceNotAvailableException { 3885 throw new UnsupportedOperationException("No support for Package's feature"); 3886 } 3887 3888 /** {@inheritDoc} */ 3889 @Override getActiveApexes()3890 public Set<ApexInfo> getActiveApexes() throws DeviceNotAvailableException { 3891 throw new UnsupportedOperationException("No support for Package's feature"); 3892 } 3893 3894 /** {@inheritDoc} */ 3895 @Override getAppPackageInfos()3896 public List<PackageInfo> getAppPackageInfos() throws DeviceNotAvailableException { 3897 throw new UnsupportedOperationException("No support for Package's feature"); 3898 } 3899 3900 /** 3901 * {@inheritDoc} 3902 */ 3903 @Override getUninstallablePackageNames()3904 public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException { 3905 throw new UnsupportedOperationException("No support for Package's feature"); 3906 } 3907 3908 /** 3909 * {@inheritDoc} 3910 */ 3911 @Override getAppPackageInfo(String packageName)3912 public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException { 3913 throw new UnsupportedOperationException("No support for Package's feature"); 3914 } 3915 3916 /** 3917 * {@inheritDoc} 3918 */ 3919 @Override getOptions()3920 public TestDeviceOptions getOptions() { 3921 return mOptions; 3922 } 3923 3924 /** 3925 * {@inheritDoc} 3926 */ 3927 @Override getApiLevel()3928 public int getApiLevel() throws DeviceNotAvailableException { 3929 int apiLevel = UNKNOWN_API_LEVEL; 3930 try { 3931 String prop = getProperty(DeviceProperties.SDK_VERSION); 3932 apiLevel = Integer.parseInt(prop); 3933 } catch (NumberFormatException nfe) { 3934 CLog.w( 3935 "Unable to get API level from " 3936 + DeviceProperties.SDK_VERSION 3937 + ", falling back to UNKNOWN.", 3938 nfe); 3939 // ignore, return unknown instead 3940 } 3941 return apiLevel; 3942 } 3943 3944 /** {@inheritDoc} */ 3945 @Override checkApiLevelAgainstNextRelease(int strictMinLevel)3946 public boolean checkApiLevelAgainstNextRelease(int strictMinLevel) 3947 throws DeviceNotAvailableException { 3948 String codeName = getProperty(DeviceProperties.BUILD_CODENAME); 3949 if (codeName == null) { 3950 throw new DeviceRuntimeException( 3951 String.format( 3952 "Failed to query property '%s'. device returned null.", 3953 DeviceProperties.BUILD_CODENAME)); 3954 } 3955 codeName = codeName.trim(); 3956 int apiLevel = getApiLevel() + ("REL".equals(codeName) ? 0 : 1); 3957 if (strictMinLevel > apiLevel) { 3958 return false; 3959 } 3960 return true; 3961 } 3962 getApiLevelSafe()3963 private int getApiLevelSafe() { 3964 try { 3965 return getApiLevel(); 3966 } catch (DeviceNotAvailableException e) { 3967 CLog.e(e); 3968 return UNKNOWN_API_LEVEL; 3969 } 3970 } 3971 3972 /** {@inheritDoc} */ 3973 @Override getLaunchApiLevel()3974 public int getLaunchApiLevel() throws DeviceNotAvailableException { 3975 try { 3976 String prop = getProperty(DeviceProperties.FIRST_API_LEVEL); 3977 return Integer.parseInt(prop); 3978 } catch (NumberFormatException nfe) { 3979 CLog.w( 3980 "Unable to get first launch API level from " 3981 + DeviceProperties.FIRST_API_LEVEL 3982 + ", falling back to getApiLevel().", 3983 nfe); 3984 } 3985 return getApiLevel(); 3986 } 3987 3988 @Override getMonitor()3989 public IDeviceStateMonitor getMonitor() { 3990 return mStateMonitor; 3991 } 3992 3993 /** 3994 * {@inheritDoc} 3995 */ 3996 @Override waitForDeviceShell(long waitTime)3997 public boolean waitForDeviceShell(long waitTime) { 3998 return mStateMonitor.waitForDeviceShell(waitTime); 3999 } 4000 4001 @Override getAllocationState()4002 public DeviceAllocationState getAllocationState() { 4003 return mAllocationState; 4004 } 4005 4006 /** 4007 * {@inheritDoc} 4008 * <p> 4009 * Process the DeviceEvent, which may or may not transition this device to a new allocation 4010 * state. 4011 * </p> 4012 */ 4013 @Override handleAllocationEvent(DeviceEvent event)4014 public DeviceEventResponse handleAllocationEvent(DeviceEvent event) { 4015 4016 // keep track of whether state has actually changed or not 4017 boolean stateChanged = false; 4018 DeviceAllocationState newState; 4019 DeviceAllocationState oldState = mAllocationState; 4020 mAllocationStateLock.lock(); 4021 try { 4022 // update oldState here, just in case in changed before we got lock 4023 oldState = mAllocationState; 4024 newState = mAllocationState.handleDeviceEvent(event); 4025 if (oldState != newState) { 4026 // state has changed! record this fact, and store the new state 4027 stateChanged = true; 4028 mAllocationState = newState; 4029 } 4030 } finally { 4031 mAllocationStateLock.unlock(); 4032 } 4033 if (stateChanged && mAllocationMonitor != null) { 4034 // state has changed! Lets inform the allocation monitor listener 4035 mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState); 4036 } 4037 return new DeviceEventResponse(newState, stateChanged); 4038 } 4039 4040 /** {@inheritDoc} */ 4041 @Override getDeviceTimeOffset(Date date)4042 public long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException { 4043 Long deviceTime = getDeviceDate(); 4044 long offset = 0; 4045 4046 if (date == null) { 4047 date = new Date(); 4048 } 4049 4050 offset = date.getTime() - deviceTime; 4051 CLog.d("Time offset = %d ms", offset); 4052 return offset; 4053 } 4054 4055 /** 4056 * {@inheritDoc} 4057 */ 4058 @Override setDate(Date date)4059 public void setDate(Date date) throws DeviceNotAvailableException { 4060 if (date == null) { 4061 date = new Date(); 4062 } 4063 long timeOffset = getDeviceTimeOffset(date); 4064 // no need to set date 4065 if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) { 4066 return; 4067 } 4068 String dateString = null; 4069 if (getApiLevel() < 23) { 4070 // set date in epoch format 4071 dateString = Long.toString(date.getTime() / 1000); //ms to s 4072 } else { 4073 // set date with POSIX like params 4074 SimpleDateFormat sdf = new java.text.SimpleDateFormat( 4075 "MMddHHmmyyyy.ss"); 4076 sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); 4077 dateString = sdf.format(date); 4078 } 4079 // best effort, no verification 4080 // Use TZ= to default to UTC timezone (b/128353510 for background) 4081 executeShellCommand("TZ=UTC date -u " + dateString); 4082 } 4083 4084 /** {@inheritDoc} */ 4085 @Override getDeviceDate()4086 public long getDeviceDate() throws DeviceNotAvailableException { 4087 String deviceTimeString = executeShellCommand("date +%s"); 4088 Long deviceTime = null; 4089 try { 4090 deviceTime = Long.valueOf(deviceTimeString.trim()); 4091 } catch (NumberFormatException nfe) { 4092 CLog.i("Invalid device time: \"%s\", ignored.", nfe); 4093 return 0; 4094 } 4095 // Convert from seconds to milliseconds 4096 return deviceTime * 1000L; 4097 } 4098 4099 /** 4100 * {@inheritDoc} 4101 */ 4102 @Override waitForBootComplete(long timeOut)4103 public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException { 4104 return mStateMonitor.waitForBootComplete(timeOut); 4105 } 4106 4107 /** 4108 * {@inheritDoc} 4109 */ 4110 @Override listUsers()4111 public ArrayList<Integer> listUsers() throws DeviceNotAvailableException { 4112 throw new UnsupportedOperationException("No support for user's feature."); 4113 } 4114 4115 /** {@inheritDoc} */ 4116 @Override getUserInfos()4117 public Map<Integer, UserInfo> getUserInfos() throws DeviceNotAvailableException { 4118 throw new UnsupportedOperationException("No support for user's feature."); 4119 } 4120 4121 /** 4122 * {@inheritDoc} 4123 */ 4124 @Override getMaxNumberOfUsersSupported()4125 public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException { 4126 throw new UnsupportedOperationException("No support for user's feature."); 4127 } 4128 4129 @Override getMaxNumberOfRunningUsersSupported()4130 public int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException { 4131 throw new UnsupportedOperationException("No support for user's feature."); 4132 } 4133 4134 /** 4135 * {@inheritDoc} 4136 */ 4137 @Override isMultiUserSupported()4138 public boolean isMultiUserSupported() throws DeviceNotAvailableException { 4139 throw new UnsupportedOperationException("No support for user's feature."); 4140 } 4141 4142 /** {@inheritDoc} */ 4143 @Override createUserNoThrow(String name)4144 public int createUserNoThrow(String name) throws DeviceNotAvailableException { 4145 throw new UnsupportedOperationException("No support for user's feature."); 4146 } 4147 4148 /** 4149 * {@inheritDoc} 4150 */ 4151 @Override createUser(String name)4152 public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException { 4153 throw new UnsupportedOperationException("No support for user's feature."); 4154 } 4155 4156 /** 4157 * {@inheritDoc} 4158 */ 4159 @Override createUser(String name, boolean guest, boolean ephemeral)4160 public int createUser(String name, boolean guest, boolean ephemeral) 4161 throws DeviceNotAvailableException, IllegalStateException { 4162 throw new UnsupportedOperationException("No support for user's feature."); 4163 } 4164 4165 /** 4166 * {@inheritDoc} 4167 */ 4168 @Override removeUser(int userId)4169 public boolean removeUser(int userId) throws DeviceNotAvailableException { 4170 throw new UnsupportedOperationException("No support for user's feature."); 4171 } 4172 4173 /** 4174 * {@inheritDoc} 4175 */ 4176 @Override startUser(int userId)4177 public boolean startUser(int userId) throws DeviceNotAvailableException { 4178 throw new UnsupportedOperationException("No support for user's feature."); 4179 } 4180 4181 /** {@inheritDoc} */ 4182 @Override startUser(int userId, boolean waitFlag)4183 public boolean startUser(int userId, boolean waitFlag) throws DeviceNotAvailableException { 4184 throw new UnsupportedOperationException("No support for user's feature."); 4185 } 4186 4187 /** 4188 * {@inheritDoc} 4189 */ 4190 @Override stopUser(int userId)4191 public boolean stopUser(int userId) throws DeviceNotAvailableException { 4192 throw new UnsupportedOperationException("No support for user's feature."); 4193 } 4194 4195 /** 4196 * {@inheritDoc} 4197 */ 4198 @Override stopUser(int userId, boolean waitFlag, boolean forceFlag)4199 public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag) 4200 throws DeviceNotAvailableException { 4201 throw new UnsupportedOperationException("No support for user's feature."); 4202 } 4203 4204 /** 4205 * {@inheritDoc} 4206 */ 4207 @Override remountSystemWritable()4208 public void remountSystemWritable() throws DeviceNotAvailableException { 4209 String verity = getProperty("partition.system.verified"); 4210 // have the property set (regardless state) implies verity is enabled, so we send adb 4211 // command to disable verity 4212 if (verity != null && !verity.isEmpty()) { 4213 executeAdbCommand("disable-verity"); 4214 reboot(); 4215 } 4216 executeAdbCommand("remount"); 4217 waitForDeviceAvailable(); 4218 } 4219 4220 /** {@inheritDoc} */ 4221 @Override remountVendorWritable()4222 public void remountVendorWritable() throws DeviceNotAvailableException { 4223 String verity = getProperty("partition.vendor.verified"); 4224 // have the property set (regardless state) implies verity is enabled, so we send adb 4225 // command to disable verity 4226 if (verity != null && !verity.isEmpty()) { 4227 executeAdbCommand("disable-verity"); 4228 reboot(); 4229 } 4230 executeAdbCommand("remount"); 4231 waitForDeviceAvailable(); 4232 } 4233 4234 /** 4235 * {@inheritDoc} 4236 */ 4237 @Override getPrimaryUserId()4238 public Integer getPrimaryUserId() throws DeviceNotAvailableException { 4239 throw new UnsupportedOperationException("No support for user's feature."); 4240 } 4241 4242 /** 4243 * {@inheritDoc} 4244 */ 4245 @Override getCurrentUser()4246 public int getCurrentUser() throws DeviceNotAvailableException { 4247 throw new UnsupportedOperationException("No support for user's feature."); 4248 } 4249 4250 /** {@inheritDoc} */ 4251 @Override isUserSecondary(int userId)4252 public boolean isUserSecondary(int userId) throws DeviceNotAvailableException { 4253 throw new UnsupportedOperationException("No support for user's feature."); 4254 } 4255 4256 4257 /** 4258 * {@inheritDoc} 4259 */ 4260 @Override getUserFlags(int userId)4261 public int getUserFlags(int userId) throws DeviceNotAvailableException { 4262 throw new UnsupportedOperationException("No support for user's feature."); 4263 } 4264 4265 /** 4266 * {@inheritDoc} 4267 */ 4268 @Override getUserSerialNumber(int userId)4269 public int getUserSerialNumber(int userId) throws DeviceNotAvailableException { 4270 throw new UnsupportedOperationException("No support for user's feature."); 4271 } 4272 4273 /** 4274 * {@inheritDoc} 4275 */ 4276 @Override switchUser(int userId)4277 public boolean switchUser(int userId) throws DeviceNotAvailableException { 4278 throw new UnsupportedOperationException("No support for user's feature."); 4279 } 4280 4281 /** 4282 * {@inheritDoc} 4283 */ 4284 @Override switchUser(int userId, long timeout)4285 public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException { 4286 throw new UnsupportedOperationException("No support for user's feature."); 4287 } 4288 4289 /** 4290 * {@inheritDoc} 4291 */ 4292 @Override isUserRunning(int userId)4293 public boolean isUserRunning(int userId) throws DeviceNotAvailableException { 4294 throw new UnsupportedOperationException("No support for user's feature."); 4295 } 4296 4297 /** 4298 * {@inheritDoc} 4299 */ 4300 @Override hasFeature(String feature)4301 public boolean hasFeature(String feature) throws DeviceNotAvailableException { 4302 throw new UnsupportedOperationException("No support pm's features."); 4303 } 4304 4305 /** 4306 * {@inheritDoc} 4307 */ 4308 @Override getSetting(String namespace, String key)4309 public String getSetting(String namespace, String key) 4310 throws DeviceNotAvailableException { 4311 throw new UnsupportedOperationException("No support for setting's feature."); 4312 } 4313 4314 /** 4315 * {@inheritDoc} 4316 */ 4317 @Override getSetting(int userId, String namespace, String key)4318 public String getSetting(int userId, String namespace, String key) 4319 throws DeviceNotAvailableException { 4320 throw new UnsupportedOperationException("No support for setting's feature."); 4321 } 4322 4323 /** {@inheritDoc} */ 4324 @Override getAllSettings(String namespace)4325 public Map<String, String> getAllSettings(String namespace) throws DeviceNotAvailableException { 4326 throw new UnsupportedOperationException("No support for setting's feature."); 4327 } 4328 4329 /** 4330 * {@inheritDoc} 4331 */ 4332 @Override setSetting(String namespace, String key, String value)4333 public void setSetting(String namespace, String key, String value) 4334 throws DeviceNotAvailableException { 4335 throw new UnsupportedOperationException("No support for setting's feature."); 4336 } 4337 4338 /** 4339 * {@inheritDoc} 4340 */ 4341 @Override setSetting(int userId, String namespace, String key, String value)4342 public void setSetting(int userId, String namespace, String key, String value) 4343 throws DeviceNotAvailableException { 4344 throw new UnsupportedOperationException("No support for setting's feature."); 4345 } 4346 4347 /** 4348 * {@inheritDoc} 4349 */ 4350 @Override getBuildSigningKeys()4351 public String getBuildSigningKeys() throws DeviceNotAvailableException { 4352 String buildTags = getProperty(DeviceProperties.BUILD_TAGS); 4353 if (buildTags != null) { 4354 String[] tags = buildTags.split(","); 4355 for (String tag : tags) { 4356 Matcher m = KEYS_PATTERN.matcher(tag); 4357 if (m.matches()) { 4358 return tag; 4359 } 4360 } 4361 } 4362 return null; 4363 } 4364 4365 /** 4366 * {@inheritDoc} 4367 */ 4368 @Override getAndroidId(int userId)4369 public String getAndroidId(int userId) throws DeviceNotAvailableException { 4370 throw new UnsupportedOperationException("No support for user's feature."); 4371 } 4372 4373 /** 4374 * {@inheritDoc} 4375 */ 4376 @Override getAndroidIds()4377 public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException { 4378 throw new UnsupportedOperationException("No support for user's feature."); 4379 } 4380 4381 /** {@inheritDoc} */ 4382 @Override setDeviceOwner(String componentName, int userId)4383 public boolean setDeviceOwner(String componentName, int userId) 4384 throws DeviceNotAvailableException { 4385 throw new UnsupportedOperationException("No support for user's feature."); 4386 } 4387 4388 /** {@inheritDoc} */ 4389 @Override removeAdmin(String componentName, int userId)4390 public boolean removeAdmin(String componentName, int userId) 4391 throws DeviceNotAvailableException { 4392 throw new UnsupportedOperationException("No support for user's feature."); 4393 } 4394 4395 /** {@inheritDoc} */ 4396 @Override removeOwners()4397 public void removeOwners() throws DeviceNotAvailableException { 4398 throw new UnsupportedOperationException("No support for user's feature."); 4399 } 4400 4401 /** 4402 * {@inheritDoc} 4403 */ 4404 @Override disableKeyguard()4405 public void disableKeyguard() throws DeviceNotAvailableException { 4406 throw new UnsupportedOperationException("No support for Window Manager's features"); 4407 } 4408 4409 /** {@inheritDoc} */ 4410 @Override getDeviceClass()4411 public String getDeviceClass() { 4412 IDevice device = getIDevice(); 4413 if (device == null) { 4414 CLog.w("No IDevice instance, cannot determine device class."); 4415 return ""; 4416 } 4417 return device.getClass().getSimpleName(); 4418 } 4419 4420 /** {@inheritDoc} */ 4421 @Override preInvocationSetup(IBuildInfo info)4422 public void preInvocationSetup(IBuildInfo info) 4423 throws TargetSetupError, DeviceNotAvailableException { 4424 // Default implementation 4425 mContentProvider = null; 4426 mShouldSkipContentProviderSetup = false; 4427 try { 4428 mExecuteShellCommandLogs = 4429 FileUtil.createTempFile("TestDevice_ExecuteShellCommands", ".txt"); 4430 } catch (IOException e) { 4431 throw new TargetSetupError( 4432 "Failed to create the executeShellCommand log file.", 4433 e, 4434 getDeviceDescriptor(), 4435 InfraErrorIdentifier.FAIL_TO_CREATE_FILE); 4436 } 4437 } 4438 4439 /** {@inheritDoc} */ 4440 @Override postInvocationTearDown(Throwable exception)4441 public void postInvocationTearDown(Throwable exception) { 4442 mIsEncryptionSupported = null; 4443 FileUtil.deleteFile(mExecuteShellCommandLogs); 4444 mExecuteShellCommandLogs = null; 4445 // Default implementation 4446 if (getIDevice() instanceof StubDevice) { 4447 return; 4448 } 4449 // Reset the Content Provider bit. 4450 mShouldSkipContentProviderSetup = false; 4451 try { 4452 // If we never installed it, don't even bother checking for it during tear down. 4453 if (mContentProvider == null) { 4454 return; 4455 } 4456 if (exception instanceof DeviceNotAvailableException) { 4457 CLog.e( 4458 "Skip Tradefed Content Provider teardown due to DeviceNotAvailableException."); 4459 return; 4460 } 4461 if (TestDeviceState.ONLINE.equals(getDeviceState())) { 4462 mContentProvider.tearDown(); 4463 } 4464 } catch (DeviceNotAvailableException e) { 4465 CLog.e(e); 4466 } 4467 } 4468 4469 /** 4470 * {@inheritDoc} 4471 */ 4472 @Override isHeadless()4473 public boolean isHeadless() throws DeviceNotAvailableException { 4474 if (getProperty(DeviceProperties.BUILD_HEADLESS) != null) { 4475 return true; 4476 } 4477 return false; 4478 } 4479 checkApiLevelAgainst(String feature, int strictMinLevel)4480 protected void checkApiLevelAgainst(String feature, int strictMinLevel) { 4481 try { 4482 if (getApiLevel() < strictMinLevel){ 4483 throw new IllegalArgumentException(String.format("%s not supported on %s. " 4484 + "Must be API %d.", feature, getSerialNumber(), strictMinLevel)); 4485 } 4486 } catch (DeviceNotAvailableException e) { 4487 throw new HarnessRuntimeException( 4488 "Device became unavailable while checking API level", 4489 e, 4490 DeviceErrorIdentifier.DEVICE_UNAVAILABLE); 4491 } 4492 } 4493 4494 /** {@inheritDoc} */ 4495 @Override getCachedDeviceDescriptor()4496 public DeviceDescriptor getCachedDeviceDescriptor() { 4497 synchronized (mCacheLock) { 4498 if (DeviceAllocationState.Allocated.equals(getAllocationState())) { 4499 if (mCachedDeviceDescriptor == null) { 4500 // Create the cache the very first time when it's allocated. 4501 mCachedDeviceDescriptor = getDeviceDescriptor(); 4502 return mCachedDeviceDescriptor; 4503 } 4504 return mCachedDeviceDescriptor; 4505 } 4506 // If device is not allocated, just return current information 4507 mCachedDeviceDescriptor = null; 4508 return getDeviceDescriptor(); 4509 } 4510 } 4511 4512 /** 4513 * {@inheritDoc} 4514 */ 4515 @Override getDeviceDescriptor()4516 public DeviceDescriptor getDeviceDescriptor() { 4517 IDeviceSelection selector = new DeviceSelectionOptions(); 4518 IDevice idevice = getIDevice(); 4519 try { 4520 boolean isTemporary = false; 4521 if (idevice instanceof NullDevice) { 4522 isTemporary = ((NullDevice) idevice).isTemporary(); 4523 } 4524 // All the operations to create the descriptor need to be safe (should not trigger any 4525 // device side effects like recovery) 4526 return new DeviceDescriptor( 4527 idevice.getSerialNumber(), 4528 null, 4529 idevice instanceof StubDevice, 4530 idevice.getState(), 4531 getAllocationState(), 4532 getDeviceState(), 4533 getDisplayString(selector.getDeviceProductType(idevice)), 4534 getDisplayString(selector.getDeviceProductVariant(idevice)), 4535 getDisplayString(idevice.getProperty(DeviceProperties.SDK_VERSION)), 4536 getDisplayString(idevice.getProperty(DeviceProperties.BUILD_ALIAS)), 4537 getDisplayString(getBattery()), 4538 getDeviceClass(), 4539 getDisplayString(getMacAddress()), 4540 getDisplayString(getSimState()), 4541 getDisplayString(getSimOperator()), 4542 isTemporary, 4543 idevice); 4544 } catch (RuntimeException e) { 4545 CLog.e("Exception while building device '%s' description:", getSerialNumber()); 4546 CLog.e(e); 4547 } 4548 return null; 4549 } 4550 4551 /** 4552 * Return the displayable string for given object 4553 */ getDisplayString(Object o)4554 private String getDisplayString(Object o) { 4555 return o == null ? "unknown" : o.toString(); 4556 } 4557 4558 /** {@inheritDoc} */ 4559 @Override getProcessByName(String processName)4560 public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException { 4561 String pidString = getProcessPid(processName); 4562 if (pidString == null) { 4563 return null; 4564 } 4565 long startTime = getProcessStartTimeByPid(pidString); 4566 if (startTime == -1L) { 4567 return null; 4568 } 4569 return new ProcessInfo( 4570 getProcessUserByPid(pidString), 4571 Integer.parseInt(pidString), 4572 processName, 4573 startTime); 4574 } 4575 4576 /** Return the process start time since epoch for the given pid string */ getProcessStartTimeByPid(String pidString)4577 private long getProcessStartTimeByPid(String pidString) throws DeviceNotAvailableException { 4578 String output = executeShellCommand(String.format("ps -p %s -o stime=", pidString)); 4579 if (output != null && !output.trim().isEmpty()) { 4580 output = output.trim(); 4581 String dateInSecond = executeShellCommand("date -d\"" + output + "\" +%s"); 4582 if (Strings.isNullOrEmpty(dateInSecond)) { 4583 return -1L; 4584 } 4585 try { 4586 return Long.parseLong(dateInSecond.trim()); 4587 } catch (NumberFormatException e) { 4588 CLog.e("Failed to parse the start time for process:"); 4589 CLog.e(e); 4590 return -1L; 4591 } 4592 } 4593 return -1L; 4594 } 4595 4596 /** Return the process user for the given pid string */ getProcessUserByPid(String pidString)4597 private String getProcessUserByPid(String pidString) throws DeviceNotAvailableException { 4598 String output = executeShellCommand("stat -c%U /proc/" + pidString); 4599 if (output != null && !output.trim().isEmpty()) { 4600 try { 4601 return output.trim(); 4602 } catch (NumberFormatException e) { 4603 return null; 4604 } 4605 } 4606 return null; 4607 } 4608 4609 /** {@inheritDoc} */ 4610 @Override getBootHistory()4611 public Map<Long, String> getBootHistory() throws DeviceNotAvailableException { 4612 String output = getProperty(DeviceProperties.BOOT_REASON_HISTORY); 4613 /* Sample output: 4614 kernel_panic,1556587278 4615 reboot,,1556238008 4616 reboot,,1556237796 4617 reboot,,1556237725 4618 */ 4619 Map<Long, String> bootHistory = new LinkedHashMap<Long, String>(); 4620 if (Strings.isNullOrEmpty(output)) { 4621 return bootHistory; 4622 } 4623 for (String line : output.split("\\n")) { 4624 String infoStr[] = line.split(","); 4625 String startStr = infoStr[infoStr.length - 1]; 4626 try { 4627 long startTime = Long.parseLong(startStr.trim()); 4628 bootHistory.put(startTime, infoStr[0].trim()); 4629 } catch (NumberFormatException e) { 4630 CLog.e("Fail to parse boot time from line %s", line); 4631 } 4632 } 4633 return bootHistory; 4634 } 4635 4636 /** {@inheritDoc} */ 4637 @Override getBootHistorySince(long utcEpochTime, TimeUnit timeUnit)4638 public Map<Long, String> getBootHistorySince(long utcEpochTime, TimeUnit timeUnit) 4639 throws DeviceNotAvailableException { 4640 long utcEpochTimeSec = TimeUnit.SECONDS.convert(utcEpochTime, timeUnit); 4641 Map<Long, String> bootHistory = new LinkedHashMap<Long, String>(); 4642 for (Map.Entry<Long, String> entry : getBootHistory().entrySet()) { 4643 if (entry.getKey() >= utcEpochTimeSec) { 4644 bootHistory.put(entry.getKey(), entry.getValue()); 4645 } 4646 } 4647 return bootHistory; 4648 } 4649 hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit)4650 private boolean hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit) 4651 throws DeviceNotAvailableException { 4652 Map<Long, String> bootHistory = getBootHistorySince(utcEpochTime, timeUnit); 4653 if (bootHistory.isEmpty()) { 4654 CLog.w("There is no reboot history since %s", utcEpochTime); 4655 return false; 4656 } 4657 4658 CLog.i( 4659 "There are new boot history since %d. NewBootHistory = %s", 4660 utcEpochTime, bootHistory); 4661 // Check if there is reboot reason other than "reboot". 4662 // Raise RuntimeException if there is abnormal reboot. 4663 for (Map.Entry<Long, String> entry : bootHistory.entrySet()) { 4664 if (!"reboot".equals(entry.getValue())) { 4665 throw new RuntimeException( 4666 String.format( 4667 "Device %s has abnormal reboot reason %s at %d", 4668 getSerialNumber(), entry.getValue(), entry.getKey())); 4669 } 4670 } 4671 return true; 4672 } 4673 4674 /** 4675 * Check current system process is restarted after last reboot 4676 * 4677 * @param systemServerProcess the system_server {@link ProcessInfo} 4678 * @return true if system_server process restarted after last reboot; false if not 4679 * @throws DeviceNotAvailableException 4680 */ checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess)4681 private boolean checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess) 4682 throws DeviceNotAvailableException { 4683 // If time gap from last reboot to current system_server process start time is more than 4684 // MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP seconds, we conclude the system_server restarted 4685 // after boot up. 4686 if (!hasNormalRebootSince( 4687 systemServerProcess.getStartTime() - MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC, 4688 TimeUnit.SECONDS)) { 4689 CLog.i( 4690 "Device last reboot is more than %s seconds away from current system_server " 4691 + "process start time. The system_server process restarted after " 4692 + "last boot up", 4693 MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC); 4694 return true; 4695 } else { 4696 // Current system_server start within MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP 4697 // seconds after device last boot up 4698 return false; 4699 } 4700 } 4701 4702 /** {@inheritDoc} */ 4703 @Override deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit)4704 public boolean deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit) 4705 throws DeviceNotAvailableException { 4706 ProcessInfo currSystemServerProcess = getProcessByName("system_server"); 4707 if (currSystemServerProcess == null) { 4708 CLog.i("The system_server process is not available on the device."); 4709 return true; 4710 } 4711 4712 // The system_server process started at or before utcEpochTime, there is no soft-restart 4713 if (Math.abs( 4714 currSystemServerProcess.getStartTime() 4715 - TimeUnit.SECONDS.convert(utcEpochTime, timeUnit)) 4716 <= 1) { 4717 return false; 4718 } 4719 4720 // The system_server process restarted after device utcEpochTime in second. 4721 // Check if there is new reboot history, if no new reboot, device soft-restarted. 4722 // If there is no normal reboot, soft-restart is detected. 4723 if (!hasNormalRebootSince(utcEpochTime, timeUnit)) { 4724 return true; 4725 } 4726 4727 // There is new reboot since utcEpochTime. Check if system_server restarted after boot up. 4728 return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess); 4729 } 4730 4731 /** {@inheritDoc} */ 4732 @Override deviceSoftRestarted(ProcessInfo prevSystemServerProcess)4733 public boolean deviceSoftRestarted(ProcessInfo prevSystemServerProcess) 4734 throws DeviceNotAvailableException { 4735 if (prevSystemServerProcess == null) { 4736 CLog.i("The given system_server process is null. Abort deviceSoftRestarted check."); 4737 return false; 4738 } 4739 ProcessInfo currSystemServerProcess = getProcessByName("system_server"); 4740 if (currSystemServerProcess == null) { 4741 CLog.i("The system_server process is not available on the device."); 4742 return true; 4743 } 4744 4745 // Compare the start time with a 1 seconds accuracy due to how the date is computed 4746 if (currSystemServerProcess.getPid() == prevSystemServerProcess.getPid() 4747 && Math.abs( 4748 currSystemServerProcess.getStartTime() 4749 - prevSystemServerProcess.getStartTime()) 4750 <= 1) { 4751 return false; 4752 } 4753 4754 CLog.v( 4755 "current system_server: %s; prev system_server: %s", 4756 currSystemServerProcess, prevSystemServerProcess); 4757 4758 // The system_server process restarted. 4759 // Check boot history with previous system_server start time. 4760 // If there is no normal reboot, soft-restart is detected 4761 if (!hasNormalRebootSince(prevSystemServerProcess.getStartTime(), TimeUnit.SECONDS)) { 4762 return true; 4763 } 4764 4765 // There is reboot since prevSystemServerProcess.getStartTime(). 4766 // Check if system_server restarted after boot up. 4767 return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess); 4768 4769 } 4770 4771 /** 4772 * Validates that the given input is a valid MAC address 4773 * 4774 * @param address input to validate 4775 * @return true if the input is a valid MAC address 4776 */ isMacAddress(String address)4777 boolean isMacAddress(String address) { 4778 Pattern macPattern = Pattern.compile(MAC_ADDRESS_PATTERN); 4779 Matcher macMatcher = macPattern.matcher(address); 4780 return macMatcher.find(); 4781 } 4782 4783 /** 4784 * {@inheritDoc} 4785 */ 4786 @Override getMacAddress()4787 public String getMacAddress() { 4788 if (getIDevice() instanceof StubDevice) { 4789 // Do not query MAC addresses from stub devices. 4790 return null; 4791 } 4792 if (!TestDeviceState.ONLINE.equals(mState)) { 4793 // Only query MAC addresses from online devices. 4794 return null; 4795 } 4796 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 4797 try { 4798 mIDevice.executeShellCommand(MAC_ADDRESS_COMMAND, receiver); 4799 } catch (IOException | TimeoutException | AdbCommandRejectedException | 4800 ShellCommandUnresponsiveException e) { 4801 CLog.w("Failed to query MAC address for %s", mIDevice.getSerialNumber()); 4802 CLog.w(e); 4803 } 4804 String output = receiver.getOutput().trim(); 4805 if (isMacAddress(output)) { 4806 return output; 4807 } 4808 CLog.d("No valid MAC address queried from device %s", mIDevice.getSerialNumber()); 4809 return null; 4810 } 4811 4812 /** {@inheritDoc} */ 4813 @Override getSimState()4814 public String getSimState() { 4815 // Use ddmlib getProperty directly to avoid possible recovery path 4816 return getIDevice().getProperty(SIM_STATE_PROP); 4817 } 4818 4819 /** {@inheritDoc} */ 4820 @Override getSimOperator()4821 public String getSimOperator() { 4822 // Use ddmlib getProperty directly to avoid possible recovery path 4823 return getIDevice().getProperty(SIM_OPERATOR_PROP); 4824 } 4825 4826 /** {@inheritDoc} */ 4827 @Override dumpHeap(String process, String devicePath)4828 public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException { 4829 throw new UnsupportedOperationException("dumpHeap is not supported."); 4830 } 4831 4832 /** {@inheritDoc} */ 4833 @Override getProcessPid(String process)4834 public String getProcessPid(String process) throws DeviceNotAvailableException { 4835 String output = executeShellCommand(String.format("pidof %s", process)).trim(); 4836 if (checkValidPid(output)) { 4837 return output; 4838 } 4839 CLog.e("Failed to find a valid pid for process."); 4840 return null; 4841 } 4842 4843 /** {@inheritDoc} */ 4844 @Override logOnDevice(String tag, LogLevel level, String format, Object... args)4845 public void logOnDevice(String tag, LogLevel level, String format, Object... args) { 4846 String message = String.format(format, args); 4847 try { 4848 String levelLetter = logLevelToLogcatLevel(level); 4849 String command = String.format("log -t %s -p %s '%s'", tag, levelLetter, message); 4850 executeShellCommand(command); 4851 } catch (DeviceNotAvailableException e) { 4852 CLog.e("Device went not available when attempting to log '%s'", message); 4853 CLog.e(e); 4854 } 4855 } 4856 4857 /** Convert the {@link LogLevel} to the letter used in log (see 'adb shell log --help'). */ logLevelToLogcatLevel(LogLevel level)4858 private String logLevelToLogcatLevel(LogLevel level) { 4859 switch (level) { 4860 case DEBUG: 4861 return "d"; 4862 case ERROR: 4863 return "e"; 4864 case INFO: 4865 return "i"; 4866 case VERBOSE: 4867 return "v"; 4868 case WARN: 4869 return "w"; 4870 default: 4871 return "i"; 4872 } 4873 } 4874 4875 /** {@inheritDoc} */ 4876 @Override getTotalMemory()4877 public long getTotalMemory() { 4878 // "/proc/meminfo" always returns value in kilobytes. 4879 long totalMemory = 0; 4880 String output = null; 4881 try { 4882 output = executeShellCommand("cat /proc/meminfo | grep MemTotal"); 4883 } catch (DeviceNotAvailableException e) { 4884 CLog.e(e); 4885 return -1; 4886 } 4887 if (output.isEmpty()) { 4888 return -1; 4889 } 4890 String[] results = output.split("\\s+"); 4891 try { 4892 totalMemory = Long.parseLong(results[1].replaceAll("\\D+", "")); 4893 } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) { 4894 CLog.e(e); 4895 return -1; 4896 } 4897 return totalMemory * 1024; 4898 } 4899 4900 /** {@inheritDoc} */ 4901 @Override getBattery()4902 public Integer getBattery() { 4903 if (getIDevice() instanceof StubDevice) { 4904 return null; 4905 } 4906 if (isStateBootloaderOrFastbootd()) { 4907 return null; 4908 } 4909 try { 4910 // Use default 5 minutes freshness 4911 Future<Integer> batteryFuture = getIDevice().getBattery(); 4912 // Get cached value or wait up to 500ms for battery level query 4913 return batteryFuture.get(500, TimeUnit.MILLISECONDS); 4914 } catch (InterruptedException 4915 | ExecutionException 4916 | java.util.concurrent.TimeoutException e) { 4917 CLog.w( 4918 "Failed to query battery level for %s: %s", 4919 getIDevice().getSerialNumber(), e.toString()); 4920 } 4921 return null; 4922 } 4923 4924 /** {@inheritDoc} */ 4925 @Override listDisplayIds()4926 public Set<Long> listDisplayIds() throws DeviceNotAvailableException { 4927 throw new UnsupportedOperationException("dumpsys SurfaceFlinger is not supported."); 4928 } 4929 4930 /** {@inheritDoc} */ 4931 @Override getLastExpectedRebootTimeMillis()4932 public long getLastExpectedRebootTimeMillis() { 4933 return mLastTradefedRebootTime; 4934 } 4935 4936 /** {@inheritDoc} */ 4937 @Override getTombstones()4938 public List<File> getTombstones() throws DeviceNotAvailableException { 4939 List<File> tombstones = new ArrayList<>(); 4940 if (!isAdbRoot()) { 4941 CLog.w("Device was not root, cannot collect tombstones."); 4942 return tombstones; 4943 } 4944 for (String tombName : getChildren(TOMBSTONE_PATH)) { 4945 File tombFile = pullFile(TOMBSTONE_PATH + tombName); 4946 if (tombFile != null) { 4947 tombstones.add(tombFile); 4948 } 4949 } 4950 return tombstones; 4951 } 4952 4953 /** Validate that pid is an integer and not empty. */ checkValidPid(String output)4954 private boolean checkValidPid(String output) { 4955 if (output.isEmpty()) { 4956 return false; 4957 } 4958 try { 4959 Integer.parseInt(output); 4960 } catch (NumberFormatException e) { 4961 CLog.e(e); 4962 return false; 4963 } 4964 return true; 4965 } 4966 4967 /** Gets the {@link IHostOptions} instance to use. */ 4968 @VisibleForTesting getHostOptions()4969 IHostOptions getHostOptions() { 4970 return GlobalConfiguration.getInstance().getHostOptions(); 4971 } 4972 4973 /** Returns the {@link ContentProviderHandler} or null if not available. */ 4974 @VisibleForTesting getContentProvider()4975 ContentProviderHandler getContentProvider() throws DeviceNotAvailableException { 4976 // If disabled at the device level, don't attempt any checks. 4977 if (!getOptions().shouldUseContentProvider()) { 4978 return null; 4979 } 4980 // Prevent usage of content provider before API 28 as it would not work well since content 4981 // tool is not working before P. 4982 if (getApiLevel() < 28) { 4983 return null; 4984 } 4985 if (mContentProvider == null) { 4986 mContentProvider = new ContentProviderHandler(this); 4987 } 4988 // Force the install if we saw an error with content provider installation. 4989 if (mContentProvider.contentProviderNotFound()) { 4990 mShouldSkipContentProviderSetup = false; 4991 } 4992 if (!mShouldSkipContentProviderSetup) { 4993 boolean res = mContentProvider.setUp(); 4994 if (!res) { 4995 // TODO: once CP becomes a requirement, throw/fail the test if CP can't be found 4996 return null; 4997 } 4998 mShouldSkipContentProviderSetup = true; 4999 } 5000 return mContentProvider; 5001 } 5002 5003 /** Reset the flag for content provider setup in order to trigger it again. */ resetContentProviderSetup()5004 void resetContentProviderSetup() { 5005 mShouldSkipContentProviderSetup = false; 5006 } 5007 5008 /** The log that contains all the {@link #executeShellCommand(String)} logs. */ getExecuteShellCommandLog()5009 public final File getExecuteShellCommandLog() { 5010 return mExecuteShellCommandLogs; 5011 } 5012 } 5013