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.ShellCommandUnresponsiveException; 26 import com.android.ddmlib.SyncException; 27 import com.android.ddmlib.SyncException.SyncError; 28 import com.android.ddmlib.SyncService; 29 import com.android.ddmlib.TimeoutException; 30 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner; 31 import com.android.ddmlib.testrunner.ITestRunListener; 32 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 33 import com.android.tradefed.build.IBuildInfo; 34 import com.android.tradefed.command.remote.DeviceDescriptor; 35 import com.android.tradefed.config.ConfigurationException; 36 import com.android.tradefed.config.GlobalConfiguration; 37 import com.android.tradefed.config.IConfiguration; 38 import com.android.tradefed.config.IConfigurationReceiver; 39 import com.android.tradefed.config.OptionSetter; 40 import com.android.tradefed.device.IWifiHelper.WifiConnectionResult; 41 import com.android.tradefed.device.cloud.GceAvdInfo; 42 import com.android.tradefed.device.connection.AbstractConnection; 43 import com.android.tradefed.device.connection.DefaultConnection; 44 import com.android.tradefed.device.connection.DefaultConnection.ConnectionBuilder; 45 import com.android.tradefed.device.contentprovider.ContentProviderHandler; 46 import com.android.tradefed.error.HarnessRuntimeException; 47 import com.android.tradefed.host.IHostOptions; 48 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 49 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 50 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 51 import com.android.tradefed.log.ITestLogger; 52 import com.android.tradefed.log.LogUtil; 53 import com.android.tradefed.log.LogUtil.CLog; 54 import com.android.tradefed.result.ByteArrayInputStreamSource; 55 import com.android.tradefed.result.FileInputStreamSource; 56 import com.android.tradefed.result.ITestLifeCycleReceiver; 57 import com.android.tradefed.result.ITestLoggerReceiver; 58 import com.android.tradefed.result.InputStreamSource; 59 import com.android.tradefed.result.LogDataType; 60 import com.android.tradefed.result.SnapshotInputStreamSource; 61 import com.android.tradefed.result.StubTestRunListener; 62 import com.android.tradefed.result.ddmlib.TestRunToTestInvocationForwarder; 63 import com.android.tradefed.result.error.DeviceErrorIdentifier; 64 import com.android.tradefed.result.error.InfraErrorIdentifier; 65 import com.android.tradefed.targetprep.TargetSetupError; 66 import com.android.tradefed.testtype.coverage.CoverageOptions.Toolchain; 67 import com.android.tradefed.util.ArrayUtil; 68 import com.android.tradefed.util.Bugreport; 69 import com.android.tradefed.util.CommandResult; 70 import com.android.tradefed.util.CommandStatus; 71 import com.android.tradefed.util.FileUtil; 72 import com.android.tradefed.util.IRunUtil; 73 import com.android.tradefed.util.KeyguardControllerState; 74 import com.android.tradefed.util.MultiMap; 75 import com.android.tradefed.util.ProcessInfo; 76 import com.android.tradefed.util.QuotationAwareTokenizer; 77 import com.android.tradefed.util.RunUtil; 78 import com.android.tradefed.util.SizeLimitedOutputStream; 79 import com.android.tradefed.util.StringEscapeUtils; 80 import com.android.tradefed.util.SystemUtil; 81 import com.android.tradefed.util.TimeUtil; 82 import com.android.tradefed.util.ZipUtil2; 83 84 import com.google.common.annotations.VisibleForTesting; 85 import com.google.common.base.Strings; 86 import com.google.common.cache.CacheBuilder; 87 import com.google.common.cache.CacheLoader; 88 import com.google.common.cache.LoadingCache; 89 import com.google.common.collect.ImmutableSet; 90 import com.google.errorprone.annotations.FormatMethod; 91 92 import java.io.File; 93 import java.io.FilenameFilter; 94 import java.io.IOException; 95 import java.io.OutputStream; 96 import java.net.Inet6Address; 97 import java.net.UnknownHostException; 98 import java.text.ParseException; 99 import java.text.SimpleDateFormat; 100 import java.time.Clock; 101 import java.util.ArrayList; 102 import java.util.Arrays; 103 import java.util.Collection; 104 import java.util.Date; 105 import java.util.HashMap; 106 import java.util.HashSet; 107 import java.util.LinkedHashMap; 108 import java.util.LinkedList; 109 import java.util.List; 110 import java.util.Locale; 111 import java.util.Map; 112 import java.util.Random; 113 import java.util.Set; 114 import java.util.TimeZone; 115 import java.util.concurrent.ExecutionException; 116 import java.util.concurrent.Future; 117 import java.util.concurrent.TimeUnit; 118 import java.util.concurrent.locks.ReentrantLock; 119 import java.util.regex.Matcher; 120 import java.util.regex.Pattern; 121 122 import javax.annotation.Nonnull; 123 import javax.annotation.Nullable; 124 import javax.annotation.concurrent.GuardedBy; 125 126 /** Default implementation of a {@link ITestDevice} Non-full stack android devices. */ 127 public class NativeDevice 128 implements IManagedTestDevice, IConfigurationReceiver, ITestLoggerReceiver { 129 130 protected static final String SD_CARD = "/sdcard/"; 131 protected static final String STORAGE_EMULATED = "/storage/emulated/"; 132 133 /** On-device path where we expect ANRs to be generated. */ 134 private static final String ANRS_PATH = "/data/anr"; 135 136 /** 137 * Allow up to 2 minutes to receives the full logcat dump. 138 */ 139 private static final int LOGCAT_DUMP_TIMEOUT = 2 * 60 * 1000; 140 141 /** the default number of command retry attempts to perform */ 142 protected static final int MAX_RETRY_ATTEMPTS = 2; 143 144 /** Value returned for any invalid/not found user id: UserHandle defined the -10000 value */ 145 public static final int INVALID_USER_ID = -10000; 146 147 /** regex to match input dispatch readiness line **/ 148 static final Pattern INPUT_DISPATCH_STATE_REGEX = 149 Pattern.compile("DispatchEnabled:\\s?([01])"); 150 /** regex to match build signing key type */ 151 private static final Pattern KEYS_PATTERN = Pattern.compile("^.*-keys$"); 152 153 private static final Pattern DF_PATTERN = 154 Pattern.compile( 155 // Fs 1K-blks Used Available Use% Mounted on 156 "^/(\\S+)\\s+\\d+\\s+\\d+\\s+(\\d+)\\s+\\d+%\\s+/\\S*$", Pattern.MULTILINE); 157 158 protected static final long MAX_HOST_DEVICE_TIME_OFFSET = 5 * 1000; 159 160 /** The password for encrypting and decrypting the device. */ 161 private static final String ENCRYPTION_PASSWORD = "android"; 162 163 /** The maximum system_server start delay in seconds after device boot up */ 164 private static final int MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC = 25; 165 166 /** The time in ms to wait before starting logcat for a device */ 167 private int mLogStartDelay = 5*1000; 168 169 /** The time in ms to wait for a device to become unavailable. Should usually be short */ 170 private static final int DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000; 171 172 private static final String SIM_STATE_PROP = "gsm.sim.state"; 173 private static final String SIM_OPERATOR_PROP = "gsm.operator.alpha"; 174 175 static final String MAC_ADDRESS_PATTERN = "([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}"; 176 static final String MAC_ADDRESS_COMMAND = "su root cat /sys/class/net/wlan0/address"; 177 static final String ETHERNET_MAC_ADDRESS_COMMAND = "cat /sys/class/net/eth0/address"; 178 179 static final int ETHER_ADDR_LEN = 6; 180 181 /** The network monitoring interval in ms. */ 182 private static final int NETWORK_MONITOR_INTERVAL = 10 * 1000; 183 184 /** Wifi reconnect check interval in ms. */ 185 private static final int WIFI_RECONNECT_CHECK_INTERVAL = 1 * 1000; 186 187 /** Wifi reconnect timeout in ms. */ 188 private static final int WIFI_RECONNECT_TIMEOUT = 60 * 1000; 189 190 /** Pattern to find an executable file. */ 191 private static final Pattern EXE_FILE = Pattern.compile("^[-l]r.x.+"); 192 193 /** Path of the device containing the tombstones */ 194 private static final String TOMBSTONE_PATH = "/data/tombstones/"; 195 196 private static final long PROPERTY_GET_TIMEOUT = 45 * 1000L; 197 198 public static final String DEBUGFS_PATH = "/sys/kernel/debug"; 199 private static final String CHECK_DEBUGFS_MNT_COMMAND = 200 String.format("mountpoint -q %s", DEBUGFS_PATH); 201 private static final String MOUNT_DEBUGFS_COMMAND = 202 String.format("mount -t debugfs debugfs %s", DEBUGFS_PATH); 203 private static final String UNMOUNT_DEBUGFS_COMMAND = String.format("umount %s", DEBUGFS_PATH); 204 205 /** Version number for a current development build */ 206 private static final int CUR_DEVELOPMENT_VERSION = 10000; 207 208 /** The time in ms to wait for a 'long' command to complete. */ 209 private long mLongCmdTimeout = 25 * 60 * 1000L; 210 211 212 213 /** 214 * The delimiter that separates the actual shell output and the exit status. 215 * 216 * <p>Used to determine the exit status of the command run by adb shell for devices that do not 217 * support shell_v2. 218 */ 219 private static final String EXIT_STATUS_DELIMITER = "x"; 220 221 private IConfiguration mConfiguration; 222 private IDevice mIDevice; 223 private IDeviceRecovery mRecovery = new WaitDeviceRecovery(); 224 protected final IDeviceStateMonitor mStateMonitor; 225 private TestDeviceState mState = TestDeviceState.ONLINE; 226 private final ReentrantLock mFastbootLock = new ReentrantLock(); 227 private LogcatReceiver mLogcatReceiver; 228 private boolean mFastbootEnabled = true; 229 private String mFastbootPath = "fastboot"; 230 231 protected TestDeviceOptions mOptions = new TestDeviceOptions(); 232 private Process mEmulatorProcess; 233 private SizeLimitedOutputStream mEmulatorOutput; 234 private Clock mClock = Clock.systemUTC(); 235 236 private RecoveryMode mRecoveryMode = RecoveryMode.AVAILABLE; 237 238 private Boolean mIsEncryptionSupported = null; 239 private ReentrantLock mAllocationStateLock = new ReentrantLock(true /*fair*/); 240 241 @GuardedBy("mAllocationStateLock") 242 private DeviceAllocationState mAllocationState = DeviceAllocationState.Unknown; 243 private IDeviceMonitor mAllocationMonitor = null; 244 245 private String mLastConnectedWifiSsid = null; 246 private String mLastConnectedWifiPsk = null; 247 private boolean mNetworkMonitorEnabled = false; 248 249 private ContentProviderHandler mContentProvider = null; 250 private boolean mShouldSkipContentProviderSetup = false; 251 /** Keep track of the last time Tradefed itself triggered a reboot. */ 252 private long mLastTradefedRebootTime = 0L; 253 254 private File mExecuteShellCommandLogs = null; 255 256 private DeviceDescriptor mCachedDeviceDescriptor = null; 257 private final Object mCacheLock = new Object(); 258 259 private String mFastbootSerialNumber = null; 260 private File mUnpackedFastbootDir = null; 261 // Connection for the device. 262 private AbstractConnection mConnection; 263 private GceAvdInfo mConnectionAvd; 264 265 private ITestLogger mTestLogger; 266 267 private List<IDeviceActionReceiver> mDeviceActionReceivers = new LinkedList<>(); 268 /** 269 * Whether callback for reboot is currently executing or not. Use this flag to avoid dead loop 270 * scenarios like calling reboot inside a callback happening for reboot. 271 */ 272 private boolean inRebootCallback = false; 273 274 /** If the device is a Microdroid, this refers to the VM process. Otherwise, it is null. */ 275 private Process mMicrodroidProcess = null; 276 277 private final LoadingCache<String, String> mPropertiesCache; 278 279 // If we increase the number of props, then increase the cache size of mPropertiesCache. 280 private final Set<String> propsToPrefetch = 281 ImmutableSet.of("ro.build.version.sdk", "ro.build.version.codename", "ro.build.id"); 282 // Avoid caching any properties in those namespace 283 private static final Set<String> NEVER_CACHE_PROPERTIES = 284 ImmutableSet.of("vendor.debug", "ro.boot"); 285 286 /** Interface for a generic device communication attempt. */ 287 abstract interface DeviceAction { 288 289 /** 290 * Execute the device operation. 291 * 292 * @return <code>true</code> if operation is performed successfully, <code>false</code> 293 * otherwise 294 * @throws IOException, TimeoutException, AdbCommandRejectedException, 295 * ShellCommandUnresponsiveException, InstallException, SyncException if operation 296 * terminated abnormally 297 */ run()298 public boolean run() 299 throws IOException, TimeoutException, AdbCommandRejectedException, 300 ShellCommandUnresponsiveException, InstallException, SyncException, 301 DeviceNotAvailableException; 302 } 303 304 /** 305 * A {@link DeviceAction} for running a OS 'adb ....' command. 306 */ 307 protected class AdbAction implements DeviceAction { 308 /** the output from the command */ 309 String mOutput = null; 310 private String[] mCmd; 311 private long mTimeout; 312 private boolean mIsShellCommand; 313 private Map<String, String> mEnvMap; 314 AdbAction(long timeout, String[] cmd, boolean isShell, Map<String, String> envMap)315 AdbAction(long timeout, String[] cmd, boolean isShell, Map<String, String> envMap) { 316 mTimeout = timeout; 317 mCmd = cmd; 318 mIsShellCommand = isShell; 319 mEnvMap = envMap; 320 } 321 logExceptionAndOutput(CommandResult result)322 private void logExceptionAndOutput(CommandResult result) { 323 CLog.w("Command exited with status: %s", result.getStatus().toString()); 324 CLog.w("Command stdout:\n%s\n", result.getStdout()); 325 CLog.w("Command stderr:\n%s\n", result.getStderr()); 326 } 327 328 @Override run()329 public boolean run() throws TimeoutException, IOException { 330 IRunUtil runUtil = getRunUtil(); 331 if (!mEnvMap.isEmpty()) { 332 runUtil = createRunUtil(); 333 } 334 for (String key : mEnvMap.keySet()) { 335 runUtil.setEnvVariable(key, mEnvMap.get(key)); 336 } 337 CommandResult result = runUtil.runTimedCmd(mTimeout, mCmd); 338 // TODO: how to determine device not present with command failing for other reasons 339 if (result.getStatus() == CommandStatus.EXCEPTION) { 340 logExceptionAndOutput(result); 341 throw new IOException("CommandStatus was EXCEPTION, details in host log"); 342 } else if (result.getStatus() == CommandStatus.TIMED_OUT) { 343 logExceptionAndOutput(result); 344 throw new TimeoutException("CommandStatus was TIMED_OUT, details in host log"); 345 } else if (result.getStatus() == CommandStatus.FAILED) { 346 347 logExceptionAndOutput(result); 348 if (mIsShellCommand) { 349 // Interpret as communication failure for shell commands 350 throw new IOException("CommandStatus was FAILED, details in host log"); 351 } else { 352 mOutput = result.getStdout(); 353 return false; 354 } 355 } 356 mOutput = result.getStdout(); 357 return true; 358 } 359 } 360 361 protected class AdbShellAction implements DeviceAction { 362 /** the output from the command */ 363 CommandResult mResult = null; 364 365 private String[] mCmd; 366 private long mTimeout; 367 private File mPipeAsInput; // Used in pushFile, uses local file as input to "content write" 368 private OutputStream mPipeToOutput; // Used in pullFile, to pipe content from "content read" 369 private OutputStream mPipeToError; 370 AdbShellAction( String[] cmd, File pipeAsInput, OutputStream pipeToOutput, OutputStream pipeToError, long timeout)371 AdbShellAction( 372 String[] cmd, 373 File pipeAsInput, 374 OutputStream pipeToOutput, 375 OutputStream pipeToError, 376 long timeout) { 377 mCmd = cmd; 378 mPipeAsInput = pipeAsInput; 379 mPipeToOutput = pipeToOutput; 380 mPipeToError = pipeToError; 381 mTimeout = timeout; 382 } 383 384 @Override run()385 public boolean run() throws TimeoutException, IOException { 386 if (mPipeAsInput != null) { 387 mResult = getRunUtil().runTimedCmdWithInputRedirect(mTimeout, mPipeAsInput, mCmd); 388 } else { 389 mResult = getRunUtil().runTimedCmd(mTimeout, mPipeToOutput, mPipeToError, mCmd); 390 } 391 if (mResult.getStatus() == CommandStatus.EXCEPTION) { 392 throw new IOException(mResult.getStderr()); 393 } else if (mResult.getStatus() == CommandStatus.TIMED_OUT) { 394 throw new TimeoutException(mResult.getStderr()); 395 } 396 String stdErr = mResult.getStderr(); 397 if (stdErr != null) { 398 stdErr = stdErr.trim(); 399 if (stdErr.contains("device offline")) { 400 throw new IOException(stdErr); 401 } 402 } 403 // If it's not some issue with running the adb command, then we return the CommandResult 404 // which will contain all the infos. 405 return true; 406 } 407 } 408 409 /** {@link DeviceAction} for rebooting a device. */ 410 protected class RebootDeviceAction implements DeviceAction { 411 412 private final RebootMode mRebootMode; 413 @Nullable private final String mReason; 414 RebootDeviceAction(RebootMode rebootMode, @Nullable String reason)415 RebootDeviceAction(RebootMode rebootMode, @Nullable String reason) { 416 mRebootMode = rebootMode; 417 mReason = reason; 418 } 419 isFastbootOrBootloader()420 public boolean isFastbootOrBootloader() { 421 return mRebootMode == RebootMode.REBOOT_INTO_BOOTLOADER 422 || mRebootMode == RebootMode.REBOOT_INTO_FASTBOOTD; 423 } 424 425 @Override run()426 public boolean run() 427 throws TimeoutException, IOException, AdbCommandRejectedException, 428 DeviceNotAvailableException { 429 // Notify of reboot started for all modes 430 notifyRebootStarted(); 431 getIDevice().reboot(mRebootMode.formatRebootCommand(mReason)); 432 return true; 433 } 434 } 435 436 /** 437 * Creates a {@link TestDevice}. 438 * 439 * @param device the associated {@link IDevice} 440 * @param stateMonitor the {@link IDeviceStateMonitor} mechanism to use 441 * @param allocationMonitor the {@link IDeviceMonitor} to inform of allocation state changes. 442 * Can be null 443 */ NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor)444 public NativeDevice(IDevice device, IDeviceStateMonitor stateMonitor, 445 IDeviceMonitor allocationMonitor) { 446 throwIfNull(device); 447 throwIfNull(stateMonitor); 448 mIDevice = device; 449 mStateMonitor = stateMonitor; 450 mAllocationMonitor = allocationMonitor; 451 // Keep a short timeout to expire key in case of large state changes 452 // such as flashing 453 mPropertiesCache = 454 CacheBuilder.newBuilder() 455 .maximumSize(50) 456 .expireAfterAccess(2, TimeUnit.MINUTES) 457 .build( 458 new CacheLoader<String, String>() { 459 @Override 460 public String load(String key) { 461 throw new IllegalStateException("Should never be called"); 462 } 463 }); 464 } 465 466 /** Get the {@link RunUtil} instance to use. */ 467 @VisibleForTesting getRunUtil()468 protected IRunUtil getRunUtil() { 469 return RunUtil.getDefault(); 470 } 471 createRunUtil()472 protected IRunUtil createRunUtil() { 473 return new RunUtil(); 474 } 475 476 /** Set the Clock instance to use. */ 477 @VisibleForTesting setClock(Clock clock)478 protected void setClock(Clock clock) { 479 mClock = clock; 480 } 481 482 /** 483 * {@inheritDoc} 484 */ 485 @Override setOptions(TestDeviceOptions options)486 public void setOptions(TestDeviceOptions options) { 487 throwIfNull(options); 488 mOptions = options; 489 if (mOptions.getFastbootBinary() != null) { 490 // Setup fastboot- if it's zipped, unzip it 491 // TODO: Dedup the logic with DeviceManager 492 if (".zip".equals(FileUtil.getExtension(mOptions.getFastbootBinary().getName()))) { 493 // Unzip the fastboot files 494 try { 495 mUnpackedFastbootDir = 496 ZipUtil2.extractZipToTemp( 497 mOptions.getFastbootBinary(), "unpacked-fastboot"); 498 File unpackedFastboot = FileUtil.findFile(mUnpackedFastbootDir, "fastboot"); 499 if (unpackedFastboot == null) { 500 throw new HarnessRuntimeException( 501 String.format( 502 "device-fastboot-binary was set, but didn't contain a" 503 + " fastboot binary."), 504 InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR); 505 } 506 setFastbootPath(unpackedFastboot.getAbsolutePath()); 507 } catch (IOException e) { 508 CLog.e("Failed to unpacked zipped fastboot."); 509 CLog.e(e); 510 FileUtil.recursiveDelete(mUnpackedFastbootDir); 511 mUnpackedFastbootDir = null; 512 } 513 } else { 514 setFastbootPath(mOptions.getFastbootBinary().getAbsolutePath()); 515 } 516 } 517 mStateMonitor.setDefaultOnlineTimeout(options.getOnlineTimeout()); 518 mStateMonitor.setDefaultAvailableTimeout(options.getAvailableTimeout()); 519 } 520 521 /** 522 * Sets the max size of a tmp logcat file. 523 * 524 * @param size max byte size of tmp file 525 */ setTmpLogcatSize(long size)526 void setTmpLogcatSize(long size) { 527 mOptions.setMaxLogcatDataSize(size); 528 } 529 530 /** 531 * Sets the time in ms to wait before starting logcat capture for a online device. 532 * 533 * @param delay the delay in ms 534 */ setLogStartDelay(int delay)535 public void setLogStartDelay(int delay) { 536 mLogStartDelay = delay; 537 } 538 539 /** {@inheritDoc} */ 540 @Override setConfiguration(IConfiguration configuration)541 public void setConfiguration(IConfiguration configuration) { 542 mConfiguration = configuration; 543 } 544 545 /** 546 * {@inheritDoc} 547 */ 548 @Override getIDevice()549 public IDevice getIDevice() { 550 synchronized (mIDevice) { 551 return mIDevice; 552 } 553 } 554 555 /** 556 * {@inheritDoc} 557 */ 558 @Override setIDevice(IDevice newDevice)559 public void setIDevice(IDevice newDevice) { 560 throwIfNull(newDevice); 561 IDevice currentDevice = mIDevice; 562 if (!getIDevice().equals(newDevice)) { 563 synchronized (currentDevice) { 564 mIDevice = newDevice; 565 } 566 mStateMonitor.setIDevice(mIDevice); 567 } 568 } 569 570 /** 571 * {@inheritDoc} 572 */ 573 @Override getSerialNumber()574 public String getSerialNumber() { 575 return getIDevice().getSerialNumber(); 576 } 577 578 /** 579 * Fetch a device property, from the ddmlib cache by default, and falling back to either `adb 580 * shell getprop` or `fastboot getvar` depending on whether the device is in Fastboot or not. 581 * 582 * @param propName The name of the device property as returned by `adb shell getprop` 583 * @param fastbootVar The name of the equivalent fastboot variable to query. if {@code null}, 584 * fastboot query will not be attempted 585 * @param description A simple description of the variable. First letter should be capitalized. 586 * @return A string, possibly {@code null} or empty, containing the value of the given property 587 */ internalGetProperty(String propName, String fastbootVar, String description)588 protected String internalGetProperty(String propName, String fastbootVar, String description) 589 throws DeviceNotAvailableException, UnsupportedOperationException { 590 if (isStateBootloaderOrFastbootd() && fastbootVar != null) { 591 CLog.i("Device %s is in fastboot mode, re-querying with '%s' for %s", getSerialNumber(), 592 fastbootVar, description); 593 return getFastbootVariable(fastbootVar); 594 } 595 String propValue = getProperty(propName); 596 if (propValue != null) { 597 return propValue; 598 } else { 599 CLog.d( 600 "property collection '%s' for device %s is null.", 601 description, getSerialNumber()); 602 return null; 603 } 604 } 605 606 /** 607 * {@inheritDoc} 608 */ 609 @Override getProperty(final String name)610 public String getProperty(final String name) throws DeviceNotAvailableException { 611 return getPropertyWithRecovery(name, false); 612 } 613 614 /** Version of getProperty that allows to check device status and trigger recovery if needed. */ getPropertyWithRecovery(final String name, boolean recovery)615 private String getPropertyWithRecovery(final String name, boolean recovery) 616 throws DeviceNotAvailableException { 617 if (getIDevice() instanceof StubDevice) { 618 return null; 619 } 620 String property = mPropertiesCache.getIfPresent(name); 621 if (property != null) { 622 CLog.d("Using property %s=%s from cache.", name, property); 623 return property; 624 } 625 try (CloseableTraceScope getProp = new CloseableTraceScope("get_property:" + name)) { 626 TestDeviceState state = getDeviceState(); 627 if (!TestDeviceState.ONLINE.equals(state) && !TestDeviceState.RECOVERY.equals(state)) { 628 if (recovery) { 629 // Only query property for online device so trigger recovery before getting 630 // property. 631 recoverDevice(); 632 } else { 633 if (mStateMonitor.waitForDeviceOnline() == null) { 634 CLog.w( 635 "Waited for device %s to be online but it is in state '%s', cannot " 636 + "get property %s.", 637 getSerialNumber(), getDeviceState(), name); 638 CLog.w( 639 new RuntimeException( 640 "This is not an actual exception but to help" 641 + " debugging. If this happens deterministically, " 642 + " it means the caller has wrong assumption of " 643 + " device state and is wasting time in waiting.")); 644 return null; 645 } 646 } 647 } 648 String cmd = String.format("getprop %s", name); 649 CommandResult result = 650 executeShellV2Command(cmd, PROPERTY_GET_TIMEOUT, TimeUnit.MILLISECONDS, 0); 651 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 652 CLog.e( 653 "Failed to run '%s' returning null. stdout: %s\nstderr: %s\nexit code: %s", 654 cmd, result.getStdout(), result.getStderr(), result.getExitCode()); 655 if (result.getStderr().contains("device offline")) { 656 if (recovery) { 657 recoverDevice(); 658 return getPropertyWithRecovery(name, false); 659 } 660 throw new DeviceNotAvailableException( 661 String.format("Device went offline when querying property: %s", name), 662 getSerialNumber(), 663 DeviceErrorIdentifier.DEVICE_UNAVAILABLE); 664 } 665 return null; 666 } 667 if (result.getStdout() == null || result.getStdout().trim().isEmpty()) { 668 return null; 669 } 670 property = result.getStdout().trim(); 671 if (property != null) { 672 if (!NEVER_CACHE_PROPERTIES.stream().anyMatch(p -> name.startsWith(p))) { 673 // Manage the cache manually to maintain exception handling 674 mPropertiesCache.put(name, property); 675 } 676 } 677 return property; 678 } 679 } 680 681 /** 682 * Micro optimization (about 400 millis) by prefetching all props we need rather than call 'adb 683 * getprop' for each one. i.e. It is just as fast to fetch all properties as it is to fetch one. 684 * Things like device.getApiLevel(), checkApiLevelAgainstNextRelease and getBuildAlias all call 685 * `adb getprop` under the hood. We fetch them in one call and call NativeDevice.setProperty. 686 * Even if we don't do this, NativeDevice will itself call setProperty and cache the result for 687 * future calls. We are just doing it slightly earlier. If the device is in recovery or there 688 * are other errors fetching the props, we just ignore them. 689 */ batchPrefetchStartupBuildProps()690 public void batchPrefetchStartupBuildProps() { 691 String cmd = "getprop"; 692 try (CloseableTraceScope ignored = new CloseableTraceScope("batchPrefetchProp")) { 693 // Skip refetching if we already have the props by counting the ones in the cache 694 // that we need to fetch. 695 int propsAlreadyPresent = 0; 696 for (String propName : propsToPrefetch) { 697 if (mPropertiesCache.getIfPresent(propName) != null) { 698 propsAlreadyPresent++; 699 } else { 700 break; 701 } 702 } 703 if (propsAlreadyPresent == propsToPrefetch.size()) { 704 return; 705 } 706 707 try { 708 CommandResult result = 709 executeShellV2Command(cmd, PROPERTY_GET_TIMEOUT, TimeUnit.MILLISECONDS, 0); 710 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 711 CLog.w( 712 "Failed to run '%s' returning null. stdout: %s\n" 713 + "stderr: %s\n" 714 + "exit code: %s", 715 cmd, result.getStdout(), result.getStderr(), result.getExitCode()); 716 if (result.getStdout() == null || result.getStdout().trim().isEmpty()) { 717 return; 718 } 719 } 720 for (String line : result.getStdout().split("\n")) { 721 String[] parts = line.trim().split("]: \\["); 722 if (parts.length != 2) { 723 continue; 724 } 725 String propName = parts[0].substring(1).trim(); 726 String propValue = parts[1].substring(0, parts[1].length() - 1).trim(); 727 if (propValue != null) { 728 if (propsToPrefetch.contains(propName)) { 729 mPropertiesCache.put(propName, propValue); 730 } 731 } 732 } 733 } catch (DeviceNotAvailableException e) { 734 // okay to ignore, the real get property will deal with it. 735 } 736 } 737 } 738 739 /** {@inheritDoc} */ 740 @Override getIntProperty(String name, long defaultValue)741 public long getIntProperty(String name, long defaultValue) throws DeviceNotAvailableException { 742 String value = getProperty(name); 743 if (value == null) { 744 return defaultValue; 745 } 746 try { 747 return Long.parseLong(value); 748 } catch (NumberFormatException e) { 749 return defaultValue; 750 } 751 } 752 753 private static final List<String> TRUE_VALUES = Arrays.asList("1", "y", "yes", "on", "true"); 754 private static final List<String> FALSE_VALUES = Arrays.asList("0", "n", "no", "off", "false"); 755 756 /** {@inheritDoc} */ 757 @Override getBooleanProperty(String name, boolean defaultValue)758 public boolean getBooleanProperty(String name, boolean defaultValue) 759 throws DeviceNotAvailableException { 760 String value = getProperty(name); 761 if (value == null) { 762 return defaultValue; 763 } 764 if (TRUE_VALUES.contains(value)) { 765 return true; 766 } 767 if (FALSE_VALUES.contains(value)) { 768 return false; 769 } 770 return defaultValue; 771 } 772 773 /** {@inheritDoc} */ 774 @Override setProperty(String propKey, String propValue)775 public boolean setProperty(String propKey, String propValue) 776 throws DeviceNotAvailableException { 777 if (propKey == null || propValue == null) { 778 throw new IllegalArgumentException("set property key or value cannot be null."); 779 } 780 String setPropCmd = String.format("\"setprop %s '%s'\"", propKey, propValue); 781 CommandResult result = executeShellV2Command(setPropCmd); 782 if (CommandStatus.SUCCESS.equals(result.getStatus())) { 783 mPropertiesCache.invalidate(propKey); 784 return true; 785 } 786 CLog.e( 787 "Something went wrong went setting property %s (command: %s): %s", 788 propKey, setPropCmd, result.getStderr()); 789 return false; 790 } 791 792 /** 793 * {@inheritDoc} 794 */ 795 @Override getBootloaderVersion()796 public String getBootloaderVersion() throws UnsupportedOperationException, 797 DeviceNotAvailableException { 798 return internalGetProperty("ro.bootloader", "version-bootloader", "Bootloader"); 799 } 800 801 @Override getBasebandVersion()802 public String getBasebandVersion() throws DeviceNotAvailableException { 803 return internalGetProperty("gsm.version.baseband", "version-baseband", "Baseband"); 804 } 805 806 /** 807 * {@inheritDoc} 808 */ 809 @Override getProductType()810 public String getProductType() throws DeviceNotAvailableException { 811 return internalGetProductType(MAX_RETRY_ATTEMPTS); 812 } 813 814 /** 815 * {@link #getProductType()} 816 * 817 * @param retryAttempts The number of times to try calling {@link #recoverDevice()} if the 818 * device's product type cannot be found. 819 */ internalGetProductType(int retryAttempts)820 private String internalGetProductType(int retryAttempts) throws DeviceNotAvailableException { 821 String productType = internalGetProperty(DeviceProperties.BOARD, "product", "Product type"); 822 // fallback to ro.hardware for legacy devices 823 if (Strings.isNullOrEmpty(productType)) { 824 productType = internalGetProperty(DeviceProperties.HARDWARE, "product", "Product type"); 825 } 826 827 // Things will likely break if we don't have a valid product type. Try recovery (in case 828 // the device is only partially booted for some reason), and if that doesn't help, bail. 829 if (Strings.isNullOrEmpty(productType)) { 830 if (retryAttempts > 0) { 831 recoverDevice(); 832 productType = internalGetProductType(retryAttempts - 1); 833 } 834 835 if (Strings.isNullOrEmpty(productType)) { 836 throw new DeviceNotAvailableException( 837 String.format( 838 "Could not determine product type for device %s.", 839 getSerialNumber()), 840 getSerialNumber(), 841 DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); 842 } 843 } 844 845 return productType.toLowerCase(); 846 } 847 848 /** 849 * {@inheritDoc} 850 */ 851 @Override getFastbootProductType()852 public String getFastbootProductType() 853 throws DeviceNotAvailableException, UnsupportedOperationException { 854 String prop = getFastbootVariable("product"); 855 if (prop != null) { 856 prop = prop.toLowerCase(); 857 } 858 return prop; 859 } 860 861 /** 862 * {@inheritDoc} 863 */ 864 @Override getProductVariant()865 public String getProductVariant() throws DeviceNotAvailableException { 866 String prop = internalGetProperty(DeviceProperties.VARIANT, "variant", "Product variant"); 867 if (prop == null) { 868 prop = 869 internalGetProperty( 870 DeviceProperties.VARIANT_LEGACY_O_MR1, "variant", "Product variant"); 871 } 872 if (prop == null) { 873 prop = 874 internalGetProperty( 875 DeviceProperties.VARIANT_LEGACY_LESS_EQUAL_O, 876 "variant", 877 "Product variant"); 878 } 879 if (prop != null) { 880 prop = prop.toLowerCase(); 881 } 882 return prop; 883 } 884 885 /** 886 * {@inheritDoc} 887 */ 888 @Override getFastbootProductVariant()889 public String getFastbootProductVariant() 890 throws DeviceNotAvailableException, UnsupportedOperationException { 891 String prop = getFastbootVariable("variant"); 892 if (prop != null) { 893 prop = prop.toLowerCase(); 894 } 895 return prop; 896 } 897 898 /** {@inheritDoc} */ 899 @Override getFastbootVariable(String variableName)900 public String getFastbootVariable(String variableName) 901 throws DeviceNotAvailableException, UnsupportedOperationException { 902 CommandResult result = executeFastbootCommand("getvar", variableName); 903 CLog.d( 904 "(getvar %s) output:\nstdout%s\nstderr:%s", 905 variableName, result.getStdout(), result.getStderr()); 906 if (result.getStatus() == CommandStatus.SUCCESS) { 907 Pattern fastbootProductPattern = Pattern.compile(variableName + ":\\s(.*)\\s"); 908 // fastboot is weird, and may dump the output on stderr instead of stdout 909 String resultText = result.getStdout(); 910 if (resultText == null || resultText.length() < 1) { 911 resultText = result.getStderr(); 912 } 913 Matcher matcher = fastbootProductPattern.matcher(resultText); 914 if (matcher.find()) { 915 return matcher.group(1); 916 } 917 } 918 return null; 919 } 920 921 /** 922 * {@inheritDoc} 923 */ 924 @Override getBuildAlias()925 public String getBuildAlias() throws DeviceNotAvailableException { 926 String alias = getProperty(DeviceProperties.BUILD_ALIAS); 927 if (alias == null || alias.isEmpty()) { 928 return getBuildId(); 929 } 930 return alias; 931 } 932 933 /** 934 * {@inheritDoc} 935 */ 936 @Override getBuildId()937 public String getBuildId() throws DeviceNotAvailableException { 938 String bid = getProperty(DeviceProperties.BUILD_ID); 939 if (bid == null) { 940 CLog.w("Could not get device %s build id.", getSerialNumber()); 941 return IBuildInfo.UNKNOWN_BUILD_ID; 942 } 943 return bid; 944 } 945 946 /** 947 * {@inheritDoc} 948 */ 949 @Override getBuildFlavor()950 public String getBuildFlavor() throws DeviceNotAvailableException { 951 String buildFlavor = getProperty(DeviceProperties.BUILD_FLAVOR); 952 if (buildFlavor != null && !buildFlavor.isEmpty()) { 953 return buildFlavor; 954 } 955 String productName = getProperty(DeviceProperties.PRODUCT); 956 String buildType = getProperty(DeviceProperties.BUILD_TYPE); 957 if (productName == null || buildType == null) { 958 CLog.w("Could not get device %s build flavor.", getSerialNumber()); 959 return null; 960 } 961 return String.format("%s-%s", productName, buildType); 962 } 963 964 /** 965 * {@inheritDoc} 966 */ 967 @Override executeShellCommand(final String command, final IShellOutputReceiver receiver)968 public void executeShellCommand(final String command, final IShellOutputReceiver receiver) 969 throws DeviceNotAvailableException { 970 DeviceAction action = new DeviceAction() { 971 @Override 972 public boolean run() throws TimeoutException, IOException, 973 AdbCommandRejectedException, ShellCommandUnresponsiveException { 974 getIDevice().executeShellCommand(command, receiver, 975 getCommandTimeout(), TimeUnit.MILLISECONDS); 976 return true; 977 } 978 }; 979 performDeviceAction(String.format("shell %s", command), action, MAX_RETRY_ATTEMPTS); 980 } 981 982 /** 983 * {@inheritDoc} 984 */ 985 @Override executeShellCommand(final String command, final IShellOutputReceiver receiver, final long maxTimeToOutputShellResponse, final TimeUnit timeUnit, final int retryAttempts)986 public void executeShellCommand(final String command, final IShellOutputReceiver receiver, 987 final long maxTimeToOutputShellResponse, final TimeUnit timeUnit, 988 final int retryAttempts) throws DeviceNotAvailableException { 989 DeviceAction action = new DeviceAction() { 990 @Override 991 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException, 992 ShellCommandUnresponsiveException { 993 getIDevice().executeShellCommand(command, receiver, 994 maxTimeToOutputShellResponse, timeUnit); 995 return true; 996 } 997 }; 998 performDeviceAction(String.format("shell %s", command), action, retryAttempts); 999 } 1000 1001 /** {@inheritDoc} */ 1002 @Override executeShellCommand( final String command, final IShellOutputReceiver receiver, final long maxTimeoutForCommand, final long maxTimeToOutputShellResponse, final TimeUnit timeUnit, final int retryAttempts)1003 public void executeShellCommand( 1004 final String command, 1005 final IShellOutputReceiver receiver, 1006 final long maxTimeoutForCommand, 1007 final long maxTimeToOutputShellResponse, 1008 final TimeUnit timeUnit, 1009 final int retryAttempts) 1010 throws DeviceNotAvailableException { 1011 DeviceAction action = 1012 new DeviceAction() { 1013 @Override 1014 public boolean run() 1015 throws TimeoutException, IOException, AdbCommandRejectedException, 1016 ShellCommandUnresponsiveException { 1017 getIDevice() 1018 .executeShellCommand( 1019 command, 1020 receiver, 1021 maxTimeoutForCommand, 1022 maxTimeToOutputShellResponse, 1023 timeUnit); 1024 return true; 1025 } 1026 }; 1027 performDeviceAction(String.format("shell %s", command), action, retryAttempts); 1028 } 1029 1030 /** 1031 * {@inheritDoc} 1032 */ 1033 @Override executeShellCommand(String command)1034 public String executeShellCommand(String command) throws DeviceNotAvailableException { 1035 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 1036 executeShellCommand(command, receiver); 1037 String output = receiver.getOutput(); 1038 if (mExecuteShellCommandLogs != null) { 1039 // Log all output to a dedicated file as it can be very verbose. 1040 String formatted = 1041 LogUtil.getLogFormatString( 1042 LogLevel.VERBOSE, 1043 "NativeDevice", 1044 String.format( 1045 "%s on %s returned %s\n==== END OF OUTPUT ====\n", 1046 command, getSerialNumber(), output)); 1047 try { 1048 FileUtil.writeToFile(formatted, mExecuteShellCommandLogs, true); 1049 } catch (IOException e) { 1050 // Ignore the full error 1051 CLog.e("Failed to log to executeShellCommand log: %s", e.getMessage()); 1052 } 1053 } 1054 if (output.length() > 80) { 1055 CLog.v( 1056 "%s on %s returned %s <truncated - See executeShellCommand log for full trace>", 1057 command, getSerialNumber(), output.substring(0, 80)); 1058 } else { 1059 CLog.v("%s on %s returned %s", command, getSerialNumber(), output); 1060 } 1061 return output; 1062 } 1063 1064 /** {@inheritDoc} */ 1065 @Override executeShellV2Command(String cmd)1066 public CommandResult executeShellV2Command(String cmd) throws DeviceNotAvailableException { 1067 return executeShellV2Command(cmd, getCommandTimeout(), TimeUnit.MILLISECONDS); 1068 } 1069 1070 /** {@inheritDoc} */ 1071 @Override executeShellV2Command(String cmd, File pipeAsInput)1072 public CommandResult executeShellV2Command(String cmd, File pipeAsInput) 1073 throws DeviceNotAvailableException { 1074 return executeShellV2Command( 1075 cmd, 1076 pipeAsInput, 1077 null, 1078 getCommandTimeout(), 1079 TimeUnit.MILLISECONDS, 1080 MAX_RETRY_ATTEMPTS); 1081 } 1082 1083 /** {@inheritDoc} */ 1084 @Override executeShellV2Command(String cmd, OutputStream pipeToOutput)1085 public CommandResult executeShellV2Command(String cmd, OutputStream pipeToOutput) 1086 throws DeviceNotAvailableException { 1087 return executeShellV2Command( 1088 cmd, 1089 null, 1090 pipeToOutput, 1091 getCommandTimeout(), 1092 TimeUnit.MILLISECONDS, 1093 MAX_RETRY_ATTEMPTS); 1094 } 1095 1096 /** {@inheritDoc} */ 1097 @Override executeShellV2Command( String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit)1098 public CommandResult executeShellV2Command( 1099 String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit) 1100 throws DeviceNotAvailableException { 1101 return executeShellV2Command( 1102 cmd, null, null, maxTimeoutForCommand, timeUnit, MAX_RETRY_ATTEMPTS); 1103 } 1104 1105 /** {@inheritDoc} */ 1106 @Override executeShellV2Command( String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)1107 public CommandResult executeShellV2Command( 1108 String cmd, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts) 1109 throws DeviceNotAvailableException { 1110 return executeShellV2Command( 1111 cmd, null, null, maxTimeoutForCommand, timeUnit, retryAttempts); 1112 } 1113 1114 /** {@inheritDoc} */ 1115 @Override executeShellV2Command( String cmd, File pipeAsInput, OutputStream pipeToOutput, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)1116 public CommandResult executeShellV2Command( 1117 String cmd, 1118 File pipeAsInput, 1119 OutputStream pipeToOutput, 1120 final long maxTimeoutForCommand, 1121 final TimeUnit timeUnit, 1122 int retryAttempts) 1123 throws DeviceNotAvailableException { 1124 return executeShellV2Command( 1125 cmd, 1126 pipeAsInput, 1127 pipeToOutput, 1128 /*pipeToError=*/ null, 1129 maxTimeoutForCommand, 1130 timeUnit, 1131 retryAttempts); 1132 } 1133 1134 /** {@inheritDoc} */ 1135 @Override executeShellV2Command( String cmd, File pipeAsInput, OutputStream pipeToOutput, OutputStream pipeToError, final long maxTimeoutForCommand, final TimeUnit timeUnit, int retryAttempts)1136 public CommandResult executeShellV2Command( 1137 String cmd, 1138 File pipeAsInput, 1139 OutputStream pipeToOutput, 1140 OutputStream pipeToError, 1141 final long maxTimeoutForCommand, 1142 final TimeUnit timeUnit, 1143 int retryAttempts) 1144 throws DeviceNotAvailableException { 1145 // If the device does not support the v2 shell features. Exit status will not be propagated 1146 // and stdout/stderr will be merged in stdout. 1147 // 1148 // There's nothing we can do to separate the two streams, but we can alter the command to 1149 // retrieve the exit status. 1150 // 1151 // Note that this does *not* call `adb features` on each invocation. ddmlib caches all the 1152 // adb features on the first query. 1153 boolean parseExitStatus = 1154 !getIDevice().supportsFeature(IDevice.Feature.SHELL_V2) 1155 && getOptions().useExitStatusWorkaround(); 1156 1157 final String[] fullCmd = buildAdbShellCommand(cmd, parseExitStatus); 1158 AdbShellAction adbActionV2 = 1159 new AdbShellAction( 1160 fullCmd, 1161 pipeAsInput, 1162 pipeToOutput, 1163 pipeToError, 1164 timeUnit.toMillis(maxTimeoutForCommand)); 1165 performDeviceAction(String.format("adb %s", fullCmd[4]), adbActionV2, retryAttempts); 1166 if (parseExitStatus) { 1167 postProcessExitStatus(adbActionV2.mResult); 1168 } 1169 return adbActionV2.mResult; 1170 } 1171 postProcessExitStatus(@onnull CommandResult result)1172 private void postProcessExitStatus(@Nonnull CommandResult result) { 1173 String stdout = result.getStdout(); 1174 int delimiterIndex = stdout.lastIndexOf(EXIT_STATUS_DELIMITER); 1175 if (delimiterIndex < 0) { 1176 result.setStatus(CommandStatus.FAILED); 1177 return; 1178 } 1179 String actualStdout = stdout.substring(0, delimiterIndex); 1180 String exitStatusText = stdout.substring(delimiterIndex + 1); 1181 result.setExitCode(Integer.parseUnsignedInt(exitStatusText.trim())); 1182 result.setStdout(actualStdout); 1183 if (result.getStatus() == CommandStatus.SUCCESS && result.getExitCode() != 0) { 1184 result.setStatus(CommandStatus.FAILED); 1185 } 1186 } 1187 1188 /** {@inheritDoc} */ 1189 @Override runInstrumentationTests( final IRemoteAndroidTestRunner runner, final Collection<ITestLifeCycleReceiver> listeners)1190 public boolean runInstrumentationTests( 1191 final IRemoteAndroidTestRunner runner, 1192 final Collection<ITestLifeCycleReceiver> listeners) 1193 throws DeviceNotAvailableException { 1194 RunFailureListener failureListener = new RunFailureListener(); 1195 List<ITestRunListener> runListeners = new ArrayList<>(); 1196 runListeners.add(failureListener); 1197 runListeners.add(new TestRunToTestInvocationForwarder(listeners)); 1198 1199 if ((mConfiguration != null) 1200 && mConfiguration.getCoverageOptions().isCoverageEnabled() 1201 && mConfiguration 1202 .getCoverageOptions() 1203 .getCoverageToolchains() 1204 .contains(Toolchain.JACOCO)) { 1205 runner.setCoverage(true); 1206 } 1207 1208 DeviceAction runTestsAction = 1209 new DeviceAction() { 1210 @Override 1211 public boolean run() 1212 throws IOException, TimeoutException, AdbCommandRejectedException, 1213 ShellCommandUnresponsiveException, InstallException, 1214 SyncException { 1215 runner.run(runListeners); 1216 return true; 1217 } 1218 }; 1219 boolean result = performDeviceAction(String.format("run %s instrumentation tests", 1220 runner.getPackageName()), runTestsAction, 0); 1221 if (failureListener.isRunFailure()) { 1222 waitForDeviceAvailable(); 1223 } 1224 return result; 1225 } 1226 1227 /** {@inheritDoc} */ 1228 @Override runInstrumentationTests( IRemoteAndroidTestRunner runner, ITestLifeCycleReceiver... listeners)1229 public boolean runInstrumentationTests( 1230 IRemoteAndroidTestRunner runner, ITestLifeCycleReceiver... listeners) 1231 throws DeviceNotAvailableException { 1232 List<ITestLifeCycleReceiver> listenerList = new ArrayList<>(); 1233 listenerList.addAll(Arrays.asList(listeners)); 1234 return runInstrumentationTests(runner, listenerList); 1235 } 1236 1237 /** {@inheritDoc} */ 1238 @Override runInstrumentationTestsAsUser( final IRemoteAndroidTestRunner runner, int userId, final Collection<ITestLifeCycleReceiver> listeners)1239 public boolean runInstrumentationTestsAsUser( 1240 final IRemoteAndroidTestRunner runner, 1241 int userId, 1242 final Collection<ITestLifeCycleReceiver> listeners) 1243 throws DeviceNotAvailableException { 1244 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId); 1245 boolean result = runInstrumentationTests(runner, listeners); 1246 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions); 1247 return result; 1248 } 1249 1250 /** {@inheritDoc} */ 1251 @Override runInstrumentationTestsAsUser( IRemoteAndroidTestRunner runner, int userId, ITestLifeCycleReceiver... listeners)1252 public boolean runInstrumentationTestsAsUser( 1253 IRemoteAndroidTestRunner runner, int userId, ITestLifeCycleReceiver... listeners) 1254 throws DeviceNotAvailableException { 1255 String oldRunTimeOptions = appendUserRunTimeOptionToRunner(runner, userId); 1256 boolean result = runInstrumentationTests(runner, listeners); 1257 resetUserRunTimeOptionToRunner(runner, oldRunTimeOptions); 1258 return result; 1259 } 1260 1261 /** 1262 * Helper method to add user run time option to {@link RemoteAndroidTestRunner} 1263 * 1264 * @param runner {@link IRemoteAndroidTestRunner} 1265 * @param userId the integer of the user id to run as. 1266 * @return original run time options. 1267 */ appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId)1268 private String appendUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, int userId) { 1269 if (runner instanceof RemoteAndroidTestRunner) { 1270 String original = ((RemoteAndroidTestRunner) runner).getRunOptions(); 1271 String userRunTimeOption = String.format("--user %s", Integer.toString(userId)); 1272 String updated = (original != null) ? (original + " " + userRunTimeOption) 1273 : userRunTimeOption; 1274 ((RemoteAndroidTestRunner) runner).setRunOptions(updated); 1275 return original; 1276 } else { 1277 throw new IllegalStateException(String.format("%s runner does not support multi-user", 1278 runner.getClass().getName())); 1279 } 1280 } 1281 1282 /** 1283 * Helper method to reset the run time options to {@link RemoteAndroidTestRunner} 1284 * 1285 * @param runner {@link IRemoteAndroidTestRunner} 1286 * @param oldRunTimeOptions 1287 */ resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, String oldRunTimeOptions)1288 private void resetUserRunTimeOptionToRunner(final IRemoteAndroidTestRunner runner, 1289 String oldRunTimeOptions) { 1290 if (runner instanceof RemoteAndroidTestRunner) { 1291 if (oldRunTimeOptions != null) { 1292 ((RemoteAndroidTestRunner) runner).setRunOptions(oldRunTimeOptions); 1293 } 1294 } else { 1295 throw new IllegalStateException(String.format("%s runner does not support multi-user", 1296 runner.getClass().getName())); 1297 } 1298 } 1299 1300 private static class RunFailureListener extends StubTestRunListener { 1301 private boolean mIsRunFailure = false; 1302 1303 @Override testRunFailed(String message)1304 public void testRunFailed(String message) { 1305 mIsRunFailure = true; 1306 } 1307 isRunFailure()1308 public boolean isRunFailure() { 1309 return mIsRunFailure; 1310 } 1311 } 1312 1313 /** 1314 * {@inheritDoc} 1315 */ 1316 @Override isRuntimePermissionSupported()1317 public boolean isRuntimePermissionSupported() throws DeviceNotAvailableException { 1318 int apiLevel = getApiLevel(); 1319 boolean condition = apiLevel > 22; 1320 if (!condition) { 1321 CLog.w( 1322 "isRuntimePermissionSupported requires api level above 22, device reported " 1323 + "'%s'", 1324 apiLevel); 1325 } 1326 return condition; 1327 } 1328 1329 /** 1330 * {@inheritDoc} 1331 */ 1332 @Override isAppEnumerationSupported()1333 public boolean isAppEnumerationSupported() throws DeviceNotAvailableException { 1334 return false; 1335 } 1336 1337 /** {@inheritDoc} */ 1338 @Override isBypassLowTargetSdkBlockSupported()1339 public boolean isBypassLowTargetSdkBlockSupported() throws DeviceNotAvailableException { 1340 return checkApiLevelAgainstNextRelease(34); 1341 } 1342 1343 /** 1344 * helper method to throw exception if runtime permission isn't supported 1345 * @throws DeviceNotAvailableException 1346 */ ensureRuntimePermissionSupported()1347 protected void ensureRuntimePermissionSupported() throws DeviceNotAvailableException { 1348 boolean runtimePermissionSupported = isRuntimePermissionSupported(); 1349 if (!runtimePermissionSupported) { 1350 throw new UnsupportedOperationException( 1351 "platform on device does not support runtime permission granting!"); 1352 } 1353 } 1354 1355 /** 1356 * {@inheritDoc} 1357 */ 1358 @Override installPackage(final File packageFile, final boolean reinstall, final String... extraArgs)1359 public String installPackage(final File packageFile, final boolean reinstall, 1360 final String... extraArgs) throws DeviceNotAvailableException { 1361 throw new UnsupportedOperationException("No support for Package Manager's features"); 1362 } 1363 1364 /** 1365 * {@inheritDoc} 1366 */ 1367 @Override installPackage(File packageFile, boolean reinstall, boolean grantPermissions, String... extraArgs)1368 public String installPackage(File packageFile, boolean reinstall, boolean grantPermissions, 1369 String... extraArgs) throws DeviceNotAvailableException { 1370 throw new UnsupportedOperationException("No support for Package Manager's features"); 1371 } 1372 1373 /** 1374 * {@inheritDoc} 1375 */ 1376 @Override installPackageForUser(File packageFile, boolean reinstall, int userId, String... extraArgs)1377 public String installPackageForUser(File packageFile, boolean reinstall, int userId, 1378 String... extraArgs) throws DeviceNotAvailableException { 1379 throw new UnsupportedOperationException("No support for Package Manager's features"); 1380 } 1381 1382 /** 1383 * {@inheritDoc} 1384 */ 1385 @Override installPackageForUser(File packageFile, boolean reinstall, boolean grantPermissions, int userId, String... extraArgs)1386 public String installPackageForUser(File packageFile, boolean reinstall, 1387 boolean grantPermissions, int userId, String... extraArgs) 1388 throws DeviceNotAvailableException { 1389 throw new UnsupportedOperationException("No support for Package Manager's features"); 1390 } 1391 1392 /** 1393 * {@inheritDoc} 1394 */ 1395 @Override uninstallPackage(final String packageName)1396 public String uninstallPackage(final String packageName) throws DeviceNotAvailableException { 1397 throw new UnsupportedOperationException("No support for Package Manager's features"); 1398 } 1399 1400 /** {@inheritDoc} */ 1401 @Override uninstallPackageForUser(final String packageName, int userId)1402 public String uninstallPackageForUser(final String packageName, int userId) 1403 throws DeviceNotAvailableException { 1404 throw new UnsupportedOperationException("No support for Package Manager's features"); 1405 } 1406 1407 /** {@inheritDoc} */ 1408 @Override pullFile(final String remoteFilePath, final File localFile, int userId)1409 public boolean pullFile(final String remoteFilePath, final File localFile, int userId) 1410 throws DeviceNotAvailableException { 1411 long startTime = System.currentTimeMillis(); 1412 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.PULL_FILE_COUNT, 1); 1413 1414 try { 1415 if (isSdcardOrEmulated(remoteFilePath) && userId != 0) { 1416 ContentProviderHandler handler = getContentProvider(userId); 1417 if (handler != null) { 1418 return handler.pullFile(remoteFilePath, localFile); 1419 } 1420 } 1421 return pullFileInternal(remoteFilePath, localFile); 1422 } finally { 1423 long totalTime = System.currentTimeMillis() - startTime; 1424 InvocationMetricLogger.addInvocationMetrics( 1425 InvocationMetricKey.PULL_FILE_TIME, totalTime); 1426 } 1427 } 1428 1429 /** {@inheritDoc} */ 1430 @Override pullFile(final String remoteFilePath, final File localFile)1431 public boolean pullFile(final String remoteFilePath, final File localFile) 1432 throws DeviceNotAvailableException { 1433 return pullFile(remoteFilePath, localFile, getCurrentUserCompatible()); 1434 } 1435 1436 /** {@inheritDoc} */ 1437 @Override pullFile(String remoteFilePath, int userId)1438 public File pullFile(String remoteFilePath, int userId) throws DeviceNotAvailableException { 1439 File localFile = null; 1440 boolean success = false; 1441 try { 1442 localFile = FileUtil.createTempFileForRemote(remoteFilePath, null); 1443 if (pullFile(remoteFilePath, localFile, userId)) { 1444 success = true; 1445 return localFile; 1446 } 1447 } catch (IOException e) { 1448 CLog.w("Encountered IOException while trying to pull '%s':", remoteFilePath); 1449 CLog.e(e); 1450 } finally { 1451 if (!success) { 1452 FileUtil.deleteFile(localFile); 1453 } 1454 } 1455 return null; 1456 } 1457 1458 /** {@inheritDoc} */ 1459 @Override pullFile(String remoteFilePath)1460 public File pullFile(String remoteFilePath) throws DeviceNotAvailableException { 1461 return pullFile(remoteFilePath, getCurrentUserCompatible()); 1462 } 1463 1464 /** 1465 * {@inheritDoc} 1466 */ 1467 @Override pullFileContents(String remoteFilePath)1468 public String pullFileContents(String remoteFilePath) throws DeviceNotAvailableException { 1469 File temp = pullFile(remoteFilePath); 1470 1471 if (temp != null) { 1472 try { 1473 return FileUtil.readStringFromFile(temp); 1474 } catch (IOException e) { 1475 CLog.e(String.format("Could not pull file: %s", remoteFilePath)); 1476 } finally { 1477 FileUtil.deleteFile(temp); 1478 } 1479 } 1480 1481 return null; 1482 } 1483 1484 /** 1485 * {@inheritDoc} 1486 */ 1487 @Override pullFileFromExternal(String remoteFilePath)1488 public File pullFileFromExternal(String remoteFilePath) throws DeviceNotAvailableException { 1489 String externalPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE); 1490 String fullPath = new File(externalPath, remoteFilePath).getPath(); 1491 return pullFile(fullPath); 1492 } 1493 pullFileInternal(String remoteFilePath, File localFile)1494 protected boolean pullFileInternal(String remoteFilePath, File localFile) 1495 throws DeviceNotAvailableException { 1496 DeviceAction pullAction = 1497 new DeviceAction() { 1498 @Override 1499 public boolean run() 1500 throws TimeoutException, IOException, AdbCommandRejectedException, 1501 SyncException { 1502 SyncService syncService = null; 1503 boolean status = false; 1504 try { 1505 syncService = getIDevice().getSyncService(); 1506 syncService.pullFile( 1507 interpolatePathVariables(remoteFilePath), 1508 localFile.getAbsolutePath(), 1509 SyncService.getNullProgressMonitor()); 1510 status = true; 1511 } catch (SyncException e) { 1512 CLog.w( 1513 "Failed to pull %s from %s to %s. Message %s", 1514 remoteFilePath, 1515 getSerialNumber(), 1516 localFile.getAbsolutePath(), 1517 e.getMessage()); 1518 throw e; 1519 } finally { 1520 if (syncService != null) { 1521 syncService.close(); 1522 } 1523 } 1524 return status; 1525 } 1526 }; 1527 return performDeviceAction( 1528 String.format("pull %s to %s", remoteFilePath, localFile.getAbsolutePath()), 1529 pullAction, 1530 MAX_RETRY_ATTEMPTS); 1531 } 1532 1533 /** 1534 * Helper function that watches for the string "${EXTERNAL_STORAGE}" and replaces it with the 1535 * pathname of the EXTERNAL_STORAGE mountpoint. Specifically intended to be used for pathnames 1536 * that are being passed to SyncService, which does not support variables inside of filenames. 1537 */ interpolatePathVariables(String path)1538 String interpolatePathVariables(String path) { 1539 final String esString = "${EXTERNAL_STORAGE}"; 1540 if (path.contains(esString)) { 1541 final String esPath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE); 1542 path = path.replace(esString, esPath); 1543 } 1544 return path; 1545 } 1546 1547 /** 1548 * {@inheritDoc} 1549 */ 1550 @Override pushFile(final File localFile, final String remoteFilePath)1551 public boolean pushFile(final File localFile, final String remoteFilePath) 1552 throws DeviceNotAvailableException { 1553 return pushFile(localFile, remoteFilePath, getCurrentUserCompatible()); 1554 } 1555 1556 @Override pushFile(final File localFile, final String remoteFilePath, int userId)1557 public boolean pushFile(final File localFile, final String remoteFilePath, int userId) 1558 throws DeviceNotAvailableException { 1559 return pushFileInternal(localFile, remoteFilePath, false, userId); 1560 } 1561 1562 @Override pushFile( final File localFile, final String remoteFilePath, boolean evaluateContentProviderNeeded)1563 public boolean pushFile( 1564 final File localFile, 1565 final String remoteFilePath, 1566 boolean evaluateContentProviderNeeded) 1567 throws DeviceNotAvailableException { 1568 boolean skipContentProvider = false; 1569 int userId = getCurrentUserCompatible(); 1570 if (evaluateContentProviderNeeded) { 1571 skipContentProvider = userId == 0; 1572 } 1573 return pushFileInternal(localFile, remoteFilePath, skipContentProvider, userId); 1574 } 1575 1576 @VisibleForTesting pushFileInternal( final File localFile, final String remoteFilePath, boolean skipContentProvider, int userId)1577 boolean pushFileInternal( 1578 final File localFile, 1579 final String remoteFilePath, 1580 boolean skipContentProvider, 1581 int userId) 1582 throws DeviceNotAvailableException { 1583 long startTime = System.currentTimeMillis(); 1584 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.PUSH_FILE_COUNT, 1); 1585 try { 1586 if (!skipContentProvider) { 1587 if (isSdcardOrEmulated(remoteFilePath)) { 1588 ContentProviderHandler handler = getContentProvider(userId); 1589 if (handler != null) { 1590 return handler.pushFile(localFile, remoteFilePath); 1591 } 1592 } 1593 } 1594 1595 DeviceAction pushAction = 1596 new DeviceAction() { 1597 @Override 1598 public boolean run() 1599 throws TimeoutException, IOException, AdbCommandRejectedException, 1600 SyncException { 1601 SyncService syncService = null; 1602 boolean status = false; 1603 try { 1604 syncService = getIDevice().getSyncService(); 1605 if (syncService == null) { 1606 throw new IOException("SyncService returned null."); 1607 } 1608 syncService.pushFile( 1609 localFile.getAbsolutePath(), 1610 interpolatePathVariables(remoteFilePath), 1611 SyncService.getNullProgressMonitor()); 1612 status = true; 1613 } catch (SyncException e) { 1614 CLog.w( 1615 "Failed to push %s to %s on device %s. Message: '%s'. " 1616 + "Error code: %s", 1617 localFile.getAbsolutePath(), 1618 remoteFilePath, 1619 getSerialNumber(), 1620 e.getMessage(), 1621 e.getErrorCode()); 1622 // TODO: check if ddmlib can report a better error 1623 if (SyncError.TRANSFER_PROTOCOL_ERROR.equals(e.getErrorCode())) { 1624 if (e.getMessage().contains("Permission denied")) { 1625 return false; 1626 } 1627 } 1628 throw e; 1629 } finally { 1630 if (syncService != null) { 1631 syncService.close(); 1632 } 1633 } 1634 return status; 1635 } 1636 }; 1637 return performDeviceAction( 1638 String.format("push %s to %s", localFile.getAbsolutePath(), remoteFilePath), 1639 pushAction, 1640 MAX_RETRY_ATTEMPTS); 1641 } finally { 1642 long totalTime = System.currentTimeMillis() - startTime; 1643 InvocationMetricLogger.addInvocationMetrics( 1644 InvocationMetricKey.PUSH_FILE_TIME, totalTime); 1645 } 1646 } 1647 1648 /** 1649 * {@inheritDoc} 1650 */ 1651 @Override pushString(final String contents, final String remoteFilePath)1652 public boolean pushString(final String contents, final String remoteFilePath) 1653 throws DeviceNotAvailableException { 1654 File tmpFile = null; 1655 try { 1656 tmpFile = FileUtil.createTempFile("temp", ".txt"); 1657 FileUtil.writeToFile(contents, tmpFile); 1658 return pushFile(tmpFile, remoteFilePath); 1659 } catch (IOException e) { 1660 CLog.e(e); 1661 return false; 1662 } finally { 1663 FileUtil.deleteFile(tmpFile); 1664 } 1665 } 1666 1667 /** {@inheritDoc} */ 1668 @Override doesFileExist(String deviceFilePath)1669 public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException { 1670 return doesFileExist(deviceFilePath, getCurrentUserCompatible()); 1671 } 1672 1673 /** {@inheritDoc} */ 1674 @Override doesFileExist(String deviceFilePath, int userId)1675 public boolean doesFileExist(String deviceFilePath, int userId) 1676 throws DeviceNotAvailableException { 1677 long startTime = System.currentTimeMillis(); 1678 try { 1679 // Skip ContentProvider for user 0 1680 if (isSdcardOrEmulated(deviceFilePath) && userId != 0) { 1681 ContentProviderHandler handler = getContentProvider(userId); 1682 if (handler != null) { 1683 CLog.d("Delegating check to ContentProvider doesFileExist(%s)", deviceFilePath); 1684 return handler.doesFileExist(deviceFilePath); 1685 } 1686 } 1687 CLog.d("Using 'ls' to check doesFileExist(%s)", deviceFilePath); 1688 String lsGrep = executeShellCommand(String.format("ls \"%s\"", deviceFilePath)); 1689 return !lsGrep.contains("No such file or directory"); 1690 } finally { 1691 InvocationMetricLogger.addInvocationMetrics( 1692 InvocationMetricKey.DOES_FILE_EXISTS_TIME, 1693 System.currentTimeMillis() - startTime); 1694 InvocationMetricLogger.addInvocationMetrics( 1695 InvocationMetricKey.DOES_FILE_EXISTS_COUNT, 1); 1696 } 1697 } 1698 1699 @Override registerDeviceActionReceiver(IDeviceActionReceiver deviceActionReceiver)1700 public void registerDeviceActionReceiver(IDeviceActionReceiver deviceActionReceiver) { 1701 mDeviceActionReceivers.add(deviceActionReceiver); 1702 } 1703 1704 @Override deregisterDeviceActionReceiver(IDeviceActionReceiver deviceActionReceiver)1705 public void deregisterDeviceActionReceiver(IDeviceActionReceiver deviceActionReceiver) { 1706 mDeviceActionReceivers.remove(deviceActionReceiver); 1707 } 1708 1709 /** {@inheritDoc} */ 1710 @Override deleteFile(String deviceFilePath)1711 public void deleteFile(String deviceFilePath) throws DeviceNotAvailableException { 1712 deleteFile(deviceFilePath, getCurrentUserCompatible()); 1713 } 1714 1715 /** {@inheritDoc} */ 1716 @Override deleteFile(String deviceFilePath, int userId)1717 public void deleteFile(String deviceFilePath, int userId) throws DeviceNotAvailableException { 1718 long startTime = System.currentTimeMillis(); 1719 try { 1720 if (isSdcardOrEmulated(deviceFilePath)) { 1721 if (userId != 0) { 1722 ContentProviderHandler handler = getContentProvider(userId); 1723 if (handler != null) { 1724 if (handler.deleteFile(deviceFilePath)) { 1725 return; 1726 } 1727 } 1728 } 1729 } 1730 // Fallback to the direct command if content provider is unsuccessful 1731 String path = StringEscapeUtils.escapeShell(deviceFilePath); 1732 // Escape spaces to handle filename with spaces 1733 path = path.replaceAll(" ", "\\ "); 1734 executeShellCommand(String.format("rm -rf %s", StringEscapeUtils.escapeShell(path))); 1735 } finally { 1736 InvocationMetricLogger.addInvocationMetrics( 1737 InvocationMetricKey.DELETE_DEVICE_FILE_TIME, 1738 System.currentTimeMillis() - startTime); 1739 InvocationMetricLogger.addInvocationMetrics( 1740 InvocationMetricKey.DELETE_DEVICE_FILE_COUNT, 1); 1741 } 1742 } 1743 1744 /** 1745 * {@inheritDoc} 1746 */ 1747 @Override getExternalStoreFreeSpace()1748 public long getExternalStoreFreeSpace() throws DeviceNotAvailableException { 1749 String externalStorePath = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE); 1750 return getPartitionFreeSpace(externalStorePath); 1751 } 1752 1753 /** {@inheritDoc} */ 1754 @Override getPartitionFreeSpace(String partition)1755 public long getPartitionFreeSpace(String partition) throws DeviceNotAvailableException { 1756 CLog.i("Checking free space for %s on partition %s", getSerialNumber(), partition); 1757 String output = getDfOutput(partition); 1758 // Try coreutils/toybox style output first. 1759 Long available = parseFreeSpaceFromModernOutput(output); 1760 if (available != null) { 1761 return available; 1762 } 1763 // Then the two legacy toolbox formats. 1764 available = parseFreeSpaceFromAvailable(output); 1765 if (available != null) { 1766 return available; 1767 } 1768 available = parseFreeSpaceFromFree(partition, output); 1769 if (available != null) { 1770 return available; 1771 } 1772 1773 CLog.e("free space command output \"%s\" did not match expected patterns", output); 1774 return 0; 1775 } 1776 1777 /** 1778 * Run the 'df' shell command and return output, making multiple attempts if necessary. 1779 * 1780 * @param externalStorePath the path to check 1781 * @return the output from 'shell df path' 1782 * @throws DeviceNotAvailableException 1783 */ getDfOutput(String externalStorePath)1784 private String getDfOutput(String externalStorePath) throws DeviceNotAvailableException { 1785 for (int i=0; i < MAX_RETRY_ATTEMPTS; i++) { 1786 String output = executeShellCommand(String.format("df %s", externalStorePath)); 1787 if (output.trim().length() > 0) { 1788 return output; 1789 } 1790 } 1791 throw new DeviceUnresponsiveException(String.format( 1792 "Device %s not returning output from df command after %d attempts", 1793 getSerialNumber(), MAX_RETRY_ATTEMPTS), getSerialNumber()); 1794 } 1795 1796 /** 1797 * Parses a partition's available space from the legacy output of a 'df' command, used 1798 * pre-gingerbread. 1799 * <p/> 1800 * Assumes output format of: 1801 * <br>/ 1802 * <code> 1803 * [partition]: 15659168K total, 51584K used, 15607584K available (block size 32768) 1804 * </code> 1805 * @param dfOutput the output of df command to parse 1806 * @return the available space in kilobytes or <code>null</code> if output could not be parsed 1807 */ parseFreeSpaceFromAvailable(String dfOutput)1808 private Long parseFreeSpaceFromAvailable(String dfOutput) { 1809 final Pattern freeSpacePattern = Pattern.compile("(\\d+)K available"); 1810 Matcher patternMatcher = freeSpacePattern.matcher(dfOutput); 1811 if (patternMatcher.find()) { 1812 String freeSpaceString = patternMatcher.group(1); 1813 try { 1814 return Long.parseLong(freeSpaceString); 1815 } catch (NumberFormatException e) { 1816 // fall through 1817 } 1818 } 1819 return null; 1820 } 1821 1822 /** 1823 * Parses a partition's available space from the 'table-formatted' output of a toolbox 'df' 1824 * command, used from gingerbread to lollipop. 1825 * <p/> 1826 * Assumes output format of: 1827 * <br/> 1828 * <code> 1829 * Filesystem Size Used Free Blksize 1830 * <br/> 1831 * [partition]: 3G 790M 2G 4096 1832 * </code> 1833 * @param dfOutput the output of df command to parse 1834 * @return the available space in kilobytes or <code>null</code> if output could not be parsed 1835 */ parseFreeSpaceFromFree(String externalStorePath, String dfOutput)1836 Long parseFreeSpaceFromFree(String externalStorePath, String dfOutput) { 1837 Long freeSpace = null; 1838 final Pattern freeSpaceTablePattern = Pattern.compile(String.format( 1839 //fs Size Used Free 1840 "%s\\s+[\\w\\d\\.]+\\s+[\\w\\d\\.]+\\s+([\\d\\.]+)(\\w)", externalStorePath)); 1841 Matcher tablePatternMatcher = freeSpaceTablePattern.matcher(dfOutput); 1842 if (tablePatternMatcher.find()) { 1843 String numericValueString = tablePatternMatcher.group(1); 1844 String unitType = tablePatternMatcher.group(2); 1845 try { 1846 float freeSpaceFloat = Float.parseFloat(numericValueString); 1847 if (unitType.equals("M")) { 1848 freeSpaceFloat = freeSpaceFloat * 1024; 1849 } else if (unitType.equals("G")) { 1850 freeSpaceFloat = freeSpaceFloat * 1024 * 1024; 1851 } 1852 freeSpace = (long) freeSpaceFloat; 1853 } catch (NumberFormatException e) { 1854 // fall through 1855 } 1856 } 1857 return freeSpace; 1858 } 1859 1860 /** 1861 * Parses a partition's available space from the modern coreutils/toybox 'df' output, used 1862 * after lollipop. 1863 * <p/> 1864 * Assumes output format of: 1865 * <br/> 1866 * <code> 1867 * Filesystem 1K-blocks Used Available Use% Mounted on 1868 * <br/> 1869 * /dev/fuse 11585536 1316348 10269188 12% /mnt/shell/emulated 1870 * </code> 1871 * @param dfOutput the output of df command to parse 1872 * @return the available space in kilobytes or <code>null</code> if output could not be parsed 1873 */ parseFreeSpaceFromModernOutput(String dfOutput)1874 Long parseFreeSpaceFromModernOutput(String dfOutput) { 1875 Matcher matcher = DF_PATTERN.matcher(dfOutput); 1876 if (matcher.find()) { 1877 try { 1878 return Long.parseLong(matcher.group(2)); 1879 } catch (NumberFormatException e) { 1880 // fall through 1881 } 1882 } 1883 return null; 1884 } 1885 1886 /** 1887 * {@inheritDoc} 1888 */ 1889 @Override getMountPoint(String mountName)1890 public String getMountPoint(String mountName) { 1891 try { 1892 return mStateMonitor.getMountPoint(mountName); 1893 } catch (DeviceNotAvailableException e) { 1894 CLog.e(e); 1895 return null; 1896 } 1897 } 1898 1899 /** 1900 * {@inheritDoc} 1901 */ 1902 @Override getMountPointInfo()1903 public List<MountPointInfo> getMountPointInfo() throws DeviceNotAvailableException { 1904 final String mountInfo = executeShellCommand("cat /proc/mounts"); 1905 final String[] mountInfoLines = mountInfo.split("\r?\n"); 1906 List<MountPointInfo> list = new ArrayList<>(mountInfoLines.length); 1907 1908 for (String line : mountInfoLines) { 1909 // We ignore the last two fields 1910 // /dev/block/mtdblock4 /cache yaffs2 rw,nosuid,nodev,relatime 0 0 1911 final String[] parts = line.split("\\s+", 5); 1912 list.add(new MountPointInfo(parts[0], parts[1], parts[2], parts[3])); 1913 } 1914 1915 return list; 1916 } 1917 1918 /** 1919 * {@inheritDoc} 1920 */ 1921 @Override getMountPointInfo(String mountpoint)1922 public MountPointInfo getMountPointInfo(String mountpoint) throws DeviceNotAvailableException { 1923 // The overhead of parsing all of the lines should be minimal 1924 List<MountPointInfo> mountpoints = getMountPointInfo(); 1925 for (MountPointInfo info : mountpoints) { 1926 if (mountpoint.equals(info.mountpoint)) { 1927 return info; 1928 } 1929 } 1930 return null; 1931 } 1932 1933 /** 1934 * {@inheritDoc} 1935 */ 1936 @Override getFileEntry(String path)1937 public IFileEntry getFileEntry(String path) throws DeviceNotAvailableException { 1938 path = interpolatePathVariables(path); 1939 String[] pathComponents = path.split(FileListingService.FILE_SEPARATOR); 1940 FileListingService service = getFileListingService(); 1941 IFileEntry rootFile = new FileEntryWrapper(this, service.getRoot()); 1942 return FileEntryWrapper.getDescendant(rootFile, Arrays.asList(pathComponents)); 1943 } 1944 1945 /** 1946 * Unofficial helper to get a {@link FileEntry} from a non-root path. FIXME: Refactor the 1947 * FileEntry system to have it available from any path. (even non root). 1948 * 1949 * @param entry a {@link FileEntry} not necessarily root as Ddmlib requires. 1950 * @return a {@link FileEntryWrapper} representing the FileEntry. 1951 * @throws DeviceNotAvailableException 1952 */ getFileEntry(FileEntry entry)1953 public IFileEntry getFileEntry(FileEntry entry) throws DeviceNotAvailableException { 1954 // FileEntryWrapper is going to construct the list of child file internally. 1955 return new FileEntryWrapper(this, entry); 1956 } 1957 1958 /** {@inheritDoc} */ 1959 @Override isExecutable(String fullPath)1960 public boolean isExecutable(String fullPath) throws DeviceNotAvailableException { 1961 String fileMode = executeShellCommand(String.format("ls -l %s", fullPath)); 1962 if (fileMode != null) { 1963 return EXE_FILE.matcher(fileMode).find(); 1964 } 1965 return false; 1966 } 1967 1968 /** 1969 * {@inheritDoc} 1970 */ 1971 @Override isDirectory(String path)1972 public boolean isDirectory(String path) throws DeviceNotAvailableException { 1973 String output = executeShellCommand(String.format("ls -ld %s", path)); 1974 return output != null && output.charAt(0) == 'd'; 1975 } 1976 1977 /** 1978 * {@inheritDoc} 1979 */ 1980 @Override getChildren(String path)1981 public String[] getChildren(String path) throws DeviceNotAvailableException { 1982 String lsOutput = executeShellCommand(String.format("ls -A1 %s", path)); 1983 if (lsOutput.trim().isEmpty()) { 1984 return new String[0]; 1985 } 1986 return lsOutput.split("\r?\n"); 1987 } 1988 1989 /** 1990 * Retrieve the {@link FileListingService} for the {@link IDevice}, making multiple attempts 1991 * and recovery operations if necessary. 1992 * <p/> 1993 * This is necessary because {@link IDevice#getFileListingService()} can return 1994 * <code>null</code> if device is in fastboot. The symptom of this condition is that the 1995 * current {@link #getIDevice()} is a {@link StubDevice}. 1996 * 1997 * @return the {@link FileListingService} 1998 * @throws DeviceNotAvailableException if device communication is lost. 1999 */ getFileListingService()2000 private FileListingService getFileListingService() throws DeviceNotAvailableException { 2001 final FileListingService[] service = new FileListingService[1]; 2002 DeviceAction serviceAction = new DeviceAction() { 2003 @Override 2004 public boolean run() throws IOException, TimeoutException, AdbCommandRejectedException, 2005 ShellCommandUnresponsiveException, InstallException, SyncException { 2006 service[0] = getIDevice().getFileListingService(); 2007 if (service[0] == null) { 2008 // could not get file listing service - must be a stub device - enter recovery 2009 throw new IOException("Could not get file listing service"); 2010 } 2011 return true; 2012 } 2013 }; 2014 performDeviceAction("getFileListingService", serviceAction, MAX_RETRY_ATTEMPTS); 2015 return service[0]; 2016 } 2017 2018 /** 2019 * {@inheritDoc} 2020 */ 2021 @Override pushDir(File localFileDir, String deviceFilePath)2022 public boolean pushDir(File localFileDir, String deviceFilePath) 2023 throws DeviceNotAvailableException { 2024 return pushDir(localFileDir, deviceFilePath, new HashSet<>()); 2025 } 2026 2027 /** {@inheritDoc} */ 2028 @Override pushDir(File localFileDir, String deviceFilePath, int userId)2029 public boolean pushDir(File localFileDir, String deviceFilePath, int userId) 2030 throws DeviceNotAvailableException { 2031 return pushDir(localFileDir, deviceFilePath, new HashSet<>(), userId); 2032 } 2033 2034 /** {@inheritDoc} */ 2035 @Override pushDir( File localFileDir, String deviceFilePath, Set<String> excludedDirectories)2036 public boolean pushDir( 2037 File localFileDir, String deviceFilePath, Set<String> excludedDirectories) 2038 throws DeviceNotAvailableException { 2039 return pushDir( 2040 localFileDir, deviceFilePath, excludedDirectories, getCurrentUserCompatible()); 2041 } 2042 pushDir( File localFileDir, String deviceFilePath, Set<String> excludedDirectories, int userId)2043 private boolean pushDir( 2044 File localFileDir, String deviceFilePath, Set<String> excludedDirectories, int userId) 2045 throws DeviceNotAvailableException { 2046 long startTime = System.currentTimeMillis(); 2047 try { 2048 if (isSdcardOrEmulated(deviceFilePath)) { 2049 if (userId != 0) { 2050 ContentProviderHandler handler = getContentProvider(userId); 2051 if (handler != null) { 2052 return handler.pushDir(localFileDir, deviceFilePath, excludedDirectories); 2053 } 2054 } else { 2055 // Remove the special handling when content provider performance is better 2056 CLog.d("Push without content provider for user '%s'", userId); 2057 } 2058 } 2059 return pushDirInternal(localFileDir, deviceFilePath, excludedDirectories, userId); 2060 } finally { 2061 InvocationMetricLogger.addInvocationMetrics( 2062 InvocationMetricKey.PUSH_DIR_TIME, System.currentTimeMillis() - startTime); 2063 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.PUSH_DIR_COUNT, 1); 2064 } 2065 } 2066 pushDirInternal( File localFileDir, String deviceFilePath, Set<String> excludedDirectories, int userId)2067 private boolean pushDirInternal( 2068 File localFileDir, String deviceFilePath, Set<String> excludedDirectories, int userId) 2069 throws DeviceNotAvailableException { 2070 if (!localFileDir.isDirectory()) { 2071 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath()); 2072 return false; 2073 } 2074 File[] childFiles = localFileDir.listFiles(); 2075 if (childFiles == null) { 2076 CLog.e("Could not read files in %s", localFileDir.getAbsolutePath()); 2077 return false; 2078 } 2079 for (File childFile : childFiles) { 2080 String remotePath = String.format("%s/%s", deviceFilePath, childFile.getName()); 2081 if (childFile.isDirectory()) { 2082 // If we encounter a filtered directory do not push it. 2083 if (excludedDirectories.contains(childFile.getName())) { 2084 CLog.d( 2085 "%s directory was not pushed because it was filtered.", 2086 childFile.getAbsolutePath()); 2087 continue; 2088 } 2089 executeShellCommand(String.format("mkdir -p \"%s\"", remotePath)); 2090 if (!pushDirInternal(childFile, remotePath, excludedDirectories, userId)) { 2091 return false; 2092 } 2093 } else if (childFile.isFile()) { 2094 if (!pushFileInternal(childFile, remotePath, true, userId)) { 2095 return false; 2096 } 2097 } 2098 } 2099 return true; 2100 } 2101 2102 /** {@inheritDoc} */ 2103 @Override pullDir(String deviceFilePath, File localDir)2104 public boolean pullDir(String deviceFilePath, File localDir) 2105 throws DeviceNotAvailableException { 2106 return pullDir(deviceFilePath, localDir, getCurrentUserCompatible()); 2107 } 2108 2109 /** {@inheritDoc} */ 2110 @Override pullDir(String deviceFilePath, File localDir, int userId)2111 public boolean pullDir(String deviceFilePath, File localDir, int userId) 2112 throws DeviceNotAvailableException { 2113 long startTime = System.currentTimeMillis(); 2114 try { 2115 if (isSdcardOrEmulated(deviceFilePath)) { 2116 if (userId != 0) { 2117 ContentProviderHandler handler = getContentProvider(userId); 2118 if (handler != null) { 2119 return handler.pullDir(deviceFilePath, localDir); 2120 } 2121 } 2122 } 2123 2124 return pullDirInternal(deviceFilePath, localDir, userId); 2125 } finally { 2126 InvocationMetricLogger.addInvocationMetrics( 2127 InvocationMetricKey.PULL_DIR_TIME, System.currentTimeMillis() - startTime); 2128 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.PULL_DIR_COUNT, 1); 2129 } 2130 } 2131 pullDirInternal(String deviceFilePath, File localDir, int userId)2132 private boolean pullDirInternal(String deviceFilePath, File localDir, int userId) 2133 throws DeviceNotAvailableException { 2134 if (!localDir.isDirectory()) { 2135 CLog.e("Local path %s is not a directory", localDir.getAbsolutePath()); 2136 return false; 2137 } 2138 if (!doesFileExist(deviceFilePath, userId)) { 2139 CLog.e("Device path %s does not exist to be pulled.", deviceFilePath); 2140 return false; 2141 } 2142 if (!isDirectory(deviceFilePath)) { 2143 CLog.e("Device path %s is not a directory", deviceFilePath); 2144 return false; 2145 } 2146 FileEntry entryRoot = 2147 new FileEntry(null, deviceFilePath, FileListingService.TYPE_DIRECTORY, false); 2148 IFileEntry entry = getFileEntry(entryRoot); 2149 Collection<IFileEntry> children = entry.getChildren(false); 2150 if (children.isEmpty()) { 2151 CLog.i("Device path is empty, nothing to do."); 2152 return true; 2153 } 2154 for (IFileEntry item : children) { 2155 if (item.isDirectory()) { 2156 // handle sub dir 2157 File subDir = new File(localDir, item.getName()); 2158 if (!subDir.mkdir()) { 2159 CLog.w( 2160 "Failed to create sub directory %s, aborting.", 2161 subDir.getAbsolutePath()); 2162 return false; 2163 } 2164 String deviceSubDir = item.getFullPath(); 2165 if (!pullDirInternal(deviceSubDir, subDir, userId)) { 2166 CLog.w("Failed to pull sub directory %s from device, aborting", deviceSubDir); 2167 return false; 2168 } 2169 } else { 2170 // handle regular file 2171 File localFile = new File(localDir, item.getName()); 2172 String fullPath = item.getFullPath(); 2173 if (!pullFileInternal(fullPath, localFile)) { 2174 CLog.w("Failed to pull file %s from device, aborting", fullPath); 2175 return false; 2176 } 2177 } 2178 } 2179 return true; 2180 } 2181 2182 /** Checks whether path is external storage path. */ isSdcardOrEmulated(String path)2183 private boolean isSdcardOrEmulated(String path) { 2184 return path.startsWith(SD_CARD) || path.startsWith(STORAGE_EMULATED); 2185 } 2186 2187 /** 2188 * {@inheritDoc} 2189 */ 2190 @Override syncFiles(File localFileDir, String deviceFilePath)2191 public boolean syncFiles(File localFileDir, String deviceFilePath) 2192 throws DeviceNotAvailableException { 2193 if (localFileDir == null || deviceFilePath == null) { 2194 throw new IllegalArgumentException("syncFiles does not take null arguments"); 2195 } 2196 CLog.i("Syncing %s to %s on device %s", 2197 localFileDir.getAbsolutePath(), deviceFilePath, getSerialNumber()); 2198 if (!localFileDir.isDirectory()) { 2199 CLog.e("file %s is not a directory", localFileDir.getAbsolutePath()); 2200 return false; 2201 } 2202 // get the real destination path. This is done because underlying syncService.push 2203 // implementation will add localFileDir.getName() to destination path 2204 deviceFilePath = String.format("%s/%s", interpolatePathVariables(deviceFilePath), 2205 localFileDir.getName()); 2206 if (!doesFileExist(deviceFilePath)) { 2207 executeShellCommand(String.format("mkdir -p \"%s\"", deviceFilePath)); 2208 } 2209 IFileEntry remoteFileEntry = getFileEntry(deviceFilePath); 2210 if (remoteFileEntry == null) { 2211 CLog.e("Could not find remote file entry %s ", deviceFilePath); 2212 return false; 2213 } 2214 2215 return syncFiles(localFileDir, remoteFileEntry); 2216 } 2217 2218 /** 2219 * Recursively sync newer files. 2220 * 2221 * @param localFileDir the local {@link File} directory to sync 2222 * @param remoteFileEntry the remote destination {@link IFileEntry} 2223 * @return <code>true</code> if files were synced successfully 2224 * @throws DeviceNotAvailableException 2225 */ syncFiles(File localFileDir, final IFileEntry remoteFileEntry)2226 private boolean syncFiles(File localFileDir, final IFileEntry remoteFileEntry) 2227 throws DeviceNotAvailableException { 2228 CLog.d("Syncing %s to %s on %s", localFileDir.getAbsolutePath(), 2229 remoteFileEntry.getFullPath(), getSerialNumber()); 2230 // find newer files to sync 2231 File[] localFiles = localFileDir.listFiles(new NoHiddenFilesFilter()); 2232 ArrayList<String> filePathsToSync = new ArrayList<>(); 2233 for (File localFile : localFiles) { 2234 IFileEntry entry = remoteFileEntry.findChild(localFile.getName()); 2235 if (entry == null) { 2236 CLog.d("Detected missing file path %s", localFile.getAbsolutePath()); 2237 filePathsToSync.add(localFile.getAbsolutePath()); 2238 } else if (localFile.isDirectory()) { 2239 // This directory exists remotely. recursively sync it to sync only its newer files 2240 // contents 2241 if (!syncFiles(localFile, entry)) { 2242 return false; 2243 } 2244 } else if (isNewer(localFile, entry)) { 2245 CLog.d("Detected newer file %s", localFile.getAbsolutePath()); 2246 filePathsToSync.add(localFile.getAbsolutePath()); 2247 } 2248 } 2249 2250 if (filePathsToSync.size() == 0) { 2251 CLog.d("No files to sync"); 2252 return true; 2253 } 2254 final String files[] = filePathsToSync.toArray(new String[filePathsToSync.size()]); 2255 DeviceAction syncAction = new DeviceAction() { 2256 @Override 2257 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException, 2258 SyncException { 2259 SyncService syncService = null; 2260 boolean status = false; 2261 try { 2262 syncService = getIDevice().getSyncService(); 2263 syncService.push(files, remoteFileEntry.getFileEntry(), 2264 SyncService.getNullProgressMonitor()); 2265 status = true; 2266 } catch (SyncException e) { 2267 CLog.w("Failed to sync files to %s on device %s. Message %s", 2268 remoteFileEntry.getFullPath(), getSerialNumber(), e.getMessage()); 2269 throw e; 2270 } finally { 2271 if (syncService != null) { 2272 syncService.close(); 2273 } 2274 } 2275 return status; 2276 } 2277 }; 2278 return performDeviceAction(String.format("sync files %s", remoteFileEntry.getFullPath()), 2279 syncAction, MAX_RETRY_ATTEMPTS); 2280 } 2281 2282 /** 2283 * Queries the file listing service for a given directory 2284 * 2285 * @param remoteFileEntry 2286 * @throws DeviceNotAvailableException 2287 */ getFileChildren(final FileEntry remoteFileEntry)2288 FileEntry[] getFileChildren(final FileEntry remoteFileEntry) 2289 throws DeviceNotAvailableException { 2290 // time this operation because its known to hang 2291 FileQueryAction action = new FileQueryAction(remoteFileEntry, 2292 getIDevice().getFileListingService()); 2293 performDeviceAction("buildFileCache", action, 1 /* one retry */); 2294 return action.mFileContents; 2295 } 2296 2297 private class FileQueryAction implements DeviceAction { 2298 2299 FileEntry[] mFileContents = null; 2300 private final FileEntry mRemoteFileEntry; 2301 private final FileListingService mService; 2302 FileQueryAction(FileEntry remoteFileEntry, FileListingService service)2303 FileQueryAction(FileEntry remoteFileEntry, FileListingService service) { 2304 throwIfNull(remoteFileEntry); 2305 throwIfNull(service); 2306 mRemoteFileEntry = remoteFileEntry; 2307 mService = service; 2308 } 2309 2310 @Override run()2311 public boolean run() throws TimeoutException, IOException, AdbCommandRejectedException, 2312 ShellCommandUnresponsiveException { 2313 mFileContents = mService.getChildrenSync(mRemoteFileEntry); 2314 return true; 2315 } 2316 } 2317 2318 /** 2319 * A {@link FilenameFilter} that rejects hidden (ie starts with ".") files. 2320 */ 2321 private static class NoHiddenFilesFilter implements FilenameFilter { 2322 /** 2323 * {@inheritDoc} 2324 */ 2325 @Override accept(File dir, String name)2326 public boolean accept(File dir, String name) { 2327 return !name.startsWith("."); 2328 } 2329 } 2330 2331 /** 2332 * helper to get the timezone from the device. Example: "Europe/London" 2333 */ getDeviceTimezone()2334 private String getDeviceTimezone() { 2335 try { 2336 // This may not be set at first, default to GMT in this case. 2337 String timezone = getProperty("persist.sys.timezone"); 2338 if (timezone != null) { 2339 return timezone.trim(); 2340 } 2341 } catch (DeviceNotAvailableException e) { 2342 // Fall through on purpose 2343 } 2344 return "GMT"; 2345 } 2346 2347 /** 2348 * Return <code>true</code> if local file is newer than remote file. {@link IFileEntry} being 2349 * accurate to the minute, in case of equal times, the file will be considered newer. 2350 */ 2351 @VisibleForTesting isNewer(File localFile, IFileEntry entry)2352 protected boolean isNewer(File localFile, IFileEntry entry) { 2353 final String entryTimeString = String.format("%s %s", entry.getDate(), entry.getTime()); 2354 try { 2355 String timezone = getDeviceTimezone(); 2356 // expected format of a FileEntry's date and time 2357 SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm"); 2358 format.setTimeZone(TimeZone.getTimeZone(timezone)); 2359 Date remoteDate = format.parse(entryTimeString); 2360 2361 long offset = 0; 2362 try { 2363 offset = getDeviceTimeOffset(null); 2364 } catch (DeviceNotAvailableException e) { 2365 offset = 0; 2366 } 2367 CLog.i("Device offset time: %s", offset); 2368 2369 // localFile.lastModified has granularity of ms, but remoteDate.getTime only has 2370 // granularity of minutes. Shift remoteDate.getTime() backward by one minute so newly 2371 // modified files get synced 2372 return localFile.lastModified() > (remoteDate.getTime() - 60 * 1000 + offset); 2373 } catch (ParseException e) { 2374 CLog.e("Error converting remote time stamp %s for %s on device %s", entryTimeString, 2375 entry.getFullPath(), getSerialNumber()); 2376 } 2377 // sync file by default 2378 return true; 2379 } 2380 2381 /** 2382 * {@inheritDoc} 2383 */ 2384 @Override executeAdbCommand(String... cmdArgs)2385 public String executeAdbCommand(String... cmdArgs) throws DeviceNotAvailableException { 2386 return executeAdbCommand(getCommandTimeout(), cmdArgs); 2387 } 2388 2389 /** {@inheritDoc} */ 2390 @Override executeAdbCommand(long timeout, String... cmdArgs)2391 public String executeAdbCommand(long timeout, String... cmdArgs) 2392 throws DeviceNotAvailableException { 2393 return executeAdbCommand(getCommandTimeout(), new HashMap<>(), cmdArgs); 2394 } 2395 2396 /** {@inheritDoc} */ 2397 @Override executeAdbCommand(long timeout, Map<String, String> envMap, String... cmdArgs)2398 public String executeAdbCommand(long timeout, Map<String, String> envMap, String... cmdArgs) 2399 throws DeviceNotAvailableException { 2400 final String[] fullCmd = buildAdbCommand(cmdArgs); 2401 AdbAction adbAction = new AdbAction(timeout, fullCmd, "shell".equals(cmdArgs[0]), envMap); 2402 performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS); 2403 return adbAction.mOutput; 2404 } 2405 2406 /** 2407 * {@inheritDoc} 2408 */ 2409 @Override executeFastbootCommand(String... cmdArgs)2410 public CommandResult executeFastbootCommand(String... cmdArgs) 2411 throws DeviceNotAvailableException, UnsupportedOperationException { 2412 // TODO: fix mixed use of fastboot timeout and command timeout 2413 return doFastbootCommand(getCommandTimeout(), cmdArgs); 2414 } 2415 2416 /** 2417 * {@inheritDoc} 2418 */ 2419 @Override executeFastbootCommand(long timeout, String... cmdArgs)2420 public CommandResult executeFastbootCommand(long timeout, String... cmdArgs) 2421 throws DeviceNotAvailableException, UnsupportedOperationException { 2422 return doFastbootCommand(timeout, cmdArgs); 2423 } 2424 2425 /** 2426 * {@inheritDoc} 2427 */ 2428 @Override executeLongFastbootCommand(String... cmdArgs)2429 public CommandResult executeLongFastbootCommand(String... cmdArgs) 2430 throws DeviceNotAvailableException, UnsupportedOperationException { 2431 return executeLongFastbootCommand(new HashMap<>(), cmdArgs); 2432 } 2433 2434 /** {@inheritDoc} */ 2435 @Override executeLongFastbootCommand( Map<String, String> envVarMap, String... cmdArgs)2436 public CommandResult executeLongFastbootCommand( 2437 Map<String, String> envVarMap, String... cmdArgs) 2438 throws DeviceNotAvailableException, UnsupportedOperationException { 2439 // TODO: fix mixed use of fastboot timeout and command timeout 2440 return doFastbootCommand(getLongCommandTimeout(), envVarMap, cmdArgs); 2441 } 2442 2443 /** 2444 * Do a fastboot command with environment variables set 2445 * 2446 * @param timeout timeout for the fastboot command 2447 * @param envVarMap environment variables that needs to be set before execute the fastboot 2448 * command 2449 * @param cmdArgs 2450 * @return {@link CommandResult} of the fastboot command 2451 * @throws DeviceNotAvailableException 2452 * @throws UnsupportedOperationException 2453 */ doFastbootCommand( final long timeout, Map<String, String> envVarMap, String... cmdArgs)2454 private CommandResult doFastbootCommand( 2455 final long timeout, Map<String, String> envVarMap, String... cmdArgs) 2456 throws DeviceNotAvailableException, UnsupportedOperationException { 2457 if (!mFastbootEnabled) { 2458 throw new UnsupportedOperationException(String.format( 2459 "Attempted to fastboot on device %s , but fastboot is not available. Aborting.", 2460 getSerialNumber())); 2461 } 2462 2463 File fastbootTmpDir = getHostOptions().getFastbootTmpDir(); 2464 if (fastbootTmpDir != null) { 2465 envVarMap.put("TMPDIR", fastbootTmpDir.getAbsolutePath()); 2466 } 2467 2468 final String[] fullCmd = buildFastbootCommand(cmdArgs); 2469 2470 for (int i = 0; i < MAX_RETRY_ATTEMPTS; i++) { 2471 try (CloseableTraceScope ignored = new CloseableTraceScope("fastboot " + cmdArgs[0])) { 2472 CommandResult result = simpleFastbootCommand(timeout, envVarMap, fullCmd); 2473 if (!isRecoveryNeeded(result)) { 2474 return result; 2475 } 2476 CLog.w("Recovery needed after executing fastboot command"); 2477 if (result != null) { 2478 CLog.v( 2479 "fastboot command output:\nstdout: %s\nstderr:%s", 2480 result.getStdout(), result.getStderr()); 2481 } 2482 recoverDeviceFromBootloader(); 2483 } 2484 } 2485 throw new DeviceUnresponsiveException( 2486 String.format( 2487 "Attempted fastboot %s multiple " 2488 + "times on device %s without communication success. Aborting.", 2489 cmdArgs[0], getSerialNumber()), 2490 getSerialNumber()); 2491 } 2492 2493 /** 2494 * Do a fastboot command 2495 * 2496 * @param timeout timeout for the fastboot command 2497 * @param cmdArgs 2498 * @return {@link CommandResult} of the fastboot command 2499 * @throws DeviceNotAvailableException 2500 * @throws UnsupportedOperationException 2501 */ doFastbootCommand(final long timeout, String... cmdArgs)2502 private CommandResult doFastbootCommand(final long timeout, String... cmdArgs) 2503 throws DeviceNotAvailableException, UnsupportedOperationException { 2504 return doFastbootCommand(timeout, new HashMap<>(), cmdArgs); 2505 } 2506 2507 /** 2508 * {@inheritDoc} 2509 */ 2510 @Override getUseFastbootErase()2511 public boolean getUseFastbootErase() { 2512 return mOptions.getUseFastbootErase(); 2513 } 2514 2515 /** 2516 * {@inheritDoc} 2517 */ 2518 @Override setUseFastbootErase(boolean useFastbootErase)2519 public void setUseFastbootErase(boolean useFastbootErase) { 2520 mOptions.setUseFastbootErase(useFastbootErase); 2521 } 2522 2523 /** 2524 * {@inheritDoc} 2525 */ 2526 @Override fastbootWipePartition(String partition)2527 public CommandResult fastbootWipePartition(String partition) 2528 throws DeviceNotAvailableException { 2529 if (mOptions.getUseFastbootErase()) { 2530 return executeLongFastbootCommand("erase", partition); 2531 } else { 2532 return executeLongFastbootCommand("format", partition); 2533 } 2534 } 2535 2536 /** 2537 * Evaluate the given fastboot result to determine if recovery mode needs to be entered 2538 * 2539 * @param fastbootResult the {@link CommandResult} from a fastboot command 2540 * @return <code>true</code> if recovery mode should be entered, <code>false</code> otherwise. 2541 */ isRecoveryNeeded(CommandResult fastbootResult)2542 private boolean isRecoveryNeeded(CommandResult fastbootResult) { 2543 if (fastbootResult.getStatus().equals(CommandStatus.TIMED_OUT)) { 2544 // fastboot commands always time out if devices is not present 2545 return true; 2546 } else { 2547 // check for specific error messages in result that indicate bad device communication 2548 // and recovery mode is needed 2549 if (fastbootResult.getStderr() == null || 2550 fastbootResult.getStderr().contains("data transfer failure (Protocol error)") || 2551 fastbootResult.getStderr().contains("status read failed (No such device)")) { 2552 CLog.w("Bad fastboot response from device %s. stderr: %s. Entering recovery", 2553 getSerialNumber(), fastbootResult.getStderr()); 2554 return true; 2555 } 2556 } 2557 return false; 2558 } 2559 2560 /** Get the max time allowed in ms for commands. */ getCommandTimeout()2561 long getCommandTimeout() { 2562 return mOptions.getAdbCommandTimeout(); 2563 } 2564 2565 /** 2566 * Set the max time allowed in ms for commands. 2567 */ setLongCommandTimeout(long timeout)2568 void setLongCommandTimeout(long timeout) { 2569 mLongCmdTimeout = timeout; 2570 } 2571 2572 /** 2573 * Get the max time allowed in ms for commands. 2574 */ getLongCommandTimeout()2575 long getLongCommandTimeout() { 2576 return mLongCmdTimeout; 2577 } 2578 2579 /** Set the max time allowed in ms for commands. */ setCommandTimeout(long timeout)2580 void setCommandTimeout(long timeout) { 2581 mOptions.setAdbCommandTimeout(timeout); 2582 } 2583 2584 /** 2585 * Builds the OS command for the given adb command and args 2586 */ buildAdbCommand(String... commandArgs)2587 private String[] buildAdbCommand(String... commandArgs) { 2588 return ArrayUtil.buildArray(new String[] {"adb", "-s", getSerialNumber()}, 2589 commandArgs); 2590 } 2591 2592 /** Builds the OS command for the given adb shell command session and args */ buildAdbShellCommand(String command, boolean forceExitStatusDetection)2593 protected String[] buildAdbShellCommand(String command, boolean forceExitStatusDetection) { 2594 // TODO: implement the shell v2 support in ddmlib itself. 2595 String[] commandArgs = 2596 QuotationAwareTokenizer.tokenizeLine( 2597 command, 2598 /** No logging */ 2599 false); 2600 2601 String[] exitStatusProbe; 2602 if (forceExitStatusDetection) { 2603 exitStatusProbe = new String[] {";", "echo", EXIT_STATUS_DELIMITER + "$?"}; 2604 } else { 2605 exitStatusProbe = new String[] {}; 2606 } 2607 return ArrayUtil.buildArray( 2608 new String[] {"adb", "-s", getSerialNumber(), "shell"}, 2609 commandArgs, 2610 exitStatusProbe); 2611 } 2612 2613 /** 2614 * Builds the OS command for the given fastboot command and args 2615 */ buildFastbootCommand(String... commandArgs)2616 private String[] buildFastbootCommand(String... commandArgs) { 2617 return ArrayUtil.buildArray( 2618 new String[] {getFastbootPath(), "-s", getFastbootSerialNumber()}, commandArgs); 2619 } 2620 2621 /** 2622 * Performs an action on this device. Attempts to recover device and optionally retry command if 2623 * action fails. 2624 * 2625 * @param actionDescription a short description of action to be performed. Used for logging 2626 * purposes only. 2627 * @param action the action to be performed 2628 * @param retryAttempts the retry attempts to make for action if it fails but recovery succeeds 2629 * @return <code>true</code> if action was performed successfully 2630 * @throws DeviceNotAvailableException if recovery attempt fails or max attempts done without 2631 * success 2632 */ performDeviceAction( String actionDescription, final DeviceAction action, int retryAttempts)2633 protected boolean performDeviceAction( 2634 String actionDescription, final DeviceAction action, int retryAttempts) 2635 throws DeviceNotAvailableException { 2636 Exception lastException = null; 2637 try (CloseableTraceScope ignored = new CloseableTraceScope(actionDescription)) { 2638 for (int i = 0; i < retryAttempts + 1; i++) { 2639 boolean shouldRecover = true; 2640 try { 2641 return action.run(); 2642 } catch (TimeoutException e) { 2643 logDeviceActionException(actionDescription, e, false); 2644 lastException = e; 2645 } catch (IOException e) { 2646 logDeviceActionException(actionDescription, e, true); 2647 lastException = e; 2648 } catch (InstallException e) { 2649 logDeviceActionException(actionDescription, e, true); 2650 lastException = e; 2651 } catch (SyncException e) { 2652 logDeviceActionException(actionDescription, e, true); 2653 lastException = e; 2654 // a SyncException is not necessarily a device communication problem 2655 // do additional diagnosis 2656 if (!e.getErrorCode().equals(SyncError.BUFFER_OVERRUN) 2657 && !e.getErrorCode().equals(SyncError.TRANSFER_PROTOCOL_ERROR)) { 2658 // this is a logic problem, doesn't need recovery or to be retried 2659 return false; 2660 } 2661 } catch (AdbCommandRejectedException e) { 2662 // Workaround to not recover device if TCP adb is used. 2663 if (isAdbTcp() 2664 && (action instanceof RebootDeviceAction) 2665 && ((RebootDeviceAction) action).isFastbootOrBootloader()) { 2666 CLog.d( 2667 "Ignore AdbCommandRejectedException when TCP device is rebooted" 2668 + " into fastboot."); 2669 return true; 2670 } 2671 lastException = e; 2672 logDeviceActionException(actionDescription, e, false); 2673 } catch (ShellCommandUnresponsiveException e) { 2674 // ShellCommandUnresponsiveException is thrown when no output occurs within the 2675 // timeout. It doesn't necessarily mean the device is offline. 2676 shouldRecover = false; 2677 lastException = e; 2678 CLog.w( 2679 "Command: '%s' on '%s' went over its timeout for outputing a response.", 2680 actionDescription, getSerialNumber()); 2681 } 2682 if (shouldRecover) { 2683 recoverDevice(); 2684 } 2685 } 2686 if (retryAttempts > 0) { 2687 throw new DeviceUnresponsiveException( 2688 String.format( 2689 "Attempted %s multiple times " 2690 + "on device %s without communication success. Aborting.", 2691 actionDescription, getSerialNumber()), 2692 lastException, 2693 getSerialNumber(), 2694 DeviceErrorIdentifier.DEVICE_UNRESPONSIVE); 2695 } 2696 return false; 2697 } 2698 } 2699 2700 /** 2701 * Log an entry for given exception 2702 * 2703 * @param actionDescription the action's description 2704 * @param e the exception 2705 * @param logFullTrace whether the full exception stack trace should be logged 2706 */ logDeviceActionException( String actionDescription, Exception e, boolean logFullTrace)2707 private void logDeviceActionException( 2708 String actionDescription, Exception e, boolean logFullTrace) { 2709 CLog.w("%s (%s) when attempting %s on device %s", e.getClass().getSimpleName(), 2710 getExceptionMessage(e), actionDescription, getSerialNumber()); 2711 if (logFullTrace) { 2712 CLog.w(e); 2713 } 2714 } 2715 2716 /** 2717 * Make a best effort attempt to retrieve a meaningful short descriptive message for given 2718 * {@link Exception} 2719 * 2720 * @param e the {@link Exception} 2721 * @return a short message 2722 */ getExceptionMessage(Exception e)2723 private String getExceptionMessage(Exception e) { 2724 StringBuilder msgBuilder = new StringBuilder(); 2725 if (e.getMessage() != null) { 2726 msgBuilder.append(e.getMessage()); 2727 } 2728 if (e.getCause() != null) { 2729 msgBuilder.append(" cause: "); 2730 msgBuilder.append(e.getCause().getClass().getSimpleName()); 2731 if (e.getCause().getMessage() != null) { 2732 msgBuilder.append(" ("); 2733 msgBuilder.append(e.getCause().getMessage()); 2734 msgBuilder.append(")"); 2735 } 2736 } 2737 return msgBuilder.toString(); 2738 } 2739 2740 /** 2741 * Attempts to recover device communication. 2742 * 2743 * @throws DeviceNotAvailableException if device is no longer available 2744 */ 2745 @Override recoverDevice()2746 public boolean recoverDevice() throws DeviceNotAvailableException { 2747 getConnection().reconnectForRecovery(getSerialNumber()); 2748 if (mRecoveryMode.equals(RecoveryMode.NONE)) { 2749 CLog.i("Skipping recovery on %s", getSerialNumber()); 2750 return false; 2751 } 2752 CLog.i("Attempting recovery on %s", getSerialNumber()); 2753 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.RECOVERY_ROUTINE_COUNT, 1); 2754 long startTime = System.currentTimeMillis(); 2755 try { 2756 try { 2757 mRecovery.recoverDevice(mStateMonitor, mRecoveryMode.equals(RecoveryMode.ONLINE)); 2758 } catch (DeviceUnresponsiveException due) { 2759 RecoveryMode previousRecoveryMode = mRecoveryMode; 2760 mRecoveryMode = RecoveryMode.NONE; 2761 try { 2762 boolean enabled = enableAdbRoot(); 2763 CLog.d( 2764 "Device Unresponsive during recovery, is root still enabled: %s", 2765 enabled); 2766 } catch (DeviceUnresponsiveException e) { 2767 // Ignore exception thrown here to rethrow original exception. 2768 CLog.e("Exception occurred during recovery adb root:"); 2769 CLog.e(e); 2770 Throwable cause = e.getCause(); 2771 if (cause != null && cause instanceof AdbCommandRejectedException) { 2772 AdbCommandRejectedException adbException = 2773 (AdbCommandRejectedException) cause; 2774 if (adbException.isDeviceOffline() 2775 || adbException.wasErrorDuringDeviceSelection()) { 2776 // Upgrade exception to DNAE to reflect gravity 2777 throw new DeviceNotAvailableException( 2778 cause.getMessage(), 2779 adbException, 2780 getSerialNumber(), 2781 DeviceErrorIdentifier.DEVICE_UNAVAILABLE); 2782 } 2783 } 2784 } 2785 mRecoveryMode = previousRecoveryMode; 2786 throw due; 2787 } 2788 if (mRecoveryMode.equals(RecoveryMode.AVAILABLE)) { 2789 // turn off recovery mode to prevent reentrant recovery 2790 // TODO: look for a better way to handle this, such as doing postBootUp steps in 2791 // recovery itself 2792 mRecoveryMode = RecoveryMode.NONE; 2793 // this might be a runtime reset - still need to run post boot setup steps 2794 if (isEncryptionSupported() && isDeviceEncrypted()) { 2795 unlockDevice(); 2796 } 2797 postBootSetup(); 2798 mRecoveryMode = RecoveryMode.AVAILABLE; 2799 } else if (mRecoveryMode.equals(RecoveryMode.ONLINE)) { 2800 // turn off recovery mode to prevent reentrant recovery 2801 // TODO: look for a better way to handle this, such as doing postBootUp steps in 2802 // recovery itself 2803 mRecoveryMode = RecoveryMode.NONE; 2804 enableAdbRoot(); 2805 mRecoveryMode = RecoveryMode.ONLINE; 2806 } 2807 } finally { 2808 InvocationMetricLogger.addInvocationMetrics( 2809 InvocationMetricKey.RECOVERY_TIME, System.currentTimeMillis() - startTime); 2810 } 2811 CLog.i("Recovery successful for %s", getSerialNumber()); 2812 return true; 2813 } 2814 2815 /** 2816 * Attempts to recover device fastboot communication. 2817 * 2818 * @throws DeviceNotAvailableException if device is not longer available 2819 */ recoverDeviceFromBootloader()2820 private void recoverDeviceFromBootloader() throws DeviceNotAvailableException { 2821 CLog.i("Attempting recovery on %s in bootloader", getSerialNumber()); 2822 mRecovery.recoverDeviceBootloader(mStateMonitor); 2823 CLog.i("Bootloader recovery successful for %s", getSerialNumber()); 2824 } 2825 recoverDeviceFromFastbootd()2826 private void recoverDeviceFromFastbootd() throws DeviceNotAvailableException { 2827 CLog.i("Attempting recovery on %s in fastbootd", getSerialNumber()); 2828 mRecovery.recoverDeviceFastbootd(mStateMonitor); 2829 CLog.i("Fastbootd recovery successful for %s", getSerialNumber()); 2830 } 2831 recoverDeviceInRecovery()2832 private void recoverDeviceInRecovery() throws DeviceNotAvailableException { 2833 CLog.i("Attempting recovery on %s in recovery", getSerialNumber()); 2834 mRecovery.recoverDeviceRecovery(mStateMonitor); 2835 CLog.i("Recovery mode recovery successful for %s", getSerialNumber()); 2836 } 2837 2838 /** 2839 * {@inheritDoc} 2840 */ 2841 @Override startLogcat()2842 public void startLogcat() { 2843 if (mLogcatReceiver != null) { 2844 CLog.d("Already capturing logcat for %s, ignoring", getSerialNumber()); 2845 return; 2846 } 2847 mLogcatReceiver = createLogcatReceiver(); 2848 mLogcatReceiver.start(); 2849 } 2850 2851 /** 2852 * {@inheritDoc} 2853 */ 2854 @Override clearLogcat()2855 public void clearLogcat() { 2856 if (mLogcatReceiver != null) { 2857 mLogcatReceiver.clear(); 2858 } 2859 } 2860 2861 /** {@inheritDoc} */ 2862 @Override 2863 @SuppressWarnings("MustBeClosedChecker") getLogcat()2864 public InputStreamSource getLogcat() { 2865 if (mLogcatReceiver == null) { 2866 if (!(getIDevice() instanceof StubDevice)) { 2867 TestDeviceState state = getDeviceState(); 2868 if (!TestDeviceState.ONLINE.equals(state)) { 2869 CLog.w("Skipping logcat capture, no buffer and device state is '%s'", state); 2870 } else { 2871 CLog.w( 2872 "Not capturing logcat for %s in background, returning a logcat dump", 2873 getSerialNumber()); 2874 return getLogcatDump(); 2875 } 2876 } 2877 return new ByteArrayInputStreamSource(new byte[0]); 2878 } else { 2879 return mLogcatReceiver.getLogcatData(); 2880 } 2881 } 2882 2883 /** {@inheritDoc} */ 2884 @Override 2885 @SuppressWarnings("MustBeClosedChecker") getLogcat(int maxBytes)2886 public InputStreamSource getLogcat(int maxBytes) { 2887 if (mLogcatReceiver == null) { 2888 CLog.w("Not capturing logcat for %s in background, returning a logcat dump " 2889 + "ignoring size", getSerialNumber()); 2890 return getLogcatDump(); 2891 } else { 2892 return mLogcatReceiver.getLogcatData(maxBytes); 2893 } 2894 } 2895 2896 /** 2897 * {@inheritDoc} 2898 */ 2899 @Override getLogcatSince(long date)2900 public InputStreamSource getLogcatSince(long date) { 2901 int deviceApiLevel; 2902 try { 2903 deviceApiLevel = getApiLevel(); 2904 if (deviceApiLevel <= 22) { 2905 CLog.i("Api level too low to use logcat -t 'time' reverting to dump"); 2906 return getLogcatDump(); 2907 } 2908 } catch (DeviceNotAvailableException e) { 2909 // For convenience of interface, we catch the DNAE here. 2910 CLog.e(e); 2911 return getLogcatDump(); 2912 } 2913 2914 String dateFormatted; 2915 if (deviceApiLevel >= 24) { 2916 // Use 'sssss.mmm' epoch time format supported since API 24. 2917 dateFormatted = String.format(Locale.US, "%d.%03d", date / 1000, date % 1000); 2918 } else { 2919 // Convert date to format needed by the command: 2920 // 'MM-DD HH:mm:ss.mmm' or 'YYYY-MM-DD HH:mm:ss.mmm' 2921 SimpleDateFormat format = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 2922 dateFormatted = format.format(new Date(date)); 2923 } 2924 2925 LargeOutputReceiver largeReceiver = null; 2926 try { 2927 // use IDevice directly because we don't want callers to handle 2928 // DeviceNotAvailableException for this method 2929 largeReceiver = 2930 new LargeOutputReceiver( 2931 "getLogcatSince", 2932 getSerialNumber(), 2933 getOptions().getMaxLogcatDataSize()); 2934 String command = 2935 String.format( 2936 "%s -t '%s'", LogcatReceiver.getDefaultLogcatCmd(this), dateFormatted); 2937 getIDevice().executeShellCommand(command, largeReceiver); 2938 return largeReceiver.getData(); 2939 } catch (IOException|AdbCommandRejectedException| 2940 ShellCommandUnresponsiveException|TimeoutException e) { 2941 CLog.w("Failed to get logcat dump from %s: %s", getSerialNumber(), e.getMessage()); 2942 CLog.e(e); 2943 } finally { 2944 if (largeReceiver != null) { 2945 largeReceiver.cancel(); 2946 largeReceiver.delete(); 2947 } 2948 } 2949 return new ByteArrayInputStreamSource(new byte[0]); 2950 } 2951 2952 /** 2953 * {@inheritDoc} 2954 */ 2955 @Override getLogcatDump()2956 public InputStreamSource getLogcatDump() { 2957 long startTime = System.currentTimeMillis(); 2958 LargeOutputReceiver largeReceiver = null; 2959 try (CloseableTraceScope ignored = new CloseableTraceScope("getLogcatDump")) { 2960 // use IDevice directly because we don't want callers to handle 2961 // DeviceNotAvailableException for this method 2962 largeReceiver = 2963 new LargeOutputReceiver( 2964 "getLogcatDump", 2965 getSerialNumber(), 2966 getOptions().getMaxLogcatDataSize()); 2967 // add -d parameter to make this a non blocking call 2968 getIDevice() 2969 .executeShellCommand( 2970 LogcatReceiver.getDefaultLogcatCmd(this) + " -d", 2971 largeReceiver, 2972 LOGCAT_DUMP_TIMEOUT, 2973 LOGCAT_DUMP_TIMEOUT, 2974 TimeUnit.MILLISECONDS); 2975 return largeReceiver.getData(); 2976 } catch (IOException e) { 2977 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage()); 2978 } catch (TimeoutException e) { 2979 CLog.w("Failed to get logcat dump from %s: timeout", getSerialNumber()); 2980 } catch (AdbCommandRejectedException e) { 2981 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage()); 2982 } catch (ShellCommandUnresponsiveException e) { 2983 CLog.w("Failed to get logcat dump from %s: ", getSerialNumber(), e.getMessage()); 2984 } finally { 2985 if (largeReceiver != null) { 2986 largeReceiver.cancel(); 2987 largeReceiver.delete(); 2988 } 2989 InvocationMetricLogger.addInvocationMetrics( 2990 InvocationMetricKey.LOGCAT_DUMP_TIME, System.currentTimeMillis() - startTime); 2991 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.LOGCAT_DUMP_COUNT, 1); 2992 } 2993 return new ByteArrayInputStreamSource(new byte[0]); 2994 } 2995 2996 /** 2997 * {@inheritDoc} 2998 */ 2999 @Override stopLogcat()3000 public void stopLogcat() { 3001 if (mLogcatReceiver != null) { 3002 mLogcatReceiver.stop(); 3003 mLogcatReceiver = null; 3004 } else { 3005 CLog.w("Attempting to stop logcat when not capturing for %s", getSerialNumber()); 3006 } 3007 } 3008 3009 /** Factory method to create a {@link LogcatReceiver}. */ 3010 @VisibleForTesting createLogcatReceiver()3011 LogcatReceiver createLogcatReceiver() { 3012 String logcatOptions = mOptions.getLogcatOptions(); 3013 if (SystemUtil.isLocalMode()) { 3014 mLogStartDelay = 0; 3015 } 3016 if (logcatOptions == null) { 3017 return new LogcatReceiver(this, mOptions.getMaxLogcatDataSize(), mLogStartDelay); 3018 } else { 3019 return new LogcatReceiver( 3020 this, 3021 String.format("%s %s", LogcatReceiver.getDefaultLogcatCmd(this), logcatOptions), 3022 mOptions.getMaxLogcatDataSize(), 3023 mLogStartDelay); 3024 } 3025 } 3026 3027 /** 3028 * {@inheritDoc} 3029 */ 3030 @Override getBugreport()3031 public InputStreamSource getBugreport() { 3032 return null; 3033 } 3034 3035 /** 3036 * {@inheritDoc} 3037 */ 3038 @Override logBugreport(String dataName, ITestLogger listener)3039 public boolean logBugreport(String dataName, ITestLogger listener) { 3040 return true; 3041 } 3042 3043 /** 3044 * {@inheritDoc} 3045 */ 3046 @Override takeBugreport()3047 public Bugreport takeBugreport() { 3048 return null; 3049 } 3050 3051 /** 3052 * {@inheritDoc} 3053 */ 3054 @Override getBugreportz()3055 public InputStreamSource getBugreportz() { 3056 return null; 3057 } 3058 3059 /** {@inheritDoc} */ 3060 @Override logAnrs(ITestLogger logger)3061 public boolean logAnrs(ITestLogger logger) throws DeviceNotAvailableException { 3062 if (!doesFileExist(ANRS_PATH)) { 3063 CLog.d("No ANRs at %s", ANRS_PATH); 3064 return true; 3065 } 3066 boolean root = enableAdbRoot(); 3067 if (!root) { 3068 CLog.d("Skipping logAnrs, need to be root."); 3069 } 3070 File localDir = null; 3071 long startTime = System.currentTimeMillis(); 3072 try { 3073 localDir = FileUtil.createTempDir("pulled-anrs"); 3074 boolean success = pullDir(ANRS_PATH, localDir); 3075 if (!success) { 3076 CLog.w("Failed to pull %s", ANRS_PATH); 3077 return false; 3078 } 3079 if (localDir.listFiles().length == 0) { 3080 return true; 3081 } 3082 for (File f : localDir.listFiles()) { 3083 try (FileInputStreamSource source = new FileInputStreamSource(f)) { 3084 String name = f.getName(); 3085 LogDataType type = LogDataType.ANRS; 3086 if (name.startsWith("dumptrace")) { 3087 type = LogDataType.DUMPTRACE; 3088 } 3089 logger.testLog(name, type, source); 3090 } 3091 } 3092 } catch (IOException e) { 3093 CLog.e(e); 3094 return false; 3095 } finally { 3096 FileUtil.recursiveDelete(localDir); 3097 } 3098 InvocationMetricLogger.addInvocationMetrics( 3099 InvocationMetricKey.ANR_TIME, System.currentTimeMillis() - startTime); 3100 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.ANR_COUNT, 1); 3101 return true; 3102 } 3103 3104 /** 3105 * {@inheritDoc} 3106 */ 3107 @Override getScreenshot()3108 public InputStreamSource getScreenshot() throws DeviceNotAvailableException { 3109 throw new UnsupportedOperationException("No support for Screenshot"); 3110 } 3111 3112 /** 3113 * {@inheritDoc} 3114 */ 3115 @Override getScreenshot(String format)3116 public InputStreamSource getScreenshot(String format) throws DeviceNotAvailableException { 3117 throw new UnsupportedOperationException("No support for Screenshot"); 3118 } 3119 3120 /** {@inheritDoc} */ 3121 @Override getScreenshot(String format, boolean rescale)3122 public InputStreamSource getScreenshot(String format, boolean rescale) 3123 throws DeviceNotAvailableException { 3124 throw new UnsupportedOperationException("No support for Screenshot"); 3125 } 3126 3127 /** {@inheritDoc} */ 3128 @Override getScreenshot(long displayId)3129 public InputStreamSource getScreenshot(long displayId) throws DeviceNotAvailableException { 3130 throw new UnsupportedOperationException("No support for Screenshot"); 3131 } 3132 3133 /** {@inheritDoc} */ 3134 @Override clearLastConnectedWifiNetwork()3135 public void clearLastConnectedWifiNetwork() { 3136 mLastConnectedWifiSsid = null; 3137 mLastConnectedWifiPsk = null; 3138 } 3139 3140 /** 3141 * {@inheritDoc} 3142 */ 3143 @Override connectToWifiNetwork(String wifiSsid, String wifiPsk)3144 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk) 3145 throws DeviceNotAvailableException { 3146 return connectToWifiNetwork(wifiSsid, wifiPsk, false); 3147 } 3148 3149 /** 3150 * {@inheritDoc} 3151 */ 3152 @Override connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid)3153 public boolean connectToWifiNetwork(String wifiSsid, String wifiPsk, boolean scanSsid) 3154 throws DeviceNotAvailableException { 3155 LinkedHashMap<String, String> ssidToPsk = new LinkedHashMap<>(); 3156 ssidToPsk.put(wifiSsid, wifiPsk); 3157 return connectToWifiNetwork(ssidToPsk, scanSsid); 3158 } 3159 3160 /** {@inheritDoc}f */ 3161 @Override connectToWifiNetwork(Map<String, String> wifiSsidToPsk)3162 public boolean connectToWifiNetwork(Map<String, String> wifiSsidToPsk) 3163 throws DeviceNotAvailableException { 3164 return connectToWifiNetwork(wifiSsidToPsk, false); 3165 } 3166 3167 /** {@inheritDoc} */ 3168 @Override connectToWifiNetwork(Map<String, String> wifiSsidToPsk, boolean scanSsid)3169 public boolean connectToWifiNetwork(Map<String, String> wifiSsidToPsk, boolean scanSsid) 3170 throws DeviceNotAvailableException { 3171 // Clears the last connected wifi network. 3172 mLastConnectedWifiSsid = null; 3173 mLastConnectedWifiPsk = null; 3174 3175 // Connects to wifi network. It retries up to {@link TestDeviceOptions@getWifiAttempts()} 3176 // times 3177 Random rnd = new Random(); 3178 int backoffSlotCount = 2; 3179 int slotTime = mOptions.getWifiRetryWaitTime(); 3180 int waitTime = 0; 3181 long startTime = mClock.millis(); 3182 try (CloseableTraceScope ignored = new CloseableTraceScope("connectToWifiNetwork")) { 3183 for (int i = 1; i <= mOptions.getWifiAttempts(); i++) { 3184 boolean failedToEnableWifi = false; 3185 for (Map.Entry<String, String> ssidToPsk : wifiSsidToPsk.entrySet()) { 3186 String wifiSsid = ssidToPsk.getKey(); 3187 String wifiPsk = Strings.emptyToNull(ssidToPsk.getValue()); 3188 3189 InvocationMetricLogger.addInvocationMetrics( 3190 InvocationMetricKey.WIFI_CONNECT_RETRY_COUNT, i); 3191 CLog.i("Connecting to wifi network %s on %s", wifiSsid, getSerialNumber()); 3192 IWifiHelper wifi = null; 3193 if (!getOptions().useCmdWifiCommands() 3194 || !enableAdbRoot() 3195 || getApiLevel() < 31) { 3196 wifi = createWifiHelper(false); 3197 } else { 3198 wifi = createWifiHelper(true); 3199 } 3200 WifiConnectionResult result = 3201 wifi.connectToNetwork( 3202 wifiSsid, 3203 wifiPsk, 3204 mOptions.getConnCheckUrl(), 3205 scanSsid, 3206 mOptions.getDefaultNetworkType()); 3207 3208 final Map<String, String> wifiInfo = wifi.getWifiInfo(); 3209 if (WifiConnectionResult.SUCCESS.equals(result)) { 3210 CLog.i( 3211 "Successfully connected to wifi network %s(%s) on %s", 3212 wifiSsid, wifiInfo.get("bssid"), getSerialNumber()); 3213 InvocationMetricLogger.addInvocationMetrics( 3214 InvocationMetricKey.WIFI_AP_NAME, wifiSsid); 3215 mLastConnectedWifiSsid = wifiSsid; 3216 mLastConnectedWifiPsk = wifiPsk; 3217 3218 return true; 3219 } else if (WifiConnectionResult.FAILED_TO_ENABLE.equals(result)) { 3220 CLog.w("Failed to enable wifi"); 3221 failedToEnableWifi = true; 3222 } else { 3223 failedToEnableWifi = false; 3224 CLog.w( 3225 "Failed to connect to wifi network %s(%s) on %s on attempt %d of" 3226 + " %d", 3227 wifiSsid, 3228 wifiInfo.get("bssid"), 3229 getSerialNumber(), 3230 i, 3231 mOptions.getWifiAttempts()); 3232 } 3233 } 3234 if (mClock.millis() - startTime >= mOptions.getMaxWifiConnectTime()) { 3235 CLog.e( 3236 "Failed to connect to wifi after %d ms. Aborting.", 3237 mOptions.getMaxWifiConnectTime()); 3238 break; 3239 } 3240 // Do not sleep for the last iteration and when failed to enable wifi. 3241 if (i < mOptions.getWifiAttempts() && !failedToEnableWifi) { 3242 if (mOptions.isWifiExpoRetryEnabled()) { 3243 // use binary exponential back-offs when retrying. 3244 waitTime = rnd.nextInt(backoffSlotCount) * slotTime; 3245 backoffSlotCount *= 2; 3246 } 3247 CLog.e( 3248 "Waiting for %d ms before reconnecting to %s...", 3249 waitTime, wifiSsidToPsk.keySet().toString()); 3250 getRunUtil().sleep(waitTime); 3251 } 3252 } 3253 } finally { 3254 InvocationMetricLogger.addInvocationMetrics( 3255 InvocationMetricKey.WIFI_CONNECT_TIME, mClock.millis() - startTime); 3256 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.WIFI_CONNECT_COUNT, 1); 3257 } 3258 return false; 3259 } 3260 3261 /** 3262 * {@inheritDoc} 3263 */ 3264 @Override checkConnectivity()3265 public boolean checkConnectivity() throws DeviceNotAvailableException { 3266 IWifiHelper wifi = createWifiHelper(); 3267 return wifi.checkConnectivity(mOptions.getConnCheckUrl()); 3268 } 3269 3270 /** 3271 * {@inheritDoc} 3272 */ 3273 @Override connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk)3274 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk) 3275 throws DeviceNotAvailableException { 3276 return connectToWifiNetworkIfNeeded(wifiSsid, wifiPsk, false); 3277 } 3278 3279 /** 3280 * {@inheritDoc} 3281 */ 3282 @Override connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid)3283 public boolean connectToWifiNetworkIfNeeded(String wifiSsid, String wifiPsk, boolean scanSsid) 3284 throws DeviceNotAvailableException { 3285 if (!checkConnectivity()) { 3286 return connectToWifiNetwork(wifiSsid, wifiPsk, scanSsid); 3287 } 3288 return true; 3289 } 3290 3291 /** 3292 * {@inheritDoc} 3293 */ 3294 @Override isWifiEnabled()3295 public boolean isWifiEnabled() throws DeviceNotAvailableException { 3296 final IWifiHelper wifi = createWifiHelper(); 3297 try { 3298 return wifi.isWifiEnabled(); 3299 } catch (RuntimeException e) { 3300 CLog.w("Failed to create WifiHelper: %s", e.getMessage()); 3301 return false; 3302 } 3303 } 3304 3305 /** 3306 * Checks that the device is currently successfully connected to given wifi SSID. 3307 * 3308 * @param wifiSSID the wifi ssid 3309 * @return <code>true</code> if device is currently connected to wifiSSID and has network 3310 * connectivity. <code>false</code> otherwise 3311 * @throws DeviceNotAvailableException if connection with device was lost 3312 */ checkWifiConnection(String wifiSSID)3313 boolean checkWifiConnection(String wifiSSID) throws DeviceNotAvailableException { 3314 CLog.i("Checking connection with wifi network %s on %s", wifiSSID, getSerialNumber()); 3315 final IWifiHelper wifi = createWifiHelper(); 3316 // getSSID returns SSID as "SSID" 3317 final String quotedSSID = String.format("\"%s\"", wifiSSID); 3318 3319 boolean test = wifi.isWifiEnabled(); 3320 CLog.v("%s: wifi enabled? %b", getSerialNumber(), test); 3321 3322 if (test) { 3323 final String actualSSID = wifi.getSSID(); 3324 test = quotedSSID.equals(actualSSID); 3325 CLog.v("%s: SSID match (%s, %s, %b)", getSerialNumber(), quotedSSID, actualSSID, test); 3326 } 3327 if (test) { 3328 test = wifi.hasValidIp(); 3329 CLog.v("%s: validIP? %b", getSerialNumber(), test); 3330 } 3331 if (test) { 3332 test = checkConnectivity(); 3333 CLog.v("%s: checkConnectivity returned %b", getSerialNumber(), test); 3334 } 3335 return test; 3336 } 3337 3338 /** 3339 * {@inheritDoc} 3340 */ 3341 @Override disconnectFromWifi()3342 public boolean disconnectFromWifi() throws DeviceNotAvailableException { 3343 CLog.i("Disconnecting from wifi on %s", getSerialNumber()); 3344 // Clears the last connected wifi network. 3345 mLastConnectedWifiSsid = null; 3346 mLastConnectedWifiPsk = null; 3347 3348 IWifiHelper wifi = createWifiHelper(); 3349 return wifi.disconnectFromNetwork(); 3350 } 3351 3352 /** 3353 * {@inheritDoc} 3354 */ 3355 @Override getIpAddress()3356 public String getIpAddress() throws DeviceNotAvailableException { 3357 IWifiHelper wifi = createWifiHelper(); 3358 return wifi.getIpAddress(); 3359 } 3360 3361 /** 3362 * {@inheritDoc} 3363 */ 3364 @Override enableNetworkMonitor()3365 public boolean enableNetworkMonitor() throws DeviceNotAvailableException { 3366 mNetworkMonitorEnabled = false; 3367 3368 IWifiHelper wifi = createWifiHelper(); 3369 wifi.stopMonitor(); 3370 if (wifi.startMonitor(NETWORK_MONITOR_INTERVAL, mOptions.getConnCheckUrl())) { 3371 mNetworkMonitorEnabled = true; 3372 return true; 3373 } 3374 return false; 3375 } 3376 3377 /** 3378 * {@inheritDoc} 3379 */ 3380 @Override disableNetworkMonitor()3381 public boolean disableNetworkMonitor() throws DeviceNotAvailableException { 3382 mNetworkMonitorEnabled = false; 3383 3384 IWifiHelper wifi = createWifiHelper(); 3385 List<Long> samples = wifi.stopMonitor(); 3386 if (!samples.isEmpty()) { 3387 int failures = 0; 3388 long totalLatency = 0; 3389 for (Long sample : samples) { 3390 if (sample < 0) { 3391 failures += 1; 3392 } else { 3393 totalLatency += sample; 3394 } 3395 } 3396 double failureRate = failures * 100.0 / samples.size(); 3397 double avgLatency = 0.0; 3398 if (failures < samples.size()) { 3399 avgLatency = totalLatency / (samples.size() - failures); 3400 } 3401 CLog.d("[metric] url=%s, window=%ss, failure_rate=%.2f%%, latency_avg=%.2f", 3402 mOptions.getConnCheckUrl(), samples.size() * NETWORK_MONITOR_INTERVAL / 1000, 3403 failureRate, avgLatency); 3404 } 3405 return true; 3406 } 3407 3408 /** 3409 * Create a {@link WifiHelper} to use 3410 * 3411 * <p> 3412 * 3413 * @throws DeviceNotAvailableException 3414 */ 3415 @VisibleForTesting createWifiHelper()3416 IWifiHelper createWifiHelper() throws DeviceNotAvailableException { 3417 // current wifi helper won't work on AndroidNativeDevice 3418 // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when 3419 // we learn what is available. 3420 throw new UnsupportedOperationException("Wifi helper is not supported."); 3421 } 3422 3423 /** 3424 * Create a {@link WifiHelper} to use 3425 * 3426 * @param useV2 Whether to use WifiHelper v2 which does not install any apk. 3427 * <p> 3428 * @throws DeviceNotAvailableException 3429 */ 3430 @VisibleForTesting createWifiHelper(boolean useV2)3431 IWifiHelper createWifiHelper(boolean useV2) throws DeviceNotAvailableException { 3432 // current wifi helper won't work on AndroidNativeDevice 3433 // TODO: create a new Wifi helper with supported feature of AndroidNativeDevice when 3434 // we learn what is available. 3435 throw new UnsupportedOperationException("Wifi helper is not supported."); 3436 } 3437 3438 /** 3439 * {@inheritDoc} 3440 */ 3441 @Override clearErrorDialogs()3442 public boolean clearErrorDialogs() throws DeviceNotAvailableException { 3443 CLog.e("No support for Screen's features"); 3444 return false; 3445 } 3446 3447 /** {@inheritDoc} */ 3448 @Override getKeyguardState()3449 public KeyguardControllerState getKeyguardState() throws DeviceNotAvailableException { 3450 throw new UnsupportedOperationException("No support for keyguard querying."); 3451 } 3452 getDeviceStateMonitor()3453 IDeviceStateMonitor getDeviceStateMonitor() { 3454 return mStateMonitor; 3455 } 3456 3457 /** {@inheritDoc} */ 3458 @Override postBootSetup()3459 public void postBootSetup() throws DeviceNotAvailableException { 3460 if (getOptions().shouldDisableReboot()) { 3461 return; 3462 } 3463 getConnection().reconnect(getSerialNumber()); 3464 CLog.d("postBootSetup started"); 3465 long startTime = System.currentTimeMillis(); 3466 try (CloseableTraceScope ignored = new CloseableTraceScope("postBootSetup")) { 3467 enableAdbRoot(); 3468 prePostBootSetup(); 3469 for (String command : mOptions.getPostBootCommands()) { 3470 long start = System.currentTimeMillis(); 3471 try (CloseableTraceScope cmdTrace = new CloseableTraceScope(command)) { 3472 executeShellCommand(command); 3473 } 3474 if (command.startsWith("sleep")) { 3475 InvocationMetricLogger.addInvocationPairMetrics( 3476 InvocationMetricKey.host_sleep, start, System.currentTimeMillis()); 3477 } 3478 } 3479 } finally { 3480 long elapsed = System.currentTimeMillis() - startTime; 3481 InvocationMetricLogger.addInvocationMetrics( 3482 InvocationMetricKey.POSTBOOT_SETUP_TIME, elapsed); 3483 InvocationMetricLogger.addInvocationMetrics( 3484 InvocationMetricKey.POSTBOOT_SETUP_COUNT, 1); 3485 CLog.d("postBootSetup done: %s", TimeUtil.formatElapsedTime(elapsed)); 3486 } 3487 } 3488 3489 /** 3490 * Allows each device type (AndroidNativeDevice, TestDevice) to override this method for 3491 * specific post boot setup. 3492 * 3493 * @throws DeviceNotAvailableException 3494 */ prePostBootSetup()3495 protected void prePostBootSetup() throws DeviceNotAvailableException { 3496 // Empty on purpose. 3497 } 3498 3499 /** 3500 * Ensure wifi connection is re-established after boot. This is intended to be called after TF 3501 * initiated reboots(ones triggered by {@link #reboot()}) only. 3502 * 3503 * @throws DeviceNotAvailableException 3504 */ postBootWifiSetup()3505 void postBootWifiSetup() throws DeviceNotAvailableException { 3506 CLog.d("postBootWifiSetup started"); 3507 long startTime = System.currentTimeMillis(); 3508 try (CloseableTraceScope ignored = new CloseableTraceScope("postBootWifiSetup")) { 3509 if (mLastConnectedWifiSsid != null) { 3510 reconnectToWifiNetwork(); 3511 } 3512 if (mNetworkMonitorEnabled) { 3513 if (!enableNetworkMonitor()) { 3514 CLog.w( 3515 "Failed to enable network monitor on %s after reboot", 3516 getSerialNumber()); 3517 } 3518 } 3519 } finally { 3520 long elapsed = System.currentTimeMillis() - startTime; 3521 InvocationMetricLogger.addInvocationMetrics( 3522 InvocationMetricKey.POSTBOOT_WIFI_SETUP_TIME, elapsed); 3523 InvocationMetricLogger.addInvocationMetrics( 3524 InvocationMetricKey.POSTBOOT_WIFI_SETUP_COUNT, 1); 3525 CLog.d("postBootWifiSetup done: %s", TimeUtil.formatElapsedTime(elapsed)); 3526 } 3527 } 3528 reconnectToWifiNetwork()3529 void reconnectToWifiNetwork() throws DeviceNotAvailableException { 3530 // First, wait for wifi to re-connect automatically. 3531 long startTime = System.currentTimeMillis(); 3532 boolean isConnected = checkConnectivity(); 3533 while (!isConnected && (System.currentTimeMillis() - startTime) < WIFI_RECONNECT_TIMEOUT) { 3534 getRunUtil().sleep(WIFI_RECONNECT_CHECK_INTERVAL); 3535 isConnected = checkConnectivity(); 3536 } 3537 3538 if (isConnected) { 3539 return; 3540 } 3541 3542 // If wifi is still not connected, try to re-connect on our own. 3543 final String wifiSsid = mLastConnectedWifiSsid; 3544 if (!connectToWifiNetworkIfNeeded(mLastConnectedWifiSsid, mLastConnectedWifiPsk)) { 3545 throw new NetworkNotAvailableException( 3546 String.format("Failed to connect to wifi network %s on %s after reboot", 3547 wifiSsid, getSerialNumber())); 3548 } 3549 } 3550 3551 /** 3552 * {@inheritDoc} 3553 */ 3554 @Override rebootIntoBootloader()3555 public void rebootIntoBootloader() 3556 throws DeviceNotAvailableException, UnsupportedOperationException { 3557 if (isInRebootCallback()) { 3558 CLog.d( 3559 "'%s' action is disabled during reboot callback. Ignoring.", 3560 "Reboot into Bootloader"); 3561 return; 3562 } 3563 rebootIntoFastbootInternal(true); 3564 } 3565 3566 /** {@inheritDoc} */ 3567 @Override rebootIntoFastbootd()3568 public void rebootIntoFastbootd() 3569 throws DeviceNotAvailableException, UnsupportedOperationException { 3570 if (isInRebootCallback()) { 3571 CLog.d( 3572 "'%s' action is disabled during reboot callback. Ignoring.", 3573 "Reboot into Fastbootd"); 3574 return; 3575 } 3576 rebootIntoFastbootInternal(false); 3577 } 3578 3579 /** 3580 * Reboots the device into bootloader or fastbootd mode. 3581 * 3582 * @param isBootloader true to boot the device into bootloader mode, false to boot the device 3583 * into fastbootd mode. 3584 * @throws DeviceNotAvailableException if connection with device is lost and cannot be 3585 * recovered. 3586 */ rebootIntoFastbootInternal(boolean isBootloader)3587 private void rebootIntoFastbootInternal(boolean isBootloader) 3588 throws DeviceNotAvailableException { 3589 invalidatePropertyCache(); 3590 final RebootMode mode = 3591 isBootloader ? RebootMode.REBOOT_INTO_BOOTLOADER : RebootMode.REBOOT_INTO_FASTBOOTD; 3592 if (!mFastbootEnabled) { 3593 throw new UnsupportedOperationException( 3594 String.format("Fastboot is not available and cannot reboot into %s", mode)); 3595 } 3596 // Force wait for snapuserd in progress just to be sure 3597 waitForSnapuserd(SnapuserdWaitPhase.BLOCK_BEFORE_RELEASING); 3598 long startTime = System.currentTimeMillis(); 3599 3600 try (CloseableTraceScope ignored = 3601 new CloseableTraceScope("reboot_in_" + mode.toString())) { 3602 // Update fastboot serial number before entering fastboot mode 3603 mStateMonitor.setFastbootSerialNumber(getFastbootSerialNumber()); 3604 3605 // If we go to bootloader, it's probably for flashing so ensure we re-check the provider 3606 mShouldSkipContentProviderSetup = false; 3607 CLog.i( 3608 "Rebooting device %s in state %s into %s", 3609 getSerialNumber(), getDeviceState(), mode); 3610 if (isStateBootloaderOrFastbootd()) { 3611 CLog.i( 3612 "device %s already in %s. Rebooting anyway", 3613 getSerialNumber(), getDeviceState()); 3614 InvocationMetricLogger.addInvocationMetrics( 3615 InvocationMetricKey.BOOTLOADER_SAME_STATE_REBOOT, 1); 3616 executeFastbootCommand(String.format("reboot-%s", mode)); 3617 } else { 3618 CLog.i("Booting device %s into %s", getSerialNumber(), mode); 3619 doAdbReboot(mode, null); 3620 } 3621 3622 // We want to wait on a command that verifies we've rebooted. 3623 // However, it is possible to issue this command too quickly and get 3624 // a response before the device has begun the reboot process (see 3625 // b/242200753). 3626 // While not as clean as we'd like, we wait 1.5 seconds before 3627 // issuing any waiting commands, as devices generally take much 3628 // longer than 1.5 seconds to reboot anyway. 3629 getRunUtil().sleep(1500); 3630 3631 if (RebootMode.REBOOT_INTO_FASTBOOTD.equals(mode) 3632 && getHostOptions().isFastbootdEnable()) { 3633 if (!mStateMonitor.waitForDeviceFastbootd( 3634 getFastbootPath(), mOptions.getFastbootTimeout())) { 3635 recoverDeviceFromFastbootd(); 3636 } 3637 } else { 3638 waitForDeviceBootloader(); 3639 } 3640 } finally { 3641 long elapsedTime = System.currentTimeMillis() - startTime; 3642 if (RebootMode.REBOOT_INTO_FASTBOOTD.equals(mode)) { 3643 InvocationMetricLogger.addInvocationMetrics( 3644 InvocationMetricKey.FASTBOOTD_REBOOT_TIME, elapsedTime); 3645 InvocationMetricLogger.addInvocationMetrics( 3646 InvocationMetricKey.FASTBOOTD_REBOOT_COUNT, 1); 3647 } else { 3648 InvocationMetricLogger.addInvocationMetrics( 3649 InvocationMetricKey.BOOTLOADER_REBOOT_TIME, elapsedTime); 3650 InvocationMetricLogger.addInvocationMetrics( 3651 InvocationMetricKey.BOOTLOADER_REBOOT_COUNT, 1); 3652 } 3653 } 3654 } 3655 3656 /** {@inheritDoc} */ 3657 @Override isStateBootloaderOrFastbootd()3658 public boolean isStateBootloaderOrFastbootd() { 3659 return TestDeviceState.FASTBOOT.equals(getDeviceState()) 3660 || TestDeviceState.FASTBOOTD.equals(getDeviceState()); 3661 } 3662 3663 /** 3664 * {@inheritDoc} 3665 */ 3666 @Override reboot()3667 public void reboot() throws DeviceNotAvailableException { 3668 reboot(null); 3669 } 3670 3671 /** {@inheritDoc} */ 3672 @Override reboot(@ullable String reason)3673 public void reboot(@Nullable String reason) throws DeviceNotAvailableException { 3674 if (isInRebootCallback()) { 3675 CLog.d("'%s' action is disabled during reboot callback. Ignoring.", "Reboot"); 3676 return; 3677 } 3678 internalRebootUntilOnline(reason); 3679 3680 RecoveryMode cachedRecoveryMode = getRecoveryMode(); 3681 setRecoveryMode(RecoveryMode.ONLINE); 3682 3683 if (isEncryptionSupported() && isDeviceEncrypted()) { 3684 unlockDevice(); 3685 } 3686 3687 setRecoveryMode(cachedRecoveryMode); 3688 3689 try (CloseableTraceScope ignored = 3690 new CloseableTraceScope("reboot_waitForDeviceAvailable")) { 3691 waitForDeviceAvailable(mOptions.getRebootTimeout()); 3692 } 3693 postBootSetup(); 3694 postBootWifiSetup(); 3695 // notify of reboot end here. Full reboots will end here as well as reboots from Bootloader 3696 // or Fastboot mode. 3697 notifyRebootEnded(); 3698 } 3699 3700 @Override rebootUserspace()3701 public void rebootUserspace() throws DeviceNotAvailableException { 3702 if (isInRebootCallback()) { 3703 CLog.d("'%s' action is disabled during reboot callback. Ignoring.", "Reboot Userspace"); 3704 return; 3705 } 3706 rebootUserspaceUntilOnline(); 3707 3708 RecoveryMode cachedRecoveryMode = getRecoveryMode(); 3709 setRecoveryMode(RecoveryMode.ONLINE); 3710 3711 if (isEncryptionSupported()) { 3712 if (isDeviceEncrypted()) { 3713 CLog.e("Device is encrypted after userspace reboot!"); 3714 unlockDevice(); 3715 } 3716 } 3717 3718 setRecoveryMode(cachedRecoveryMode); 3719 3720 waitForDeviceAvailable(mOptions.getRebootTimeout()); 3721 postBootSetup(); 3722 postBootWifiSetup(); 3723 } 3724 3725 @Override rebootUntilOnline()3726 public void rebootUntilOnline() throws DeviceNotAvailableException { 3727 if (isInRebootCallback()) { 3728 CLog.d( 3729 "'%s' action is disabled during reboot callback. Ignoring.", 3730 "Reboot Until Online"); 3731 return; 3732 } 3733 try { 3734 internalRebootUntilOnline(null); 3735 } finally { 3736 if (!mDeviceActionReceivers.isEmpty()) { 3737 CLog.d( 3738 "DeviceActionReceivers were not notified after rebootUntilOnline on %s.", 3739 getSerialNumber()); 3740 } 3741 } 3742 } 3743 3744 /** {@inheritDoc} */ 3745 @Override rebootUntilOnline(@ullable String reason)3746 public void rebootUntilOnline(@Nullable String reason) throws DeviceNotAvailableException { 3747 if (isInRebootCallback()) { 3748 CLog.d( 3749 "'%s' action is disabled during reboot callback. Ignoring.", 3750 "Reboot Until Online"); 3751 return; 3752 } 3753 try { 3754 internalRebootUntilOnline(reason); 3755 } finally { 3756 if (!mDeviceActionReceivers.isEmpty()) { 3757 CLog.d( 3758 "DeviceActionReceivers were not notified after rebootUntilOnline on %s.", 3759 getSerialNumber()); 3760 } 3761 } 3762 } 3763 internalRebootUntilOnline(@ullable String reason)3764 private void internalRebootUntilOnline(@Nullable String reason) 3765 throws DeviceNotAvailableException { 3766 long rebootStart = System.currentTimeMillis(); 3767 try (CloseableTraceScope ignored = new CloseableTraceScope("rebootUntilOnline")) { 3768 // Invalidate cache before reboots 3769 mPropertiesCache.invalidateAll(); 3770 doReboot(RebootMode.REBOOT_FULL, reason); 3771 RecoveryMode cachedRecoveryMode = getRecoveryMode(); 3772 setRecoveryMode(RecoveryMode.ONLINE); 3773 waitForDeviceOnline(); 3774 enableAdbRoot(); 3775 setRecoveryMode(cachedRecoveryMode); 3776 } finally { 3777 InvocationMetricLogger.addInvocationMetrics( 3778 InvocationMetricKey.ADB_REBOOT_TIME, System.currentTimeMillis() - rebootStart); 3779 InvocationMetricLogger.addInvocationMetrics( 3780 InvocationMetricKey.ADB_REBOOT_ROUTINE_COUNT, 1); 3781 } 3782 } 3783 3784 @Override rebootUserspaceUntilOnline()3785 public void rebootUserspaceUntilOnline() throws DeviceNotAvailableException { 3786 if (isInRebootCallback()) { 3787 CLog.d( 3788 "'%s' action is disabled during reboot callback. Ignoring.", 3789 "Reboot Userspace Until Online"); 3790 return; 3791 } 3792 doReboot(RebootMode.REBOOT_USERSPACE, null); 3793 RecoveryMode cachedRecoveryMode = getRecoveryMode(); 3794 setRecoveryMode(RecoveryMode.ONLINE); 3795 waitForDeviceOnline(); 3796 enableAdbRoot(); 3797 setRecoveryMode(cachedRecoveryMode); 3798 } 3799 3800 /** 3801 * {@inheritDoc} 3802 */ 3803 @Override rebootIntoRecovery()3804 public void rebootIntoRecovery() throws DeviceNotAvailableException { 3805 if (isInRebootCallback()) { 3806 CLog.d( 3807 "'%s' action is disabled during reboot callback. Ignoring.", 3808 "Reboot into Recovery"); 3809 return; 3810 } 3811 if (isStateBootloaderOrFastbootd()) { 3812 CLog.w("device %s in fastboot when requesting boot to recovery. " + 3813 "Rebooting to userspace first.", getSerialNumber()); 3814 internalRebootUntilOnline(null); 3815 } 3816 doAdbReboot(RebootMode.REBOOT_INTO_RECOVERY, null); 3817 if (!waitForDeviceInRecovery(mOptions.getAdbRecoveryTimeout())) { 3818 recoverDeviceInRecovery(); 3819 } 3820 } 3821 3822 3823 /** {@inheritDoc} */ 3824 @Override rebootIntoSideload()3825 public void rebootIntoSideload() throws DeviceNotAvailableException { 3826 rebootIntoSideload(false); 3827 } 3828 /** {@inheritDoc} */ 3829 @Override rebootIntoSideload(boolean autoReboot)3830 public void rebootIntoSideload(boolean autoReboot) throws DeviceNotAvailableException { 3831 if (isInRebootCallback()) { 3832 CLog.d( 3833 "'%s' action is disabled during reboot callback. Ignoring.", 3834 "Reboot into Sideload"); 3835 return; 3836 } 3837 if (isStateBootloaderOrFastbootd()) { 3838 CLog.w( 3839 "device %s in fastboot when requesting boot to sideload. " 3840 + "Rebooting to userspace first.", 3841 getSerialNumber()); 3842 internalRebootUntilOnline(null); 3843 } 3844 final RebootMode rebootMode; 3845 if (autoReboot) { 3846 rebootMode = RebootMode.REBOOT_INTO_SIDELOAD_AUTO_REBOOT; 3847 } else { 3848 rebootMode = RebootMode.REBOOT_INTO_SIDELOAD; 3849 } 3850 doAdbReboot(rebootMode, null); 3851 if (!waitForDeviceInSideload(mOptions.getAdbRecoveryTimeout())) { 3852 // using recovery mode because sideload is a sub-mode under recovery 3853 recoverDeviceInRecovery(); 3854 } 3855 } 3856 3857 /** 3858 * {@inheritDoc} 3859 */ 3860 @Override nonBlockingReboot()3861 public void nonBlockingReboot() throws DeviceNotAvailableException { 3862 if (isInRebootCallback()) { 3863 CLog.d( 3864 "'%s' action is disabled during reboot callback. Ignoring.", 3865 "Non Blocking Reboot"); 3866 return; 3867 } 3868 try { 3869 doReboot(RebootMode.REBOOT_FULL, null); 3870 } finally { 3871 if (!mDeviceActionReceivers.isEmpty()) { 3872 CLog.d( 3873 "DeviceActionReceivers were not notified after nonBlockingReboot on %s.", 3874 getSerialNumber()); 3875 } 3876 } 3877 } 3878 3879 /** 3880 * A mode of a reboot. 3881 * 3882 * <p>Source of truth for available modes is defined in init. 3883 */ 3884 @VisibleForTesting 3885 protected enum RebootMode { 3886 REBOOT_FULL(""), 3887 REBOOT_USERSPACE("userspace"), 3888 REBOOT_INTO_FASTBOOTD("fastboot"), 3889 REBOOT_INTO_BOOTLOADER("bootloader"), 3890 REBOOT_INTO_SIDELOAD("sideload"), 3891 REBOOT_INTO_SIDELOAD_AUTO_REBOOT("sideload-auto-reboot"), 3892 REBOOT_INTO_RECOVERY("recovery"); 3893 3894 private final String mRebootTarget; 3895 RebootMode(String rebootTarget)3896 RebootMode(String rebootTarget) { 3897 mRebootTarget = rebootTarget; 3898 } 3899 3900 @Nullable formatRebootCommand(@ullable String reason)3901 String formatRebootCommand(@Nullable String reason) { 3902 if (this == REBOOT_FULL) { 3903 return Strings.isNullOrEmpty(reason) ? null : reason; 3904 } else { 3905 return Strings.isNullOrEmpty(reason) ? mRebootTarget : mRebootTarget + "," + reason; 3906 } 3907 } 3908 3909 @Override toString()3910 public String toString() { 3911 return mRebootTarget; 3912 } 3913 } 3914 3915 /** 3916 * Trigger a reboot of the device, offers no guarantee of the device state after the call. 3917 * 3918 * @param rebootMode a mode of this reboot 3919 * @param reason reason for this reboot 3920 * @throws DeviceNotAvailableException 3921 * @throws UnsupportedOperationException 3922 */ 3923 @VisibleForTesting doReboot(RebootMode rebootMode, @Nullable final String reason)3924 void doReboot(RebootMode rebootMode, @Nullable final String reason) 3925 throws DeviceNotAvailableException, UnsupportedOperationException { 3926 // Track Tradefed reboot time 3927 mLastTradefedRebootTime = System.currentTimeMillis(); 3928 3929 if (isStateBootloaderOrFastbootd()) { 3930 CLog.i("device %s in %s. Rebooting to userspace.", getSerialNumber(), getDeviceState()); 3931 executeFastbootCommand("reboot"); 3932 } else { 3933 if (mOptions.shouldDisableReboot()) { 3934 CLog.i("Device reboot disabled by options, skipped."); 3935 return; 3936 } 3937 if (reason == null) { 3938 CLog.i("Rebooting device %s mode: %s", getSerialNumber(), rebootMode.name()); 3939 } else { 3940 CLog.i( 3941 "Rebooting device %s mode: %s reason: %s", 3942 getSerialNumber(), rebootMode.name(), reason); 3943 } 3944 doAdbReboot(rebootMode, reason); 3945 postAdbReboot(); 3946 } 3947 } 3948 3949 /** 3950 * Possible extra actions that can be taken after a reboot. 3951 * 3952 * @throws DeviceNotAvailableException 3953 */ postAdbReboot()3954 protected void postAdbReboot() throws DeviceNotAvailableException { 3955 // Check if device shows as unavailable (as expected after reboot). 3956 boolean notAvailable = waitForDeviceNotAvailable(DEFAULT_UNAVAILABLE_TIMEOUT); 3957 if (!notAvailable) { 3958 CLog.w("Did not detect device %s becoming unavailable after reboot", getSerialNumber()); 3959 } 3960 getConnection().reconnect(getSerialNumber()); 3961 } 3962 3963 /** 3964 * Perform a adb reboot. 3965 * 3966 * @param rebootMode a mode of this reboot. 3967 * @param reason for this reboot. 3968 * @throws DeviceNotAvailableException 3969 */ doAdbReboot(RebootMode rebootMode, @Nullable final String reason)3970 protected void doAdbReboot(RebootMode rebootMode, @Nullable final String reason) 3971 throws DeviceNotAvailableException { 3972 getConnection().notifyAdbRebootCalled(); 3973 DeviceAction rebootAction = createRebootDeviceAction(rebootMode, reason); 3974 performDeviceAction("reboot", rebootAction, MAX_RETRY_ATTEMPTS); 3975 } 3976 3977 /** 3978 * Create a {@link RebootDeviceAction} to be used when performing a reboot action. 3979 * 3980 * @param rebootMode a mode of this reboot. 3981 * @param reason for this reboot. 3982 * @return the created {@link RebootDeviceAction}. 3983 */ createRebootDeviceAction( RebootMode rebootMode, @Nullable final String reason)3984 protected RebootDeviceAction createRebootDeviceAction( 3985 RebootMode rebootMode, @Nullable final String reason) { 3986 return new RebootDeviceAction(rebootMode, reason); 3987 } 3988 3989 /** 3990 * Wait to see the device going unavailable (stop reporting to adb). 3991 * 3992 * @param operationDesc The name of the operation that is waiting for unavailable. 3993 * @param time The time to wait for unavailable to occur. 3994 * @return True if device did become unavailable. 3995 */ waitForDeviceNotAvailable(String operationDesc, long time)3996 protected boolean waitForDeviceNotAvailable(String operationDesc, long time) { 3997 // TODO: a bit of a race condition here. Would be better to start a 3998 // before the operation 3999 if (!mStateMonitor.waitForDeviceNotAvailable(time)) { 4000 // above check is flaky, ignore till better solution is found 4001 CLog.w("Did not detect device %s becoming unavailable after %s", getSerialNumber(), 4002 operationDesc); 4003 return false; 4004 } 4005 return true; 4006 } 4007 4008 /** 4009 * {@inheritDoc} 4010 */ 4011 @Override waitForDeviceNotAvailable(long waitTime)4012 public boolean waitForDeviceNotAvailable(long waitTime) { 4013 return mStateMonitor.waitForDeviceNotAvailable(waitTime); 4014 } 4015 4016 /** 4017 * {@inheritDoc} 4018 */ 4019 @Override enableAdbRoot()4020 public boolean enableAdbRoot() throws DeviceNotAvailableException { 4021 // adb root is a relatively intensive command, so do a brief check first to see 4022 // if its necessary or not 4023 if (isAdbRoot()) { 4024 CLog.i("adb is already running as root on %s", getSerialNumber()); 4025 // Still check for online, in some case we could see the root, but device could be 4026 // very early in its cycle. 4027 waitForDeviceOnline(); 4028 return true; 4029 } 4030 // Don't enable root if user requested no root 4031 if (!isEnableAdbRoot()) { 4032 CLog.i("\"enable-root\" set to false; ignoring 'adb root' request"); 4033 return false; 4034 } 4035 InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.ADB_ROOT_ROUTINE_COUNT, 1); 4036 long startTime = System.currentTimeMillis(); 4037 try (CloseableTraceScope ignored = new CloseableTraceScope("adb_root")) { 4038 CLog.i("adb root on device %s", getSerialNumber()); 4039 int attempts = MAX_RETRY_ATTEMPTS + 1; 4040 for (int i = 1; i <= attempts; i++) { 4041 String output = executeAdbCommand("root"); 4042 // wait for device to disappear from adb 4043 boolean res = 4044 waitForDeviceNotAvailable( 4045 "root", getOptions().getAdbRootUnavailableTimeout()); 4046 if (!res && TestDeviceState.ONLINE.equals(getDeviceState())) { 4047 if (isAdbRoot()) { 4048 return true; 4049 } 4050 } 4051 4052 postAdbRootAction(); 4053 4054 // wait for device to be back online 4055 waitForDeviceOnline(); 4056 4057 if (isAdbRoot()) { 4058 return true; 4059 } 4060 CLog.w( 4061 "'adb root' on %s unsuccessful on attempt %d of %d. Output: '%s'", 4062 getSerialNumber(), i, attempts, output); 4063 } 4064 return false; 4065 } finally { 4066 InvocationMetricLogger.addInvocationMetrics( 4067 InvocationMetricKey.ADB_ROOT_TIME, System.currentTimeMillis() - startTime); 4068 } 4069 } 4070 4071 /** 4072 * {@inheritDoc} 4073 */ 4074 @Override disableAdbRoot()4075 public boolean disableAdbRoot() throws DeviceNotAvailableException { 4076 if (!isAdbRoot()) { 4077 CLog.i("adb is already unroot on %s", getSerialNumber()); 4078 return true; 4079 } 4080 4081 CLog.i("adb unroot on device %s", getSerialNumber()); 4082 int attempts = MAX_RETRY_ATTEMPTS + 1; 4083 for (int i=1; i <= attempts; i++) { 4084 String output = executeAdbCommand("unroot"); 4085 // wait for device to disappear from adb 4086 waitForDeviceNotAvailable("unroot", 5 * 1000); 4087 4088 postAdbUnrootAction(); 4089 4090 // wait for device to be back online 4091 waitForDeviceOnline(); 4092 4093 if (!isAdbRoot()) { 4094 return true; 4095 } 4096 CLog.w("'adb unroot' on %s unsuccessful on attempt %d of %d. Output: '%s'", 4097 getSerialNumber(), i, attempts, output); 4098 } 4099 return false; 4100 } 4101 4102 /** 4103 * Override if the device needs some specific actions to be taken after adb root and before the 4104 * device is back online. 4105 * Default implementation doesn't include any addition actions. 4106 * adb root is not guaranteed to be enabled at this stage. 4107 * @throws DeviceNotAvailableException 4108 */ postAdbRootAction()4109 public void postAdbRootAction() throws DeviceNotAvailableException { 4110 getConnection().reconnect(getSerialNumber()); 4111 } 4112 4113 /** 4114 * Override if the device needs some specific actions to be taken after adb unroot and before 4115 * the device is back online. 4116 * Default implementation doesn't include any additional actions. 4117 * adb root is not guaranteed to be disabled at this stage. 4118 * @throws DeviceNotAvailableException 4119 */ postAdbUnrootAction()4120 public void postAdbUnrootAction() throws DeviceNotAvailableException { 4121 getConnection().reconnect(getSerialNumber()); 4122 } 4123 4124 /** 4125 * {@inheritDoc} 4126 */ 4127 @Override isAdbRoot()4128 public boolean isAdbRoot() throws DeviceNotAvailableException { 4129 String output = executeShellCommand("id"); 4130 return output.contains("uid=0(root)"); 4131 } 4132 4133 /** 4134 * {@inheritDoc} 4135 */ 4136 @Override unlockDevice()4137 public boolean unlockDevice() throws DeviceNotAvailableException, 4138 UnsupportedOperationException { 4139 if (!isEncryptionSupported()) { 4140 throw new UnsupportedOperationException(String.format("Can't unlock device %s: " 4141 + "encryption not supported", getSerialNumber())); 4142 } 4143 4144 if (!isDeviceEncrypted()) { 4145 CLog.d("Device %s is not encrypted, skipping", getSerialNumber()); 4146 return true; 4147 } 4148 String encryptionType = getProperty("ro.crypto.type"); 4149 if (!"block".equals(encryptionType)) { 4150 CLog.d( 4151 "Skipping unlockDevice since it's not encrypted. ro.crypto.type=%s", 4152 encryptionType); 4153 return true; 4154 } 4155 4156 CLog.i("Unlocking device %s", getSerialNumber()); 4157 4158 enableAdbRoot(); 4159 4160 // FIXME: currently, vcd checkpw can return an empty string when it never should. Try 3 4161 // times. 4162 String output; 4163 int i = 0; 4164 do { 4165 // Enter the password. Output will be: 4166 // "200 [X] -1" if the password has already been entered correctly, 4167 // "200 [X] 0" if the password is entered correctly, 4168 // "200 [X] N" where N is any positive number if the password is incorrect, 4169 // any other string if there is an error. 4170 output = executeShellCommand(String.format("vdc cryptfs checkpw \"%s\"", 4171 ENCRYPTION_PASSWORD)).trim(); 4172 4173 if (output.startsWith("200 ") && output.endsWith(" -1")) { 4174 return true; 4175 } 4176 4177 if (!output.isEmpty() && !(output.startsWith("200 ") && output.endsWith(" 0"))) { 4178 CLog.e("checkpw gave output '%s' while trying to unlock device %s", 4179 output, getSerialNumber()); 4180 return false; 4181 } 4182 4183 getRunUtil().sleep(500); 4184 } while (output.isEmpty() && ++i < 3); 4185 4186 if (output.isEmpty()) { 4187 CLog.e("checkpw gave no output while trying to unlock device %s"); 4188 } 4189 4190 // Restart the framework. Output will be: 4191 // "200 [X] 0" if the user data partition can be mounted, 4192 // "200 [X] -1" if the user data partition can not be mounted (no correct password given), 4193 // any other string if there is an error. 4194 output = executeShellCommand("vdc cryptfs restart").trim(); 4195 4196 if (!(output.startsWith("200 ") && output.endsWith(" 0"))) { 4197 CLog.e("restart gave output '%s' while trying to unlock device %s", output, 4198 getSerialNumber()); 4199 return false; 4200 } 4201 4202 waitForDeviceAvailable(); 4203 4204 return true; 4205 } 4206 4207 /** 4208 * {@inheritDoc} 4209 */ 4210 @Override isDeviceEncrypted()4211 public boolean isDeviceEncrypted() throws DeviceNotAvailableException { 4212 String output = getProperty("ro.crypto.state"); 4213 4214 if (output == null && isEncryptionSupported()) { 4215 CLog.w("Property ro.crypto.state is null on device %s", getSerialNumber()); 4216 } 4217 if (output == null) { 4218 return false; 4219 } 4220 return "encrypted".equals(output.trim()); 4221 } 4222 4223 /** 4224 * {@inheritDoc} 4225 */ 4226 @Override isEncryptionSupported()4227 public boolean isEncryptionSupported() throws DeviceNotAvailableException { 4228 if (!isEnableAdbRoot()) { 4229 CLog.i("root is required for encryption"); 4230 mIsEncryptionSupported = false; 4231 return mIsEncryptionSupported; 4232 } 4233 if (mIsEncryptionSupported != null) { 4234 return mIsEncryptionSupported.booleanValue(); 4235 } 4236 enableAdbRoot(); 4237 4238 String output = getProperty("ro.crypto.state"); 4239 if (output == null || "unsupported".equals(output.trim())) { 4240 mIsEncryptionSupported = false; 4241 return mIsEncryptionSupported; 4242 } 4243 mIsEncryptionSupported = true; 4244 return mIsEncryptionSupported; 4245 } 4246 4247 /** 4248 * {@inheritDoc} 4249 */ 4250 @Override waitForDeviceOnline(long waitTime)4251 public void waitForDeviceOnline(long waitTime) throws DeviceNotAvailableException { 4252 if (mStateMonitor.waitForDeviceOnline(waitTime) == null) { 4253 recoverDevice(); 4254 } 4255 } 4256 4257 /** 4258 * {@inheritDoc} 4259 */ 4260 @Override waitForDeviceOnline()4261 public void waitForDeviceOnline() throws DeviceNotAvailableException { 4262 if (mStateMonitor.waitForDeviceOnline() == null) { 4263 recoverDevice(); 4264 } 4265 } 4266 4267 /** {@inheritDoc} */ 4268 @Override waitForDeviceAvailable(long waitTime)4269 public boolean waitForDeviceAvailable(long waitTime) throws DeviceNotAvailableException { 4270 if (mStateMonitor.waitForDeviceAvailable(waitTime) == null) { 4271 return recoverDevice(); 4272 } 4273 return true; 4274 } 4275 4276 /** {@inheritDoc} */ 4277 @Override waitForDeviceAvailable()4278 public boolean waitForDeviceAvailable() throws DeviceNotAvailableException { 4279 if (mStateMonitor.waitForDeviceAvailable() == null) { 4280 return recoverDevice(); 4281 } 4282 return true; 4283 } 4284 4285 /** {@inheritDoc} */ 4286 @Override waitForDeviceAvailableInRecoverPath(final long waitTime)4287 public boolean waitForDeviceAvailableInRecoverPath(final long waitTime) 4288 throws DeviceNotAvailableException { 4289 return mStateMonitor.waitForDeviceAvailableInRecoverPath(waitTime) != null; 4290 } 4291 4292 /** 4293 * {@inheritDoc} 4294 */ 4295 @Override waitForDeviceInRecovery(long waitTime)4296 public boolean waitForDeviceInRecovery(long waitTime) { 4297 return mStateMonitor.waitForDeviceInRecovery(waitTime); 4298 } 4299 4300 /** {@inheritDoc} */ 4301 @Override waitForDeviceBootloader()4302 public void waitForDeviceBootloader() throws DeviceNotAvailableException { 4303 if (mOptions.useUpdatedBootloaderStatus()) { 4304 CommandResult commandResult = 4305 simpleFastbootCommand( 4306 mOptions.getFastbootTimeout(), 4307 buildFastbootCommand("getvar", "product")); 4308 if (!CommandStatus.SUCCESS.equals(commandResult.getStatus())) { 4309 CLog.e( 4310 "Waiting for device in bootloader. Status: %s.\nstdout:%s\nstderr:%s", 4311 commandResult.getStatus(), 4312 commandResult.getStdout(), 4313 commandResult.getStderr()); 4314 recoverDeviceFromBootloader(); 4315 } else { 4316 setDeviceState(TestDeviceState.FASTBOOT); 4317 } 4318 } else { 4319 if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) { 4320 recoverDeviceFromBootloader(); 4321 } 4322 } 4323 } 4324 4325 /** {@inheritDoc} */ 4326 @Override waitForDeviceInSideload(long waitTime)4327 public boolean waitForDeviceInSideload(long waitTime) { 4328 return mStateMonitor.waitForDeviceInSideload(waitTime); 4329 } 4330 4331 /** 4332 * Small helper function to throw an NPE if the passed arg is null. This should be used when 4333 * some value will be stored and used later, in which case it'll avoid hard-to-trace 4334 * asynchronous NullPointerExceptions by throwing the exception synchronously. This is not 4335 * intended to be used where the NPE would be thrown synchronously -- just let the jvm take care 4336 * of it in that case. 4337 */ throwIfNull(Object obj)4338 private void throwIfNull(Object obj) { 4339 if (obj == null) { 4340 throw new NullPointerException(); 4341 } 4342 } 4343 4344 /** Retrieve this device's recovery mechanism. */ 4345 @VisibleForTesting getRecovery()4346 IDeviceRecovery getRecovery() { 4347 return mRecovery; 4348 } 4349 4350 /** 4351 * {@inheritDoc} 4352 */ 4353 @Override setRecovery(IDeviceRecovery recovery)4354 public void setRecovery(IDeviceRecovery recovery) { 4355 throwIfNull(recovery); 4356 mRecovery = recovery; 4357 } 4358 4359 /** 4360 * {@inheritDoc} 4361 */ 4362 @Override setRecoveryMode(RecoveryMode mode)4363 public void setRecoveryMode(RecoveryMode mode) { 4364 throwIfNull(mRecoveryMode); 4365 mRecoveryMode = mode; 4366 } 4367 4368 /** 4369 * {@inheritDoc} 4370 */ 4371 @Override getRecoveryMode()4372 public RecoveryMode getRecoveryMode() { 4373 return mRecoveryMode; 4374 } 4375 4376 /** 4377 * {@inheritDoc} 4378 */ 4379 @Override setFastbootEnabled(boolean fastbootEnabled)4380 public void setFastbootEnabled(boolean fastbootEnabled) { 4381 mFastbootEnabled = fastbootEnabled; 4382 } 4383 4384 /** 4385 * {@inheritDoc} 4386 */ 4387 @Override isFastbootEnabled()4388 public boolean isFastbootEnabled() { 4389 return mFastbootEnabled; 4390 } 4391 4392 /** 4393 * {@inheritDoc} 4394 */ 4395 @Override setFastbootPath(String fastbootPath)4396 public void setFastbootPath(String fastbootPath) { 4397 mFastbootPath = fastbootPath; 4398 // ensure the device and its associated recovery use the same fastboot version. 4399 mRecovery.setFastbootPath(fastbootPath); 4400 } 4401 4402 /** 4403 * {@inheritDoc} 4404 */ 4405 @Override getFastbootPath()4406 public String getFastbootPath() { 4407 return mFastbootPath; 4408 } 4409 4410 /** {@inheritDoc} */ 4411 @Override getFastbootVersion()4412 public String getFastbootVersion() { 4413 try { 4414 CommandResult res = executeFastbootCommand("--version"); 4415 return res.getStdout().trim(); 4416 } catch (DeviceNotAvailableException e) { 4417 // Ignored for host side request 4418 } 4419 return null; 4420 } 4421 4422 /** {@inheritDoc} */ 4423 @Override getFastbootSerialNumber()4424 public String getFastbootSerialNumber() { 4425 if (mFastbootSerialNumber != null) { 4426 return mFastbootSerialNumber; 4427 } 4428 4429 // Only devices which use TCP adb have different fastboot serial number because IPv6 4430 // link-local address will be used in fastboot mode. 4431 if (!isAdbTcp()) { 4432 mFastbootSerialNumber = getSerialNumber(); 4433 CLog.i( 4434 "Device %s's fastboot serial number is %s", 4435 getSerialNumber(), mFastbootSerialNumber); 4436 return mFastbootSerialNumber; 4437 } 4438 4439 mFastbootSerialNumber = getSerialNumber(); 4440 byte[] macEui48Bytes; 4441 4442 try { 4443 boolean adbRoot = isAdbRoot(); 4444 if (!adbRoot) { 4445 enableAdbRoot(); 4446 } 4447 macEui48Bytes = getEUI48MacAddressInBytes(ETHERNET_MAC_ADDRESS_COMMAND); 4448 if (!adbRoot) { 4449 disableAdbRoot(); 4450 } 4451 } catch (DeviceNotAvailableException e) { 4452 CLog.e("Device %s isn't available when get fastboot serial number", getSerialNumber()); 4453 CLog.e(e); 4454 return getSerialNumber(); 4455 } 4456 4457 String net_interface = getHostOptions().getNetworkInterface(); 4458 if (net_interface == null || macEui48Bytes == null) { 4459 CLog.i( 4460 "Device %s's fastboot serial number is %s", 4461 getSerialNumber(), mFastbootSerialNumber); 4462 return mFastbootSerialNumber; 4463 } 4464 4465 // Create a link-local Inet6Address from the MAC address. The EUI-48 MAC address 4466 // is converted to an EUI-64 MAC address per RFC 4291. The resulting EUI-64 is 4467 // used to construct a link-local IPv6 address per RFC 4862. 4468 byte[] addr = new byte[16]; 4469 addr[0] = (byte) 0xfe; 4470 addr[1] = (byte) 0x80; 4471 addr[8] = (byte) (macEui48Bytes[0] ^ (byte) 0x02); // flip the link-local bit 4472 addr[9] = macEui48Bytes[1]; 4473 addr[10] = macEui48Bytes[2]; 4474 addr[11] = (byte) 0xff; 4475 addr[12] = (byte) 0xfe; 4476 addr[13] = macEui48Bytes[3]; 4477 addr[14] = macEui48Bytes[4]; 4478 addr[15] = macEui48Bytes[5]; 4479 4480 try { 4481 String host_addr = Inet6Address.getByAddress(null, addr, 0).getHostAddress(); 4482 mFastbootSerialNumber = "tcp:" + host_addr.split("%")[0] + "%" + net_interface; 4483 } catch (UnknownHostException e) { 4484 CLog.w("Failed to get %s's IPv6 link-local address", getSerialNumber()); 4485 CLog.w(e); 4486 } 4487 4488 CLog.i( 4489 "Device %s's fastboot serial number is %s", 4490 getSerialNumber(), mFastbootSerialNumber); 4491 return mFastbootSerialNumber; 4492 } 4493 4494 /** 4495 * {@inheritDoc} 4496 */ 4497 @Override setDeviceState(final TestDeviceState deviceState)4498 public void setDeviceState(final TestDeviceState deviceState) { 4499 if (!deviceState.equals(getDeviceState())) { 4500 // disable state changes while fastboot lock is held, because issuing fastboot command 4501 // will disrupt state 4502 if (isStateBootloaderOrFastbootd() && mFastbootLock.isLocked()) { 4503 return; 4504 } 4505 mState = deviceState; 4506 if (!(getIDevice() instanceof StubDevice)) { 4507 CLog.logAndDisplay( 4508 LogLevel.DEBUG, 4509 "Device %s state is now %s", 4510 getSerialNumber(), 4511 deviceState); 4512 } 4513 mStateMonitor.setState(deviceState); 4514 } 4515 } 4516 4517 /** 4518 * {@inheritDoc} 4519 */ 4520 @Override getDeviceState()4521 public TestDeviceState getDeviceState() { 4522 return mState; 4523 } 4524 4525 @Override isAdbTcp()4526 public boolean isAdbTcp() { 4527 return mStateMonitor.isAdbTcp(); 4528 } 4529 4530 /** 4531 * {@inheritDoc} 4532 */ 4533 @Override switchToAdbTcp()4534 public String switchToAdbTcp() throws DeviceNotAvailableException { 4535 String ipAddress = getIpAddress(); 4536 if (ipAddress == null) { 4537 CLog.e("connectToTcp failed: Device %s doesn't have an IP", getSerialNumber()); 4538 return null; 4539 } 4540 String port = "5555"; 4541 executeAdbCommand("tcpip", port); 4542 // TODO: analyze result? wait for device offline? 4543 return String.format("%s:%s", ipAddress, port); 4544 } 4545 4546 /** 4547 * {@inheritDoc} 4548 */ 4549 @Override switchToAdbUsb()4550 public boolean switchToAdbUsb() throws DeviceNotAvailableException { 4551 executeAdbCommand("usb"); 4552 // TODO: analyze result? wait for device offline? 4553 return true; 4554 } 4555 4556 /** 4557 * {@inheritDoc} 4558 */ 4559 @Override setEmulatorProcess(Process p)4560 public void setEmulatorProcess(Process p) { 4561 mEmulatorProcess = p; 4562 4563 } 4564 4565 /** 4566 * For emulator set {@link SizeLimitedOutputStream} to log output 4567 * @param output to log the output 4568 */ setEmulatorOutputStream(SizeLimitedOutputStream output)4569 public void setEmulatorOutputStream(SizeLimitedOutputStream output) { 4570 mEmulatorOutput = output; 4571 } 4572 4573 /** 4574 * {@inheritDoc} 4575 */ 4576 @Override stopEmulatorOutput()4577 public void stopEmulatorOutput() { 4578 if (mEmulatorOutput != null) { 4579 mEmulatorOutput.delete(); 4580 mEmulatorOutput = null; 4581 } 4582 } 4583 4584 /** 4585 * {@inheritDoc} 4586 */ 4587 @Override getEmulatorOutput()4588 public InputStreamSource getEmulatorOutput() { 4589 if (getIDevice().isEmulator()) { 4590 if (mEmulatorOutput == null) { 4591 CLog.w("Emulator output for %s was not captured in background", 4592 getSerialNumber()); 4593 } else { 4594 try { 4595 return new SnapshotInputStreamSource( 4596 "getEmulatorOutput", mEmulatorOutput.getData()); 4597 } catch (IOException e) { 4598 CLog.e("Failed to get %s data.", getSerialNumber()); 4599 CLog.e(e); 4600 } 4601 } 4602 } 4603 return new ByteArrayInputStreamSource(new byte[0]); 4604 } 4605 4606 /** 4607 * {@inheritDoc} 4608 */ 4609 @Override getEmulatorProcess()4610 public Process getEmulatorProcess() { 4611 return mEmulatorProcess; 4612 } 4613 4614 /** 4615 * @return <code>true</code> if adb root should be enabled on device 4616 */ isEnableAdbRoot()4617 public boolean isEnableAdbRoot() { 4618 return mOptions.isEnableAdbRoot(); 4619 } 4620 4621 /** 4622 * {@inheritDoc} 4623 */ 4624 @Override getInstalledPackageNames()4625 public Set<String> getInstalledPackageNames() throws DeviceNotAvailableException { 4626 throw new UnsupportedOperationException("No support for Package's feature"); 4627 } 4628 4629 /** {@inheritDoc} */ 4630 @Override isPackageInstalled(String packageName)4631 public boolean isPackageInstalled(String packageName) throws DeviceNotAvailableException { 4632 throw new UnsupportedOperationException("No support for Package's feature"); 4633 } 4634 4635 /** {@inheritDoc} */ 4636 @Override isPackageInstalled(String packageName, String userId)4637 public boolean isPackageInstalled(String packageName, String userId) 4638 throws DeviceNotAvailableException { 4639 throw new UnsupportedOperationException("No support for Package's feature"); 4640 } 4641 4642 /** {@inheritDoc} */ 4643 @Override getActiveApexes()4644 public Set<ApexInfo> getActiveApexes() throws DeviceNotAvailableException { 4645 throw new UnsupportedOperationException("No support for Package's feature"); 4646 } 4647 4648 /** {@inheritDoc} */ 4649 @Override getAppPackageInfos()4650 public List<PackageInfo> getAppPackageInfos() throws DeviceNotAvailableException { 4651 throw new UnsupportedOperationException("No support for Package's feature"); 4652 } 4653 4654 /** {@inheritDoc} */ 4655 @Override getMainlineModuleInfo()4656 public Set<String> getMainlineModuleInfo() throws DeviceNotAvailableException { 4657 throw new UnsupportedOperationException("No support for Package's feature"); 4658 } 4659 4660 /** 4661 * {@inheritDoc} 4662 */ 4663 @Override getUninstallablePackageNames()4664 public Set<String> getUninstallablePackageNames() throws DeviceNotAvailableException { 4665 throw new UnsupportedOperationException("No support for Package's feature"); 4666 } 4667 4668 /** 4669 * {@inheritDoc} 4670 */ 4671 @Override getAppPackageInfo(String packageName)4672 public PackageInfo getAppPackageInfo(String packageName) throws DeviceNotAvailableException { 4673 throw new UnsupportedOperationException("No support for Package's feature"); 4674 } 4675 4676 /** 4677 * {@inheritDoc} 4678 */ 4679 @Override getOptions()4680 public TestDeviceOptions getOptions() { 4681 return mOptions; 4682 } 4683 4684 /** 4685 * {@inheritDoc} 4686 */ 4687 @Override getApiLevel()4688 public int getApiLevel() throws DeviceNotAvailableException { 4689 int apiLevel = UNKNOWN_API_LEVEL; 4690 try { 4691 String prop = getProperty(DeviceProperties.SDK_VERSION); 4692 apiLevel = Integer.parseInt(prop); 4693 } catch (NumberFormatException nfe) { 4694 CLog.w( 4695 "Unable to get API level from " 4696 + DeviceProperties.SDK_VERSION 4697 + ", falling back to UNKNOWN.", 4698 nfe); 4699 // ignore, return unknown instead 4700 } 4701 return apiLevel; 4702 } 4703 4704 /** {@inheritDoc} */ 4705 @Override checkApiLevelAgainstNextRelease(int strictMinLevel)4706 public boolean checkApiLevelAgainstNextRelease(int strictMinLevel) 4707 throws DeviceNotAvailableException { 4708 int apiLevel = getApiLevel(); 4709 if (apiLevel > strictMinLevel) { 4710 return true; 4711 } 4712 String codeName = getPropertyWithRecovery(DeviceProperties.BUILD_CODENAME, true); 4713 if (codeName == null) { 4714 throw new DeviceRuntimeException( 4715 String.format( 4716 "Failed to query property '%s'. device returned null.", 4717 DeviceProperties.BUILD_CODENAME), 4718 DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); 4719 } 4720 codeName = codeName.trim(); 4721 // CUR_DEVELOPMENT_VERSION is the code used by Android for a pre-finalized SDK 4722 if (strictMinLevel == CUR_DEVELOPMENT_VERSION && !"REL".equals(codeName)) { 4723 return true; 4724 } 4725 apiLevel = apiLevel + ("REL".equals(codeName) ? 0 : 1); 4726 if (strictMinLevel > apiLevel) { 4727 return false; 4728 } 4729 return true; 4730 } 4731 getApiLevelSafe()4732 protected int getApiLevelSafe() { 4733 try { 4734 return getApiLevel(); 4735 } catch (DeviceNotAvailableException e) { 4736 CLog.e(e); 4737 return UNKNOWN_API_LEVEL; 4738 } 4739 } 4740 4741 /** {@inheritDoc} */ 4742 @Override getLaunchApiLevel()4743 public int getLaunchApiLevel() throws DeviceNotAvailableException { 4744 try { 4745 String prop = getProperty(DeviceProperties.FIRST_API_LEVEL); 4746 return Integer.parseInt(prop); 4747 } catch (NumberFormatException nfe) { 4748 CLog.w( 4749 "Unable to get first launch API level from " 4750 + DeviceProperties.FIRST_API_LEVEL 4751 + ", falling back to getApiLevel().", 4752 nfe); 4753 } 4754 return getApiLevel(); 4755 } 4756 4757 @Override getMonitor()4758 public IDeviceStateMonitor getMonitor() { 4759 return mStateMonitor; 4760 } 4761 4762 /** 4763 * {@inheritDoc} 4764 */ 4765 @Override waitForDeviceShell(long waitTime)4766 public boolean waitForDeviceShell(long waitTime) { 4767 return mStateMonitor.waitForDeviceShell(waitTime); 4768 } 4769 4770 @Override getAllocationState()4771 public DeviceAllocationState getAllocationState() { 4772 return mAllocationState; 4773 } 4774 4775 /** 4776 * {@inheritDoc} 4777 * <p> 4778 * Process the DeviceEvent, which may or may not transition this device to a new allocation 4779 * state. 4780 * </p> 4781 */ 4782 @Override handleAllocationEvent(DeviceEvent event)4783 public DeviceEventResponse handleAllocationEvent(DeviceEvent event) { 4784 4785 // keep track of whether state has actually changed or not 4786 boolean stateChanged = false; 4787 DeviceAllocationState newState; 4788 DeviceAllocationState oldState = mAllocationState; 4789 mAllocationStateLock.lock(); 4790 try { 4791 // update oldState here, just in case in changed before we got lock 4792 oldState = mAllocationState; 4793 newState = mAllocationState.handleDeviceEvent(event); 4794 if (oldState != newState) { 4795 // state has changed! record this fact, and store the new state 4796 stateChanged = true; 4797 mAllocationState = newState; 4798 } 4799 } finally { 4800 mAllocationStateLock.unlock(); 4801 } 4802 if (stateChanged && mAllocationMonitor != null) { 4803 // state has changed! Lets inform the allocation monitor listener 4804 mAllocationMonitor.notifyDeviceStateChange(getSerialNumber(), oldState, newState); 4805 } 4806 return new DeviceEventResponse(newState, stateChanged); 4807 } 4808 4809 /** {@inheritDoc} */ 4810 @Override getDeviceTimeOffset(Date date)4811 public long getDeviceTimeOffset(Date date) throws DeviceNotAvailableException { 4812 long deviceTime = getDeviceDate(); 4813 4814 if (date == null) { 4815 date = new Date(); 4816 } 4817 4818 long offset = date.getTime() - deviceTime; 4819 CLog.d("Time offset = %d ms", offset); 4820 return offset; 4821 } 4822 4823 /** 4824 * {@inheritDoc} 4825 */ 4826 @Override setDate(Date date)4827 public void setDate(Date date) throws DeviceNotAvailableException { 4828 if (date == null) { 4829 date = new Date(); 4830 } 4831 long timeOffset = getDeviceTimeOffset(date); 4832 // no need to set date 4833 if (Math.abs(timeOffset) <= MAX_HOST_DEVICE_TIME_OFFSET) { 4834 return; 4835 } 4836 String dateString = null; 4837 if (getApiLevel() < 23) { 4838 // set date in epoch format 4839 dateString = Long.toString(date.getTime() / 1000); //ms to s 4840 } else { 4841 // set date with POSIX like params 4842 SimpleDateFormat sdf = new java.text.SimpleDateFormat( 4843 "MMddHHmmyyyy.ss"); 4844 sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); 4845 dateString = sdf.format(date); 4846 } 4847 // best effort, no verification 4848 // Use TZ= to default to UTC timezone (b/128353510 for background) 4849 executeShellCommand("TZ=UTC date -u " + dateString); 4850 } 4851 4852 /** {@inheritDoc} */ 4853 @Override getDeviceDate()4854 public long getDeviceDate() throws DeviceNotAvailableException { 4855 String deviceTimeString = executeShellCommand("date +%s"); 4856 Long deviceTime = null; 4857 try { 4858 deviceTime = Long.valueOf(deviceTimeString.trim()); 4859 } catch (NumberFormatException nfe) { 4860 CLog.i("Invalid device time: \"%s\", ignored.", nfe); 4861 return 0; 4862 } 4863 // Convert from seconds to milliseconds 4864 return deviceTime * 1000L; 4865 } 4866 4867 /** 4868 * {@inheritDoc} 4869 */ 4870 @Override waitForBootComplete(long timeOut)4871 public boolean waitForBootComplete(long timeOut) throws DeviceNotAvailableException { 4872 return mStateMonitor.waitForBootComplete(timeOut); 4873 } 4874 4875 /** 4876 * {@inheritDoc} 4877 */ 4878 @Override listUsers()4879 public ArrayList<Integer> listUsers() throws DeviceNotAvailableException { 4880 throw new UnsupportedOperationException("No support for user's feature."); 4881 } 4882 4883 /** {@inheritDoc} */ 4884 @Override getUserInfos()4885 public Map<Integer, UserInfo> getUserInfos() throws DeviceNotAvailableException { 4886 throw new UnsupportedOperationException("No support for user's feature."); 4887 } 4888 4889 /** 4890 * {@inheritDoc} 4891 */ 4892 @Override getMaxNumberOfUsersSupported()4893 public int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException { 4894 throw new UnsupportedOperationException("No support for user's feature."); 4895 } 4896 4897 @Override getMaxNumberOfRunningUsersSupported()4898 public int getMaxNumberOfRunningUsersSupported() throws DeviceNotAvailableException { 4899 throw new UnsupportedOperationException("No support for user's feature."); 4900 } 4901 4902 /** 4903 * {@inheritDoc} 4904 */ 4905 @Override isMultiUserSupported()4906 public boolean isMultiUserSupported() throws DeviceNotAvailableException { 4907 throw new UnsupportedOperationException("No support for user's feature."); 4908 } 4909 4910 @Override isHeadlessSystemUserMode()4911 public boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException { 4912 throw new UnsupportedOperationException("No support for user's feature."); 4913 } 4914 4915 @Override canSwitchToHeadlessSystemUser()4916 public boolean canSwitchToHeadlessSystemUser() throws DeviceNotAvailableException { 4917 throw new UnsupportedOperationException("No support for user's feature."); 4918 } 4919 4920 @Override isMainUserPermanentAdmin()4921 public boolean isMainUserPermanentAdmin() throws DeviceNotAvailableException { 4922 throw new UnsupportedOperationException("No support for user's feature."); 4923 } 4924 4925 /** {@inheritDoc} */ 4926 @Override createUserNoThrow(String name)4927 public int createUserNoThrow(String name) throws DeviceNotAvailableException { 4928 throw new UnsupportedOperationException("No support for user's feature."); 4929 } 4930 4931 /** 4932 * {@inheritDoc} 4933 */ 4934 @Override createUser(String name)4935 public int createUser(String name) throws DeviceNotAvailableException, IllegalStateException { 4936 throw new UnsupportedOperationException("No support for user's feature."); 4937 } 4938 4939 /** 4940 * {@inheritDoc} 4941 */ 4942 @Override createUser(String name, boolean guest, boolean ephemeral)4943 public int createUser(String name, boolean guest, boolean ephemeral) 4944 throws DeviceNotAvailableException, IllegalStateException { 4945 throw new UnsupportedOperationException("No support for user's feature."); 4946 } 4947 4948 /** {@inheritDoc} */ 4949 @Override createUser(String name, boolean guest, boolean ephemeral, boolean forTesting)4950 public int createUser(String name, boolean guest, boolean ephemeral, boolean forTesting) 4951 throws DeviceNotAvailableException, IllegalStateException { 4952 throw new UnsupportedOperationException("No support for user's feature."); 4953 } 4954 4955 /** 4956 * {@inheritDoc} 4957 */ 4958 @Override removeUser(int userId)4959 public boolean removeUser(int userId) throws DeviceNotAvailableException { 4960 throw new UnsupportedOperationException("No support for user's feature."); 4961 } 4962 4963 /** 4964 * {@inheritDoc} 4965 */ 4966 @Override startUser(int userId)4967 public boolean startUser(int userId) throws DeviceNotAvailableException { 4968 throw new UnsupportedOperationException("No support for user's feature."); 4969 } 4970 4971 /** {@inheritDoc} */ 4972 @Override startUser(int userId, boolean waitFlag)4973 public boolean startUser(int userId, boolean waitFlag) throws DeviceNotAvailableException { 4974 throw new UnsupportedOperationException("No support for user's feature."); 4975 } 4976 4977 @Override startVisibleBackgroundUser(int userId, int displayId, boolean waitFlag)4978 public boolean startVisibleBackgroundUser(int userId, int displayId, boolean waitFlag) 4979 throws DeviceNotAvailableException { 4980 throw new UnsupportedOperationException("No support for user's feature."); 4981 } 4982 4983 /** 4984 * {@inheritDoc} 4985 */ 4986 @Override stopUser(int userId)4987 public boolean stopUser(int userId) throws DeviceNotAvailableException { 4988 throw new UnsupportedOperationException("No support for user's feature."); 4989 } 4990 4991 /** 4992 * {@inheritDoc} 4993 */ 4994 @Override stopUser(int userId, boolean waitFlag, boolean forceFlag)4995 public boolean stopUser(int userId, boolean waitFlag, boolean forceFlag) 4996 throws DeviceNotAvailableException { 4997 throw new UnsupportedOperationException("No support for user's feature."); 4998 } 4999 5000 @Override isVisibleBackgroundUsersSupported()5001 public boolean isVisibleBackgroundUsersSupported() throws DeviceNotAvailableException { 5002 throw new UnsupportedOperationException("No support for user's feature."); 5003 } 5004 5005 @Override isVisibleBackgroundUsersOnDefaultDisplaySupported()5006 public boolean isVisibleBackgroundUsersOnDefaultDisplaySupported() 5007 throws DeviceNotAvailableException { 5008 throw new UnsupportedOperationException("No support for user's feature."); 5009 } 5010 5011 /** 5012 * {@inheritDoc} 5013 */ 5014 @Override remountSystemWritable()5015 public void remountSystemWritable() throws DeviceNotAvailableException { 5016 String verity = getProperty("partition.system.verified"); 5017 // have the property set (regardless state) implies verity is enabled, so we send adb 5018 // command to disable verity 5019 if (verity != null && !verity.isEmpty()) { 5020 executeAdbCommand("disable-verity"); 5021 mPropertiesCache.invalidate("partition.system.verified"); 5022 reboot(); 5023 } 5024 enableAdbRoot(); 5025 executeAdbCommand("remount"); 5026 waitForDeviceAvailable(); 5027 } 5028 5029 /** {@inheritDoc} */ 5030 @Override remountVendorWritable()5031 public void remountVendorWritable() throws DeviceNotAvailableException { 5032 String verity = getProperty("partition.vendor.verified"); 5033 // have the property set (regardless state) implies verity is enabled, so we send adb 5034 // command to disable verity 5035 if (verity != null && !verity.isEmpty()) { 5036 executeAdbCommand("disable-verity"); 5037 mPropertiesCache.invalidate("partition.vendor.verified"); 5038 reboot(); 5039 } 5040 enableAdbRoot(); 5041 executeAdbCommand("remount"); 5042 waitForDeviceAvailable(); 5043 } 5044 5045 /** {@inheritDoc} */ 5046 @Override remountSystemReadOnly()5047 public void remountSystemReadOnly() throws DeviceNotAvailableException { 5048 String verity = getProperty("partition.system.verified"); 5049 // have the property set (regardless state) implies verity is enabled, so we send adb 5050 // command to disable verity 5051 if (verity == null || verity.isEmpty()) { 5052 executeAdbCommand("enable-verity"); 5053 reboot(); 5054 } 5055 } 5056 5057 /** {@inheritDoc} */ 5058 @Override remountVendorReadOnly()5059 public void remountVendorReadOnly() throws DeviceNotAvailableException { 5060 String verity = getProperty("partition.vendor.verified"); 5061 // have the property set (regardless state) implies verity is enabled, so we send adb 5062 // command to disable verity 5063 if (verity == null || verity.isEmpty()) { 5064 executeAdbCommand("enable-verity"); 5065 reboot(); 5066 } 5067 } 5068 5069 /** {@inheritDoc} */ 5070 @Override getPrimaryUserId()5071 public Integer getPrimaryUserId() throws DeviceNotAvailableException { 5072 throw new UnsupportedOperationException("No support for user's feature."); 5073 } 5074 5075 /** {@inheritDoc} */ 5076 @Override getMainUserId()5077 public Integer getMainUserId() throws DeviceNotAvailableException { 5078 throw new UnsupportedOperationException("No support for user's feature."); 5079 } 5080 5081 /** Used internally to fallback to non-user logic */ getCurrentUserCompatible()5082 private int getCurrentUserCompatible() throws DeviceNotAvailableException { 5083 try { 5084 return getCurrentUser(); 5085 } catch (RuntimeException e) { 5086 return 0; 5087 } 5088 } 5089 5090 /** 5091 * {@inheritDoc} 5092 */ 5093 @Override getCurrentUser()5094 public int getCurrentUser() throws DeviceNotAvailableException { 5095 throw new UnsupportedOperationException("No support for user's feature."); 5096 } 5097 5098 @Override isUserVisible(int userId)5099 public boolean isUserVisible(int userId) throws DeviceNotAvailableException { 5100 throw new UnsupportedOperationException("No support for user's feature."); 5101 } 5102 5103 @Override isUserVisibleOnDisplay(int userId, int displayId)5104 public boolean isUserVisibleOnDisplay(int userId, int displayId) 5105 throws DeviceNotAvailableException { 5106 throw new UnsupportedOperationException("No support for user's feature."); 5107 } 5108 5109 /** {@inheritDoc} */ 5110 @Override isUserSecondary(int userId)5111 public boolean isUserSecondary(int userId) throws DeviceNotAvailableException { 5112 throw new UnsupportedOperationException("No support for user's feature."); 5113 } 5114 5115 5116 /** 5117 * {@inheritDoc} 5118 */ 5119 @Override getUserFlags(int userId)5120 public int getUserFlags(int userId) throws DeviceNotAvailableException { 5121 throw new UnsupportedOperationException("No support for user's feature."); 5122 } 5123 5124 /** 5125 * {@inheritDoc} 5126 */ 5127 @Override getUserSerialNumber(int userId)5128 public int getUserSerialNumber(int userId) throws DeviceNotAvailableException { 5129 throw new UnsupportedOperationException("No support for user's feature."); 5130 } 5131 5132 /** 5133 * {@inheritDoc} 5134 */ 5135 @Override switchUser(int userId)5136 public boolean switchUser(int userId) throws DeviceNotAvailableException { 5137 throw new UnsupportedOperationException("No support for user's feature."); 5138 } 5139 5140 /** 5141 * {@inheritDoc} 5142 */ 5143 @Override switchUser(int userId, long timeout)5144 public boolean switchUser(int userId, long timeout) throws DeviceNotAvailableException { 5145 throw new UnsupportedOperationException("No support for user's feature."); 5146 } 5147 5148 /** 5149 * {@inheritDoc} 5150 */ 5151 @Override isUserRunning(int userId)5152 public boolean isUserRunning(int userId) throws DeviceNotAvailableException { 5153 throw new UnsupportedOperationException("No support for user's feature."); 5154 } 5155 5156 /** 5157 * {@inheritDoc} 5158 */ 5159 @Override hasFeature(String feature)5160 public boolean hasFeature(String feature) throws DeviceNotAvailableException { 5161 throw new UnsupportedOperationException("No support pm's features."); 5162 } 5163 5164 /** 5165 * {@inheritDoc} 5166 */ 5167 @Override getSetting(String namespace, String key)5168 public String getSetting(String namespace, String key) 5169 throws DeviceNotAvailableException { 5170 throw new UnsupportedOperationException("No support for setting's feature."); 5171 } 5172 5173 /** 5174 * {@inheritDoc} 5175 */ 5176 @Override getSetting(int userId, String namespace, String key)5177 public String getSetting(int userId, String namespace, String key) 5178 throws DeviceNotAvailableException { 5179 throw new UnsupportedOperationException("No support for setting's feature."); 5180 } 5181 5182 /** {@inheritDoc} */ 5183 @Override getAllSettings(String namespace)5184 public Map<String, String> getAllSettings(String namespace) throws DeviceNotAvailableException { 5185 throw new UnsupportedOperationException("No support for setting's feature."); 5186 } 5187 5188 /** 5189 * {@inheritDoc} 5190 */ 5191 @Override setSetting(String namespace, String key, String value)5192 public void setSetting(String namespace, String key, String value) 5193 throws DeviceNotAvailableException { 5194 throw new UnsupportedOperationException("No support for setting's feature."); 5195 } 5196 5197 /** 5198 * {@inheritDoc} 5199 */ 5200 @Override setSetting(int userId, String namespace, String key, String value)5201 public void setSetting(int userId, String namespace, String key, String value) 5202 throws DeviceNotAvailableException { 5203 throw new UnsupportedOperationException("No support for setting's feature."); 5204 } 5205 5206 /** 5207 * {@inheritDoc} 5208 */ 5209 @Override getBuildSigningKeys()5210 public String getBuildSigningKeys() throws DeviceNotAvailableException { 5211 String buildTags = getProperty(DeviceProperties.BUILD_TAGS); 5212 if (buildTags != null) { 5213 String[] tags = buildTags.split(","); 5214 for (String tag : tags) { 5215 Matcher m = KEYS_PATTERN.matcher(tag); 5216 if (m.matches()) { 5217 return tag; 5218 } 5219 } 5220 } 5221 return null; 5222 } 5223 5224 /** 5225 * {@inheritDoc} 5226 */ 5227 @Override getAndroidId(int userId)5228 public String getAndroidId(int userId) throws DeviceNotAvailableException { 5229 throw new UnsupportedOperationException("No support for user's feature."); 5230 } 5231 5232 /** 5233 * {@inheritDoc} 5234 */ 5235 @Override getAndroidIds()5236 public Map<Integer, String> getAndroidIds() throws DeviceNotAvailableException { 5237 throw new UnsupportedOperationException("No support for user's feature."); 5238 } 5239 5240 /** {@inheritDoc} */ 5241 @Override setDeviceOwner(String componentName, int userId)5242 public boolean setDeviceOwner(String componentName, int userId) 5243 throws DeviceNotAvailableException { 5244 throw new UnsupportedOperationException("No support for user's feature."); 5245 } 5246 5247 /** {@inheritDoc} */ 5248 @Override removeAdmin(String componentName, int userId)5249 public boolean removeAdmin(String componentName, int userId) 5250 throws DeviceNotAvailableException { 5251 throw new UnsupportedOperationException("No support for user's feature."); 5252 } 5253 5254 /** {@inheritDoc} */ 5255 @Override removeOwners()5256 public void removeOwners() throws DeviceNotAvailableException { 5257 throw new UnsupportedOperationException("No support for user's feature."); 5258 } 5259 5260 /** 5261 * {@inheritDoc} 5262 */ 5263 @Override disableKeyguard()5264 public void disableKeyguard() throws DeviceNotAvailableException { 5265 throw new UnsupportedOperationException("No support for Window Manager's features"); 5266 } 5267 5268 /** {@inheritDoc} */ 5269 @Override getDeviceClass()5270 public String getDeviceClass() { 5271 IDevice device = getIDevice(); 5272 if (device == null) { 5273 CLog.w("No IDevice instance, cannot determine device class."); 5274 return ""; 5275 } 5276 return device.getClass().getSimpleName(); 5277 } 5278 5279 /** {@inheritDoc} */ 5280 @Override preInvocationSetup(IBuildInfo info, MultiMap<String, String> attributes)5281 public void preInvocationSetup(IBuildInfo info, MultiMap<String, String> attributes) 5282 throws TargetSetupError, DeviceNotAvailableException { 5283 // Default implementation 5284 mContentProvider = null; 5285 mShouldSkipContentProviderSetup = false; 5286 try { 5287 mExecuteShellCommandLogs = 5288 FileUtil.createTempFile("TestDevice_ExecuteShellCommands", ".txt"); 5289 } catch (IOException e) { 5290 throw new TargetSetupError( 5291 "Failed to create the executeShellCommand log file.", 5292 e, 5293 getDeviceDescriptor(), 5294 InfraErrorIdentifier.FAIL_TO_CREATE_FILE); 5295 } 5296 initializeConnection(info, attributes); 5297 } 5298 initializeConnection(IBuildInfo info, MultiMap<String, String> attributes)5299 protected void initializeConnection(IBuildInfo info, MultiMap<String, String> attributes) 5300 throws DeviceNotAvailableException, TargetSetupError { 5301 try (CloseableTraceScope ignored = new CloseableTraceScope("initializeConnection")) { 5302 ConnectionBuilder builder = 5303 new ConnectionBuilder(getRunUtil(), this, info, getLogger()); 5304 if (attributes != null) { 5305 builder.addAttributes(attributes); 5306 } 5307 addExtraConnectionBuilderArgs(builder); 5308 mConnection = DefaultConnection.createConnection(builder); 5309 CLog.d("Using connection: %s (%s)", mConnection, getIDevice()); 5310 mConnection.initializeConnection(); 5311 } 5312 } 5313 addExtraConnectionBuilderArgs(ConnectionBuilder builder)5314 protected void addExtraConnectionBuilderArgs(ConnectionBuilder builder) { 5315 if (mConnectionAvd != null) { 5316 builder.setExistingAvdInfo(mConnectionAvd); 5317 } 5318 } 5319 setConnectionAvdInfo(GceAvdInfo avdInfo)5320 public final void setConnectionAvdInfo(GceAvdInfo avdInfo) { 5321 mConnectionAvd = avdInfo; 5322 } 5323 5324 /** {@inheritDoc} */ 5325 @Override postInvocationTearDown(Throwable exception)5326 public void postInvocationTearDown(Throwable exception) { 5327 invalidatePropertyCache(); 5328 mConfiguration = null; 5329 mIsEncryptionSupported = null; 5330 FileUtil.deleteFile(mExecuteShellCommandLogs); 5331 mExecuteShellCommandLogs = null; 5332 FileUtil.recursiveDelete(mUnpackedFastbootDir); 5333 getConnection().tearDownConnection(); 5334 mConnectionAvd = null; 5335 mDeviceActionReceivers.clear(); 5336 // Default implementation 5337 if (getIDevice() instanceof StubDevice) { 5338 return; 5339 } 5340 // Reset the Content Provider bit. 5341 mShouldSkipContentProviderSetup = false; 5342 try { 5343 // If we never installed it, don't even bother checking for it during tear down. 5344 if (mContentProvider == null) { 5345 return; 5346 } 5347 if (exception instanceof DeviceNotAvailableException) { 5348 CLog.e( 5349 "Skip Tradefed Content Provider teardown due to" 5350 + " DeviceNotAvailableException."); 5351 return; 5352 } 5353 if (TestDeviceState.ONLINE.equals(getDeviceState())) { 5354 mContentProvider.tearDown(); 5355 } 5356 } catch (DeviceNotAvailableException e) { 5357 CLog.e(e); 5358 } 5359 } 5360 5361 /** 5362 * {@inheritDoc} 5363 */ 5364 @Override isHeadless()5365 public boolean isHeadless() throws DeviceNotAvailableException { 5366 if (getProperty(DeviceProperties.BUILD_HEADLESS) != null) { 5367 return true; 5368 } 5369 return false; 5370 } 5371 checkApiLevelAgainst(String feature, int strictMinLevel)5372 protected void checkApiLevelAgainst(String feature, int strictMinLevel) { 5373 try { 5374 if (getApiLevel() < strictMinLevel){ 5375 throw new HarnessRuntimeException( 5376 String.format( 5377 "%s not supported on %s. " + "Must be API %d.", 5378 feature, getSerialNumber(), strictMinLevel), 5379 DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE); 5380 } 5381 } catch (DeviceNotAvailableException e) { 5382 throw new HarnessRuntimeException( 5383 "Device became unavailable while checking API level", 5384 e, 5385 DeviceErrorIdentifier.DEVICE_UNAVAILABLE); 5386 } 5387 } 5388 5389 @Override getCachedDeviceDescriptor()5390 public DeviceDescriptor getCachedDeviceDescriptor() { 5391 return getCachedDeviceDescriptor(false); 5392 } 5393 5394 /** {@inheritDoc} */ 5395 @Override getCachedDeviceDescriptor(boolean shortDescriptor)5396 public DeviceDescriptor getCachedDeviceDescriptor(boolean shortDescriptor) { 5397 synchronized (mCacheLock) { 5398 if (DeviceAllocationState.Allocated.equals(getAllocationState())) { 5399 if (mCachedDeviceDescriptor == null) { 5400 // Create the cache the very first time when it's allocated. 5401 mCachedDeviceDescriptor = getDeviceDescriptor(false); 5402 return mCachedDeviceDescriptor; 5403 } 5404 return mCachedDeviceDescriptor; 5405 } 5406 // If device is not allocated, just return current information 5407 mCachedDeviceDescriptor = null; 5408 return getDeviceDescriptor(shortDescriptor); 5409 } 5410 } 5411 5412 @Override getDeviceDescriptor()5413 public DeviceDescriptor getDeviceDescriptor() { 5414 return getDeviceDescriptor(false); 5415 } 5416 5417 /** {@inheritDoc} */ 5418 @Override getDeviceDescriptor(boolean shortDescriptor)5419 public DeviceDescriptor getDeviceDescriptor(boolean shortDescriptor) { 5420 IDeviceSelection selector = new DeviceSelectionOptions(); 5421 IDevice idevice = getIDevice(); 5422 try { 5423 boolean isTemporary = false; 5424 if (idevice instanceof NullDevice) { 5425 isTemporary = ((NullDevice) idevice).isTemporary(); 5426 } 5427 if (shortDescriptor) { 5428 // Return only info that do not require device inspection 5429 return new DeviceDescriptor( 5430 idevice.getSerialNumber(), 5431 null, 5432 idevice instanceof StubDevice, 5433 idevice.getState(), 5434 getAllocationState(), 5435 getDeviceState(), 5436 null, 5437 null, 5438 null, 5439 null, 5440 null, 5441 null, 5442 getDeviceClass(), 5443 null, 5444 null, 5445 null, 5446 isTemporary, 5447 null, 5448 null, 5449 idevice); 5450 } 5451 // All the operations to create the descriptor need to be safe (should not trigger any 5452 // device side effects like recovery) 5453 String sdkVersion = null; 5454 String buildAlias = null; 5455 String hardwareRev = null; 5456 if (TestDeviceState.ONLINE.equals(getDeviceState())) { 5457 sdkVersion = getPropertyWithRecovery(DeviceProperties.SDK_VERSION, false); 5458 buildAlias = getPropertyWithRecovery(DeviceProperties.BUILD_ALIAS, false); 5459 hardwareRev = getPropertyWithRecovery(DeviceProperties.HARDWARE_REVISION, false); 5460 } 5461 return new DeviceDescriptor( 5462 idevice.getSerialNumber(), 5463 null, 5464 idevice instanceof StubDevice, 5465 idevice.getState(), 5466 getAllocationState(), 5467 getDeviceState(), 5468 getDisplayString(selector.getDeviceProductType(idevice)), 5469 getDisplayString(selector.getDeviceProductVariant(idevice)), 5470 getDisplayString(sdkVersion), 5471 getDisplayString(buildAlias), 5472 getDisplayString(hardwareRev), 5473 getDisplayString(getBattery()), 5474 getDeviceClass(), 5475 getDisplayString(getMacAddress()), 5476 getDisplayString(getSimState()), 5477 getDisplayString(getSimOperator()), 5478 isTemporary, 5479 null, 5480 null, 5481 idevice); 5482 } catch (RuntimeException|DeviceNotAvailableException e) { 5483 CLog.e("Exception while building device '%s' description:", getSerialNumber()); 5484 CLog.e(e); 5485 } 5486 return null; 5487 } 5488 5489 /** 5490 * Return the displayable string for given object 5491 */ getDisplayString(Object o)5492 private String getDisplayString(Object o) { 5493 return o == null ? "unknown" : o.toString(); 5494 } 5495 5496 /** {@inheritDoc} */ 5497 @Override getProcessByName(String processName)5498 public ProcessInfo getProcessByName(String processName) throws DeviceNotAvailableException { 5499 String pidString = getProcessPid(processName); 5500 if (pidString == null) { 5501 return null; 5502 } 5503 long startTime = getProcessStartTimeByPid(pidString); 5504 if (startTime == -1L) { 5505 return null; 5506 } 5507 return new ProcessInfo( 5508 getProcessUserByPid(pidString), 5509 Integer.parseInt(pidString), 5510 processName, 5511 startTime); 5512 } 5513 5514 /** Return the process start time since epoch for the given pid string */ getProcessStartTimeByPid(String pidString)5515 private long getProcessStartTimeByPid(String pidString) throws DeviceNotAvailableException { 5516 String output = executeShellCommand(String.format("ps -p %s -o stime=", pidString)); 5517 if (output != null && !output.trim().isEmpty()) { 5518 output = output.trim(); 5519 5520 String dateInSeconds; 5521 5522 // On API 28 and lower, there is a bug in toybox that prevents date from parsing 5523 // timestamps containing a space, e.g. -D"%Y-%m-%d %H:%M:%S" cannot be used to parse 5524 // the stime:19 output from ps. Instead, we'll reconstruct the timestamp. 5525 if (getApiLevel() <= 28) { 5526 dateInSeconds = 5527 executeShellCommand( 5528 "date -d \"$(date +%Y:%m:%d):" 5529 + output 5530 + "\" +%s -D \"%Y:%m:%d:%H:%M:%S\""); 5531 } else { 5532 dateInSeconds = executeShellCommand("date -d\"" + output + "\" +%s"); 5533 } 5534 if (Strings.isNullOrEmpty(dateInSeconds)) { 5535 return -1L; 5536 } 5537 try { 5538 return Long.parseLong(dateInSeconds.trim()); 5539 } catch (NumberFormatException e) { 5540 CLog.e("Failed to parse the start time for process:"); 5541 CLog.e(e); 5542 return -1L; 5543 } 5544 } 5545 return -1L; 5546 } 5547 5548 /** Return the process user for the given pid string */ getProcessUserByPid(String pidString)5549 private String getProcessUserByPid(String pidString) throws DeviceNotAvailableException { 5550 String output = executeShellCommand("stat -c%U /proc/" + pidString); 5551 if (output != null && !output.trim().isEmpty()) { 5552 try { 5553 return output.trim(); 5554 } catch (NumberFormatException e) { 5555 return null; 5556 } 5557 } 5558 return null; 5559 } 5560 5561 /** {@inheritDoc} */ 5562 @Override getBootHistory()5563 public Map<Long, String> getBootHistory() throws DeviceNotAvailableException { 5564 String output = getProperty(DeviceProperties.BOOT_REASON_HISTORY); 5565 /* Sample output: 5566 kernel_panic,1556587278 5567 reboot,,1556238008 5568 reboot,,1556237796 5569 reboot,,1556237725 5570 */ 5571 Map<Long, String> bootHistory = new LinkedHashMap<Long, String>(); 5572 if (Strings.isNullOrEmpty(output)) { 5573 return bootHistory; 5574 } 5575 for (String line : output.split("\\n")) { 5576 String infoStr[] = line.split(","); 5577 String startStr = infoStr[infoStr.length - 1]; 5578 try { 5579 long startTime = Long.parseLong(startStr.trim()); 5580 bootHistory.put(startTime, infoStr[0].trim()); 5581 } catch (NumberFormatException e) { 5582 CLog.e("Fail to parse boot time from line %s", line); 5583 } 5584 } 5585 return bootHistory; 5586 } 5587 5588 /** {@inheritDoc} */ 5589 @Override getBootHistorySince(long utcEpochTime, TimeUnit timeUnit)5590 public Map<Long, String> getBootHistorySince(long utcEpochTime, TimeUnit timeUnit) 5591 throws DeviceNotAvailableException { 5592 long utcEpochTimeSec = TimeUnit.SECONDS.convert(utcEpochTime, timeUnit); 5593 Map<Long, String> bootHistory = new LinkedHashMap<Long, String>(); 5594 for (Map.Entry<Long, String> entry : getBootHistory().entrySet()) { 5595 if (entry.getKey() >= utcEpochTimeSec) { 5596 bootHistory.put(entry.getKey(), entry.getValue()); 5597 } 5598 } 5599 return bootHistory; 5600 } 5601 hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit)5602 private boolean hasNormalRebootSince(long utcEpochTime, TimeUnit timeUnit) 5603 throws DeviceNotAvailableException { 5604 Map<Long, String> bootHistory = getBootHistorySince(utcEpochTime, timeUnit); 5605 if (bootHistory.isEmpty()) { 5606 CLog.w("There is no reboot history since %s", utcEpochTime); 5607 return false; 5608 } 5609 5610 CLog.i( 5611 "There are new boot history since %d. NewBootHistory = %s", 5612 utcEpochTime, bootHistory); 5613 // Check if there is reboot reason other than "reboot". 5614 // Raise RuntimeException if there is abnormal reboot. 5615 for (Map.Entry<Long, String> entry : bootHistory.entrySet()) { 5616 if (!"reboot".equals(entry.getValue())) { 5617 throw new HarnessRuntimeException( 5618 String.format( 5619 "Device %s has abnormal reboot reason %s at %d", 5620 getSerialNumber(), entry.getValue(), entry.getKey()), 5621 DeviceErrorIdentifier.UNEXPECTED_REBOOT); 5622 } 5623 } 5624 return true; 5625 } 5626 5627 /** 5628 * Check current system process is restarted after last reboot 5629 * 5630 * @param systemServerProcess the system_server {@link ProcessInfo} 5631 * @return true if system_server process restarted after last reboot; false if not 5632 * @throws DeviceNotAvailableException 5633 */ checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess)5634 private boolean checkSystemProcessRestartedAfterLastReboot(ProcessInfo systemServerProcess) 5635 throws DeviceNotAvailableException { 5636 // If time gap from last reboot to current system_server process start time is more than 5637 // MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP seconds, we conclude the system_server restarted 5638 // after boot up. 5639 if (!hasNormalRebootSince( 5640 systemServerProcess.getStartTime() - MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC, 5641 TimeUnit.SECONDS)) { 5642 CLog.i( 5643 "Device last reboot is more than %s seconds away from current system_server " 5644 + "process start time. The system_server process restarted after " 5645 + "last boot up", 5646 MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP_SEC); 5647 return true; 5648 } else { 5649 // Current system_server start within MAX_SYSTEM_SERVER_DELAY_AFTER_BOOT_UP 5650 // seconds after device last boot up 5651 return false; 5652 } 5653 } 5654 5655 /** {@inheritDoc} */ 5656 @Override deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit)5657 public boolean deviceSoftRestartedSince(long utcEpochTime, TimeUnit timeUnit) 5658 throws DeviceNotAvailableException { 5659 ProcessInfo currSystemServerProcess = getProcessByName("system_server"); 5660 if (currSystemServerProcess == null) { 5661 CLog.i("The system_server process is not available on the device."); 5662 return true; 5663 } 5664 5665 // The system_server process started at or before utcEpochTime, there is no soft-restart 5666 if (Math.abs( 5667 currSystemServerProcess.getStartTime() 5668 - TimeUnit.SECONDS.convert(utcEpochTime, timeUnit)) 5669 <= 1) { 5670 return false; 5671 } 5672 5673 // The system_server process restarted after device utcEpochTime in second. 5674 // Check if there is new reboot history, if no new reboot, device soft-restarted. 5675 // If there is no normal reboot, soft-restart is detected. 5676 if (!hasNormalRebootSince(utcEpochTime, timeUnit)) { 5677 return true; 5678 } 5679 5680 // There is new reboot since utcEpochTime. Check if system_server restarted after boot up. 5681 return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess); 5682 } 5683 5684 /** {@inheritDoc} */ 5685 @Override deviceSoftRestarted(ProcessInfo prevSystemServerProcess)5686 public boolean deviceSoftRestarted(ProcessInfo prevSystemServerProcess) 5687 throws DeviceNotAvailableException { 5688 if (prevSystemServerProcess == null) { 5689 CLog.i("The given system_server process is null. Abort deviceSoftRestarted check."); 5690 return false; 5691 } 5692 ProcessInfo currSystemServerProcess = getProcessByName("system_server"); 5693 if (currSystemServerProcess == null) { 5694 CLog.i("The system_server process is not available on the device."); 5695 return true; 5696 } 5697 5698 // Compare the start time with a 1 seconds accuracy due to how the date is computed 5699 if (currSystemServerProcess.getPid() == prevSystemServerProcess.getPid() 5700 && Math.abs( 5701 currSystemServerProcess.getStartTime() 5702 - prevSystemServerProcess.getStartTime()) 5703 <= 1) { 5704 return false; 5705 } 5706 5707 CLog.v( 5708 "current system_server: %s; prev system_server: %s", 5709 currSystemServerProcess, prevSystemServerProcess); 5710 5711 // The system_server process restarted. 5712 // Check boot history with previous system_server start time. 5713 // If there is no normal reboot, soft-restart is detected 5714 if (!hasNormalRebootSince(prevSystemServerProcess.getStartTime(), TimeUnit.SECONDS)) { 5715 return true; 5716 } 5717 5718 // There is reboot since prevSystemServerProcess.getStartTime(). 5719 // Check if system_server restarted after boot up. 5720 return checkSystemProcessRestartedAfterLastReboot(currSystemServerProcess); 5721 5722 } 5723 5724 /** 5725 * Validates that the given input is a valid MAC address 5726 * 5727 * @param address input to validate 5728 * @return true if the input is a valid MAC address 5729 */ isMacAddress(String address)5730 boolean isMacAddress(String address) { 5731 Pattern macPattern = Pattern.compile(MAC_ADDRESS_PATTERN); 5732 Matcher macMatcher = macPattern.matcher(address); 5733 return macMatcher.find(); 5734 } 5735 5736 /** 5737 * Query Mac address from the device 5738 * 5739 * @param command the query command 5740 * @return the MAC address of the device, null if it fails to query from the device 5741 */ getMacAddress(String command)5742 private String getMacAddress(String command) { 5743 if (getIDevice() instanceof StubDevice) { 5744 // Do not query MAC addresses from stub devices. 5745 return null; 5746 } 5747 if (!TestDeviceState.ONLINE.equals(mState)) { 5748 // Only query MAC addresses from online devices. 5749 return null; 5750 } 5751 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 5752 try { 5753 mIDevice.executeShellCommand(command, receiver); 5754 } catch (IOException | TimeoutException | AdbCommandRejectedException | 5755 ShellCommandUnresponsiveException e) { 5756 CLog.w( 5757 "Failed to query MAC address for %s by '%s'", 5758 mIDevice.getSerialNumber(), command); 5759 CLog.w(e); 5760 } 5761 String output = receiver.getOutput().trim(); 5762 if (isMacAddress(output)) { 5763 return output; 5764 } 5765 CLog.d( 5766 "No valid MAC address queried from device %s by '%s'", 5767 mIDevice.getSerialNumber(), command); 5768 return null; 5769 } 5770 5771 /** {@inheritDoc} */ 5772 @Override getMacAddress()5773 public String getMacAddress() { 5774 return getMacAddress(MAC_ADDRESS_COMMAND); 5775 } 5776 5777 /** 5778 * Query EUI-48 MAC address from the device 5779 * 5780 * @param command the query command 5781 * @return the EUI-48 MAC address in long, 0 if it fails to query from the device 5782 * @throws IllegalArgumentException 5783 */ getEUI48MacAddressInLong(String command)5784 long getEUI48MacAddressInLong(String command) { 5785 String addr = getMacAddress(command); 5786 if (addr == null) { 5787 return 0; 5788 } 5789 5790 String[] parts = addr.split(":"); 5791 if (parts.length != ETHER_ADDR_LEN) { 5792 throw new IllegalArgumentException(addr + " was not a valid MAC address"); 5793 } 5794 long longAddr = 0; 5795 for (int i = 0; i < parts.length; i++) { 5796 int x = Integer.valueOf(parts[i], 16); 5797 if (x < 0 || 0xff < x) { 5798 throw new IllegalArgumentException(addr + "was not a valid MAC address"); 5799 } 5800 longAddr = x + (longAddr << 8); 5801 } 5802 5803 return longAddr; 5804 } 5805 5806 /** 5807 * Query EUI-48 MAC address from the device 5808 * 5809 * @param command the query command 5810 * @return the EUI-48 MAC address in byte[], null if it fails to query from the device 5811 * @throws IllegalArgumentException 5812 */ getEUI48MacAddressInBytes(String command)5813 byte[] getEUI48MacAddressInBytes(String command) { 5814 long addr = getEUI48MacAddressInLong(command); 5815 if (addr == 0) { 5816 return null; 5817 } 5818 5819 byte[] bytes = new byte[ETHER_ADDR_LEN]; 5820 int index = ETHER_ADDR_LEN; 5821 while (index-- > 0) { 5822 bytes[index] = (byte) addr; 5823 addr = addr >> 8; 5824 } 5825 return bytes; 5826 } 5827 5828 /** {@inheritDoc} */ 5829 @Override getSimState()5830 public String getSimState() { 5831 if (getIDevice() instanceof StubDevice) { 5832 // Do not query SIM state from stub devices. 5833 return null; 5834 } 5835 if (!TestDeviceState.ONLINE.equals(mState)) { 5836 // Only query SIM state from online devices. 5837 return null; 5838 } 5839 try { 5840 return getPropertyWithRecovery(SIM_STATE_PROP, false); 5841 } catch (DeviceNotAvailableException dnae) { 5842 CLog.w("DeviceNotAvailableException while fetching SIM state"); 5843 return null; 5844 } 5845 } 5846 5847 /** {@inheritDoc} */ 5848 @Override getSimOperator()5849 public String getSimOperator() { 5850 if (getIDevice() instanceof StubDevice) { 5851 // Do not query SIM operator from stub devices. 5852 return null; 5853 } 5854 if (!TestDeviceState.ONLINE.equals(mState)) { 5855 // Only query SIM operator from online devices. 5856 return null; 5857 } 5858 try { 5859 return getPropertyWithRecovery(SIM_OPERATOR_PROP, false); 5860 } catch (DeviceNotAvailableException dnae) { 5861 CLog.w("DeviceNotAvailableException while fetching SIM operator"); 5862 return null; 5863 } 5864 } 5865 5866 /** {@inheritDoc} */ 5867 @Override dumpHeap(String process, String devicePath)5868 public File dumpHeap(String process, String devicePath) throws DeviceNotAvailableException { 5869 throw new UnsupportedOperationException("dumpHeap is not supported."); 5870 } 5871 5872 /** {@inheritDoc} */ 5873 @Override getProcessPid(String process)5874 public String getProcessPid(String process) throws DeviceNotAvailableException { 5875 String output = executeShellCommand(String.format("pidof %s", process)).trim(); 5876 if (checkValidPid(output)) { 5877 return output; 5878 } 5879 CLog.e("Failed to find a valid pid for process '%s'.", process); 5880 return null; 5881 } 5882 5883 /** {@inheritDoc} */ 5884 @Override 5885 @FormatMethod logOnDevice(String tag, LogLevel level, String format, Object... args)5886 public void logOnDevice(String tag, LogLevel level, String format, Object... args) { 5887 String message = String.format(format, args); 5888 try { 5889 String levelLetter = logLevelToLogcatLevel(level); 5890 String command = String.format("log -t %s -p %s '%s'", tag, levelLetter, message); 5891 executeShellCommand(command); 5892 } catch (DeviceNotAvailableException e) { 5893 CLog.e("Device went not available when attempting to log '%s'", message); 5894 CLog.e(e); 5895 } 5896 } 5897 5898 /** Convert the {@link LogLevel} to the letter used in log (see 'adb shell log --help'). */ logLevelToLogcatLevel(LogLevel level)5899 private String logLevelToLogcatLevel(LogLevel level) { 5900 switch (level) { 5901 case DEBUG: 5902 return "d"; 5903 case ERROR: 5904 return "e"; 5905 case INFO: 5906 return "i"; 5907 case VERBOSE: 5908 return "v"; 5909 case WARN: 5910 return "w"; 5911 default: 5912 return "i"; 5913 } 5914 } 5915 5916 /** {@inheritDoc} */ 5917 @Override getTotalMemory()5918 public long getTotalMemory() { 5919 // "/proc/meminfo" always returns value in kilobytes. 5920 long totalMemory = 0; 5921 String output = null; 5922 try { 5923 output = executeShellCommand("cat /proc/meminfo | grep MemTotal"); 5924 } catch (DeviceNotAvailableException e) { 5925 CLog.e(e); 5926 return -1; 5927 } 5928 if (output.isEmpty()) { 5929 return -1; 5930 } 5931 String[] results = output.split("\\s+"); 5932 try { 5933 totalMemory = Long.parseLong(results[1].replaceAll("\\D+", "")); 5934 } catch (ArrayIndexOutOfBoundsException | NumberFormatException e) { 5935 CLog.e(e); 5936 return -1; 5937 } 5938 return totalMemory * 1024; 5939 } 5940 5941 /** {@inheritDoc} */ 5942 @Override getBattery()5943 public Integer getBattery() { 5944 if (getIDevice() instanceof StubDevice) { 5945 return null; 5946 } 5947 if (isStateBootloaderOrFastbootd()) { 5948 return null; 5949 } 5950 try { 5951 // Use default 5 minutes freshness 5952 Future<Integer> batteryFuture = getIDevice().getBattery(); 5953 // Get cached value or wait up to 500ms for battery level query 5954 return batteryFuture.get(500, TimeUnit.MILLISECONDS); 5955 } catch (InterruptedException 5956 | ExecutionException 5957 | java.util.concurrent.TimeoutException e) { 5958 CLog.w( 5959 "Failed to query battery level for %s: %s", 5960 getIDevice().getSerialNumber(), e.toString()); 5961 } 5962 return null; 5963 } 5964 5965 /** {@inheritDoc} */ 5966 @Override listDisplayIds()5967 public Set<Long> listDisplayIds() throws DeviceNotAvailableException { 5968 throw new UnsupportedOperationException("dumpsys SurfaceFlinger is not supported."); 5969 } 5970 5971 @Override listDisplayIdsForStartingVisibleBackgroundUsers()5972 public Set<Integer> listDisplayIdsForStartingVisibleBackgroundUsers() 5973 throws DeviceNotAvailableException { 5974 throw new UnsupportedOperationException("No support for user's feature."); 5975 } 5976 5977 /** {@inheritDoc} */ 5978 @Override getLastExpectedRebootTimeMillis()5979 public long getLastExpectedRebootTimeMillis() { 5980 return mLastTradefedRebootTime; 5981 } 5982 5983 /** {@inheritDoc} */ 5984 @Override getTombstones()5985 public List<File> getTombstones() throws DeviceNotAvailableException { 5986 List<File> tombstones = new ArrayList<>(); 5987 if (!isAdbRoot()) { 5988 CLog.w("Device was not root, cannot collect tombstones."); 5989 return tombstones; 5990 } 5991 for (String tombName : getChildren(TOMBSTONE_PATH)) { 5992 File tombFile = pullFile(TOMBSTONE_PATH + tombName); 5993 if (tombFile != null) { 5994 tombstones.add(tombFile); 5995 } 5996 } 5997 return tombstones; 5998 } 5999 6000 @Override getFoldableStates()6001 public Set<DeviceFoldableState> getFoldableStates() throws DeviceNotAvailableException { 6002 throw new UnsupportedOperationException("No support for foldable states."); 6003 } 6004 6005 @Override getCurrentFoldableState()6006 public DeviceFoldableState getCurrentFoldableState() throws DeviceNotAvailableException { 6007 throw new UnsupportedOperationException("No support for foldable states."); 6008 } 6009 6010 /** Validate that pid is an integer and not empty. */ checkValidPid(String output)6011 private boolean checkValidPid(String output) { 6012 if (output.isEmpty()) { 6013 return false; 6014 } 6015 try { 6016 Integer.parseInt(output); 6017 } catch (NumberFormatException e) { 6018 CLog.e(e); 6019 return false; 6020 } 6021 return true; 6022 } 6023 6024 /** Gets the {@link IHostOptions} instance to use. */ 6025 @VisibleForTesting getHostOptions()6026 IHostOptions getHostOptions() { 6027 return GlobalConfiguration.getInstance().getHostOptions(); 6028 } 6029 6030 /** 6031 * Returns the {@link ContentProviderHandler} or null if not available. 6032 * 6033 * <p>Content provider can be reused if it was constructed before with the same {@code userId}. 6034 * 6035 * @param userId the user id to initialize the content provider with. 6036 */ getContentProvider(int userId)6037 public ContentProviderHandler getContentProvider(int userId) throws DeviceNotAvailableException { 6038 // If disabled at the device level, don't attempt any checks. 6039 if (!getOptions().shouldUseContentProvider()) { 6040 return null; 6041 } 6042 // Prevent usage of content provider before API 28 as it would not work well since content 6043 // tool is not working before P. 6044 if (getApiLevel() < 28) { 6045 return null; 6046 } 6047 // Construct a content provider if null, or if the current user has changed since last time. 6048 if (mContentProvider == null || mContentProvider.getUserId() != userId) { 6049 mContentProvider = new ContentProviderHandler(this, userId); 6050 } 6051 // Force the install if we saw an error with content provider installation. 6052 if (mContentProvider.contentProviderNotFound()) { 6053 mShouldSkipContentProviderSetup = false; 6054 } 6055 if (!mShouldSkipContentProviderSetup) { 6056 boolean res = mContentProvider.setUp(); 6057 if (!res) { 6058 // TODO: once CP becomes a requirement, throw/fail the test if CP can't be found 6059 return null; 6060 } 6061 mShouldSkipContentProviderSetup = true; 6062 } 6063 return mContentProvider; 6064 } 6065 6066 /** Reset the flag for content provider setup in order to trigger it again. */ resetContentProviderSetup()6067 public void resetContentProviderSetup() { 6068 mShouldSkipContentProviderSetup = false; 6069 } 6070 6071 /** The log that contains all the {@link #executeShellCommand(String)} logs. */ getExecuteShellCommandLog()6072 public final File getExecuteShellCommandLog() { 6073 return mExecuteShellCommandLogs; 6074 } 6075 6076 /** Executes a simple fastboot command and report the status of the command. */ 6077 @VisibleForTesting simpleFastbootCommand(final long timeout, String[] fullCmd)6078 protected CommandResult simpleFastbootCommand(final long timeout, String[] fullCmd) 6079 throws UnsupportedOperationException { 6080 return simpleFastbootCommand(timeout, new HashMap<>(), fullCmd); 6081 } 6082 6083 /** 6084 * Executes a simple fastboot command with environment variables and report the status of the 6085 * command. 6086 */ 6087 @VisibleForTesting simpleFastbootCommand( final long timeout, Map<String, String> envVarMap, String[] fullCmd)6088 protected CommandResult simpleFastbootCommand( 6089 final long timeout, Map<String, String> envVarMap, String[] fullCmd) 6090 throws UnsupportedOperationException { 6091 if (!mFastbootEnabled) { 6092 throw new UnsupportedOperationException( 6093 String.format( 6094 "Attempted to fastboot on device %s , but fastboot " 6095 + "is disabled. Aborting.", 6096 getSerialNumber())); 6097 } 6098 IRunUtil runUtil; 6099 if (!envVarMap.isEmpty()) { 6100 runUtil = new RunUtil(); 6101 } else { 6102 runUtil = getRunUtil(); 6103 } 6104 for (Map.Entry<String, String> entry : envVarMap.entrySet()) { 6105 CLog.v( 6106 String.format( 6107 "Set environment variable %s to %s", entry.getKey(), entry.getValue())); 6108 runUtil.setEnvVariable(entry.getKey(), entry.getValue()); 6109 } 6110 CommandResult result = new CommandResult(CommandStatus.EXCEPTION); 6111 // block state changes while executing a fastboot command, since 6112 // device will disappear from fastboot devices while command is being executed 6113 mFastbootLock.lock(); 6114 try { 6115 if (mOptions.getFastbootOutputTimeout() > 0) { 6116 result = 6117 runUtil.runTimedCmdWithOutputMonitor( 6118 timeout, mOptions.getFastbootOutputTimeout(), fullCmd); 6119 } else { 6120 result = runUtil.runTimedCmd(timeout, fullCmd); 6121 } 6122 } finally { 6123 mFastbootLock.unlock(); 6124 } 6125 return result; 6126 } 6127 6128 /** The current connection associated with the device. */ 6129 @Override getConnection()6130 public AbstractConnection getConnection() { 6131 if (mConnection == null) { 6132 mConnection = 6133 DefaultConnection.createInopConnection( 6134 new ConnectionBuilder(getRunUtil(), this, null, getLogger())); 6135 } 6136 return mConnection; 6137 } 6138 6139 /** Check if debugfs is mounted. */ 6140 @Override isDebugfsMounted()6141 public boolean isDebugfsMounted() throws DeviceNotAvailableException { 6142 return CommandStatus.SUCCESS.equals( 6143 executeShellV2Command(CHECK_DEBUGFS_MNT_COMMAND).getStatus()); 6144 } 6145 6146 /** Mount debugfs. */ 6147 @Override mountDebugfs()6148 public void mountDebugfs() throws DeviceNotAvailableException { 6149 if (isDebugfsMounted()) { 6150 CLog.w("debugfs already mounted."); 6151 return; 6152 } 6153 6154 CommandResult result = executeShellV2Command(MOUNT_DEBUGFS_COMMAND); 6155 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 6156 CLog.e("Failed to mount debugfs. %s", result); 6157 throw new DeviceRuntimeException( 6158 "'" + MOUNT_DEBUGFS_COMMAND + "' has failed: " + result, 6159 DeviceErrorIdentifier.SHELL_COMMAND_ERROR); 6160 } 6161 } 6162 6163 /** Unmount debugfs. */ 6164 @Override unmountDebugfs()6165 public void unmountDebugfs() throws DeviceNotAvailableException { 6166 if (!isDebugfsMounted()) { 6167 CLog.w("debugfs not mounted to unmount."); 6168 return; 6169 } 6170 6171 CommandResult result = executeShellV2Command(UNMOUNT_DEBUGFS_COMMAND); 6172 if (!CommandStatus.SUCCESS.equals(result.getStatus())) { 6173 CLog.e("Failed to unmount debugfs. %s", result); 6174 throw new DeviceRuntimeException( 6175 "'" + UNMOUNT_DEBUGFS_COMMAND + "' has failed: " + result, 6176 DeviceErrorIdentifier.SHELL_COMMAND_ERROR); 6177 } 6178 } 6179 6180 /** 6181 * Notifies all {@link IDeviceActionReceiver} about reboot start event. 6182 * 6183 * @throws DeviceNotAvailableException 6184 */ notifyRebootStarted()6185 protected void notifyRebootStarted() throws DeviceNotAvailableException { 6186 try (CloseableTraceScope ignored = new CloseableTraceScope("rebootStartedCallbacks")) { 6187 for (IDeviceActionReceiver dar : mDeviceActionReceivers) { 6188 try { 6189 inRebootCallback = true; 6190 dar.rebootStarted(this); 6191 } catch (DeviceNotAvailableException dnae) { 6192 inRebootCallback = false; 6193 throw dnae; 6194 } catch (Exception e) { 6195 logDeviceActionException("notifyRebootStarted", e, true); 6196 } finally { 6197 inRebootCallback = false; 6198 } 6199 } 6200 } 6201 } 6202 6203 /** 6204 * Notifies all {@link IDeviceActionReceiver} about reboot end event. 6205 * 6206 * @throws DeviceNotAvailableException 6207 */ notifyRebootEnded()6208 protected void notifyRebootEnded() throws DeviceNotAvailableException { 6209 try (CloseableTraceScope ignored = new CloseableTraceScope("rebootEndedCallbacks")) { 6210 for (IDeviceActionReceiver dar : mDeviceActionReceivers) { 6211 try { 6212 inRebootCallback = true; 6213 dar.rebootEnded(this); 6214 } catch (DeviceNotAvailableException dnae) { 6215 inRebootCallback = false; 6216 throw dnae; 6217 } catch (Exception e) { 6218 logDeviceActionException("notifyRebootEnded", e, true); 6219 } finally { 6220 inRebootCallback = false; 6221 } 6222 } 6223 } 6224 } 6225 6226 /** 6227 * Returns whether reboot callbacks is currently being executed or not. All public api's for 6228 * reboot should be disabled if true. 6229 */ isInRebootCallback()6230 protected boolean isInRebootCallback() { 6231 return inRebootCallback; 6232 } 6233 6234 @Override setTestLogger(ITestLogger testLogger)6235 public void setTestLogger(ITestLogger testLogger) { 6236 mTestLogger = testLogger; 6237 } 6238 getLogger()6239 protected ITestLogger getLogger() { 6240 return mTestLogger; 6241 } 6242 6243 /** 6244 * Marks the TestDevice as microdroid and sets its CID. 6245 * 6246 * @param process Process of the Microdroid VM. 6247 */ setMicrodroidProcess(Process process)6248 protected void setMicrodroidProcess(Process process) { 6249 mMicrodroidProcess = process; 6250 } 6251 6252 /** 6253 * @return Returns the Process of the Microdroid VM. If TestDevice is not a Microdroid, returns 6254 * null. 6255 */ getMicrodroidProcess()6256 public Process getMicrodroidProcess() { 6257 return mMicrodroidProcess; 6258 } 6259 setTestDeviceOptions(Map<String, String> deviceOptions)6260 protected void setTestDeviceOptions(Map<String, String> deviceOptions) { 6261 try { 6262 OptionSetter setter = new OptionSetter(this.getOptions()); 6263 for (Map.Entry<String, String> optionsKeyValue : deviceOptions.entrySet()) { 6264 setter.setOptionValue(optionsKeyValue.getKey(), optionsKeyValue.getValue()); 6265 } 6266 } catch (ConfigurationException e) { 6267 CLog.w(e); 6268 } 6269 } 6270 invalidatePropertyCache()6271 public void invalidatePropertyCache() { 6272 mPropertiesCache.invalidateAll(); 6273 } 6274 } 6275