| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cts; |
| |
| import com.android.ddmlib.AndroidDebugBridge; |
| import com.android.ddmlib.IDevice; |
| import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.concurrent.Semaphore; |
| import java.util.concurrent.TimeUnit; |
| |
| /** |
| * Initializing and managing devices. |
| */ |
| public class DeviceManager implements IDeviceChangeListener { |
| |
| private static final int SHORT_DELAY = 1000 * 15; // 15 seconds |
| private static final int LONG_DELAY = 1000 * 60 * 10; // 10 minutes |
| /** Time to wait after issuing reboot command */ |
| private static final int REBOOT_DELAY = 5 * 1000; // 5 seconds |
| /** Time to wait after device reports that boot is complete. */ |
| private static final int POST_BOOT_DELAY = 1000 * 60; // 1 minute |
| /** Maximal number of attempts to restart ADB connection. */ |
| private static final int MAX_ADB_RESTART_ATTEMPTS = 10; |
| ArrayList<TestDevice> mDevices; |
| /** This is used during device restart for blocking until the device has been reconnected. */ |
| private Semaphore mSemaphore = new Semaphore(0); |
| |
| public DeviceManager() { |
| mDevices = new ArrayList<TestDevice>(); |
| } |
| |
| /** |
| * Initialize Android debug bridge. This function should be called after |
| * {@link DeviceManager} initialized. |
| */ |
| public void initAdb() { |
| String adbLocation = getAdbLocation(); |
| |
| Log.d("init adb..."); |
| AndroidDebugBridge.init(true); |
| AndroidDebugBridge.addDeviceChangeListener(this); |
| AndroidDebugBridge.createBridge(adbLocation, true); |
| } |
| |
| /** |
| * Get the location of the adb command. |
| * |
| * @return The location of the adb location. |
| */ |
| public static String getAdbLocation() { |
| return "adb"; |
| } |
| |
| /** |
| * Allocate devices by specified number for testing. |
| * @param num the number of required device |
| * @return the specified number of devices. |
| */ |
| public TestDevice[] allocateDevices(final int num) throws DeviceNotAvailableException { |
| |
| ArrayList<TestDevice> deviceList; |
| TestDevice td; |
| int index = 0; |
| |
| if (num < 0) { |
| throw new IllegalArgumentException(); |
| } |
| if (num > mDevices.size()) { |
| throw new DeviceNotAvailableException("The number of connected device(" |
| + mDevices.size() + " is less than the specified number(" |
| + num + "). Please plug in enough devices"); |
| } |
| deviceList = new ArrayList<TestDevice>(); |
| |
| while (index < mDevices.size() && deviceList.size() != num) { |
| td = mDevices.get(index); |
| if (td.getStatus() == TestDevice.STATUS_IDLE) { |
| deviceList.add(td); |
| } |
| index++; |
| } |
| if (deviceList.size() != num) { |
| throw new DeviceNotAvailableException("Can't get the specified number(" |
| + num + ") of idle device(s)."); |
| } |
| return deviceList.toArray(new TestDevice[num]); |
| } |
| |
| /** |
| * Get TestDevice list that available for executing tests. |
| * |
| * @return The device list. |
| */ |
| public final TestDevice[] getDeviceList() { |
| return mDevices.toArray(new TestDevice[mDevices.size()]); |
| } |
| |
| /** |
| * Get the number of all free devices. |
| * |
| * @return the number of all free devices |
| */ |
| public int getCountOfFreeDevices() { |
| int count = 0; |
| for (TestDevice td : mDevices) { |
| if (td.getStatus() == TestDevice.STATUS_IDLE) { |
| count++; |
| } |
| } |
| return count; |
| } |
| |
| /** |
| * Append the device to the device list. |
| * |
| * @param device The device to be appended to the device list. |
| */ |
| private void appendDevice(final IDevice device) { |
| if (-1 == getDeviceIndex(device)) { |
| TestDevice td = new TestDevice(device); |
| mDevices.add(td); |
| } |
| } |
| |
| /** |
| * Remove specified TestDevice from managed list. |
| * |
| * @param device The device to be removed from the device list. |
| */ |
| private void removeDevice(final IDevice device) { |
| int index = getDeviceIndex(device); |
| if (index == -1) { |
| Log.d("Can't find " + device + " in device list of DeviceManager"); |
| return; |
| } |
| mDevices.get(index).disconnected(); |
| mDevices.remove(index); |
| } |
| |
| /** |
| * Get the index of the specified device in the device array. |
| * |
| * @param device The device to be found. |
| * @return The index of the device if it exists; else -1. |
| */ |
| private int getDeviceIndex(final IDevice device) { |
| TestDevice td; |
| |
| for (int index = 0; index < mDevices.size(); index++) { |
| td = mDevices.get(index); |
| if (td.getSerialNumber().equals(device.getSerialNumber())) { |
| return index; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Search a <code>TestDevice</code> by serial number. |
| * |
| * @param deviceSerialNumber The serial number of the device to be found. |
| * @return The test device, if it exists, otherwise null. |
| */ |
| private TestDevice searchTestDevice(final String deviceSerialNumber) { |
| for (TestDevice td : mDevices) { |
| if (td.getSerialNumber().equals(deviceSerialNumber)) { |
| return td; |
| } |
| } |
| return null; |
| } |
| |
| /** {@inheritDoc} */ |
| public void deviceChanged(IDevice device, int changeMask) { |
| Log.d("device " + device.getSerialNumber() + " changed with changeMask=" + changeMask); |
| Log.d("Device state:" + device.getState()); |
| } |
| |
| /** {@inheritDoc} */ |
| public void deviceConnected(IDevice device) { |
| new DeviceServiceMonitor(device).start(); |
| } |
| |
| /** |
| * To make sure that connection between {@link AndroidDebugBridge} |
| * and {@link IDevice} is initialized properly. In fact, it just make sure |
| * the sync service isn't null and device's build values are collected |
| * before appending device. |
| */ |
| private class DeviceServiceMonitor extends Thread { |
| private IDevice mDevice; |
| |
| public DeviceServiceMonitor(IDevice device) { |
| mDevice = device; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| while (mDevice.getSyncService() == null || mDevice.getPropertyCount() == 0) { |
| try { |
| Thread.sleep(100); |
| } catch (InterruptedException e) { |
| Log.e("", e); |
| } |
| } |
| CUIOutputStream.println("Device(" + mDevice + ") connected"); |
| if (!TestSession.isADBServerRestartedMode()) { |
| CUIOutputStream.printPrompt(); |
| } |
| appendDevice(mDevice); |
| // increment the counter semaphore to unblock threads waiting for devices |
| mSemaphore.release(); |
| } catch (IOException e) { |
| // FIXME: handle failed connection to device. |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| public void deviceDisconnected(IDevice device) { |
| removeDevice(device); |
| } |
| |
| /** |
| * Allocate device by specified Id for testing. |
| * @param deviceId the ID of the test device. |
| * @return a {@link TestDevice} if the specified device is free. |
| */ |
| public TestDevice allocateFreeDeviceById(String deviceId) throws DeviceNotAvailableException { |
| for (TestDevice td : mDevices) { |
| if (td.getSerialNumber().equals(deviceId)) { |
| if (td.getStatus() != TestDevice.STATUS_IDLE) { |
| String msg = "The specifed device(" + deviceId + ") is " + |
| td.getStatusAsString(); |
| throw new DeviceNotAvailableException(msg); |
| } |
| return td; |
| } |
| } |
| throw new DeviceNotAvailableException("The specified device(" + |
| deviceId + "cannot be found"); |
| } |
| |
| /** |
| * Reset the online {@link TestDevice} to STATUS_IDLE |
| * |
| * @param device of the specified {@link TestDevice} |
| */ |
| public void resetTestDevice(final TestDevice device) { |
| if (device.getStatus() != TestDevice.STATUS_OFFLINE) { |
| device.setStatus(TestDevice.STATUS_IDLE); |
| } |
| } |
| |
| /** |
| * Restart ADB server. |
| * |
| * @param ts The test session. |
| */ |
| public void restartADBServer(TestSession ts) throws DeviceDisconnectedException { |
| try { |
| Thread.sleep(SHORT_DELAY); // time to collect outstanding logs |
| Log.i("Restarting device ..."); |
| rebootDevice(ts); |
| Log.i("Restart complete."); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| } |
| } |
| |
| /** |
| * Reboot the device. |
| * |
| * @param ts The test session. |
| */ |
| private void rebootDevice(TestSession ts) throws InterruptedException, |
| DeviceDisconnectedException { |
| |
| String deviceSerialNumber = ts.getDeviceId(); |
| if (!deviceSerialNumber.toLowerCase().startsWith("emulator")) { |
| // try to reboot the device using the command line adb |
| // TODO: do we need logic to retry this |
| executeCommand("adb -s " + deviceSerialNumber + " reboot"); |
| // wait to make sure the reboot gets through before we tear down the connection |
| |
| // TODO: this is flaky, no guarantee device has actually rebooted, host should wait till |
| // device goes offline |
| Thread.sleep(REBOOT_DELAY); |
| |
| int attempts = 0; |
| boolean deviceConnected = false; |
| while (!deviceConnected && (attempts < MAX_ADB_RESTART_ATTEMPTS)) { |
| AndroidDebugBridge.disconnectBridge(); |
| |
| // kill the server while the device is rebooting |
| executeCommand("adb kill-server"); |
| |
| // Reset the device counter semaphore. We will wait below until at least one device |
| // has come online. This can happen any time during or after the call to |
| // createBridge(). The counter gets increased by the DeviceServiceMonitor when a |
| // device is added. |
| mSemaphore.drainPermits(); |
| AndroidDebugBridge.createBridge(getAdbLocation(), true); |
| |
| boolean deviceFound = false; |
| while (!deviceFound) { |
| // wait until at least one device has been added |
| mSemaphore.tryAcquire(LONG_DELAY, TimeUnit.MILLISECONDS); |
| TestDevice device = searchTestDevice(deviceSerialNumber); |
| if (device != null) { |
| ts.setTestDevice(device); |
| deviceFound = true; |
| deviceConnected = device.waitForBootComplete(); |
| // After boot is complete, the ADB connection sometimes drops |
| // for a short time. Wait for things to stabilize. |
| try { |
| Thread.sleep(POST_BOOT_DELAY); |
| } catch (InterruptedException ignored) { |
| // ignore |
| } |
| // If the connection dropped during the sleep above, the TestDevice |
| // instance is no longer valid. |
| TestDevice newDevice = searchTestDevice(deviceSerialNumber); |
| if (newDevice != null) { |
| ts.setTestDevice(newDevice); |
| if (newDevice != device) { |
| // the connection was dropped or a second reboot occurred |
| executeCommand("adb -s " + deviceSerialNumber + " shell " + |
| "bugreport -o /sdcard/bugreports/doubleReboot"); |
| } |
| } else { |
| // connection dropped and has not come back up |
| deviceFound = false; // go wait for next semaphore permit |
| } |
| } |
| } |
| attempts += 1; |
| } |
| // dismiss the screen lock by sending a MENU key event |
| // TODO: this command isn't going to work on a user device, seems like its not needed |
| executeCommand("adb -s " + deviceSerialNumber + " shell input keyevent 82"); |
| } |
| } |
| |
| /** |
| * Execute the given command and wait for its completion. |
| * |
| * @param command The command to be executed. |
| * @return True if the command was executed successfully, otherwise false. |
| */ |
| private boolean executeCommand(String command) { |
| Log.d("executeCommand(): cmd=" + command); |
| try { |
| Process proc = Runtime.getRuntime().exec(command); |
| TimeoutThread tt = new TimeoutThread(proc, SHORT_DELAY); |
| tt.start(); |
| proc.waitFor(); // ignore exit value |
| tt.interrupt(); // wake timeout thread |
| } catch (Exception e) { |
| return false; |
| } |
| return true; |
| } |
| |
| class TimeoutThread extends Thread { |
| Process mProcess; |
| long mTimeout; |
| |
| TimeoutThread(Process process, long timeout) { |
| mProcess = process; |
| mTimeout = timeout; |
| } |
| |
| @Override |
| public void run() { |
| try { |
| Thread.sleep(mTimeout); |
| } catch (InterruptedException e) { |
| // process has already completed |
| return; |
| } |
| // destroy process and wake up thread waiting for its completion |
| mProcess.destroy(); |
| } |
| } |
| } |