blob: 4aa0daaa425dc65db88e52ef1cdb970412b8ad89 [file] [log] [blame]
/*
* Copyright (C) 2007 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.ddmlib;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.annotations.VisibleForTesting;
import com.android.ddmlib.AdbHelper.AdbResponse;
import com.android.ddmlib.ClientData.DebuggerStatus;
import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
import com.android.ddmlib.IDevice.DeviceState;
import com.android.ddmlib.utils.DebuggerPorts;
import com.android.utils.Pair;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Queues;
import com.google.common.util.concurrent.Uninterruptibles;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* The {@link DeviceMonitor} monitors devices attached to adb.
*
* On one thread, it runs the {@link com.android.ddmlib.DeviceMonitor.DeviceListMonitorTask}.
* This establishes a socket connection to the adb host, and issues a
* {@link #ADB_TRACK_DEVICES_COMMAND}. It then monitors that socket for all changes about device
* connection and device state.
*
* For each device that is detected to be online, it then opens a new socket connection to adb,
* and issues a "track-jdwp" command to that device. On this connection, it monitors active
* clients on the device. Note: a single thread monitors jdwp connections from all devices.
* The different socket connections to adb (one per device) are multiplexed over a single selector.
*/
final class DeviceMonitor {
private static final String ADB_TRACK_DEVICES_COMMAND = "host:track-devices";
private static final String ADB_TRACK_JDWP_COMMAND = "track-jdwp";
private final byte[] mLengthBuffer2 = new byte[4];
private volatile boolean mQuit = false;
private final AndroidDebugBridge mServer;
private DeviceListMonitorTask mDeviceListMonitorTask;
private Selector mSelector;
private final List<Device> mDevices = Lists.newCopyOnWriteArrayList();
private final DebuggerPorts mDebuggerPorts =
new DebuggerPorts(DdmPreferences.getDebugPortBase());
private final Map<Client, Integer> mClientsToReopen = new HashMap<Client, Integer>();
private final BlockingQueue<Pair<SocketChannel,Device>> mChannelsToRegister =
Queues.newLinkedBlockingQueue();
/**
* Creates a new {@link DeviceMonitor} object and links it to the running
* {@link AndroidDebugBridge} object.
* @param server the running {@link AndroidDebugBridge}.
*/
DeviceMonitor(@NonNull AndroidDebugBridge server) {
mServer = server;
}
/**
* Starts the monitoring.
*/
void start() {
mDeviceListMonitorTask = new DeviceListMonitorTask(mServer, new DeviceListUpdateListener());
new Thread(mDeviceListMonitorTask, "Device List Monitor").start(); //$NON-NLS-1$
}
/**
* Stops the monitoring.
*/
void stop() {
mQuit = true;
if (mDeviceListMonitorTask != null) {
mDeviceListMonitorTask.stop();
}
// wake up the secondary loop by closing the selector.
if (mSelector != null) {
mSelector.wakeup();
}
}
/**
* Returns whether the monitor is currently connected to the debug bridge server.
*/
boolean isMonitoring() {
return mDeviceListMonitorTask != null && mDeviceListMonitorTask.isMonitoring();
}
int getConnectionAttemptCount() {
return mDeviceListMonitorTask == null ? 0
: mDeviceListMonitorTask.getConnectionAttemptCount();
}
int getRestartAttemptCount() {
return mDeviceListMonitorTask == null ? 0 : mDeviceListMonitorTask.getRestartAttemptCount();
}
boolean hasInitialDeviceList() {
return mDeviceListMonitorTask != null && mDeviceListMonitorTask.hasInitialDeviceList();
}
/**
* Returns the devices.
*/
@NonNull Device[] getDevices() {
// Since this is a copy of write array list, we don't want to do a compound operation
// (toArray with an appropriate size) without locking, so we just let the container provide
// an appropriately sized array
//noinspection ToArrayCallWithZeroLengthArrayArgument
return mDevices.toArray(new Device[0]);
}
@NonNull
AndroidDebugBridge getServer() {
return mServer;
}
void addClientToDropAndReopen(Client client, int port) {
synchronized (mClientsToReopen) {
Log.d("DeviceMonitor",
"Adding " + client + " to list of client to reopen (" + port + ").");
if (mClientsToReopen.get(client) == null) {
mClientsToReopen.put(client, port);
}
}
mSelector.wakeup();
}
/**
* Attempts to connect to the debug bridge server.
* @return a connect socket if success, null otherwise
*/
@Nullable
private static SocketChannel openAdbConnection() {
try {
SocketChannel adbChannel = SocketChannel.open(AndroidDebugBridge.getSocketAddress());
adbChannel.socket().setTcpNoDelay(true);
return adbChannel;
} catch (IOException e) {
return null;
}
}
/**
* Updates the device list with the new items received from the monitoring service.
*/
private void updateDevices(@NonNull List<Device> newList) {
DeviceListComparisonResult result = DeviceListComparisonResult.compare(mDevices, newList);
for (IDevice device : result.removed) {
removeDevice((Device) device);
mServer.deviceDisconnected(device);
}
List<Device> newlyOnline = Lists.newArrayListWithExpectedSize(mDevices.size());
for (Map.Entry<IDevice, DeviceState> entry : result.updated.entrySet()) {
Device device = (Device) entry.getKey();
device.setState(entry.getValue());
device.update(Device.CHANGE_STATE);
if (device.isOnline()) {
newlyOnline.add(device);
}
}
for (IDevice device : result.added) {
mDevices.add((Device) device);
mServer.deviceConnected(device);
if (device.isOnline()) {
newlyOnline.add((Device) device);
}
}
if (AndroidDebugBridge.getClientSupport()) {
for (Device device : newlyOnline) {
if (!startMonitoringDevice(device)) {
Log.e("DeviceMonitor", "Failed to start monitoring "
+ device.getSerialNumber());
}
}
}
for (Device device : newlyOnline) {
queryAvdName(device);
}
}
private void removeDevice(@NonNull Device device) {
device.clearClientList();
mDevices.remove(device);
SocketChannel channel = device.getClientMonitoringSocket();
if (channel != null) {
try {
channel.close();
} catch (IOException e) {
// doesn't really matter if the close fails.
}
}
}
private static void queryAvdName(@NonNull Device device) {
if (!device.isEmulator()) {
return;
}
EmulatorConsole console = EmulatorConsole.getConsole(device);
if (console != null) {
device.setAvdName(console.getAvdName());
console.close();
}
}
/**
* Starts a monitoring service for a device.
* @param device the device to monitor.
* @return true if success.
*/
private boolean startMonitoringDevice(@NonNull Device device) {
SocketChannel socketChannel = openAdbConnection();
if (socketChannel != null) {
try {
boolean result = sendDeviceMonitoringRequest(socketChannel, device);
if (result) {
if (mSelector == null) {
startDeviceMonitorThread();
}
device.setClientMonitoringSocket(socketChannel);
socketChannel.configureBlocking(false);
try {
mChannelsToRegister.put(Pair.of(socketChannel, device));
} catch (InterruptedException e) {
// the queue is unbounded, and isn't going to block
}
mSelector.wakeup();
return true;
}
} catch (TimeoutException e) {
try {
// attempt to close the socket if needed.
socketChannel.close();
} catch (IOException e1) {
// we can ignore that one. It may already have been closed.
}
Log.d("DeviceMonitor",
"Connection Failure when starting to monitor device '"
+ device + "' : timeout");
} catch (AdbCommandRejectedException e) {
try {
// attempt to close the socket if needed.
socketChannel.close();
} catch (IOException e1) {
// we can ignore that one. It may already have been closed.
}
Log.d("DeviceMonitor",
"Adb refused to start monitoring device '"
+ device + "' : " + e.getMessage());
} catch (IOException e) {
try {
// attempt to close the socket if needed.
socketChannel.close();
} catch (IOException e1) {
// we can ignore that one. It may already have been closed.
}
Log.d("DeviceMonitor",
"Connection Failure when starting to monitor device '"
+ device + "' : " + e.getMessage());
}
}
return false;
}
private void startDeviceMonitorThread() throws IOException {
mSelector = Selector.open();
new Thread("Device Client Monitor") { //$NON-NLS-1$
@Override
public void run() {
deviceClientMonitorLoop();
}
}.start();
}
private void deviceClientMonitorLoop() {
do {
try {
int count = mSelector.select();
if (mQuit) {
return;
}
synchronized (mClientsToReopen) {
if (!mClientsToReopen.isEmpty()) {
Set<Client> clients = mClientsToReopen.keySet();
MonitorThread monitorThread = MonitorThread.getInstance();
for (Client client : clients) {
Device device = client.getDeviceImpl();
int pid = client.getClientData().getPid();
monitorThread.dropClient(client, false /* notify */);
// This is kinda bad, but if we don't wait a bit, the client
// will never answer the second handshake!
Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
int port = mClientsToReopen.get(client);
if (port == IDebugPortProvider.NO_STATIC_PORT) {
port = getNextDebuggerPort();
}
Log.d("DeviceMonitor", "Reopening " + client);
openClient(device, pid, port, monitorThread);
device.update(Device.CHANGE_CLIENT_LIST);
}
mClientsToReopen.clear();
}
}
// register any new channels
while (!mChannelsToRegister.isEmpty()) {
try {
Pair<SocketChannel, Device> data = mChannelsToRegister.take();
data.getFirst().register(mSelector, SelectionKey.OP_READ, data.getSecond());
} catch (InterruptedException e) {
// doesn't block: this thread is the only reader and it reads only when
// there is data
}
}
if (count == 0) {
continue;
}
Set<SelectionKey> keys = mSelector.selectedKeys();
Iterator<SelectionKey> iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = iter.next();
iter.remove();
if (key.isValid() && key.isReadable()) {
Object attachment = key.attachment();
if (attachment instanceof Device) {
Device device = (Device)attachment;
SocketChannel socket = device.getClientMonitoringSocket();
if (socket != null) {
try {
int length = readLength(socket, mLengthBuffer2);
processIncomingJdwpData(device, socket, length);
} catch (IOException ioe) {
Log.d("DeviceMonitor",
"Error reading jdwp list: " + ioe.getMessage());
socket.close();
// restart the monitoring of that device
if (mDevices.contains(device)) {
Log.d("DeviceMonitor",
"Restarting monitoring service for " + device);
startMonitoringDevice(device);
}
}
}
}
}
}
} catch (IOException e) {
Log.e("DeviceMonitor", "Connection error while monitoring clients.");
}
} while (!mQuit);
}
private static boolean sendDeviceMonitoringRequest(@NonNull SocketChannel socket,
@NonNull Device device)
throws TimeoutException, AdbCommandRejectedException, IOException {
try {
AdbHelper.setDevice(socket, device);
AdbHelper.write(socket, AdbHelper.formAdbRequest(ADB_TRACK_JDWP_COMMAND));
AdbResponse resp = AdbHelper.readAdbResponse(socket, false);
if (!resp.okay) {
// request was refused by adb!
Log.e("DeviceMonitor", "adb refused request: " + resp.message);
}
return resp.okay;
} catch (TimeoutException e) {
Log.e("DeviceMonitor", "Sending jdwp tracking request timed out!");
throw e;
} catch (IOException e) {
Log.e("DeviceMonitor", "Sending jdwp tracking request failed!");
throw e;
}
}
private void processIncomingJdwpData(@NonNull Device device,
@NonNull SocketChannel monitorSocket, int length) throws IOException {
// This methods reads @length bytes from the @monitorSocket channel.
// These bytes correspond to the pids of the current set of processes on the device.
// It takes this set of pids and compares them with the existing set of clients
// for the device. Clients that correspond to pids that are not alive anymore are
// dropped, and new clients are created for pids that don't have a corresponding Client.
if (length >= 0) {
// array for the current pids.
Set<Integer> newPids = new HashSet<Integer>();
// get the string data if there are any
if (length > 0) {
byte[] buffer = new byte[length];
String result = read(monitorSocket, buffer);
// split each line in its own list and create an array of integer pid
String[] pids = result == null ? new String[0] : result.split("\n"); //$NON-NLS-1$
for (String pid : pids) {
try {
newPids.add(Integer.valueOf(pid));
} catch (NumberFormatException nfe) {
// looks like this pid is not really a number. Lets ignore it.
continue;
}
}
}
MonitorThread monitorThread = MonitorThread.getInstance();
List<Client> clients = device.getClientList();
Map<Integer, Client> existingClients = new HashMap<Integer, Client>();
synchronized (clients) {
for (Client c : clients) {
existingClients.put(c.getClientData().getPid(), c);
}
}
Set<Client> clientsToRemove = new HashSet<Client>();
for (Integer pid : existingClients.keySet()) {
if (!newPids.contains(pid)) {
clientsToRemove.add(existingClients.get(pid));
}
}
Set<Integer> pidsToAdd = new HashSet<Integer>(newPids);
pidsToAdd.removeAll(existingClients.keySet());
monitorThread.dropClients(clientsToRemove, false);
// at this point whatever pid is left in the list needs to be converted into Clients.
for (int newPid : pidsToAdd) {
openClient(device, newPid, getNextDebuggerPort(), monitorThread);
}
if (!pidsToAdd.isEmpty() || !clientsToRemove.isEmpty()) {
mServer.deviceChanged(device, Device.CHANGE_CLIENT_LIST);
}
}
}
/** Opens and creates a new client. */
private static void openClient(@NonNull Device device, int pid, int port,
@NonNull MonitorThread monitorThread) {
SocketChannel clientSocket;
try {
clientSocket = AdbHelper.createPassThroughConnection(
AndroidDebugBridge.getSocketAddress(), device, pid);
// required for Selector
clientSocket.configureBlocking(false);
} catch (UnknownHostException uhe) {
Log.d("DeviceMonitor", "Unknown Jdwp pid: " + pid);
return;
} catch (TimeoutException e) {
Log.w("DeviceMonitor",
"Failed to connect to client '" + pid + "': timeout");
return;
} catch (AdbCommandRejectedException e) {
Log.w("DeviceMonitor",
"Adb rejected connection to client '" + pid + "': " + e.getMessage());
return;
} catch (IOException ioe) {
Log.w("DeviceMonitor",
"Failed to connect to client '" + pid + "': " + ioe.getMessage());
return ;
}
createClient(device, pid, clientSocket, port, monitorThread);
}
/** Creates a client and register it to the monitor thread */
private static void createClient(@NonNull Device device, int pid, @NonNull SocketChannel socket,
int debuggerPort, @NonNull MonitorThread monitorThread) {
/*
* Successfully connected to something. Create a Client object, add
* it to the list, and initiate the JDWP handshake.
*/
Client client = new Client(device, socket, pid);
if (client.sendHandshake()) {
try {
if (AndroidDebugBridge.getClientSupport()) {
client.listenForDebugger(debuggerPort);
}
} catch (IOException ioe) {
client.getClientData().setDebuggerConnectionStatus(DebuggerStatus.ERROR);
Log.e("ddms", "Can't bind to local " + debuggerPort + " for debugger");
// oh well
}
client.requestAllocationStatus();
} else {
Log.e("ddms", "Handshake with " + client + " failed!");
/*
* The handshake send failed. We could remove it now, but if the
* failure is "permanent" we'll just keep banging on it and
* getting the same result. Keep it in the list with its "error"
* state so we don't try to reopen it.
*/
}
if (client.isValid()) {
device.addClient(client);
monitorThread.addClient(client);
}
}
private int getNextDebuggerPort() {
return mDebuggerPorts.next();
}
void addPortToAvailableList(int port) {
mDebuggerPorts.free(port);
}
/**
* Reads the length of the next message from a socket.
* @param socket The {@link SocketChannel} to read from.
* @return the length, or 0 (zero) if no data is available from the socket.
* @throws IOException if the connection failed.
*/
private static int readLength(@NonNull SocketChannel socket, @NonNull byte[] buffer)
throws IOException {
String msg = read(socket, buffer);
if (msg != null) {
try {
return Integer.parseInt(msg, 16);
} catch (NumberFormatException nfe) {
// we'll throw an exception below.
}
}
// we receive something we can't read. It's better to reset the connection at this point.
throw new IOException("Unable to read length");
}
/**
* Fills a buffer by reading data from a socket.
* @return the content of the buffer as a string, or null if it failed to convert the buffer.
* @throws IOException if there was not enough data to fill the buffer
*/
@Nullable
private static String read(@NonNull SocketChannel socket, @NonNull byte[] buffer)
throws IOException {
ByteBuffer buf = ByteBuffer.wrap(buffer, 0, buffer.length);
while (buf.position() != buf.limit()) {
int count;
count = socket.read(buf);
if (count < 0) {
throw new IOException("EOF");
}
}
try {
return new String(buffer, 0, buf.position(), AdbHelper.DEFAULT_ENCODING);
} catch (UnsupportedEncodingException e) {
return null;
}
}
private class DeviceListUpdateListener implements DeviceListMonitorTask.UpdateListener {
@Override
public void connectionError(@NonNull Exception e) {
for (Device device : mDevices) {
removeDevice(device);
mServer.deviceDisconnected(device);
}
}
@Override
public void deviceListUpdate(@NonNull Map<String, DeviceState> devices) {
List<Device> l = Lists.newArrayListWithExpectedSize(devices.size());
for (Map.Entry<String, DeviceState> entry : devices.entrySet()) {
l.add(new Device(DeviceMonitor.this, entry.getKey(), entry.getValue()));
}
// now merge the new devices with the old ones.
updateDevices(l);
}
}
@VisibleForTesting
static class DeviceListComparisonResult {
@NonNull public final Map<IDevice,DeviceState> updated;
@NonNull public final List<IDevice> added;
@NonNull public final List<IDevice> removed;
private DeviceListComparisonResult(@NonNull Map<IDevice,DeviceState> updated,
@NonNull List<IDevice> added,
@NonNull List<IDevice> removed) {
this.updated = updated;
this.added = added;
this.removed = removed;
}
@NonNull
public static DeviceListComparisonResult compare(@NonNull List<? extends IDevice> previous,
@NonNull List<? extends IDevice> current) {
current = Lists.newArrayList(current);
final Map<IDevice,DeviceState> updated = Maps.newHashMapWithExpectedSize(current.size());
final List<IDevice> added = Lists.newArrayListWithExpectedSize(1);
final List<IDevice> removed = Lists.newArrayListWithExpectedSize(1);
for (IDevice device : previous) {
IDevice currentDevice = find(current, device);
if (currentDevice != null) {
if (currentDevice.getState() != device.getState()) {
updated.put(device, currentDevice.getState());
}
current.remove(currentDevice);
} else {
removed.add(device);
}
}
added.addAll(current);
return new DeviceListComparisonResult(updated, added, removed);
}
@Nullable
private static IDevice find(@NonNull List<? extends IDevice> devices,
@NonNull IDevice device) {
for (IDevice d : devices) {
if (d.getSerialNumber().equals(device.getSerialNumber())) {
return d;
}
}
return null;
}
}
@VisibleForTesting
static class DeviceListMonitorTask implements Runnable {
private final byte[] mLengthBuffer = new byte[4];
private final AndroidDebugBridge mBridge;
private final UpdateListener mListener;
private SocketChannel mAdbConnection = null;
private boolean mMonitoring = false;
private int mConnectionAttempt = 0;
private int mRestartAttemptCount = 0;
private boolean mInitialDeviceListDone = false;
private volatile boolean mQuit;
private interface UpdateListener {
void connectionError(@NonNull Exception e);
void deviceListUpdate(@NonNull Map<String,DeviceState> devices);
}
public DeviceListMonitorTask(@NonNull AndroidDebugBridge bridge,
@NonNull UpdateListener listener) {
mBridge = bridge;
mListener = listener;
}
@Override
public void run() {
do {
if (mAdbConnection == null) {
Log.d("DeviceMonitor", "Opening adb connection");
mAdbConnection = openAdbConnection();
if (mAdbConnection == null) {
mConnectionAttempt++;
Log.e("DeviceMonitor", "Connection attempts: " + mConnectionAttempt);
if (mConnectionAttempt > 10) {
if (!mBridge.startAdb()) {
mRestartAttemptCount++;
Log.e("DeviceMonitor",
"adb restart attempts: " + mRestartAttemptCount);
} else {
Log.i("DeviceMonitor", "adb restarted");
mRestartAttemptCount = 0;
}
}
Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
} else {
Log.d("DeviceMonitor", "Connected to adb for device monitoring");
mConnectionAttempt = 0;
}
}
try {
if (mAdbConnection != null && !mMonitoring) {
mMonitoring = sendDeviceListMonitoringRequest();
}
if (mMonitoring) {
int length = readLength(mAdbConnection, mLengthBuffer);
if (length >= 0) {
// read the incoming message
processIncomingDeviceData(length);
// flag the fact that we have build the list at least once.
mInitialDeviceListDone = true;
}
}
} catch (AsynchronousCloseException ace) {
// this happens because of a call to Quit. We do nothing, and the loop will break.
} catch (TimeoutException ioe) {
handleExceptionInMonitorLoop(ioe);
} catch (IOException ioe) {
handleExceptionInMonitorLoop(ioe);
}
} while (!mQuit);
}
private boolean sendDeviceListMonitoringRequest() throws TimeoutException, IOException {
byte[] request = AdbHelper.formAdbRequest(ADB_TRACK_DEVICES_COMMAND);
try {
AdbHelper.write(mAdbConnection, request);
AdbResponse resp = AdbHelper.readAdbResponse(mAdbConnection, false);
if (!resp.okay) {
// request was refused by adb!
Log.e("DeviceMonitor", "adb refused request: " + resp.message);
}
return resp.okay;
} catch (IOException e) {
Log.e("DeviceMonitor", "Sending Tracking request failed!");
mAdbConnection.close();
throw e;
}
}
private void handleExceptionInMonitorLoop(@NonNull Exception e) {
if (!mQuit) {
if (e instanceof TimeoutException) {
Log.e("DeviceMonitor", "Adb connection Error: timeout");
} else {
Log.e("DeviceMonitor", "Adb connection Error:" + e.getMessage());
}
mMonitoring = false;
if (mAdbConnection != null) {
try {
mAdbConnection.close();
} catch (IOException ioe) {
// we can safely ignore that one.
}
mAdbConnection = null;
mListener.connectionError(e);
}
}
}
/** Processes an incoming device message from the socket */
private void processIncomingDeviceData(int length) throws IOException {
Map<String, DeviceState> result;
if (length <= 0) {
result = Collections.emptyMap();
} else {
String response = read(mAdbConnection, new byte[length]);
result = parseDeviceListResponse(response);
}
mListener.deviceListUpdate(result);
}
@VisibleForTesting
static Map<String, DeviceState> parseDeviceListResponse(@Nullable String result) {
Map<String, DeviceState> deviceStateMap = Maps.newHashMap();
String[] devices = result == null ? new String[0] : result.split("\n"); //$NON-NLS-1$
for (String d : devices) {
String[] param = d.split("\t"); //$NON-NLS-1$
if (param.length == 2) {
// new adb uses only serial numbers to identify devices
deviceStateMap.put(param[0], DeviceState.getState(param[1]));
}
}
return deviceStateMap;
}
boolean isMonitoring() {
return mMonitoring;
}
boolean hasInitialDeviceList() {
return mInitialDeviceListDone;
}
int getConnectionAttemptCount() {
return mConnectionAttempt;
}
int getRestartAttemptCount() {
return mRestartAttemptCount;
}
public void stop() {
mQuit = true;
// wakeup the main loop thread by closing the main connection to adb.
if (mAdbConnection != null) {
try {
mAdbConnection.close();
} catch (IOException ignored) {
}
}
}
}
}