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