• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 
17 package com.android.cts;
18 
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.AndroidDebugBridge;
21 import com.android.ddmlib.IDevice;
22 import com.android.ddmlib.TimeoutException;
23 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
24 
25 import java.io.IOException;
26 import java.util.ArrayList;
27 import java.util.concurrent.Semaphore;
28 import java.util.concurrent.TimeUnit;
29 
30 /**
31  * Initializing and managing devices.
32  */
33 public class DeviceManager implements IDeviceChangeListener {
34 
35     private static final int SHORT_DELAY = 1000 * 15; // 15 seconds
36     private static final int LONG_DELAY = 1000 * 60 * 10; // 10 minutes
37     /** Time to wait after issuing reboot command  */
38     private static final int REBOOT_DELAY = 5 * 1000; // 5 seconds
39     /** Time to wait after device reports that boot is complete. */
40     private static final int POST_BOOT_DELAY = 1000 * 60; // 1 minute
41     /** Maximal number of attempts to restart ADB connection. */
42     private static final int MAX_ADB_RESTART_ATTEMPTS = 10;
43     ArrayList<TestDevice> mDevices;
44     /** This is used during device restart for blocking until the device has been reconnected. */
45     private Semaphore mSemaphore = new Semaphore(0);
46 
DeviceManager()47     public DeviceManager() {
48         mDevices = new ArrayList<TestDevice>();
49     }
50 
51     /**
52      * Initialize Android debug bridge. This function should be called after
53      * {@link DeviceManager} initialized.
54      */
initAdb()55     public void initAdb() {
56         String adbLocation = getAdbLocation();
57 
58         Log.d("init adb...");
59         AndroidDebugBridge.init(true);
60         AndroidDebugBridge.addDeviceChangeListener(this);
61         AndroidDebugBridge.createBridge(adbLocation, true);
62     }
63 
64     /**
65      * Get the location of the adb command.
66      *
67      * @return The location of the adb location.
68      */
getAdbLocation()69     public static String getAdbLocation() {
70         return "adb";
71     }
72 
73     /**
74      * Allocate devices by specified number for testing.
75      * @param num the number of required device
76      * @return the specified number of devices.
77      */
allocateDevices(final int num)78     public TestDevice[] allocateDevices(final int num) throws DeviceNotAvailableException {
79 
80         ArrayList<TestDevice> deviceList;
81         TestDevice td;
82         int index = 0;
83 
84         if (num < 0) {
85             throw new IllegalArgumentException();
86         }
87         if (num > mDevices.size()) {
88             throw new DeviceNotAvailableException("The number of connected device("
89                     + mDevices.size() + " is less than the specified number("
90                     + num + "). Please plug in enough devices");
91         }
92         deviceList = new ArrayList<TestDevice>();
93 
94         while (index < mDevices.size() && deviceList.size() != num) {
95             td = mDevices.get(index);
96             if (td.getStatus() == TestDevice.STATUS_IDLE) {
97                 deviceList.add(td);
98             }
99             index++;
100         }
101         if (deviceList.size() != num) {
102             throw new DeviceNotAvailableException("Can't get the specified number("
103                     + num + ") of idle device(s).");
104         }
105         return deviceList.toArray(new TestDevice[num]);
106     }
107 
108     /**
109      * Get TestDevice list that available for executing tests.
110      *
111      * @return The device list.
112      */
getDeviceList()113     public final TestDevice[] getDeviceList() {
114         return mDevices.toArray(new TestDevice[mDevices.size()]);
115     }
116 
117     /**
118      * Get the number of all free devices.
119      *
120      * @return the number of all free devices
121      */
getCountOfFreeDevices()122     public int getCountOfFreeDevices() {
123         int count = 0;
124         for (TestDevice td : mDevices) {
125             if (td.getStatus() == TestDevice.STATUS_IDLE) {
126                 count++;
127             }
128         }
129         return count;
130     }
131 
132     /**
133      * Append the device to the device list.
134      *
135      * @param device The device to be appended to the device list.
136      */
appendDevice(final IDevice device)137     private void appendDevice(final IDevice device) {
138         if (-1 == getDeviceIndex(device)) {
139             TestDevice td = new TestDevice(device);
140             mDevices.add(td);
141         }
142     }
143 
144     /**
145      * Remove specified TestDevice from managed list.
146      *
147      * @param device The device to be removed from the device list.
148      */
removeDevice(final IDevice device)149     private void removeDevice(final IDevice device) {
150         int index = getDeviceIndex(device);
151         if (index == -1) {
152             Log.d("Can't find " + device + " in device list of DeviceManager");
153             return;
154         }
155         mDevices.get(index).disconnected();
156         mDevices.remove(index);
157     }
158 
159     /**
160      * Get the index of the specified device in the device array.
161      *
162      * @param device The device to be found.
163      * @return The index of the device if it exists; else -1.
164      */
getDeviceIndex(final IDevice device)165     private int getDeviceIndex(final IDevice device) {
166         TestDevice td;
167 
168         for (int index = 0; index < mDevices.size(); index++) {
169             td = mDevices.get(index);
170             if (td.getSerialNumber().equals(device.getSerialNumber())) {
171                 return index;
172             }
173         }
174         return -1;
175     }
176 
177     /**
178      * Search a <code>TestDevice</code> by serial number.
179      *
180      * @param deviceSerialNumber The serial number of the device to be found.
181      * @return The test device, if it exists, otherwise null.
182      */
searchTestDevice(final String deviceSerialNumber)183     private TestDevice searchTestDevice(final String deviceSerialNumber) {
184         for (TestDevice td : mDevices) {
185             if (td.getSerialNumber().equals(deviceSerialNumber)) {
186                 return td;
187             }
188         }
189         return null;
190     }
191 
192     /** {@inheritDoc} */
deviceChanged(IDevice device, int changeMask)193     public void deviceChanged(IDevice device, int changeMask) {
194         Log.d("device " + device.getSerialNumber() + " changed with changeMask=" + changeMask);
195         Log.d("Device state:" + device.getState());
196     }
197 
198     /** {@inheritDoc} */
deviceConnected(IDevice device)199     public void deviceConnected(IDevice device) {
200         new DeviceServiceMonitor(device).start();
201     }
202 
203     /**
204      * To make sure that connection between {@link AndroidDebugBridge}
205      * and {@link IDevice} is initialized properly. In fact, it just make sure
206      * the sync service isn't null and device's build values are collected
207      * before appending device.
208      */
209     private class DeviceServiceMonitor extends Thread {
210         private IDevice mDevice;
211 
DeviceServiceMonitor(IDevice device)212         public DeviceServiceMonitor(IDevice device) {
213             mDevice = device;
214         }
215 
216         @Override
run()217         public void run() {
218             try {
219                while (mDevice.getSyncService() == null || mDevice.getPropertyCount() == 0) {
220                     try {
221                         Thread.sleep(100);
222                     } catch (InterruptedException e) {
223                         Log.d("polling for device sync service interrupted");
224                     }
225                 }
226                 CUIOutputStream.println("Device(" + mDevice + ") connected");
227                 if (!TestSession.isADBServerRestartedMode()) {
228                     CUIOutputStream.printPrompt();
229                 }
230                 appendDevice(mDevice);
231                 // increment the counter semaphore to unblock threads waiting for devices
232                 mSemaphore.release();
233             } catch (IOException e) {
234                 // FIXME: handle failed connection to device.
235             } catch (TimeoutException e) {
236                 // FIXME: handle failed connection to device.
237             } catch (AdbCommandRejectedException e) {
238                 // FIXME: handle failed connection to device.
239             }
240         }
241     }
242 
243     /** {@inheritDoc} */
deviceDisconnected(IDevice device)244     public void deviceDisconnected(IDevice device) {
245         removeDevice(device);
246     }
247 
248     /**
249      * Allocate device by specified Id for testing.
250      * @param deviceId the ID of the test device.
251      * @return a {@link TestDevice} if the specified device is free.
252      */
allocateFreeDeviceById(String deviceId)253     public TestDevice allocateFreeDeviceById(String deviceId) throws DeviceNotAvailableException {
254         for (TestDevice td : mDevices) {
255             if (td.getSerialNumber().equals(deviceId)) {
256                 if (td.getStatus() != TestDevice.STATUS_IDLE) {
257                     String msg = "The specifed device(" + deviceId + ") is " +
258                     td.getStatusAsString();
259                     throw new DeviceNotAvailableException(msg);
260                 }
261                 return td;
262             }
263         }
264         throw new DeviceNotAvailableException("The specified device(" +
265                 deviceId + "cannot be found");
266     }
267 
268     /**
269      * Reset the online {@link TestDevice} to STATUS_IDLE
270      *
271      * @param device of the specified {@link TestDevice}
272      */
resetTestDevice(final TestDevice device)273     public void resetTestDevice(final TestDevice device) {
274         if (device.getStatus() != TestDevice.STATUS_OFFLINE) {
275             device.setStatus(TestDevice.STATUS_IDLE);
276         }
277     }
278 
279     /**
280      * Restart ADB server.
281      *
282      * @param ts The test session.
283      */
restartADBServer(TestSession ts)284     public void restartADBServer(TestSession ts) throws DeviceDisconnectedException {
285         try {
286             Thread.sleep(SHORT_DELAY); // time to collect outstanding logs
287             Log.i("Restarting device ...");
288             rebootDevice(ts);
289             Log.i("Restart complete.");
290         } catch (InterruptedException e) {
291             e.printStackTrace();
292         }
293     }
294 
295     /**
296      * Reboot the device.
297      *
298      * @param ts The test session.
299      */
rebootDevice(TestSession ts)300     private void rebootDevice(TestSession ts) throws InterruptedException,
301                 DeviceDisconnectedException {
302 
303         String deviceSerialNumber = ts.getDeviceId();
304         if (!deviceSerialNumber.toLowerCase().startsWith("emulator")) {
305             // try to reboot the device using the command line adb
306             // TODO: do we need logic to retry this
307             executeCommand("adb -s " + deviceSerialNumber + " reboot");
308             // wait to make sure the reboot gets through before we tear down the connection
309 
310             // TODO: this is flaky, no guarantee device has actually rebooted, host should wait till
311             // device goes offline
312             Thread.sleep(REBOOT_DELAY);
313 
314             int attempts = 0;
315             boolean deviceConnected = false;
316             while (!deviceConnected && (attempts < MAX_ADB_RESTART_ATTEMPTS)) {
317                 AndroidDebugBridge.disconnectBridge();
318 
319                 // kill the server while the device is rebooting
320                 executeCommand("adb kill-server");
321 
322                 // Reset the device counter semaphore. We will wait below until at least one device
323                 // has come online. This can happen any time during or after the call to
324                 // createBridge(). The counter gets increased by the DeviceServiceMonitor when a
325                 // device is added.
326                 mSemaphore.drainPermits();
327                 AndroidDebugBridge.createBridge(getAdbLocation(), true);
328 
329                 boolean deviceFound = false;
330                 while (!deviceFound) {
331                     // wait until at least one device has been added
332                     mSemaphore.tryAcquire(LONG_DELAY, TimeUnit.MILLISECONDS);
333                     TestDevice device = searchTestDevice(deviceSerialNumber);
334                     if (device != null) {
335                         ts.setTestDevice(device);
336                         deviceFound = true;
337                         deviceConnected = device.waitForBootComplete();
338                         // After boot is complete, the ADB connection sometimes drops
339                         // for a short time. Wait for things to stabilize.
340                         try {
341                             Thread.sleep(POST_BOOT_DELAY);
342                         } catch (InterruptedException ignored) {
343                             // ignore
344                         }
345                         // If the connection dropped during the sleep above, the TestDevice
346                         // instance is no longer valid.
347                         TestDevice newDevice = searchTestDevice(deviceSerialNumber);
348                         if (newDevice != null) {
349                             ts.setTestDevice(newDevice);
350                             if (newDevice != device) {
351                                 // the connection was dropped or a second reboot occurred
352                                 // TODO: replace the hardcoded /sdcard
353                                 String cmd = String.format("adb -s %s shell bugreport -o " +
354                                             "/sdcard/bugreports/doubleReboot", deviceSerialNumber);
355                                 executeCommand(cmd);
356                             }
357                         } else {
358                             // connection dropped and has not come back up
359                             deviceFound = false; // go wait for next semaphore permit
360                         }
361                     }
362                 }
363                 attempts += 1;
364             }
365         }
366     }
367 
368     /**
369      * Execute the given command and wait for its completion.
370      *
371      * @param command The command to be executed.
372      * @return True if the command was executed successfully, otherwise false.
373      */
executeCommand(String command)374     private boolean executeCommand(String command) {
375         Log.d("executeCommand(): cmd=" + command);
376         try {
377             Process proc = Runtime.getRuntime().exec(command);
378             TimeoutThread tt = new TimeoutThread(proc, SHORT_DELAY);
379             tt.start();
380             proc.waitFor(); // ignore exit value
381             tt.interrupt(); // wake timeout thread
382         } catch (Exception e) {
383             return false;
384         }
385         return true;
386     }
387 
388     class TimeoutThread extends Thread {
389         Process mProcess;
390         long mTimeout;
391 
TimeoutThread(Process process, long timeout)392         TimeoutThread(Process process, long timeout) {
393             mProcess = process;
394             mTimeout = timeout;
395         }
396 
397         @Override
run()398         public void run() {
399             try {
400                 Thread.sleep(mTimeout);
401             } catch (InterruptedException e) {
402                 // process has already completed
403                 return;
404             }
405             // destroy process and wake up thread waiting for its completion
406             mProcess.destroy();
407         }
408     }
409 }
410