| /* |
| * Copyright (C) 2010 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.tradefed.device; |
| |
| import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; |
| import com.android.ddmlib.DdmPreferences; |
| import com.android.ddmlib.EmulatorConsole; |
| import com.android.ddmlib.IDevice; |
| import com.android.ddmlib.Log; |
| import com.android.ddmlib.Log.LogLevel; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.util.ArrayUtil; |
| import com.android.tradefed.util.CommandResult; |
| import com.android.tradefed.util.CommandStatus; |
| import com.android.tradefed.util.ConditionPriorityBlockingQueue; |
| import com.android.tradefed.util.IRunUtil; |
| import com.android.tradefed.util.RunUtil; |
| import com.android.tradefed.util.StreamUtil; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.TimeUnit; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public class DeviceManager implements IDeviceManager { |
| |
| private static final String LOG_TAG = "DeviceManager"; |
| |
| /** max wait time in ms for fastboot devices command to complete */ |
| private static final long FASTBOOT_CMD_TIMEOUT = 1 * 60 * 1000; |
| /** time to wait in ms between fastboot devices requests */ |
| private static final long FASTBOOT_POLL_WAIT_TIME = 5 * 1000; |
| /** time to wait for device adb shell responsive connection before declaring it unavailable |
| * for testing */ |
| private static final int CHECK_WAIT_DEVICE_AVAIL_MS = 30 * 1000; |
| |
| /** a {@link DeviceSelectionOptions} that matches any device */ |
| private static final IDeviceSelection ANY_DEVICE_OPTIONS = new DeviceSelectionOptions(); |
| |
| private static DeviceManager sInstance; |
| |
| private boolean mIsInitialized = false; |
| /** A thread-safe map that tracks the devices currently allocated for testing.*/ |
| private Map<String, IManagedTestDevice> mAllocatedDeviceMap; |
| /** A FIFO, thread-safe queue for holding devices visible on adb available for testing */ |
| private ConditionPriorityBlockingQueue<IDevice> mAvailableDeviceQueue; |
| private IAndroidDebugBridge mAdbBridge; |
| private ManagedDeviceListener mManagedDeviceListener; |
| private boolean mFastbootEnabled; |
| private Set<IFastbootListener> mFastbootListeners; |
| private FastbootMonitor mFastbootMonitor; |
| private Map<String, IDeviceStateMonitor> mCheckDeviceMap; |
| private boolean mEnableLogcat = true; |
| private boolean mIsTerminated = false; |
| private IDeviceSelection mGlobalDeviceFilter; |
| /** the maximum number of emulators that can be allocated at one time */ |
| private int mNumEmulatorSupported = 1; |
| /** the maximum number of no device runs that can be allocated at one time */ |
| private int mNumNullDevicesSupported = 1; |
| |
| private boolean mSynchronousMode = false; |
| |
| /** |
| * Package-private constructor, should only be used by this class and its associated unit test. |
| * Use {@link #getInstance()} instead. |
| */ |
| DeviceManager() { |
| } |
| |
| public void init() { |
| init(ANY_DEVICE_OPTIONS); |
| } |
| |
| /** |
| * Initialize the device manager. This must be called once and only once before any other |
| * methods are called. |
| */ |
| @Override |
| public synchronized void init(IDeviceSelection globalDeviceFilter) { |
| if (mIsInitialized) { |
| throw new IllegalStateException("already initialized"); |
| } |
| mIsInitialized = true; |
| mGlobalDeviceFilter = globalDeviceFilter; |
| // use Hashtable since it is synchronized |
| mAllocatedDeviceMap = new Hashtable<String, IManagedTestDevice>(); |
| mAvailableDeviceQueue = new ConditionPriorityBlockingQueue<IDevice>(); |
| mCheckDeviceMap = new Hashtable<String, IDeviceStateMonitor>(); |
| |
| if (isFastbootAvailable()) { |
| mFastbootListeners = Collections.synchronizedSet(new HashSet<IFastbootListener>()); |
| mFastbootMonitor = new FastbootMonitor(); |
| startFastbootMonitor(); |
| // don't set fastboot enabled bit until mFastbootListeners has been initialized |
| mFastbootEnabled = true; |
| // TODO: consider only adding fastboot devices if explicit option is set, because |
| // device property selection options won't work properly with a device in fastboot |
| addFastbootDevices(); |
| } else { |
| Log.w(LOG_TAG, "Fastboot is not available."); |
| mFastbootListeners = null; |
| mFastbootMonitor = null; |
| mFastbootEnabled = false; |
| } |
| |
| // don't start adding devices until fastboot support has been established |
| // TODO: Temporarily increase default timeout as workaround for syncFiles timeouts |
| DdmPreferences.setTimeOut(30*1000); |
| mAdbBridge = createAdbBridge(); |
| mManagedDeviceListener = new ManagedDeviceListener(); |
| // It's important to add the listener before initializing the ADB bridge to avoid a race |
| // condition when detecting devices. |
| mAdbBridge.addDeviceChangeListener(mManagedDeviceListener); |
| // assume "adb" is in PATH |
| // TODO: make this configurable |
| mAdbBridge.init(false /* client support */, "adb"); |
| addEmulators(); |
| addNullDevices(); |
| } |
| |
| /** |
| * Instruct DeviceManager whether to use background threads or not. |
| * <p/> |
| * Exposed to make unit tests more deterministic. |
| * |
| * @param syncMode |
| */ |
| void setSynchronousMode(boolean syncMode) { |
| mSynchronousMode = syncMode; |
| } |
| |
| private void checkInit() { |
| if (!mIsInitialized) { |
| throw new IllegalStateException("DeviceManager has not been initialized"); |
| } |
| } |
| |
| /** |
| * Determine if fastboot is available for use. |
| */ |
| private boolean isFastbootAvailable() { |
| CommandResult fastbootResult = getRunUtil().runTimedCmdSilently(5000, "fastboot", "help"); |
| if (fastbootResult.getStatus() == CommandStatus.SUCCESS) { |
| return true; |
| } |
| if (fastbootResult.getStderr() != null && |
| fastbootResult.getStderr().indexOf("usage: fastboot") >= 0) { |
| Log.logAndDisplay(LogLevel.WARN, LOG_TAG, |
| "You are running an older version of fastboot, please update it."); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Start fastboot monitoring. |
| * <p/> |
| * Exposed for unit testing. |
| */ |
| void startFastbootMonitor() { |
| mFastbootMonitor.start(); |
| } |
| |
| /** |
| * Get the {@link RunUtil} instance to use. |
| * <p/> |
| * Exposed for unit testing. |
| */ |
| IRunUtil getRunUtil() { |
| return RunUtil.getDefault(); |
| } |
| |
| /** |
| * Toggle whether allocated devices should capture logcat in background |
| */ |
| public void setEnableLogcat(boolean enableLogcat) { |
| mEnableLogcat = enableLogcat; |
| } |
| |
| /** |
| * Asynchronously checks if device is available, and adds to queue |
| * @param device |
| */ |
| private void checkAndAddAvailableDevice(final IDevice device) { |
| if (mCheckDeviceMap.containsKey(device.getSerialNumber())) { |
| // device already being checked, ignore |
| Log.d(LOG_TAG, String.format("Already checking new device %s, ignoring", |
| device.getSerialNumber())); |
| return; |
| } |
| if (!mGlobalDeviceFilter.matches(device)) { |
| Log.v(LOG_TAG, String.format("New device %s doesn't match global filter, ignoring", |
| device.getSerialNumber())); |
| return; |
| } |
| final IDeviceStateMonitor monitor = createStateMonitor(device); |
| mCheckDeviceMap.put(device.getSerialNumber(), monitor); |
| |
| final String threadName = String.format("Check device %s", device.getSerialNumber()); |
| Runnable checkRunnable = new Runnable() { |
| @Override |
| public void run() { |
| Log.d(LOG_TAG, String.format("checking new device %s responsiveness", |
| device.getSerialNumber())); |
| if (monitor.waitForDeviceShell(CHECK_WAIT_DEVICE_AVAIL_MS)) { |
| Log.logAndDisplay(LogLevel.INFO, LOG_TAG, String.format( |
| "Detected new device %s", device.getSerialNumber())); |
| addAvailableDevice(device); |
| } else{ |
| CLog.e("Device %s is not responsive to adb shell command , " + |
| "skip adding to available pool", device.getSerialNumber()); |
| } |
| mCheckDeviceMap.remove(device.getSerialNumber()); |
| } |
| }; |
| if (mSynchronousMode ) { |
| checkRunnable.run(); |
| } else { |
| new Thread(checkRunnable, threadName).start(); |
| } |
| } |
| |
| /** |
| * Add placeholder objects for the max number of 'no device required' concurrent allocations |
| */ |
| private void addNullDevices() { |
| for (int i = 0; i < mNumNullDevicesSupported; i++) { |
| mAvailableDeviceQueue.add(new NullDevice(String.format("null-device-%d", i))); |
| } |
| } |
| |
| /** |
| * Add placeholder objects for the max number of emulators that can be allocated |
| */ |
| private void addEmulators() { |
| // TODO currently this means 'additional emulators not already running' |
| int port = 5554; |
| for (int i = 0; i < mNumEmulatorSupported; i++) { |
| mAvailableDeviceQueue.add(new StubDevice(String.format("emulator-%d", port), true)); |
| port += 2; |
| } |
| } |
| |
| private void addFastbootDevices() { |
| Set<String> serials = getDevicesOnFastboot(); |
| if (serials != null) { |
| for (String serial: serials) { |
| mAvailableDeviceQueue.add(new FastbootDevice(serial)); |
| } |
| } |
| } |
| |
| private static class FastbootDevice extends StubDevice { |
| FastbootDevice(String serial) { |
| super(serial, false); |
| } |
| } |
| |
| /** |
| * Creates a {@link IDeviceStateMonitor} to use. |
| * <p/> |
| * Exposed so unit tests can mock |
| */ |
| IDeviceStateMonitor createStateMonitor(IDevice device) { |
| return new DeviceStateMonitor(this, device); |
| } |
| |
| private void addAvailableDevice(IDevice device) { |
| mAvailableDeviceQueue.add(device); |
| } |
| |
| /** |
| * Return the {@link IDeviceManager} singleton, creating if necessary. |
| */ |
| public synchronized static IDeviceManager getInstance() { |
| if (sInstance == null) { |
| sInstance = new DeviceManager(); |
| } |
| return sInstance; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ITestDevice allocateDevice() { |
| checkInit(); |
| IDevice allocatedDevice = takeAvailableDevice(); |
| if (allocatedDevice == null) { |
| return null; |
| } |
| return createAllocatedDevice(allocatedDevice); |
| } |
| |
| /** |
| * Retrieves and removes a IDevice from the available device queue, waiting indefinitely if |
| * necessary until an IDevice becomes available. |
| * |
| * @return the {@link IDevice} or <code>null</code> if interrupted |
| */ |
| private IDevice takeAvailableDevice() { |
| try { |
| return mAvailableDeviceQueue.take(ANY_DEVICE_OPTIONS); |
| } catch (InterruptedException e) { |
| Log.w(LOG_TAG, "interrupted while taking device"); |
| return null; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ITestDevice allocateDevice(long timeout) { |
| checkInit(); |
| IDevice allocatedDevice = pollAvailableDevice(timeout, ANY_DEVICE_OPTIONS); |
| if (allocatedDevice == null) { |
| return null; |
| } |
| return createAllocatedDevice(allocatedDevice); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ITestDevice allocateDevice(long timeout, IDeviceSelection options) { |
| checkInit(); |
| IDevice allocatedDevice = pollAvailableDevice(timeout, options); |
| if (allocatedDevice == null) { |
| return null; |
| } |
| return createAllocatedDevice(allocatedDevice); |
| } |
| |
| /** |
| * Retrieves and removes a IDevice from the available device queue, waiting for timeout if |
| * necessary until an IDevice becomes available. |
| * |
| * @param timeout the number of ms to wait for device |
| * @param options the {@link DeviceSelectionOptions} the returned device must meet |
| * |
| * @return the {@link IDevice} or <code>null</code> if interrupted |
| */ |
| private IDevice pollAvailableDevice(long timeout, IDeviceSelection options) { |
| try { |
| return mAvailableDeviceQueue.poll(timeout, TimeUnit.MILLISECONDS, options); |
| } catch (InterruptedException e) { |
| Log.w(LOG_TAG, "interrupted while polling for device"); |
| return null; |
| } |
| } |
| |
| private ITestDevice createAllocatedDevice(IDevice allocatedDevice) { |
| IManagedTestDevice testDevice = createTestDevice(allocatedDevice, |
| createStateMonitor(allocatedDevice)); |
| if (mEnableLogcat && !(allocatedDevice instanceof StubDevice)) { |
| testDevice.startLogcat(); |
| } |
| mAllocatedDeviceMap.put(allocatedDevice.getSerialNumber(), testDevice); |
| Log.i(LOG_TAG, String.format("Allocated device %s", testDevice.getSerialNumber())); |
| return testDevice; |
| } |
| |
| /** |
| * Factory method to create a {@link IManagedTestDevice}. |
| * <p/> |
| * Exposed so unit tests can mock |
| * |
| * @param allocatedDevice |
| * @param monitor |
| * @return a {@link IManagedTestDevice} |
| */ |
| IManagedTestDevice createTestDevice(IDevice allocatedDevice, IDeviceStateMonitor monitor) { |
| IManagedTestDevice testDevice = new TestDevice(allocatedDevice, monitor); |
| testDevice.setFastbootEnabled(mFastbootEnabled); |
| if (allocatedDevice instanceof FastbootDevice) { |
| testDevice.setDeviceState(TestDeviceState.FASTBOOT); |
| } else if (allocatedDevice instanceof StubDevice) { |
| testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE); |
| } |
| return testDevice; |
| } |
| |
| /** |
| * Creates the {@link IAndroidDebugBridge} to use. |
| * <p/> |
| * Exposed so tests can mock this. |
| * @returns the {@link IAndroidDebugBridge} |
| */ |
| synchronized IAndroidDebugBridge createAdbBridge() { |
| return new AndroidDebugBridgeWrapper(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void freeDevice(ITestDevice device, FreeDeviceState deviceState) { |
| checkInit(); |
| IManagedTestDevice managedDevice = (IManagedTestDevice)device; |
| managedDevice.stopLogcat(); |
| IDevice ideviceToReturn = device.getIDevice(); |
| // don't kill emulator if it wasn't launched by launchEmulator (ie emulatorProcess is null). |
| if (ideviceToReturn.isEmulator() && managedDevice.getEmulatorProcess() != null) { |
| try { |
| killEmulator(device); |
| // emulator killed - return a stub device |
| // TODO: this is a bit of a hack. Consider having DeviceManager inject a StubDevice |
| // when deviceDisconnected event is received |
| ideviceToReturn = new StubDevice(ideviceToReturn.getSerialNumber(), true); |
| deviceState = FreeDeviceState.AVAILABLE; |
| } catch (DeviceNotAvailableException e) { |
| Log.e(LOG_TAG, e); |
| deviceState = FreeDeviceState.UNAVAILABLE; |
| } |
| } |
| if (mAllocatedDeviceMap.remove(device.getSerialNumber()) == null) { |
| Log.e(LOG_TAG, String.format("freeDevice called with unallocated device %s", |
| device.getSerialNumber())); |
| } else if (deviceState == FreeDeviceState.UNRESPONSIVE) { |
| // TODO: add class flag to control if unresponsive device's are returned to pool |
| // TODO: also consider tracking unresponsive events received per device - so a |
| // device that is continually unresponsive could be removed from available queue |
| addAvailableDevice(ideviceToReturn); |
| } else if (deviceState == FreeDeviceState.AVAILABLE) { |
| addAvailableDevice(ideviceToReturn); |
| } else if (deviceState == FreeDeviceState.UNAVAILABLE) { |
| Log.logAndDisplay(LogLevel.WARN, LOG_TAG, String.format( |
| "Freed device %s is unavailable. Removing from use.", |
| device.getSerialNumber())); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void launchEmulator(ITestDevice device, long bootTimeout, IRunUtil runUtil, |
| List<String> emulatorArgs) |
| throws DeviceNotAvailableException { |
| if (!device.getIDevice().isEmulator()) { |
| throw new IllegalStateException(String.format("Device %s is not an emulator", |
| device.getSerialNumber())); |
| } |
| if (!device.getDeviceState().equals(TestDeviceState.NOT_AVAILABLE)) { |
| throw new IllegalStateException(String.format( |
| "Emulator device %s is in state %s. Expected: %s", device.getSerialNumber(), |
| device.getDeviceState(), TestDeviceState.NOT_AVAILABLE)); |
| } |
| Integer port = EmulatorConsole.getEmulatorPort(device.getSerialNumber()); |
| if (port == null) { |
| // serial number is not in expected format |
| throw new IllegalArgumentException(String.format( |
| "Failed to determine emulator port for %s", device.getSerialNumber())); |
| } |
| List<String> fullArgs = new ArrayList<String>(emulatorArgs); |
| fullArgs.add("-port"); |
| fullArgs.add(port.toString()); |
| |
| try { |
| Process p = runUtil.runCmdInBackground(fullArgs); |
| // sleep a small amount to wait for process to start successfully |
| getRunUtil().sleep(500); |
| checkProcessDied(p); |
| IManagedTestDevice managedDevice = (IManagedTestDevice)device; |
| managedDevice.setEmulatorProcess(p); |
| } catch (IOException e) { |
| // TODO: is this the most appropriate exception to throw? |
| throw new DeviceNotAvailableException("Failed to start emulator process", e); |
| } |
| |
| device.waitForDeviceAvailable(bootTimeout); |
| } |
| |
| /** |
| * Check if emulator process has died |
| * |
| * @param p the {@link Process} to check |
| * @throws DeviceNotAvailableException if process has died |
| */ |
| private void checkProcessDied(Process p) throws DeviceNotAvailableException { |
| try { |
| int exitValue = p.exitValue(); |
| // should have thrown IllegalThreadStateException |
| CLog.e("Emulator process has died with exit value %d. stdout: '%s', stderr: '%s'", |
| exitValue, StreamUtil.getStringFromStream(p.getInputStream()), |
| StreamUtil.getStringFromStream(p.getErrorStream())); |
| } catch (IllegalThreadStateException e) { |
| // expected if process is still alive |
| return; |
| } catch (IOException e) { |
| // fall through |
| } |
| throw new DeviceNotAvailableException("Emulator process has died unexpectedly"); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void killEmulator(ITestDevice device) throws DeviceNotAvailableException { |
| EmulatorConsole console = EmulatorConsole.getConsole(device.getIDevice()); |
| if (console != null) { |
| console.kill(); |
| // lets ensure process is killed too - fall through |
| } else { |
| CLog.w("Could not get emulator console for %s", device.getSerialNumber()); |
| } |
| // lets try killing the process |
| Process emulatorProcess = ((IManagedTestDevice)device).getEmulatorProcess(); |
| if (emulatorProcess != null) { |
| emulatorProcess.destroy(); |
| } |
| if (!device.waitForDeviceNotAvailable(20*1000)) { |
| throw new DeviceNotAvailableException(String.format("Failed to kill emulator %s", |
| device.getSerialNumber())); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ITestDevice connectToTcpDevice(String ipAndPort) { |
| if (mAllocatedDeviceMap.containsKey(ipAndPort)) { |
| Log.w(LOG_TAG, String.format("Device with tcp serial %s is already allocated", |
| ipAndPort)); |
| return null; |
| } |
| // create a mapping between this device, and its soon-to-be associated tcp serial number |
| // this is done so a) the device can get state updates and b) this device isn't allocated |
| // to another caller when it goes online with new serial |
| ITestDevice tcpDevice = createAllocatedDevice(new StubDevice(ipAndPort)); |
| if (doAdbConnect(ipAndPort)) { |
| try { |
| tcpDevice.setRecovery(new WaitDeviceRecovery()); |
| tcpDevice.waitForDeviceOnline(); |
| return tcpDevice; |
| } catch (DeviceNotAvailableException e) { |
| Log.w(LOG_TAG, String.format("Device with tcp serial %s did not come online", |
| ipAndPort)); |
| } |
| } |
| freeDevice(tcpDevice, FreeDeviceState.IGNORE); |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public ITestDevice reconnectDeviceToTcp(ITestDevice usbDevice) |
| throws DeviceNotAvailableException { |
| Log.i(LOG_TAG, String.format("Reconnecting device %s to adb over tcpip", |
| usbDevice.getSerialNumber())); |
| ITestDevice tcpDevice = null; |
| if (usbDevice instanceof IManagedTestDevice) { |
| IManagedTestDevice managedUsbDevice = (IManagedTestDevice)usbDevice; |
| String ipAndPort = managedUsbDevice.switchToAdbTcp(); |
| if (ipAndPort != null) { |
| Log.d(LOG_TAG, String.format("Device %s was switched to adb tcp on %s", |
| usbDevice.getSerialNumber(), ipAndPort)); |
| tcpDevice = connectToTcpDevice(ipAndPort); |
| if (tcpDevice == null) { |
| // ruh roh, could not connect to device |
| // Try to re-establish connection back to usb device |
| managedUsbDevice.recoverDevice(); |
| } |
| } |
| } else { |
| Log.e(LOG_TAG, "reconnectDeviceToTcp: unrecognized device type."); |
| } |
| return tcpDevice; |
| } |
| |
| @Override |
| public boolean disconnectFromTcpDevice(ITestDevice tcpDevice) { |
| Log.i(LOG_TAG, String.format("Disconnecting and freeing tcp device %s", |
| tcpDevice.getSerialNumber())); |
| boolean result = false; |
| try { |
| result = tcpDevice.switchToAdbUsb(); |
| } catch (DeviceNotAvailableException e) { |
| Log.w(LOG_TAG, String.format("Failed to switch device %s to usb mode: %s", |
| tcpDevice.getSerialNumber(), e.getMessage())); |
| } |
| freeDevice(tcpDevice, FreeDeviceState.IGNORE); |
| return result; |
| } |
| |
| private boolean doAdbConnect(String ipAndPort) { |
| final String resultSuccess = String.format("connected to %s", ipAndPort); |
| for (int i = 1; i <= 3; i++) { |
| String adbConnectResult = executeGlobalAdbCommand("connect", ipAndPort); |
| // runcommand "adb connect ipAndPort" |
| if (adbConnectResult.startsWith(resultSuccess)) { |
| return true; |
| } |
| Log.w(LOG_TAG, String.format( |
| "Failed to connect to device on %s, attempt %d of 3. Response: %s.", |
| ipAndPort, i, adbConnectResult)); |
| getRunUtil().sleep(5*1000); |
| } |
| return false; |
| } |
| |
| /** |
| * Execute a adb command not targeted to a particular device eg. 'adb connect' |
| * |
| * @param cmdArgs |
| * @return |
| */ |
| public String executeGlobalAdbCommand(String... cmdArgs) { |
| String[] fullCmd = ArrayUtil.buildArray(new String[] {"adb"}, cmdArgs); |
| CommandResult result = getRunUtil().runTimedCmd(FASTBOOT_CMD_TIMEOUT, fullCmd); |
| if (CommandStatus.SUCCESS.equals(result.getStatus())) { |
| return result.getStdout(); |
| } |
| Log.w(LOG_TAG, String.format("adb %s failed", cmdArgs[0])); |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized void terminate() { |
| checkInit(); |
| if (!mIsTerminated ) { |
| mIsTerminated = true; |
| mAdbBridge.removeDeviceChangeListener(mManagedDeviceListener); |
| mAdbBridge.terminate(); |
| if (mFastbootMonitor != null) { |
| mFastbootMonitor.terminate(); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public synchronized void terminateHard() { |
| checkInit(); |
| if (!mIsTerminated ) { |
| for (IManagedTestDevice device : mAllocatedDeviceMap.values()) { |
| device.setRecovery(new AbortRecovery()); |
| } |
| mAdbBridge.disconnectBridge(); |
| terminate(); |
| } |
| } |
| |
| private static class AbortRecovery implements IDeviceRecovery { |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void recoverDevice(IDeviceStateMonitor monitor, boolean recoverUntilOnline) |
| throws DeviceNotAvailableException { |
| throw new DeviceNotAvailableException("aborted test session"); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void recoverDeviceBootloader(IDeviceStateMonitor monitor) |
| throws DeviceNotAvailableException { |
| throw new DeviceNotAvailableException("aborted test session"); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void recoverDeviceRecovery(IDeviceStateMonitor monitor) |
| throws DeviceNotAvailableException { |
| throw new DeviceNotAvailableException("aborted test session"); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized Collection<String> getAllocatedDevices() { |
| checkInit(); |
| Collection<String> allocatedDeviceSerials = new ArrayList<String>( |
| mAllocatedDeviceMap.size()); |
| allocatedDeviceSerials.addAll(mAllocatedDeviceMap.keySet()); |
| return allocatedDeviceSerials; |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized Collection<String> getAvailableDevices() { |
| checkInit(); |
| Collection<String> availableDeviceSerials = new ArrayList<String>( |
| mAvailableDeviceQueue.size()); |
| synchronized (mAvailableDeviceQueue) { |
| for (IDevice device : mAvailableDeviceQueue) { |
| // don't add placeholder devices to available devices display |
| if (!(device instanceof StubDevice)) { |
| availableDeviceSerials.add(device.getSerialNumber()); |
| } |
| } |
| } |
| return availableDeviceSerials; |
| |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public synchronized Collection<String> getUnavailableDevices() { |
| checkInit(); |
| IDevice[] visibleDevices = mAdbBridge.getDevices(); |
| Collection<String> unavailableSerials = new ArrayList<String>( |
| visibleDevices.length); |
| Collection<String> availSerials = getAvailableDevices(); |
| Collection<String> allocatedSerials = getAllocatedDevices(); |
| for (IDevice device : visibleDevices) { |
| if (!availSerials.contains(device.getSerialNumber()) && |
| !allocatedSerials.contains(device.getSerialNumber())) { |
| unavailableSerials.add(device.getSerialNumber()); |
| } |
| } |
| return unavailableSerials; |
| } |
| |
| /** |
| * A class to listen for and act on device presence updates from ddmlib |
| */ |
| private class ManagedDeviceListener implements IDeviceChangeListener { |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void deviceChanged(IDevice device, int changeMask) { |
| IManagedTestDevice testDevice = mAllocatedDeviceMap.get(device.getSerialNumber()); |
| if ((changeMask & IDevice.CHANGE_STATE) != 0) { |
| if (testDevice != null) { |
| TestDeviceState newState = TestDeviceState.getStateByDdms(device.getState()); |
| testDevice.setDeviceState(newState); |
| } else if (mCheckDeviceMap.containsKey(device.getSerialNumber())) { |
| IDeviceStateMonitor monitor = mCheckDeviceMap.get(device.getSerialNumber()); |
| monitor.setState(TestDeviceState.getStateByDdms(device.getState())); |
| } else if (!mAvailableDeviceQueue.contains(device) && |
| device.getState() == IDevice.DeviceState.ONLINE) { |
| checkAndAddAvailableDevice(device); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void deviceConnected(IDevice device) { |
| Log.d(LOG_TAG, String.format("Detected device connect %s, id %d", |
| device.getSerialNumber(), device.hashCode())); |
| IManagedTestDevice testDevice = mAllocatedDeviceMap.get(device.getSerialNumber()); |
| if (testDevice == null) { |
| if (isValidDeviceSerial(device.getSerialNumber()) && |
| device.getState() == IDevice.DeviceState.ONLINE) { |
| checkAndAddAvailableDevice(device); |
| } else if (mCheckDeviceMap.containsKey(device.getSerialNumber())) { |
| IDeviceStateMonitor monitor = mCheckDeviceMap.get(device.getSerialNumber()); |
| monitor.setState(TestDeviceState.getStateByDdms(device.getState())); |
| } |
| } else { |
| // this device is known already. However DDMS will allocate a new IDevice, so need |
| // to update the TestDevice record with the new device |
| Log.d(LOG_TAG, String.format("Updating IDevice for device %s", |
| device.getSerialNumber())); |
| testDevice.setIDevice(device); |
| TestDeviceState newState = TestDeviceState.getStateByDdms(device.getState()); |
| testDevice.setDeviceState(newState); |
| } |
| } |
| |
| private boolean isValidDeviceSerial(String serial) { |
| return serial.length() > 1 && !serial.contains("?"); |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void deviceDisconnected(IDevice disconnectedDevice) { |
| if (mAvailableDeviceQueue.remove(disconnectedDevice)) { |
| Log.i(LOG_TAG, String.format("Removed disconnected device %s from available queue", |
| disconnectedDevice.getSerialNumber())); |
| } |
| IManagedTestDevice testDevice = mAllocatedDeviceMap.get( |
| disconnectedDevice.getSerialNumber()); |
| if (testDevice != null) { |
| testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE); |
| } else if (mCheckDeviceMap.containsKey(disconnectedDevice.getSerialNumber())) { |
| IDeviceStateMonitor monitor = mCheckDeviceMap.get( |
| disconnectedDevice.getSerialNumber()); |
| monitor.setState(TestDeviceState.NOT_AVAILABLE); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void addFastbootListener(IFastbootListener listener) { |
| checkInit(); |
| if (mFastbootEnabled) { |
| mFastbootListeners.add(listener); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void removeFastbootListener(IFastbootListener listener) { |
| checkInit(); |
| if (mFastbootEnabled) { |
| mFastbootListeners.remove(listener); |
| } |
| } |
| |
| private class FastbootMonitor extends Thread { |
| |
| private boolean mQuit = false; |
| |
| FastbootMonitor() { |
| super("FastbootMonitor"); |
| } |
| |
| public void terminate() { |
| mQuit = true; |
| interrupt(); |
| } |
| |
| @Override |
| public void run() { |
| while (!mQuit) { |
| // only poll fastboot devices if there are listeners, as polling it |
| // indiscriminately can cause fastboot commands to hang |
| if (!mFastbootListeners.isEmpty()) { |
| Set<String> serials = getDevicesOnFastboot(); |
| if (serials != null) { |
| for (String serial: serials) { |
| IManagedTestDevice testDevice = mAllocatedDeviceMap.get(serial); |
| if (testDevice != null && |
| !testDevice.getDeviceState().equals(TestDeviceState.FASTBOOT)) { |
| testDevice.setDeviceState(TestDeviceState.FASTBOOT); |
| } |
| } |
| // now update devices that are no longer on fastboot |
| synchronized (mAllocatedDeviceMap) { |
| for (IManagedTestDevice testDevice : mAllocatedDeviceMap.values()) { |
| if (!serials.contains(testDevice.getSerialNumber()) |
| && testDevice.getDeviceState().equals( |
| TestDeviceState.FASTBOOT)) { |
| testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE); |
| } |
| } |
| } |
| // create a copy of listeners for notification to prevent deadlocks |
| Collection<IFastbootListener> listenersCopy = new ArrayList<IFastbootListener>( |
| mFastbootListeners.size()); |
| listenersCopy.addAll(mFastbootListeners); |
| for (IFastbootListener listener : listenersCopy) { |
| listener.stateUpdated(); |
| } |
| } |
| } |
| getRunUtil().sleep(FASTBOOT_POLL_WAIT_TIME); |
| } |
| } |
| } |
| |
| private Set<String> getDevicesOnFastboot() { |
| CommandResult fastbootResult = getRunUtil().runTimedCmd(FASTBOOT_CMD_TIMEOUT, |
| "fastboot", "devices"); |
| if (fastbootResult.getStatus().equals(CommandStatus.SUCCESS)) { |
| Log.v(LOG_TAG, String.format("fastboot devices returned %s", |
| fastbootResult.getStdout())); |
| return parseDevicesOnFastboot(fastbootResult.getStdout()); |
| } else { |
| CLog.w("'fastboot devices' failed. Result: %s, stderr: %s", fastbootResult.getStatus(), |
| fastbootResult.getStderr()); |
| } |
| return null; |
| } |
| |
| static Set<String> parseDevicesOnFastboot(String fastbootOutput) { |
| Set<String> serials = new HashSet<String>(); |
| Pattern fastbootPattern = Pattern.compile("([\\w\\d]+)\\s+fastboot\\s*"); |
| Matcher fastbootMatcher = fastbootPattern.matcher(fastbootOutput); |
| while (fastbootMatcher.find()) { |
| serials.add(fastbootMatcher.group(1)); |
| } |
| return serials; |
| } |
| } |