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.CollectingOutputReceiver; 20 import com.android.ddmlib.IDevice; 21 import com.android.ddmlib.ShellCommandUnresponsiveException; 22 import com.android.ddmlib.TimeoutException; 23 import com.android.tradefed.device.IDeviceManager.IFastbootListener; 24 import com.android.tradefed.invoker.tracing.CloseableTraceScope; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.result.error.DeviceErrorIdentifier; 27 import com.android.tradefed.result.error.InfraErrorIdentifier; 28 import com.android.tradefed.util.IRunUtil; 29 import com.android.tradefed.util.RunInterruptedException; 30 import com.android.tradefed.util.RunUtil; 31 import com.android.tradefed.util.TimeUtil; 32 33 import com.google.common.base.Strings; 34 import com.google.common.primitives.Longs; 35 36 import java.io.IOException; 37 import java.util.ArrayList; 38 import java.util.Arrays; 39 import java.util.Collection; 40 import java.util.HashSet; 41 import java.util.List; 42 import java.util.Set; 43 import java.util.concurrent.Callable; 44 import java.util.concurrent.TimeUnit; 45 46 /** 47 * Helper class for monitoring the state of a {@link IDevice} with no framework support. 48 */ 49 public class NativeDeviceStateMonitor implements IDeviceStateMonitor { 50 51 static final String BOOTCOMPLETE_PROP = "dev.bootcomplete"; 52 53 private IDevice mDevice; 54 private TestDeviceState mDeviceState; 55 56 /** the time in ms to wait between 'poll for responsiveness' attempts */ 57 private static final long CHECK_POLL_TIME = 1 * 1000; 58 59 protected static final long MAX_CHECK_POLL_TIME = 3 * 1000; 60 61 /** the maximum operation time in ms for a 'poll for responsiveness' command */ 62 protected static final int MAX_OP_TIME = 10 * 1000; 63 /** Reference for TMPFS from 'man statfs' */ 64 private static final Set<String> TMPFS_MAGIC = 65 new HashSet<>(Arrays.asList("1021994", "01021994")); 66 67 /** The time in ms to wait for a device to be online. */ 68 private long mDefaultOnlineTimeout = 1 * 60 * 1000; 69 70 /** The time in ms to wait for a device to available. */ 71 private long mDefaultAvailableTimeout = 6 * 60 * 1000; 72 73 /** The fastboot mode serial number */ 74 private String mFastbootSerialNumber = null; 75 76 private List<DeviceStateListener> mStateListeners; 77 private IDeviceManager mMgr; 78 private final boolean mFastbootEnabled; 79 private boolean mMountFileSystemCheckEnabled = false; 80 private TestDeviceState mFinalState = null; 81 82 protected static final String PERM_DENIED_ERROR_PATTERN = "Permission denied"; 83 NativeDeviceStateMonitor(IDeviceManager mgr, IDevice device, boolean fastbootEnabled)84 public NativeDeviceStateMonitor(IDeviceManager mgr, IDevice device, 85 boolean fastbootEnabled) { 86 mMgr = mgr; 87 mDevice = device; 88 mStateListeners = new ArrayList<DeviceStateListener>(); 89 mDeviceState = TestDeviceState.getStateByDdms(device.getState()); 90 mFastbootEnabled = fastbootEnabled; 91 mMountFileSystemCheckEnabled = mMgr.isFileSystemMountCheckEnabled(); 92 } 93 94 @Override attachFinalState(TestDeviceState finalState)95 public void attachFinalState(TestDeviceState finalState) { 96 mFinalState = finalState; 97 } 98 99 /** 100 * Get the {@link RunUtil} instance to use. 101 * <p/> 102 * Exposed for unit testing. 103 */ getRunUtil()104 IRunUtil getRunUtil() { 105 return RunUtil.getDefault(); 106 } 107 108 /** 109 * Set the time in ms to wait for a device to be online in {@link #waitForDeviceOnline()}. 110 */ 111 @Override setDefaultOnlineTimeout(long timeoutMs)112 public void setDefaultOnlineTimeout(long timeoutMs) { 113 mDefaultOnlineTimeout = timeoutMs; 114 } 115 116 /** 117 * Set the time in ms to wait for a device to be available in {@link #waitForDeviceAvailable()}. 118 */ 119 @Override setDefaultAvailableTimeout(long timeoutMs)120 public void setDefaultAvailableTimeout(long timeoutMs) { 121 mDefaultAvailableTimeout = timeoutMs; 122 } 123 124 /** Set the fastboot mode serial number. */ 125 @Override setFastbootSerialNumber(String serial)126 public void setFastbootSerialNumber(String serial) { 127 mFastbootSerialNumber = serial; 128 129 if (mFastbootSerialNumber != null && !mFastbootSerialNumber.equals(getSerialNumber())) { 130 // Add to IDeviceManager to monitor it 131 mMgr.addMonitoringTcpFastbootDevice(getSerialNumber(), mFastbootSerialNumber); 132 } 133 } 134 135 /** 136 * {@inheritDoc} 137 */ 138 @Override waitForDeviceOnline(long waitTime)139 public IDevice waitForDeviceOnline(long waitTime) { 140 try (CloseableTraceScope ignored = new CloseableTraceScope("waitForDeviceOnline")) { 141 if (waitForDeviceState(TestDeviceState.ONLINE, waitTime)) { 142 return getIDevice(); 143 } 144 } 145 return null; 146 } 147 148 /** 149 * {@inheritDoc} 150 */ 151 @Override waitForDeviceOnline()152 public IDevice waitForDeviceOnline() { 153 return waitForDeviceOnline(mDefaultOnlineTimeout); 154 } 155 156 @Override waitForDeviceInRecovery()157 public IDevice waitForDeviceInRecovery() { 158 if (waitForDeviceState(TestDeviceState.RECOVERY, mDefaultOnlineTimeout)) { 159 return getIDevice(); 160 } 161 return null; 162 } 163 164 /** 165 * {@inheritDoc} 166 */ 167 @Override waitForDeviceInRecovery(long waitTime)168 public boolean waitForDeviceInRecovery(long waitTime) { 169 return waitForDeviceState(TestDeviceState.RECOVERY, waitTime); 170 } 171 172 /** 173 * @return {@link IDevice} associate with the state monitor 174 */ getIDevice()175 protected IDevice getIDevice() { 176 synchronized (mDevice) { 177 return mDevice; 178 } 179 } 180 181 /** 182 * {@inheritDoc} 183 */ 184 @Override getSerialNumber()185 public String getSerialNumber() { 186 return getIDevice().getSerialNumber(); 187 } 188 189 /** {@inheritDoc} */ 190 @Override getFastbootSerialNumber()191 public String getFastbootSerialNumber() { 192 if (mFastbootSerialNumber == null) { 193 mFastbootSerialNumber = getSerialNumber(); 194 } 195 return mFastbootSerialNumber; 196 } 197 198 /** 199 * {@inheritDoc} 200 */ 201 @Override waitForDeviceNotAvailable(long waitTime)202 public boolean waitForDeviceNotAvailable(long waitTime) { 203 IFastbootListener listener = new StubFastbootListener(); 204 if (mFastbootEnabled) { 205 mMgr.addFastbootListener(listener); 206 } 207 boolean result = waitForDeviceState(TestDeviceState.NOT_AVAILABLE, waitTime); 208 if (mFastbootEnabled) { 209 mMgr.removeFastbootListener(listener); 210 } 211 return result; 212 } 213 214 /** {@inheritDoc} */ 215 @Override waitForDeviceInSideload(long waitTime)216 public boolean waitForDeviceInSideload(long waitTime) { 217 return waitForDeviceState(TestDeviceState.SIDELOAD, waitTime); 218 } 219 220 /** 221 * {@inheritDoc} 222 */ 223 @Override waitForDeviceShell(final long waitTime)224 public boolean waitForDeviceShell(final long waitTime) { 225 CLog.i("Waiting %d ms for device %s shell to be responsive", waitTime, 226 getSerialNumber()); 227 Callable<BUSY_WAIT_STATUS> bootComplete = 228 () -> { 229 final CollectingOutputReceiver receiver = createOutputReceiver(); 230 final String cmd = "id"; 231 try { 232 getIDevice() 233 .executeShellCommand( 234 cmd, receiver, MAX_OP_TIME, TimeUnit.MILLISECONDS); 235 String output = receiver.getOutput(); 236 if (output.contains("uid=")) { 237 CLog.i("shell ready. id output: %s", output); 238 return BUSY_WAIT_STATUS.SUCCESS; 239 } 240 } catch (IOException 241 | AdbCommandRejectedException 242 | ShellCommandUnresponsiveException e) { 243 CLog.e("%s failed on: %s", cmd, getSerialNumber()); 244 CLog.e(e); 245 } catch (TimeoutException e) { 246 CLog.e("%s failed on %s: timeout", cmd, getSerialNumber()); 247 CLog.e(e); 248 } 249 return BUSY_WAIT_STATUS.CONTINUE_WAITING; 250 }; 251 boolean result = busyWaitFunction(bootComplete, waitTime); 252 if (!result) { 253 CLog.w("Device %s shell is unresponsive", getSerialNumber()); 254 } 255 return result; 256 } 257 258 /** 259 * {@inheritDoc} 260 */ 261 @Override waitForDeviceAvailable(final long waitTime)262 public IDevice waitForDeviceAvailable(final long waitTime) { 263 try { 264 return internalWaitForDeviceAvailable(waitTime); 265 } catch (DeviceNotAvailableException e) { 266 return null; 267 } 268 } 269 270 /** {@inheritDoc} */ 271 @Override waitForDeviceAvailable()272 public IDevice waitForDeviceAvailable() { 273 return waitForDeviceAvailable(mDefaultAvailableTimeout); 274 } 275 276 /** {@inheritDoc} */ 277 @Override waitForDeviceAvailableInRecoverPath(final long waitTime)278 public IDevice waitForDeviceAvailableInRecoverPath(final long waitTime) 279 throws DeviceNotAvailableException { 280 return internalWaitForDeviceAvailable(waitTime); 281 } 282 internalWaitForDeviceAvailable(final long waitTime)283 private IDevice internalWaitForDeviceAvailable(final long waitTime) 284 throws DeviceNotAvailableException { 285 // A device is currently considered "available" if and only if four events are true: 286 // 1. Device is online aka visible via DDMS/adb 287 // 2. Device has dev.bootcomplete flag set 288 // 3. Device's package manager is responsive (may be inop) 289 // 4. Device's external storage is mounted 290 // 291 // The current implementation waits for each event to occur in sequence. 292 // 293 // it will track the currently elapsed time and fail if it is 294 // greater than waitTime 295 296 long startTime = System.currentTimeMillis(); 297 IDevice device = waitForDeviceOnline(waitTime); 298 if (device == null) { 299 return null; 300 } 301 long elapsedTime = System.currentTimeMillis() - startTime; 302 if (!waitForBootComplete(waitTime - elapsedTime)) { 303 return null; 304 } 305 elapsedTime = System.currentTimeMillis() - startTime; 306 if (!postOnlineCheck(waitTime - elapsedTime)) { 307 return null; 308 } 309 return device; 310 } 311 312 /** 313 * {@inheritDoc} 314 */ 315 @Override waitForBootComplete(final long waitTime)316 public boolean waitForBootComplete(final long waitTime) { 317 try (CloseableTraceScope ignored = new CloseableTraceScope("waitForBootComplete")) { 318 CLog.i("Waiting %d ms for device %s boot complete", waitTime, getSerialNumber()); 319 long start = System.currentTimeMillis(); 320 // For the first boot (first adb command after ONLINE state), we allow a few miscall for 321 // stability. 322 int[] offlineCount = new int[1]; 323 offlineCount[0] = 5; 324 Callable<BUSY_WAIT_STATUS> bootComplete = 325 () -> { 326 final String cmd = "getprop " + BOOTCOMPLETE_PROP; 327 try { 328 CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 329 getIDevice() 330 .executeShellCommand( 331 "getprop " + BOOTCOMPLETE_PROP, 332 receiver, 333 60000L, 334 TimeUnit.MILLISECONDS); 335 String bootFlag = receiver.getOutput(); 336 if (bootFlag != null) { 337 // Workaround for microdroid: `adb shell` prints permission warnings 338 bootFlag = bootFlag.lines().reduce((a, b) -> b).orElse(null); 339 } 340 if (bootFlag != null && "1".equals(bootFlag.trim())) { 341 return BUSY_WAIT_STATUS.SUCCESS; 342 } 343 } catch (IOException | ShellCommandUnresponsiveException e) { 344 CLog.e("%s failed on: %s", cmd, getSerialNumber()); 345 CLog.e(e); 346 } catch (TimeoutException e) { 347 CLog.e("%s failed on %s: timeout", cmd, getSerialNumber()); 348 CLog.e(e); 349 } catch (AdbCommandRejectedException e) { 350 CLog.e("%s failed on: %s", cmd, getSerialNumber()); 351 CLog.e(e); 352 if (e.isDeviceOffline() || e.wasErrorDuringDeviceSelection()) { 353 offlineCount[0]--; 354 if (offlineCount[0] <= 0) { 355 return BUSY_WAIT_STATUS.ABORT; 356 } 357 } 358 } 359 return BUSY_WAIT_STATUS.CONTINUE_WAITING; 360 }; 361 boolean result = busyWaitFunction(bootComplete, waitTime); 362 if (!result) { 363 CLog.w( 364 "Device %s did not boot after %s ms", 365 getSerialNumber(), 366 TimeUtil.formatElapsedTime(System.currentTimeMillis() - start)); 367 } 368 return result; 369 } 370 } 371 372 /** 373 * Additional checks to be done on an Online device 374 * 375 * @param waitTime time in ms to wait before giving up 376 * @return <code>true</code> if checks are successful before waitTime expires. <code>false 377 * </code> otherwise 378 * @throws DeviceNotAvailableException 379 */ postOnlineCheck(final long waitTime)380 protected boolean postOnlineCheck(final long waitTime) throws DeviceNotAvailableException { 381 // Until we have clarity on storage requirements, move the check to 382 // full device only. 383 // return waitForStoreMount(waitTime); 384 return true; 385 } 386 387 /** 388 * Waits for the device's external store to be mounted. 389 * 390 * @param waitTime time in ms to wait before giving up 391 * @return <code>true</code> if external store is mounted before waitTime expires. <code>false 392 * </code> otherwise 393 */ waitForStoreMount(final long waitTime)394 protected boolean waitForStoreMount(final long waitTime) throws DeviceNotAvailableException { 395 CLog.i("Waiting %d ms for device %s external store", waitTime, getSerialNumber()); 396 long startTime = System.currentTimeMillis(); 397 int counter = 0; 398 // TODO(b/151119210): Remove this 'retryOnPermissionDenied' workaround when we figure out 399 // what causes "Permission denied" to be returned incorrectly. 400 int retryOnPermissionDenied = 1; 401 while (System.currentTimeMillis() - startTime < waitTime) { 402 if (counter > 0) { 403 getRunUtil().sleep(Math.min(getCheckPollTime() * counter, MAX_CHECK_POLL_TIME)); 404 } 405 counter++; 406 final CollectingOutputReceiver receiver = createOutputReceiver(); 407 final CollectingOutputReceiver bitBucket = new CollectingOutputReceiver(); 408 final long number = getCurrentTime(); 409 final String externalStore = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE); 410 if (externalStore == null) { 411 CLog.w("Failed to get external store mount point for %s", getSerialNumber()); 412 continue; 413 } 414 415 if (mMountFileSystemCheckEnabled) { 416 String fileSystem = getFileSystem(externalStore); 417 if (Strings.isNullOrEmpty(fileSystem)) { 418 CLog.w("Failed to get the fileSystem of '%s'", externalStore); 419 continue; 420 } 421 if (TMPFS_MAGIC.contains(fileSystem)) { 422 CLog.w( 423 "External storage fileSystem is '%s', waiting for it to be mounted.", 424 fileSystem); 425 continue; 426 } 427 } 428 final String testFile = String.format("'%s/%d'", externalStore, number); 429 final String testString = String.format("number %d one", number); 430 final String writeCmd = String.format("echo '%s' > %s", testString, testFile); 431 final String checkCmd = String.format("cat %s", testFile); 432 final String cleanupCmd = String.format("rm %s", testFile); 433 String cmd = null; 434 435 try { 436 cmd = writeCmd; 437 getIDevice() 438 .executeShellCommand( 439 writeCmd, bitBucket, MAX_OP_TIME, TimeUnit.MILLISECONDS); 440 cmd = checkCmd; 441 getIDevice() 442 .executeShellCommand( 443 checkCmd, receiver, MAX_OP_TIME, TimeUnit.MILLISECONDS); 444 cmd = cleanupCmd; 445 getIDevice() 446 .executeShellCommand( 447 cleanupCmd, bitBucket, MAX_OP_TIME, TimeUnit.MILLISECONDS); 448 449 String output = receiver.getOutput(); 450 CLog.v("%s returned %s", checkCmd, output); 451 if (output.contains(testString)) { 452 return true; 453 } else if (output.contains(PERM_DENIED_ERROR_PATTERN) 454 && --retryOnPermissionDenied < 0) { 455 CLog.w( 456 "Device %s mount check returned Permission Denied, " 457 + "issue with mounting.", 458 getSerialNumber()); 459 return false; 460 } 461 } catch (IOException 462 | ShellCommandUnresponsiveException e) { 463 CLog.i("%s on device %s failed:", cmd, getSerialNumber()); 464 CLog.e(e); 465 } catch (TimeoutException e) { 466 CLog.i("%s on device %s failed: timeout", cmd, getSerialNumber()); 467 CLog.e(e); 468 } catch (AdbCommandRejectedException e) { 469 String message = 470 String.format("%s on device %s was rejected:", cmd, getSerialNumber()); 471 CLog.i(message); 472 CLog.e(e); 473 rejectToUnavailable(message, e); 474 } 475 } 476 CLog.w("Device %s external storage is not mounted after %d ms", 477 getSerialNumber(), waitTime); 478 return false; 479 } 480 481 /** {@inheritDoc} */ 482 @Override getMountPoint(String mountName)483 public String getMountPoint(String mountName) throws DeviceNotAvailableException { 484 String mountPoint = getIDevice().getMountPoint(mountName); 485 if (mountPoint != null) { 486 return mountPoint; 487 } 488 // cached mount point is null - try querying directly 489 CollectingOutputReceiver receiver = createOutputReceiver(); 490 String command = "echo $" + mountName; 491 try { 492 getIDevice().executeShellCommand(command, receiver); 493 return receiver.getOutput().trim(); 494 } catch (IOException e) { 495 return null; 496 } catch (TimeoutException e) { 497 return null; 498 } catch (AdbCommandRejectedException e) { 499 rejectToUnavailable(command, e); 500 return null; 501 } catch (ShellCommandUnresponsiveException e) { 502 return null; 503 } 504 } 505 506 /** 507 * {@inheritDoc} 508 */ 509 @Override getDeviceState()510 public TestDeviceState getDeviceState() { 511 return mDeviceState; 512 } 513 514 /** 515 * {@inheritDoc} 516 */ 517 @Override waitForDeviceBootloader(long time)518 public boolean waitForDeviceBootloader(long time) { 519 return waitForDeviceBootloaderOrFastbootd(time, false); 520 } 521 522 /** {@inheritDoc} */ 523 @Override waitForDeviceFastbootd(String fastbootPath, long time)524 public boolean waitForDeviceFastbootd(String fastbootPath, long time) { 525 return waitForDeviceBootloaderOrFastbootd(time, true); 526 } 527 waitForDeviceBootloaderOrFastbootd(long time, boolean fastbootd)528 private boolean waitForDeviceBootloaderOrFastbootd(long time, boolean fastbootd) { 529 if (!mFastbootEnabled) { 530 return false; 531 } 532 long startTime = System.currentTimeMillis(); 533 // ensure fastboot state is updated at least once 534 waitForDeviceBootloaderStateUpdate(); 535 long elapsedTime = System.currentTimeMillis() - startTime; 536 IFastbootListener listener = new StubFastbootListener(); 537 mMgr.addFastbootListener(listener); 538 long waitTime = time - elapsedTime; 539 if (waitTime < 0) { 540 // wait at least 200ms 541 waitTime = 200; 542 } 543 TestDeviceState mode = TestDeviceState.FASTBOOT; 544 if (fastbootd) { 545 mode = TestDeviceState.FASTBOOTD; 546 } 547 boolean result = waitForDeviceState(mode, waitTime); 548 mMgr.removeFastbootListener(listener); 549 return result; 550 } 551 552 @Override waitForDeviceBootloaderStateUpdate()553 public void waitForDeviceBootloaderStateUpdate() { 554 if (!mFastbootEnabled) { 555 return; 556 } 557 IFastbootListener listener = new NotifyFastbootListener(); 558 synchronized (listener) { 559 mMgr.addFastbootListener(listener); 560 try { 561 listener.wait(); 562 } catch (InterruptedException e) { 563 CLog.w("wait for device bootloader state update interrupted"); 564 CLog.w(e); 565 throw new RunInterruptedException( 566 e.getMessage(), e, InfraErrorIdentifier.UNDETERMINED); 567 } finally { 568 mMgr.removeFastbootListener(listener); 569 } 570 } 571 } 572 waitForDeviceState(TestDeviceState state, long time)573 private boolean waitForDeviceState(TestDeviceState state, long time) { 574 try { 575 String deviceSerial = getSerialNumber(); 576 TestDeviceState currentStatus = getDeviceState(); 577 if (currentStatus.equals(state)) { 578 CLog.i("Device %s is already %s", deviceSerial, state); 579 return true; 580 } 581 CLog.i( 582 "Waiting for device %s to be in %s mode for '%s'; it is currently in %s" 583 + " mode...", 584 deviceSerial, state, TimeUtil.formatElapsedTime(time), currentStatus); 585 DeviceStateListener listener = new DeviceStateListener(state, mFinalState); 586 addDeviceStateListener(listener); 587 synchronized (listener) { 588 try { 589 listener.wait(time); 590 } catch (InterruptedException e) { 591 CLog.w("wait for device state interrupted"); 592 CLog.w(e); 593 throw new RunInterruptedException( 594 e.getMessage(), e, InfraErrorIdentifier.UNDETERMINED); 595 } finally { 596 removeDeviceStateListener(listener); 597 } 598 } 599 return getDeviceState().equals(state); 600 } finally { 601 mFinalState = null; 602 } 603 } 604 605 /** 606 * @param listener 607 */ removeDeviceStateListener(DeviceStateListener listener)608 private void removeDeviceStateListener(DeviceStateListener listener) { 609 synchronized (mStateListeners) { 610 mStateListeners.remove(listener); 611 } 612 } 613 614 /** 615 * @param listener 616 */ addDeviceStateListener(DeviceStateListener listener)617 private void addDeviceStateListener(DeviceStateListener listener) { 618 synchronized (mStateListeners) { 619 mStateListeners.add(listener); 620 } 621 } 622 623 /** 624 * {@inheritDoc} 625 */ 626 @Override setState(TestDeviceState deviceState)627 public void setState(TestDeviceState deviceState) { 628 mDeviceState = deviceState; 629 // create a copy of listeners to prevent holding mStateListeners lock when notifying 630 // and to protect from list modification when iterating 631 Collection<DeviceStateListener> listenerCopy = new ArrayList<DeviceStateListener>( 632 mStateListeners.size()); 633 synchronized (mStateListeners) { 634 listenerCopy.addAll(mStateListeners); 635 } 636 for (DeviceStateListener listener: listenerCopy) { 637 listener.stateChanged(deviceState); 638 } 639 } 640 641 @Override setIDevice(IDevice newDevice)642 public void setIDevice(IDevice newDevice) { 643 IDevice currentDevice = mDevice; 644 if (!getIDevice().equals(newDevice)) { 645 synchronized (currentDevice) { 646 mDevice = newDevice; 647 } 648 } 649 } 650 651 private static class DeviceStateListener { 652 private final TestDeviceState mExpectedState; 653 private final TestDeviceState mFinalState; 654 DeviceStateListener(TestDeviceState expectedState, TestDeviceState finalState)655 public DeviceStateListener(TestDeviceState expectedState, TestDeviceState finalState) { 656 mExpectedState = expectedState; 657 mFinalState = finalState; 658 } 659 stateChanged(TestDeviceState newState)660 public void stateChanged(TestDeviceState newState) { 661 if (mExpectedState.equals(newState)) { 662 synchronized (this) { 663 notify(); 664 } 665 } 666 if (mFinalState != null && mFinalState.equals(newState)) { 667 synchronized (this) { 668 CLog.e("Reached final state: %s", mFinalState); 669 notify(); 670 } 671 } 672 } 673 } 674 675 /** 676 * An empty implementation of {@link IFastbootListener} 677 */ 678 private static class StubFastbootListener implements IFastbootListener { 679 @Override stateUpdated()680 public void stateUpdated() { 681 // ignore 682 } 683 } 684 685 /** 686 * A {@link IFastbootListener} that notifies when a status update has been received. 687 */ 688 private static class NotifyFastbootListener implements IFastbootListener { 689 @Override stateUpdated()690 public void stateUpdated() { 691 synchronized (this) { 692 notify(); 693 } 694 } 695 } 696 697 /** 698 * {@inheritDoc} 699 */ 700 @Override isAdbTcp()701 public boolean isAdbTcp() { 702 return mDevice.getSerialNumber().contains(":"); 703 } 704 705 /** 706 * Exposed for testing 707 * @return {@link CollectingOutputReceiver} 708 */ createOutputReceiver()709 protected CollectingOutputReceiver createOutputReceiver() { 710 return new CollectingOutputReceiver(); 711 } 712 713 /** 714 * Exposed for testing 715 */ getCheckPollTime()716 protected long getCheckPollTime() { 717 return CHECK_POLL_TIME; 718 } 719 720 /** 721 * Exposed for testing 722 */ getCurrentTime()723 protected long getCurrentTime() { 724 return System.currentTimeMillis(); 725 } 726 getFileSystem(String externalStorePath)727 private String getFileSystem(String externalStorePath) throws DeviceNotAvailableException { 728 final CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 729 String statCommand = "stat -f -c \"%t\" " + externalStorePath; 730 try { 731 getIDevice().executeShellCommand(statCommand, receiver, 10000, TimeUnit.MILLISECONDS); 732 } catch (TimeoutException 733 | ShellCommandUnresponsiveException 734 | IOException e) { 735 CLog.e("Exception while attempting to read filesystem of '%s'", externalStorePath); 736 CLog.e(e); 737 return null; 738 } catch (AdbCommandRejectedException e) { 739 rejectToUnavailable( 740 String.format( 741 "Exception while attempting to read filesystem of '%s'", 742 externalStorePath), 743 e); 744 return null; 745 } 746 String output = receiver.getOutput().trim(); 747 CLog.v("'%s' returned %s", statCommand, output); 748 if (Longs.tryParse(output) == null && Longs.tryParse(output, 16) == null) { 749 CLog.w("stat command return value should be a number. output: %s", output); 750 return null; 751 } 752 return output; 753 } 754 busyWaitFunction(Callable<BUSY_WAIT_STATUS> callable, long maxWaitTime)755 private boolean busyWaitFunction(Callable<BUSY_WAIT_STATUS> callable, long maxWaitTime) { 756 int counter = 0; 757 long startTime = System.currentTimeMillis(); 758 long currentTotalWaitTime = 0L; 759 while ((System.currentTimeMillis() - startTime) < maxWaitTime) { 760 if (counter > 0) { 761 long nextWaitTime = Math.min(getCheckPollTime() * counter, MAX_CHECK_POLL_TIME); 762 if (currentTotalWaitTime + nextWaitTime > maxWaitTime) { 763 nextWaitTime = maxWaitTime - currentTotalWaitTime; 764 } 765 getRunUtil().sleep(nextWaitTime); 766 currentTotalWaitTime += nextWaitTime; 767 } 768 counter++; 769 try { 770 BUSY_WAIT_STATUS res = callable.call(); 771 if (BUSY_WAIT_STATUS.SUCCESS.equals(res)) { 772 return true; 773 } 774 if (BUSY_WAIT_STATUS.ABORT.equals(res)) { 775 return false; 776 } 777 } catch (Exception e) { 778 CLog.e(e); 779 } 780 } 781 return false; 782 } 783 784 private enum BUSY_WAIT_STATUS { 785 CONTINUE_WAITING, 786 ABORT, 787 SUCCESS, 788 } 789 790 /** Translate a reject adb command exception into DeviceNotAvailableException */ rejectToUnavailable(String command, AdbCommandRejectedException e)791 private void rejectToUnavailable(String command, AdbCommandRejectedException e) 792 throws DeviceNotAvailableException { 793 if (e.isDeviceOffline() || e.wasErrorDuringDeviceSelection()) { 794 throw new DeviceNotAvailableException( 795 String.format("%s: %s", command, e.getMessage()), 796 e, 797 getSerialNumber(), 798 DeviceErrorIdentifier.DEVICE_UNAVAILABLE); 799 } 800 } 801 } 802