/*
* Copyright 2012-2014 eBay Software Foundation and selendroid committers.
*
* 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 io.selendroid.standalone.android.impl;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.IDevice;
import io.selendroid.standalone.android.*;
import io.selendroid.standalone.exceptions.AndroidDeviceException;
import io.selendroid.standalone.exceptions.DeviceOfflineException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
public class DefaultDeviceManager extends Thread implements IDeviceChangeListener, DeviceManager {
private static final Logger log = Logger.getLogger(DefaultDeviceManager.class.getName());
private String adbPath;
private List<HardwareDeviceListener> deviceListeners = new ArrayList<HardwareDeviceListener>();
private List<AndroidEmulatorPowerStateListener> emulatorPowerStateListener =
new ArrayList<AndroidEmulatorPowerStateListener>();
private Map<IDevice, DefaultHardwareDevice> connectedDevices =
new HashMap<IDevice, DefaultHardwareDevice>();
private Map<String, IDevice> virtualDevices = new HashMap<String, IDevice>();
private AndroidDebugBridge bridge;
private boolean shouldKeepAdbAlive;
public DefaultDeviceManager(String adbPath, boolean shouldKeepAdbAlive) {
this.adbPath = adbPath;
this.shouldKeepAdbAlive = shouldKeepAdbAlive;
}
/**
* Initializes the AndroidDebugBridge and registers the DefaultHardwareDeviceManager with the
* AndroidDebugBridge device change listener.
*/
protected void initializeAdbConnection() {
// Get a device bridge instance. Initialize, create and restart.
try {
AndroidDebugBridge.init(false);
} catch (IllegalStateException e) {
if (!shouldKeepAdbAlive) {
log.log(
Level.WARNING,
"AndroidDebugBridge may have been already initialized at this point. It is OK to proceed.",
e);
}
}
bridge = AndroidDebugBridge.getBridge();
if (bridge == null) {
bridge = AndroidDebugBridge.createBridge(adbPath, false);
}
IDevice[] devices = bridge.getDevices();
AndroidDebugBridge.addDeviceChangeListener(this);
// Add the existing devices to the list of devices we are tracking.
if (devices.length > 0) {
for (int i = 0; i < devices.length; i++) {
deviceConnected(devices[i]);
log.info("my devices: " + devices[i].getAvdName());
}
} else {
long timeout = System.currentTimeMillis() + 2000;
while ((devices = bridge.getDevices()).length == 0 && System.currentTimeMillis() < timeout) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
if (devices.length > 0) {
for (int i = 0; i < devices.length; i++) {
deviceConnected(devices[i]);
log.info("my devices: " + devices[i].getAvdName());
}
}
}
}
/**
* Shutdown the AndroidDebugBridge and clean up all connected devices.
*/
public void shutdown() {
log.info("Notifying device listener about shutdown");
for (HardwareDeviceListener listener : deviceListeners) {
for (AndroidDevice device : connectedDevices.values()) {
listener.onDeviceDisconnected(connectedDevices.get(device));
}
}
log.info("Removing Device Manager listener from ADB");
AndroidDebugBridge.removeDeviceChangeListener(this);
if (!shouldKeepAdbAlive) {
AndroidDebugBridge.disconnectBridge();
}
AndroidDebugBridge.terminate();
log.info("stopping Device Manager");
// TODO add thread interrupt and join handling
}
@Override
public void deviceChanged(IDevice device, int changeMask) {
// Only fire events if the phone properties are available
if (IDevice.CHANGE_BUILD_INFO == changeMask && !device.isEmulator()) {
for (HardwareDeviceListener listener : deviceListeners) {
listener.onDeviceChanged(connectedDevices.get(device));
}
}
}
@Override
public void deviceConnected(IDevice device) {
if (device == null) {
return;
}
if (device.isEmulator()) {
String serial = device.getSerialNumber();
Integer port = Integer.parseInt(serial.replace("emulator-", ""));
String avdName = null;
TelnetClient client = null;
try {
client = new TelnetClient(port);
avdName = client.sendCommand("avd name");
} catch (AndroidDeviceException e) {
String logMessage = "Could not get avdName for device " + serial;
log.log(Level.WARNING, logMessage, e);
} finally {
if (client != null) {
client.close();
}
}
virtualDevices.put(avdName, device);
for (AndroidEmulatorPowerStateListener listener : emulatorPowerStateListener) {
listener.onDeviceStarted(avdName, device.getSerialNumber());
}
} else {
try {
connectedDevices.put(device, new DefaultHardwareDevice(device));
for (HardwareDeviceListener listener : deviceListeners) {
listener.onDeviceConnected(connectedDevices.get(device));
}
} catch (DeviceOfflineException doe) {
// skip devices that are currently "offline"
log.warning("Skipping adding 'offline' hardware device");
}
}
}
@Override
public void deviceDisconnected(IDevice device) {
if (device == null) {
return;
}
if (device.isEmulator()) {
virtualDevices.remove(device.getAvdName());
for (AndroidEmulatorPowerStateListener listener : emulatorPowerStateListener) {
listener.onDeviceStopped(device.getSerialNumber());
}
} else if (connectedDevices.containsKey(device)) {
for (HardwareDeviceListener listener : deviceListeners) {
listener.onDeviceDisconnected(connectedDevices.get(device));
}
connectedDevices.remove(device);
}
}
@Override
public void registerListener(HardwareDeviceListener deviceListener) {
this.deviceListeners.add(deviceListener);
}
@Override
public void unregisterListener(HardwareDeviceListener deviceListener) {
if (deviceListeners.contains(deviceListener)) {
deviceListeners.remove(deviceListener);
}
}
@Override
public void registerListener(AndroidEmulatorPowerStateListener deviceListener) {
this.emulatorPowerStateListener.add(deviceListener);
}
@Override
public void unregisterListener(AndroidEmulatorPowerStateListener deviceListener) {
if (emulatorPowerStateListener.contains(deviceListener)) {
emulatorPowerStateListener.remove(deviceListener);
}
}
@Override
public void initialize(HardwareDeviceListener defaultHardwareListener,
AndroidEmulatorPowerStateListener emulatorListener) {
registerListener(defaultHardwareListener);
registerListener(emulatorListener);
initializeAdbConnection();
}
public IDevice getVirtualDevice(String avdName) {
return virtualDevices.get(avdName);
}
}