| /* |
| * 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.ddmlib.AdbHelper.AdbResponse; |
| import com.android.ddmlib.ClientData.DebuggerStatus; |
| import com.android.ddmlib.DebugPortManager.IDebugPortProvider; |
| import com.android.ddmlib.IDevice.DeviceState; |
| |
| 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.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * A Device monitor. This connects to the Android Debug Bridge and get device and |
| * debuggable process information from it. |
| */ |
| final class DeviceMonitor { |
| private byte[] mLengthBuffer = new byte[4]; |
| private byte[] mLengthBuffer2 = new byte[4]; |
| |
| private boolean mQuit = false; |
| |
| private AndroidDebugBridge mServer; |
| |
| private SocketChannel mMainAdbConnection = null; |
| private boolean mMonitoring = false; |
| private int mConnectionAttempt = 0; |
| private int mRestartAttemptCount = 0; |
| private boolean mInitialDeviceListDone = false; |
| |
| private Selector mSelector; |
| |
| private final ArrayList<Device> mDevices = new ArrayList<Device>(); |
| |
| private final ArrayList<Integer> mDebuggerPorts = new ArrayList<Integer>(); |
| |
| private final HashMap<Client, Integer> mClientsToReopen = new HashMap<Client, Integer>(); |
| |
| /** |
| * Creates a new {@link DeviceMonitor} object and links it to the running |
| * {@link AndroidDebugBridge} object. |
| * @param server the running {@link AndroidDebugBridge}. |
| */ |
| DeviceMonitor(AndroidDebugBridge server) { |
| mServer = server; |
| |
| mDebuggerPorts.add(DdmPreferences.getDebugPortBase()); |
| } |
| |
| /** |
| * Starts the monitoring. |
| */ |
| void start() { |
| new Thread("Device List Monitor") { //$NON-NLS-1$ |
| @Override |
| public void run() { |
| deviceMonitorLoop(); |
| } |
| }.start(); |
| } |
| |
| /** |
| * Stops the monitoring. |
| */ |
| void stop() { |
| mQuit = true; |
| |
| // wakeup the main loop thread by closing the main connection to adb. |
| try { |
| if (mMainAdbConnection != null) { |
| mMainAdbConnection.close(); |
| } |
| } catch (IOException e1) { |
| } |
| |
| // wake up the secondary loop by closing the selector. |
| if (mSelector != null) { |
| mSelector.wakeup(); |
| } |
| } |
| |
| |
| |
| /** |
| * Returns if the monitor is currently connected to the debug bridge server. |
| * @return |
| */ |
| boolean isMonitoring() { |
| return mMonitoring; |
| } |
| |
| int getConnectionAttemptCount() { |
| return mConnectionAttempt; |
| } |
| |
| int getRestartAttemptCount() { |
| return mRestartAttemptCount; |
| } |
| |
| /** |
| * Returns the devices. |
| */ |
| Device[] getDevices() { |
| synchronized (mDevices) { |
| return mDevices.toArray(new Device[mDevices.size()]); |
| } |
| } |
| |
| boolean hasInitialDeviceList() { |
| return mInitialDeviceListDone; |
| } |
| |
| 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(); |
| } |
| |
| /** |
| * Monitors the devices. This connects to the Debug Bridge |
| */ |
| private void deviceMonitorLoop() { |
| do { |
| try { |
| if (mMainAdbConnection == null) { |
| Log.d("DeviceMonitor", "Opening adb connection"); |
| mMainAdbConnection = openAdbConnection(); |
| if (mMainAdbConnection == null) { |
| mConnectionAttempt++; |
| Log.e("DeviceMonitor", "Connection attempts: " + mConnectionAttempt); |
| if (mConnectionAttempt > 10) { |
| if (!mServer.startAdb()) { |
| mRestartAttemptCount++; |
| Log.e("DeviceMonitor", |
| "adb restart attempts: " + mRestartAttemptCount); |
| } else { |
| mRestartAttemptCount = 0; |
| } |
| } |
| waitABit(); |
| } else { |
| Log.d("DeviceMonitor", "Connected to adb for device monitoring"); |
| mConnectionAttempt = 0; |
| } |
| } |
| |
| if (mMainAdbConnection != null && !mMonitoring) { |
| mMonitoring = sendDeviceListMonitoringRequest(); |
| } |
| |
| if (mMonitoring) { |
| // read the length of the incoming message |
| int length = readLength(mMainAdbConnection, 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) { |
| handleExpectionInMonitorLoop(ioe); |
| } catch (IOException ioe) { |
| handleExpectionInMonitorLoop(ioe); |
| } |
| } while (!mQuit); |
| } |
| |
| private void handleExpectionInMonitorLoop(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 (mMainAdbConnection != null) { |
| try { |
| mMainAdbConnection.close(); |
| } catch (IOException ioe) { |
| // we can safely ignore that one. |
| } |
| mMainAdbConnection = null; |
| |
| // remove all devices from list |
| // because we are going to call mServer.deviceDisconnected which will acquire this |
| // lock we lock it first, so that the AndroidDebugBridge lock is always locked |
| // first. |
| synchronized (AndroidDebugBridge.getLock()) { |
| synchronized (mDevices) { |
| for (int n = mDevices.size() - 1; n >= 0; n--) { |
| Device device = mDevices.get(0); |
| removeDevice(device); |
| mServer.deviceDisconnected(device); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sleeps for a little bit. |
| */ |
| private void waitABit() { |
| try { |
| Thread.sleep(1000); |
| } catch (InterruptedException e1) { |
| } |
| } |
| |
| /** |
| * Attempts to connect to the debug bridge server. |
| * @return a connect socket if success, null otherwise |
| */ |
| private SocketChannel openAdbConnection() { |
| Log.d("DeviceMonitor", "Connecting to adb for Device List Monitoring..."); |
| |
| SocketChannel adbChannel = null; |
| try { |
| adbChannel = SocketChannel.open(AndroidDebugBridge.getSocketAddress()); |
| adbChannel.socket().setTcpNoDelay(true); |
| } catch (IOException e) { |
| } |
| |
| return adbChannel; |
| } |
| |
| /** |
| * |
| * @return |
| * @throws IOException |
| */ |
| private boolean sendDeviceListMonitoringRequest() throws TimeoutException, IOException { |
| byte[] request = AdbHelper.formAdbRequest("host:track-devices"); //$NON-NLS-1$ |
| |
| try { |
| AdbHelper.write(mMainAdbConnection, request); |
| |
| AdbResponse resp = AdbHelper.readAdbResponse(mMainAdbConnection, |
| false /* readDiagString */); |
| |
| 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!"); |
| mMainAdbConnection.close(); |
| throw e; |
| } |
| } |
| |
| /** Processes an incoming device message from the socket */ |
| private void processIncomingDeviceData(int length) throws IOException { |
| ArrayList<Device> list = new ArrayList<Device>(); |
| |
| if (length > 0) { |
| byte[] buffer = new byte[length]; |
| String result = read(mMainAdbConnection, buffer); |
| |
| String[] devices = 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 |
| Device device = new Device(this, param[0] /*serialnumber*/, |
| DeviceState.getState(param[1])); |
| |
| //add the device to the list |
| list.add(device); |
| } |
| } |
| } |
| |
| // now merge the new devices with the old ones. |
| updateDevices(list); |
| } |
| |
| /** |
| * Updates the device list with the new items received from the monitoring service. |
| */ |
| private void updateDevices(ArrayList<Device> newList) { |
| // because we are going to call mServer.deviceDisconnected which will acquire this lock |
| // we lock it first, so that the AndroidDebugBridge lock is always locked first. |
| synchronized (AndroidDebugBridge.getLock()) { |
| // array to store the devices that must be queried for information. |
| // it's important to not do it inside the synchronized loop as this could block |
| // the whole workspace (this lock is acquired during build too). |
| ArrayList<Device> devicesToQuery = new ArrayList<Device>(); |
| synchronized (mDevices) { |
| // For each device in the current list, we look for a matching the new list. |
| // * if we find it, we update the current object with whatever new information |
| // there is |
| // (mostly state change, if the device becomes ready, we query for build info). |
| // We also remove the device from the new list to mark it as "processed" |
| // * if we do not find it, we remove it from the current list. |
| // Once this is done, the new list contains device we aren't monitoring yet, so we |
| // add them to the list, and start monitoring them. |
| |
| for (int d = 0 ; d < mDevices.size() ;) { |
| Device device = mDevices.get(d); |
| |
| // look for a similar device in the new list. |
| int count = newList.size(); |
| boolean foundMatch = false; |
| for (int dd = 0 ; dd < count ; dd++) { |
| Device newDevice = newList.get(dd); |
| // see if it matches in id and serial number. |
| if (newDevice.getSerialNumber().equals(device.getSerialNumber())) { |
| foundMatch = true; |
| |
| // update the state if needed. |
| if (device.getState() != newDevice.getState()) { |
| device.setState(newDevice.getState()); |
| device.update(Device.CHANGE_STATE); |
| |
| // if the device just got ready/online, we need to start |
| // monitoring it. |
| if (device.isOnline()) { |
| if (AndroidDebugBridge.getClientSupport()) { |
| if (!startMonitoringDevice(device)) { |
| Log.e("DeviceMonitor", |
| "Failed to start monitoring " |
| + device.getSerialNumber()); |
| } |
| } |
| |
| if (device.getPropertyCount() == 0) { |
| devicesToQuery.add(device); |
| } |
| } |
| } |
| |
| // remove the new device from the list since it's been used |
| newList.remove(dd); |
| break; |
| } |
| } |
| |
| if (!foundMatch) { |
| // the device is gone, we need to remove it, and keep current index |
| // to process the next one. |
| removeDevice(device); |
| mServer.deviceDisconnected(device); |
| } else { |
| // process the next one |
| d++; |
| } |
| } |
| |
| // at this point we should still have some new devices in newList, so we |
| // process them. |
| for (Device newDevice : newList) { |
| // add them to the list |
| mDevices.add(newDevice); |
| mServer.deviceConnected(newDevice); |
| |
| // start monitoring them. |
| if (AndroidDebugBridge.getClientSupport()) { |
| if (newDevice.isOnline()) { |
| startMonitoringDevice(newDevice); |
| } |
| } |
| |
| // look for their build info. |
| if (newDevice.isOnline()) { |
| devicesToQuery.add(newDevice); |
| } |
| } |
| } |
| |
| // query the new devices for info. |
| for (Device d : devicesToQuery) { |
| queryNewDeviceForInfo(d); |
| } |
| } |
| newList.clear(); |
| } |
| |
| private void removeDevice(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. |
| } |
| } |
| } |
| |
| /** |
| * Queries a device for its build info. |
| * @param device the device to query. |
| */ |
| private void queryNewDeviceForInfo(Device device) { |
| // TODO: do this in a separate thread. |
| try { |
| // first get the list of properties. |
| device.executeShellCommand(GetPropReceiver.GETPROP_COMMAND, |
| new GetPropReceiver(device)); |
| |
| queryNewDeviceForMountingPoint(device, IDevice.MNT_EXTERNAL_STORAGE); |
| queryNewDeviceForMountingPoint(device, IDevice.MNT_DATA); |
| queryNewDeviceForMountingPoint(device, IDevice.MNT_ROOT); |
| |
| // now get the emulator Virtual Device name (if applicable). |
| if (device.isEmulator()) { |
| EmulatorConsole console = EmulatorConsole.getConsole(device); |
| if (console != null) { |
| device.setAvdName(console.getAvdName()); |
| console.close(); |
| } |
| } |
| } catch (TimeoutException e) { |
| Log.w("DeviceMonitor", String.format("Connection timeout getting info for device %s", |
| device.getSerialNumber())); |
| |
| } catch (AdbCommandRejectedException e) { |
| // This should never happen as we only do this once the device is online. |
| Log.w("DeviceMonitor", String.format( |
| "Adb rejected command to get device %1$s info: %2$s", |
| device.getSerialNumber(), e.getMessage())); |
| |
| } catch (ShellCommandUnresponsiveException e) { |
| Log.w("DeviceMonitor", String.format( |
| "Adb shell command took too long returning info for device %s", |
| device.getSerialNumber())); |
| |
| } catch (IOException e) { |
| Log.w("DeviceMonitor", String.format( |
| "IO Error getting info for device %s", |
| device.getSerialNumber())); |
| } |
| } |
| |
| private void queryNewDeviceForMountingPoint(final Device device, final String name) |
| throws TimeoutException, AdbCommandRejectedException, ShellCommandUnresponsiveException, |
| IOException { |
| device.executeShellCommand("echo $" + name, new MultiLineReceiver() { //$NON-NLS-1$ |
| @Override |
| public boolean isCancelled() { |
| return false; |
| } |
| |
| @Override |
| public void processNewLines(String[] lines) { |
| for (String line : lines) { |
| if (!line.isEmpty()) { |
| // this should be the only one. |
| device.setMountingPoint(name, line); |
| } |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Starts a monitoring service for a device. |
| * @param device the device to monitor. |
| * @return true if success. |
| */ |
| private boolean startMonitoringDevice(Device device) { |
| SocketChannel socketChannel = openAdbConnection(); |
| |
| if (socketChannel != null) { |
| try { |
| boolean result = sendDeviceMonitoringRequest(socketChannel, device); |
| if (result) { |
| |
| if (mSelector == null) { |
| startDeviceMonitorThread(); |
| } |
| |
| device.setClientMonitoringSocket(socketChannel); |
| |
| synchronized (mDevices) { |
| // always wakeup before doing the register. The synchronized block |
| // ensure that the selector won't select() before the end of this block. |
| // @see deviceClientMonitorLoop |
| mSelector.wakeup(); |
| |
| socketChannel.configureBlocking(false); |
| socketChannel.register(mSelector, SelectionKey.OP_READ, device); |
| } |
| |
| 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 { |
| // This synchronized block stops us from doing the select() if a new |
| // Device is being added. |
| // @see startMonitoringDevice() |
| synchronized (mDevices) { |
| } |
| |
| 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! |
| waitABit(); |
| |
| 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(); |
| } |
| } |
| |
| 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 |
| synchronized (mDevices) { |
| 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 boolean sendDeviceMonitoringRequest(SocketChannel socket, Device device) |
| throws TimeoutException, AdbCommandRejectedException, IOException { |
| |
| try { |
| AdbHelper.setDevice(socket, device); |
| |
| byte[] request = AdbHelper.formAdbRequest("track-jdwp"); //$NON-NLS-1$ |
| |
| AdbHelper.write(socket, request); |
| |
| AdbResponse resp = AdbHelper.readAdbResponse(socket, false /* readDiagString */); |
| |
| 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(Device device, 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.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. |
| * @return |
| */ |
| private void openClient(Device device, int pid, int port, 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 |
| * @param device |
| * @param pid |
| * @param socket |
| * @param debuggerPort the debugger port. |
| * @param monitorThread the {@link MonitorThread} object. |
| */ |
| private void createClient(Device device, int pid, SocketChannel socket, int debuggerPort, |
| 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); |
| } else { |
| client = null; |
| } |
| } |
| |
| private int getNextDebuggerPort() { |
| // get the first port and remove it |
| synchronized (mDebuggerPorts) { |
| if (!mDebuggerPorts.isEmpty()) { |
| int port = mDebuggerPorts.get(0); |
| |
| // remove it. |
| mDebuggerPorts.remove(0); |
| |
| // if there's nothing left, add the next port to the list |
| if (mDebuggerPorts.isEmpty()) { |
| mDebuggerPorts.add(port+1); |
| } |
| |
| return port; |
| } |
| } |
| |
| return -1; |
| } |
| |
| void addPortToAvailableList(int port) { |
| if (port > 0) { |
| synchronized (mDebuggerPorts) { |
| // because there could be case where clients are closed twice, we have to make |
| // sure the port number is not already in the list. |
| if (mDebuggerPorts.indexOf(port) == -1) { |
| // add the port to the list while keeping it sorted. It's not like there's |
| // going to be tons of objects so we do it linearly. |
| int count = mDebuggerPorts.size(); |
| for (int i = 0 ; i < count ; i++) { |
| if (port < mDebuggerPorts.get(i)) { |
| mDebuggerPorts.add(i, port); |
| break; |
| } |
| } |
| // TODO: check if we can compact the end of the list. |
| } |
| } |
| } |
| } |
| |
| /** |
| * 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 int readLength(SocketChannel socket, 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 from a socket. |
| * @param socket |
| * @param buffer |
| * @return the content of the buffer as a string, or null if it failed to convert the buffer. |
| * @throws IOException |
| */ |
| private String read(SocketChannel socket, 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) { |
| // we'll return null below. |
| } |
| |
| return null; |
| } |
| |
| } |