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