blob: 43f48eb21958c25623d1775694601f9722d4075f [file] [log] [blame]
Julien Desprez227af682016-06-01 15:02:58 +01001/*
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 */
16package com.android.tradefed.device;
17
18import com.android.ddmlib.AdbCommandRejectedException;
19import com.android.ddmlib.CollectingOutputReceiver;
20import com.android.ddmlib.IDevice;
21import com.android.ddmlib.ShellCommandUnresponsiveException;
22import com.android.ddmlib.TimeoutException;
23import com.android.tradefed.device.IDeviceManager.IFastbootListener;
24import com.android.tradefed.log.LogUtil.CLog;
25import com.android.tradefed.util.IRunUtil;
26import com.android.tradefed.util.RunUtil;
27
28import java.io.IOException;
29import java.util.ArrayList;
30import java.util.Collection;
31import java.util.List;
32import java.util.concurrent.ExecutionException;
33import java.util.concurrent.TimeUnit;
34
35/**
36 * Helper class for monitoring the state of a {@link IDevice} with no framework support.
37 */
Julien Desprez2f34e382016-06-21 12:30:39 +010038public class NativeDeviceStateMonitor implements IDeviceStateMonitor {
Julien Desprez227af682016-06-01 15:02:58 +010039
40 static final String BOOTCOMPLETE_PROP = "dev.bootcomplete";
41
42 private IDevice mDevice;
43 private TestDeviceState mDeviceState;
44
45 /** the time in ms to wait between 'poll for responsiveness' attempts */
46 private static final long CHECK_POLL_TIME = 3 * 1000;
47 protected static final long MAX_CHECK_POLL_TIME = 30 * 1000;
48 /** the maximum operation time in ms for a 'poll for responsiveness' command */
49 protected static final int MAX_OP_TIME = 10 * 1000;
50
51 /** The time in ms to wait for a device to be online. */
52 private long mDefaultOnlineTimeout = 1 * 60 * 1000;
53
54 /** The time in ms to wait for a device to available. */
55 private long mDefaultAvailableTimeout = 6 * 60 * 1000;
56
57 private List<DeviceStateListener> mStateListeners;
58 private IDeviceManager mMgr;
59 private final boolean mFastbootEnabled;
60
61 protected static final String PERM_DENIED_ERROR_PATTERN = "Permission denied";
62
Julien Desprez2f34e382016-06-21 12:30:39 +010063 public NativeDeviceStateMonitor(IDeviceManager mgr, IDevice device,
Julien Desprez227af682016-06-01 15:02:58 +010064 boolean fastbootEnabled) {
65 mMgr = mgr;
66 mDevice = device;
67 mStateListeners = new ArrayList<DeviceStateListener>();
68 mDeviceState = TestDeviceState.getStateByDdms(device.getState());
69 mFastbootEnabled = fastbootEnabled;
70 }
71
72 /**
73 * Get the {@link RunUtil} instance to use.
74 * <p/>
75 * Exposed for unit testing.
76 */
77 IRunUtil getRunUtil() {
78 return RunUtil.getDefault();
79 }
80
81 /**
82 * Set the time in ms to wait for a device to be online in {@link #waitForDeviceOnline()}.
83 */
84 @Override
85 public void setDefaultOnlineTimeout(long timeoutMs) {
86 mDefaultOnlineTimeout = timeoutMs;
87 }
88
89 /**
90 * Set the time in ms to wait for a device to be available in {@link #waitForDeviceAvailable()}.
91 */
92 @Override
93 public void setDefaultAvailableTimeout(long timeoutMs) {
94 mDefaultAvailableTimeout = timeoutMs;
95 }
96
97 /**
98 * {@inheritDoc}
99 */
100 @Override
101 public IDevice waitForDeviceOnline(long waitTime) {
102 if (waitForDeviceState(TestDeviceState.ONLINE, waitTime)) {
103 return getIDevice();
104 }
105 return null;
106 }
107
108 /**
109 * @return {@link IDevice} associate with the state monitor
110 */
111 protected IDevice getIDevice() {
112 synchronized (mDevice) {
113 return mDevice;
114 }
115 }
116
117 /**
118 * {@inheritDoc}
119 */
120 @Override
121 public String getSerialNumber() {
122 return getIDevice().getSerialNumber();
123 }
124
125 /**
126 * {@inheritDoc}
127 */
128 @Override
129 public IDevice waitForDeviceOnline() {
130 return waitForDeviceOnline(mDefaultOnlineTimeout);
131 }
132
133 /**
134 * {@inheritDoc}
135 */
136 @Override
137 public boolean waitForDeviceNotAvailable(long waitTime) {
138 IFastbootListener listener = new StubFastbootListener();
139 if (mFastbootEnabled) {
140 mMgr.addFastbootListener(listener);
141 }
142 boolean result = waitForDeviceState(TestDeviceState.NOT_AVAILABLE, waitTime);
143 if (mFastbootEnabled) {
144 mMgr.removeFastbootListener(listener);
145 }
146 return result;
147 }
148
149 /**
150 * {@inheritDoc}
151 */
152 @Override
153 public boolean waitForDeviceInRecovery(long waitTime) {
154 return waitForDeviceState(TestDeviceState.RECOVERY, waitTime);
155 }
156
Guang Zhuac76f7a2019-05-22 16:46:23 -0700157 /** {@inheritDoc} */
158 @Override
159 public boolean waitForDeviceInSideload(long waitTime) {
160 return waitForDeviceState(TestDeviceState.SIDELOAD, waitTime);
161 }
162
Julien Desprez227af682016-06-01 15:02:58 +0100163 /**
164 * {@inheritDoc}
165 */
166 @Override
167 public boolean waitForDeviceShell(final long waitTime) {
168 CLog.i("Waiting %d ms for device %s shell to be responsive", waitTime,
169 getSerialNumber());
170 long startTime = System.currentTimeMillis();
171 int counter = 1;
172 while (System.currentTimeMillis() - startTime < waitTime) {
173 final CollectingOutputReceiver receiver = createOutputReceiver();
174 final String cmd = "ls /system/bin/adb";
175 try {
176 getIDevice().executeShellCommand(cmd, receiver, MAX_OP_TIME, TimeUnit.MILLISECONDS);
177 String output = receiver.getOutput();
178 if (output.contains("/system/bin/adb")) {
179 return true;
180 }
Julien Desprez1320e592016-12-06 09:51:53 +0000181 } catch (IOException | AdbCommandRejectedException |
182 ShellCommandUnresponsiveException e) {
183 CLog.i("%s failed:", cmd);
184 CLog.e(e);
Julien Desprez227af682016-06-01 15:02:58 +0100185 } catch (TimeoutException e) {
186 CLog.i("%s failed: timeout", cmd);
Julien Desprez1320e592016-12-06 09:51:53 +0000187 CLog.e(e);
Julien Desprez227af682016-06-01 15:02:58 +0100188 }
189 getRunUtil().sleep(Math.min(getCheckPollTime() * counter, MAX_CHECK_POLL_TIME));
190 counter++;
191 }
192 CLog.w("Device %s shell is unresponsive", getSerialNumber());
193 return false;
194 }
195
196 /**
197 * {@inheritDoc}
198 */
199 @Override
200 public IDevice waitForDeviceAvailable(final long waitTime) {
201 // A device is currently considered "available" if and only if four events are true:
202 // 1. Device is online aka visible via DDMS/adb
203 // 2. Device has dev.bootcomplete flag set
204 // 3. Device's package manager is responsive (may be inop)
205 // 4. Device's external storage is mounted
206 //
207 // The current implementation waits for each event to occur in sequence.
208 //
209 // it will track the currently elapsed time and fail if it is
210 // greater than waitTime
211
212 long startTime = System.currentTimeMillis();
213 IDevice device = waitForDeviceOnline(waitTime);
214 if (device == null) {
215 return null;
216 }
217 long elapsedTime = System.currentTimeMillis() - startTime;
218 if (!waitForBootComplete(waitTime - elapsedTime)) {
219 return null;
220 }
221 elapsedTime = System.currentTimeMillis() - startTime;
222 if (!postOnlineCheck(waitTime - elapsedTime)) {
223 return null;
224 }
225 return device;
226 }
227
228 /**
229 * {@inheritDoc}
230 */
231 @Override
232 public IDevice waitForDeviceAvailable() {
233 return waitForDeviceAvailable(mDefaultAvailableTimeout);
234 }
235
236 /**
237 * {@inheritDoc}
238 */
239 @Override
240 public boolean waitForBootComplete(final long waitTime) {
241 CLog.i("Waiting %d ms for device %s boot complete", waitTime, getSerialNumber());
242 int counter = 1;
243 long startTime = System.currentTimeMillis();
244 final String cmd = "getprop " + BOOTCOMPLETE_PROP;
245 while ((System.currentTimeMillis() - startTime) < waitTime) {
246 try {
247 String bootFlag = getIDevice().getSystemProperty("dev.bootcomplete").get();
248 if ("1".equals(bootFlag)) {
249 return true;
250 }
251 } catch (InterruptedException e) {
252 CLog.i("%s on device %s failed: %s", cmd, getSerialNumber(), e.getMessage());
Julien Desprez5416e132016-06-13 11:58:03 +0100253 // exit the loop for InterruptedException
254 break;
Julien Desprez227af682016-06-01 15:02:58 +0100255 } catch (ExecutionException e) {
256 CLog.i("%s on device %s failed: %s", cmd, getSerialNumber(), e.getMessage());
257 }
258 getRunUtil().sleep(Math.min(getCheckPollTime() * counter, MAX_CHECK_POLL_TIME));
259 counter++;
260 }
261 CLog.w("Device %s did not boot after %d ms", getSerialNumber(), waitTime);
262 return false;
263 }
264
265 /**
266 * Additional checks to be done on an Online device
267 *
268 * @param waitTime time in ms to wait before giving up
269 * @return <code>true</code> if checks are successful before waitTime expires.
270 * <code>false</code> otherwise
271 */
272 protected boolean postOnlineCheck(final long waitTime) {
273 return waitForStoreMount(waitTime);
274 }
275
276 /**
277 * Waits for the device's external store to be mounted.
278 *
279 * @param waitTime time in ms to wait before giving up
280 * @return <code>true</code> if external store is mounted before waitTime expires.
281 * <code>false</code> otherwise
282 */
283 protected boolean waitForStoreMount(final long waitTime) {
284 CLog.i("Waiting %d ms for device %s external store", waitTime, getSerialNumber());
285 long startTime = System.currentTimeMillis();
286 int counter = 1;
287 while (System.currentTimeMillis() - startTime < waitTime) {
288 final CollectingOutputReceiver receiver = createOutputReceiver();
289 final CollectingOutputReceiver bitBucket = new CollectingOutputReceiver();
290 final long number = getCurrentTime();
291 final String externalStore = getMountPoint(IDevice.MNT_EXTERNAL_STORAGE);
292
293 final String testFile = String.format("'%s/%d'", externalStore, number);
294 final String testString = String.format("number %d one", number);
295 final String writeCmd = String.format("echo '%s' > %s", testString, testFile);
296 final String checkCmd = String.format("cat %s", testFile);
297 final String cleanupCmd = String.format("rm %s", testFile);
298 String cmd = null;
299 if (externalStore != null) {
300 try {
301 cmd = writeCmd;
302 getIDevice().executeShellCommand(writeCmd, bitBucket,
303 MAX_OP_TIME, TimeUnit.MILLISECONDS);
304 cmd = checkCmd;
305 getIDevice().executeShellCommand(checkCmd, receiver,
306 MAX_OP_TIME, TimeUnit.MILLISECONDS);
307 cmd = cleanupCmd;
308 getIDevice().executeShellCommand(cleanupCmd, bitBucket,
309 MAX_OP_TIME, TimeUnit.MILLISECONDS);
310
311 String output = receiver.getOutput();
312 CLog.v("%s returned %s", checkCmd, output);
313 if (output.contains(testString)) {
314 return true;
315 } else if (output.contains(PERM_DENIED_ERROR_PATTERN)) {
316 CLog.w("Device %s mount check returned Permision Denied, "
317 + "issue with mounting.", getSerialNumber());
318 return false;
319 }
Julien Desprez1320e592016-12-06 09:51:53 +0000320 } catch (IOException | AdbCommandRejectedException |
321 ShellCommandUnresponsiveException e) {
322 CLog.i("%s on device %s failed:", cmd, getSerialNumber());
323 CLog.e(e);
Julien Desprez227af682016-06-01 15:02:58 +0100324 } catch (TimeoutException e) {
325 CLog.i("%s on device %s failed: timeout", cmd, getSerialNumber());
Julien Desprez1320e592016-12-06 09:51:53 +0000326 CLog.e(e);
Julien Desprez227af682016-06-01 15:02:58 +0100327 }
328 } else {
329 CLog.w("Failed to get external store mount point for %s", getSerialNumber());
330 }
331 getRunUtil().sleep(Math.min(getCheckPollTime() * counter, MAX_CHECK_POLL_TIME));
332 counter++;
333 }
334 CLog.w("Device %s external storage is not mounted after %d ms",
335 getSerialNumber(), waitTime);
336 return false;
337 }
338
339 /**
340 * {@inheritDoc}
341 */
342 @Override
343 public String getMountPoint(String mountName) {
344 String mountPoint = getIDevice().getMountPoint(mountName);
345 if (mountPoint != null) {
346 return mountPoint;
347 }
348 // cached mount point is null - try querying directly
349 CollectingOutputReceiver receiver = createOutputReceiver();
350 try {
351 getIDevice().executeShellCommand("echo $" + mountName, receiver);
352 return receiver.getOutput().trim();
353 } catch (IOException e) {
354 return null;
355 } catch (TimeoutException e) {
356 return null;
357 } catch (AdbCommandRejectedException e) {
358 return null;
359 } catch (ShellCommandUnresponsiveException e) {
360 return null;
361 }
362 }
363
364 /**
365 * {@inheritDoc}
366 */
367 @Override
368 public TestDeviceState getDeviceState() {
369 return mDeviceState;
370 }
371
372 /**
373 * {@inheritDoc}
374 */
375 @Override
376 public boolean waitForDeviceBootloader(long time) {
377 if (!mFastbootEnabled) {
378 return false;
379 }
380 long startTime = System.currentTimeMillis();
381 // ensure fastboot state is updated at least once
382 waitForDeviceBootloaderStateUpdate();
383 long elapsedTime = System.currentTimeMillis() - startTime;
384 IFastbootListener listener = new StubFastbootListener();
385 mMgr.addFastbootListener(listener);
386 long waitTime = time - elapsedTime;
387 if (waitTime < 0) {
388 // wait at least 200ms
389 waitTime = 200;
390 }
391 boolean result = waitForDeviceState(TestDeviceState.FASTBOOT, waitTime);
392 mMgr.removeFastbootListener(listener);
393 return result;
394 }
395
396 @Override
397 public void waitForDeviceBootloaderStateUpdate() {
398 if (!mFastbootEnabled) {
399 return;
400 }
401 IFastbootListener listener = new NotifyFastbootListener();
402 synchronized (listener) {
403 mMgr.addFastbootListener(listener);
404 try {
405 listener.wait();
406 } catch (InterruptedException e) {
407 CLog.w("wait for device bootloader state update interrupted");
Julien Desprez5416e132016-06-13 11:58:03 +0100408 throw new RuntimeException(e);
409 } finally {
410 mMgr.removeFastbootListener(listener);
Julien Desprez227af682016-06-01 15:02:58 +0100411 }
412 }
Julien Desprez227af682016-06-01 15:02:58 +0100413 }
414
415 private boolean waitForDeviceState(TestDeviceState state, long time) {
416 String deviceSerial = getSerialNumber();
417 if (getDeviceState() == state) {
418 CLog.i("Device %s is already %s", deviceSerial, state);
419 return true;
420 }
421 CLog.i("Waiting for device %s to be %s; it is currently %s...", deviceSerial,
422 state, getDeviceState());
423 DeviceStateListener listener = new DeviceStateListener(state);
424 addDeviceStateListener(listener);
425 synchronized (listener) {
426 try {
427 listener.wait(time);
428 } catch (InterruptedException e) {
429 CLog.w("wait for device state interrupted");
Julien Desprez5416e132016-06-13 11:58:03 +0100430 throw new RuntimeException(e);
431 } finally {
432 removeDeviceStateListener(listener);
Julien Desprez227af682016-06-01 15:02:58 +0100433 }
434 }
Julien Desprez227af682016-06-01 15:02:58 +0100435 return getDeviceState().equals(state);
436 }
437
438 /**
439 * @param listener
440 */
441 private void removeDeviceStateListener(DeviceStateListener listener) {
442 synchronized (mStateListeners) {
443 mStateListeners.remove(listener);
444 }
445 }
446
447 /**
448 * @param listener
449 */
450 private void addDeviceStateListener(DeviceStateListener listener) {
451 synchronized (mStateListeners) {
452 mStateListeners.add(listener);
453 }
454 }
455
456 /**
457 * {@inheritDoc}
458 */
459 @Override
460 public void setState(TestDeviceState deviceState) {
461 mDeviceState = deviceState;
462 // create a copy of listeners to prevent holding mStateListeners lock when notifying
463 // and to protect from list modification when iterating
464 Collection<DeviceStateListener> listenerCopy = new ArrayList<DeviceStateListener>(
465 mStateListeners.size());
466 synchronized (mStateListeners) {
467 listenerCopy.addAll(mStateListeners);
468 }
469 for (DeviceStateListener listener: listenerCopy) {
470 listener.stateChanged(deviceState);
471 }
472 }
473
474 @Override
475 public void setIDevice(IDevice newDevice) {
476 IDevice currentDevice = mDevice;
477 if (!getIDevice().equals(newDevice)) {
478 synchronized (currentDevice) {
479 mDevice = newDevice;
480 }
481 }
482 }
483
484 private static class DeviceStateListener {
485 private final TestDeviceState mExpectedState;
486
487 public DeviceStateListener(TestDeviceState expectedState) {
488 mExpectedState = expectedState;
489 }
490
491 public void stateChanged(TestDeviceState newState) {
492 if (mExpectedState.equals(newState)) {
493 synchronized (this) {
494 notify();
495 }
496 }
497 }
498 }
499
500 /**
501 * An empty implementation of {@link IFastbootListener}
502 */
503 private static class StubFastbootListener implements IFastbootListener {
504 @Override
505 public void stateUpdated() {
506 // ignore
507 }
508 }
509
510 /**
511 * A {@link IFastbootListener} that notifies when a status update has been received.
512 */
513 private static class NotifyFastbootListener implements IFastbootListener {
514 @Override
515 public void stateUpdated() {
516 synchronized (this) {
517 notify();
518 }
519 }
520 }
521
522 /**
523 * {@inheritDoc}
524 */
525 @Override
526 public boolean isAdbTcp() {
527 return mDevice.getSerialNumber().contains(":");
528 }
529
530 /**
531 * Exposed for testing
532 * @return {@link CollectingOutputReceiver}
533 */
534 protected CollectingOutputReceiver createOutputReceiver() {
535 return new CollectingOutputReceiver();
536 }
537
538 /**
539 * Exposed for testing
540 */
541 protected long getCheckPollTime() {
542 return CHECK_POLL_TIME;
543 }
544
545 /**
546 * Exposed for testing
547 */
548 protected long getCurrentTime() {
549 return System.currentTimeMillis();
550 }
551}