• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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