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