Home | History | Annotate | Download | only in ddmlib
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ddmlib;
     18 
     19 
     20 import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
     21 import com.android.ddmlib.Log.LogLevel;
     22 
     23 import java.io.IOException;
     24 import java.net.InetAddress;
     25 import java.net.InetSocketAddress;
     26 import java.nio.BufferOverflowException;
     27 import java.nio.ByteBuffer;
     28 import java.nio.channels.CancelledKeyException;
     29 import java.nio.channels.NotYetBoundException;
     30 import java.nio.channels.SelectionKey;
     31 import java.nio.channels.Selector;
     32 import java.nio.channels.ServerSocketChannel;
     33 import java.nio.channels.SocketChannel;
     34 import java.util.ArrayList;
     35 import java.util.Collection;
     36 import java.util.HashMap;
     37 import java.util.HashSet;
     38 import java.util.Iterator;
     39 import java.util.Set;
     40 
     41 /**
     42  * Monitor open connections.
     43  */
     44 final class MonitorThread extends Thread {
     45 
     46     // For broadcasts to message handlers
     47     //private static final int CLIENT_CONNECTED = 1;
     48 
     49     private static final int CLIENT_READY = 2;
     50 
     51     private static final int CLIENT_DISCONNECTED = 3;
     52 
     53     private volatile boolean mQuit = false;
     54 
     55     // List of clients we're paying attention to
     56     private ArrayList<Client> mClientList;
     57 
     58     // The almighty mux
     59     private Selector mSelector;
     60 
     61     // Map chunk types to handlers
     62     private HashMap<Integer, ChunkHandler> mHandlerMap;
     63 
     64     // port for "debug selected"
     65     private ServerSocketChannel mDebugSelectedChan;
     66 
     67     private int mNewDebugSelectedPort;
     68 
     69     private int mDebugSelectedPort = -1;
     70 
     71     /**
     72      * "Selected" client setup to answer debugging connection to the mNewDebugSelectedPort port.
     73      */
     74     private Client mSelectedClient = null;
     75 
     76     // singleton
     77     private static MonitorThread mInstance;
     78 
     79     /**
     80      * Generic constructor.
     81      */
     82     private MonitorThread() {
     83         super("Monitor");
     84         mClientList = new ArrayList<Client>();
     85         mHandlerMap = new HashMap<Integer, ChunkHandler>();
     86 
     87         mNewDebugSelectedPort = DdmPreferences.getSelectedDebugPort();
     88     }
     89 
     90     /**
     91      * Creates and return the singleton instance of the client monitor thread.
     92      */
     93     static MonitorThread createInstance() {
     94         return mInstance = new MonitorThread();
     95     }
     96 
     97     /**
     98      * Get singleton instance of the client monitor thread.
     99      */
    100     static MonitorThread getInstance() {
    101         return mInstance;
    102     }
    103 
    104 
    105     /**
    106      * Sets or changes the port number for "debug selected".
    107      */
    108     synchronized void setDebugSelectedPort(int port) throws IllegalStateException {
    109         if (mInstance == null) {
    110             return;
    111         }
    112 
    113         if (AndroidDebugBridge.getClientSupport() == false) {
    114             return;
    115         }
    116 
    117         if (mDebugSelectedChan != null) {
    118             Log.d("ddms", "Changing debug-selected port to " + port);
    119             mNewDebugSelectedPort = port;
    120             wakeup();
    121         } else {
    122             // we set mNewDebugSelectedPort instead of mDebugSelectedPort so that it's automatically
    123             // opened on the first run loop.
    124             mNewDebugSelectedPort = port;
    125         }
    126     }
    127 
    128     /**
    129      * Sets the client to accept debugger connection on the custom "Selected debug port".
    130      * @param selectedClient the client. Can be null.
    131      */
    132     synchronized void setSelectedClient(Client selectedClient) {
    133         if (mInstance == null) {
    134             return;
    135         }
    136 
    137         if (mSelectedClient != selectedClient) {
    138             Client oldClient = mSelectedClient;
    139             mSelectedClient = selectedClient;
    140 
    141             if (oldClient != null) {
    142                 oldClient.update(Client.CHANGE_PORT);
    143             }
    144 
    145             if (mSelectedClient != null) {
    146                 mSelectedClient.update(Client.CHANGE_PORT);
    147             }
    148         }
    149     }
    150 
    151     /**
    152      * Returns the client accepting debugger connection on the custom "Selected debug port".
    153      */
    154     Client getSelectedClient() {
    155         return mSelectedClient;
    156     }
    157 
    158 
    159     /**
    160      * Returns "true" if we want to retry connections to clients if we get a bad
    161      * JDWP handshake back, "false" if we want to just mark them as bad and
    162      * leave them alone.
    163      */
    164     boolean getRetryOnBadHandshake() {
    165         return true; // TODO? make configurable
    166     }
    167 
    168     /**
    169      * Get an array of known clients.
    170      */
    171     Client[] getClients() {
    172         synchronized (mClientList) {
    173             return mClientList.toArray(new Client[0]);
    174         }
    175     }
    176 
    177     /**
    178      * Register "handler" as the handler for type "type".
    179      */
    180     synchronized void registerChunkHandler(int type, ChunkHandler handler) {
    181         if (mInstance == null) {
    182             return;
    183         }
    184 
    185         synchronized (mHandlerMap) {
    186             if (mHandlerMap.get(type) == null) {
    187                 mHandlerMap.put(type, handler);
    188             }
    189         }
    190     }
    191 
    192     /**
    193      * Watch for activity from clients and debuggers.
    194      */
    195     @Override
    196     public void run() {
    197         Log.d("ddms", "Monitor is up");
    198 
    199         // create a selector
    200         try {
    201             mSelector = Selector.open();
    202         } catch (IOException ioe) {
    203             Log.logAndDisplay(LogLevel.ERROR, "ddms",
    204                     "Failed to initialize Monitor Thread: " + ioe.getMessage());
    205             return;
    206         }
    207 
    208         while (!mQuit) {
    209 
    210             try {
    211                 /*
    212                  * sync with new registrations: we wait until addClient is done before going through
    213                  * and doing mSelector.select() again.
    214                  * @see {@link #addClient(Client)}
    215                  */
    216                 synchronized (mClientList) {
    217                 }
    218 
    219                 // (re-)open the "debug selected" port, if it's not opened yet or
    220                 // if the port changed.
    221                 try {
    222                     if (AndroidDebugBridge.getClientSupport()) {
    223                         if ((mDebugSelectedChan == null ||
    224                                 mNewDebugSelectedPort != mDebugSelectedPort) &&
    225                                 mNewDebugSelectedPort != -1) {
    226                             if (reopenDebugSelectedPort()) {
    227                                 mDebugSelectedPort = mNewDebugSelectedPort;
    228                             }
    229                         }
    230                     }
    231                 } catch (IOException ioe) {
    232                     Log.e("ddms",
    233                             "Failed to reopen debug port for Selected Client to: " + mNewDebugSelectedPort);
    234                     Log.e("ddms", ioe);
    235                     mNewDebugSelectedPort = mDebugSelectedPort; // no retry
    236                 }
    237 
    238                 int count;
    239                 try {
    240                     count = mSelector.select();
    241                 } catch (IOException ioe) {
    242                     ioe.printStackTrace();
    243                     continue;
    244                 } catch (CancelledKeyException cke) {
    245                     continue;
    246                 }
    247 
    248                 if (count == 0) {
    249                     // somebody called wakeup() ?
    250                     // Log.i("ddms", "selector looping");
    251                     continue;
    252                 }
    253 
    254                 Set<SelectionKey> keys = mSelector.selectedKeys();
    255                 Iterator<SelectionKey> iter = keys.iterator();
    256 
    257                 while (iter.hasNext()) {
    258                     SelectionKey key = iter.next();
    259                     iter.remove();
    260 
    261                     try {
    262                         if (key.attachment() instanceof Client) {
    263                             processClientActivity(key);
    264                         }
    265                         else if (key.attachment() instanceof Debugger) {
    266                             processDebuggerActivity(key);
    267                         }
    268                         else if (key.attachment() instanceof MonitorThread) {
    269                             processDebugSelectedActivity(key);
    270                         }
    271                         else {
    272                             Log.e("ddms", "unknown activity key");
    273                         }
    274                     } catch (Exception e) {
    275                         // we don't want to have our thread be killed because of any uncaught
    276                         // exception, so we intercept all here.
    277                         Log.e("ddms", "Exception during activity from Selector.");
    278                         Log.e("ddms", e);
    279                     }
    280                 }
    281             } catch (Exception e) {
    282                 // we don't want to have our thread be killed because of any uncaught
    283                 // exception, so we intercept all here.
    284                 Log.e("ddms", "Exception MonitorThread.run()");
    285                 Log.e("ddms", e);
    286             }
    287         }
    288     }
    289 
    290 
    291     /**
    292      * Returns the port on which the selected client listen for debugger
    293      */
    294     int getDebugSelectedPort() {
    295         return mDebugSelectedPort;
    296     }
    297 
    298     /*
    299      * Something happened. Figure out what.
    300      */
    301     private void processClientActivity(SelectionKey key) {
    302         Client client = (Client)key.attachment();
    303 
    304         try {
    305             if (key.isReadable() == false || key.isValid() == false) {
    306                 Log.d("ddms", "Invalid key from " + client + ". Dropping client.");
    307                 dropClient(client, true /* notify */);
    308                 return;
    309             }
    310 
    311             client.read();
    312 
    313             /*
    314              * See if we have a full packet in the buffer. It's possible we have
    315              * more than one packet, so we have to loop.
    316              */
    317             JdwpPacket packet = client.getJdwpPacket();
    318             while (packet != null) {
    319                 if (packet.isDdmPacket()) {
    320                     // unsolicited DDM request - hand it off
    321                     assert !packet.isReply();
    322                     callHandler(client, packet, null);
    323                     packet.consume();
    324                 } else if (packet.isReply()
    325                         && client.isResponseToUs(packet.getId()) != null) {
    326                     // reply to earlier DDM request
    327                     ChunkHandler handler = client
    328                             .isResponseToUs(packet.getId());
    329                     if (packet.isError())
    330                         client.packetFailed(packet);
    331                     else if (packet.isEmpty())
    332                         Log.d("ddms", "Got empty reply for 0x"
    333                                 + Integer.toHexString(packet.getId())
    334                                 + " from " + client);
    335                     else
    336                         callHandler(client, packet, handler);
    337                     packet.consume();
    338                     client.removeRequestId(packet.getId());
    339                 } else {
    340                     Log.v("ddms", "Forwarding client "
    341                             + (packet.isReply() ? "reply" : "event") + " 0x"
    342                             + Integer.toHexString(packet.getId()) + " to "
    343                             + client.getDebugger());
    344                     client.forwardPacketToDebugger(packet);
    345                 }
    346 
    347                 // find next
    348                 packet = client.getJdwpPacket();
    349             }
    350         } catch (CancelledKeyException e) {
    351             // key was canceled probably due to a disconnected client before we could
    352             // read stuff coming from the client, so we drop it.
    353             dropClient(client, true /* notify */);
    354         } catch (IOException ex) {
    355             // something closed down, no need to print anything. The client is simply dropped.
    356             dropClient(client, true /* notify */);
    357         } catch (Exception ex) {
    358             Log.e("ddms", ex);
    359 
    360             /* close the client; automatically un-registers from selector */
    361             dropClient(client, true /* notify */);
    362 
    363             if (ex instanceof BufferOverflowException) {
    364                 Log.w("ddms",
    365                         "Client data packet exceeded maximum buffer size "
    366                                 + client);
    367             } else {
    368                 // don't know what this is, display it
    369                 Log.e("ddms", ex);
    370             }
    371         }
    372     }
    373 
    374     /*
    375      * Process an incoming DDM packet. If this is a reply to an earlier request,
    376      * "handler" will be set to the handler responsible for the original
    377      * request. The spec allows a JDWP message to include multiple DDM chunks.
    378      */
    379     private void callHandler(Client client, JdwpPacket packet,
    380             ChunkHandler handler) {
    381 
    382         // on first DDM packet received, broadcast a "ready" message
    383         if (!client.ddmSeen())
    384             broadcast(CLIENT_READY, client);
    385 
    386         ByteBuffer buf = packet.getPayload();
    387         int type, length;
    388         boolean reply = true;
    389 
    390         type = buf.getInt();
    391         length = buf.getInt();
    392 
    393         if (handler == null) {
    394             // not a reply, figure out who wants it
    395             synchronized (mHandlerMap) {
    396                 handler = mHandlerMap.get(type);
    397                 reply = false;
    398             }
    399         }
    400 
    401         if (handler == null) {
    402             Log.w("ddms", "Received unsupported chunk type "
    403                     + ChunkHandler.name(type) + " (len=" + length + ")");
    404         } else {
    405             Log.d("ddms", "Calling handler for " + ChunkHandler.name(type)
    406                     + " [" + handler + "] (len=" + length + ")");
    407             ByteBuffer ibuf = buf.slice();
    408             ByteBuffer roBuf = ibuf.asReadOnlyBuffer(); // enforce R/O
    409             roBuf.order(ChunkHandler.CHUNK_ORDER);
    410             // do the handling of the chunk synchronized on the client list
    411             // to be sure there's no concurrency issue when we look for HOME
    412             // in hasApp()
    413             synchronized (mClientList) {
    414                 handler.handleChunk(client, type, roBuf, reply, packet.getId());
    415             }
    416         }
    417     }
    418 
    419     /**
    420      * Drops a client from the monitor.
    421      * <p/>This will lock the {@link Client} list of the {@link Device} running <var>client</var>.
    422      * @param client
    423      * @param notify
    424      */
    425     synchronized void dropClient(Client client, boolean notify) {
    426         if (mInstance == null) {
    427             return;
    428         }
    429 
    430         synchronized (mClientList) {
    431             if (mClientList.remove(client) == false) {
    432                 return;
    433             }
    434         }
    435         client.close(notify);
    436         broadcast(CLIENT_DISCONNECTED, client);
    437 
    438         /*
    439          * http://forum.java.sun.com/thread.jspa?threadID=726715&start=0
    440          * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5073504
    441          */
    442         wakeup();
    443     }
    444 
    445     /**
    446      * Drops the provided list of clients from the monitor. This will lock the {@link Client}
    447      * list of the {@link Device} running each of the clients.
    448      */
    449     synchronized void dropClients(Collection<? extends Client> clients, boolean notify) {
    450         for (Client c : clients) {
    451             dropClient(c, notify);
    452         }
    453     }
    454 
    455     /*
    456      * Process activity from one of the debugger sockets. This could be a new
    457      * connection or a data packet.
    458      */
    459     private void processDebuggerActivity(SelectionKey key) {
    460         Debugger dbg = (Debugger)key.attachment();
    461 
    462         try {
    463             if (key.isAcceptable()) {
    464                 try {
    465                     acceptNewDebugger(dbg, null);
    466                 } catch (IOException ioe) {
    467                     Log.w("ddms", "debugger accept() failed");
    468                     ioe.printStackTrace();
    469                 }
    470             } else if (key.isReadable()) {
    471                 processDebuggerData(key);
    472             } else {
    473                 Log.d("ddm-debugger", "key in unknown state");
    474             }
    475         } catch (CancelledKeyException cke) {
    476             // key has been cancelled we can ignore that.
    477         }
    478     }
    479 
    480     /*
    481      * Accept a new connection from a debugger. If successful, register it with
    482      * the Selector.
    483      */
    484     private void acceptNewDebugger(Debugger dbg, ServerSocketChannel acceptChan)
    485             throws IOException {
    486 
    487         synchronized (mClientList) {
    488             SocketChannel chan;
    489 
    490             if (acceptChan == null)
    491                 chan = dbg.accept();
    492             else
    493                 chan = dbg.accept(acceptChan);
    494 
    495             if (chan != null) {
    496                 chan.socket().setTcpNoDelay(true);
    497 
    498                 wakeup();
    499 
    500                 try {
    501                     chan.register(mSelector, SelectionKey.OP_READ, dbg);
    502                 } catch (IOException ioe) {
    503                     // failed, drop the connection
    504                     dbg.closeData();
    505                     throw ioe;
    506                 } catch (RuntimeException re) {
    507                     // failed, drop the connection
    508                     dbg.closeData();
    509                     throw re;
    510                 }
    511             } else {
    512                 Log.w("ddms", "ignoring duplicate debugger");
    513                 // new connection already closed
    514             }
    515         }
    516     }
    517 
    518     /*
    519      * We have incoming data from the debugger. Forward it to the client.
    520      */
    521     private void processDebuggerData(SelectionKey key) {
    522         Debugger dbg = (Debugger)key.attachment();
    523 
    524         try {
    525             /*
    526              * Read pending data.
    527              */
    528             dbg.read();
    529 
    530             /*
    531              * See if we have a full packet in the buffer. It's possible we have
    532              * more than one packet, so we have to loop.
    533              */
    534             JdwpPacket packet = dbg.getJdwpPacket();
    535             while (packet != null) {
    536                 Log.v("ddms", "Forwarding dbg req 0x"
    537                         + Integer.toHexString(packet.getId()) + " to "
    538                         + dbg.getClient());
    539 
    540                 dbg.forwardPacketToClient(packet);
    541 
    542                 packet = dbg.getJdwpPacket();
    543             }
    544         } catch (IOException ioe) {
    545             /*
    546              * Close data connection; automatically un-registers dbg from
    547              * selector. The failure could be caused by the debugger going away,
    548              * or by the client going away and failing to accept our data.
    549              * Either way, the debugger connection does not need to exist any
    550              * longer. We also need to recycle the connection to the client, so
    551              * that the VM sees the debugger disconnect. For a DDM-aware client
    552              * this won't be necessary, and we can just send a "debugger
    553              * disconnected" message.
    554              */
    555             Log.d("ddms", "Closing connection to debugger " + dbg);
    556             dbg.closeData();
    557             Client client = dbg.getClient();
    558             if (client.isDdmAware()) {
    559                 // TODO: soft-disconnect DDM-aware clients
    560                 Log.d("ddms", " (recycling client connection as well)");
    561 
    562                 // we should drop the client, but also attempt to reopen it.
    563                 // This is done by the DeviceMonitor.
    564                 client.getDeviceImpl().getMonitor().addClientToDropAndReopen(client,
    565                         IDebugPortProvider.NO_STATIC_PORT);
    566             } else {
    567                 Log.d("ddms", " (recycling client connection as well)");
    568                 // we should drop the client, but also attempt to reopen it.
    569                 // This is done by the DeviceMonitor.
    570                 client.getDeviceImpl().getMonitor().addClientToDropAndReopen(client,
    571                         IDebugPortProvider.NO_STATIC_PORT);
    572             }
    573         }
    574     }
    575 
    576     /*
    577      * Tell the thread that something has changed.
    578      */
    579     private void wakeup() {
    580         mSelector.wakeup();
    581     }
    582 
    583     /**
    584      * Tell the thread to stop. Called from UI thread.
    585      */
    586     synchronized void quit() {
    587         mQuit = true;
    588         wakeup();
    589         Log.d("ddms", "Waiting for Monitor thread");
    590         try {
    591             this.join();
    592             // since we're quitting, lets drop all the client and disconnect
    593             // the DebugSelectedPort
    594             synchronized (mClientList) {
    595                 for (Client c : mClientList) {
    596                     c.close(false /* notify */);
    597                     broadcast(CLIENT_DISCONNECTED, c);
    598                 }
    599                 mClientList.clear();
    600             }
    601 
    602             if (mDebugSelectedChan != null) {
    603                 mDebugSelectedChan.close();
    604                 mDebugSelectedChan.socket().close();
    605                 mDebugSelectedChan = null;
    606             }
    607             mSelector.close();
    608         } catch (InterruptedException ie) {
    609             ie.printStackTrace();
    610         } catch (IOException e) {
    611             // TODO Auto-generated catch block
    612             e.printStackTrace();
    613         }
    614 
    615         mInstance = null;
    616     }
    617 
    618     /**
    619      * Add a new Client to the list of things we monitor. Also adds the client's
    620      * channel and the client's debugger listener to the selection list. This
    621      * should only be called from one thread (the VMWatcherThread) to avoid a
    622      * race between "alreadyOpen" and Client creation.
    623      */
    624     synchronized void addClient(Client client) {
    625         if (mInstance == null) {
    626             return;
    627         }
    628 
    629         Log.d("ddms", "Adding new client " + client);
    630 
    631         synchronized (mClientList) {
    632             mClientList.add(client);
    633 
    634             /*
    635              * Register the Client's socket channel with the selector. We attach
    636              * the Client to the SelectionKey. If you try to register a new
    637              * channel with the Selector while it is waiting for I/O, you will
    638              * block. The solution is to call wakeup() and then hold a lock to
    639              * ensure that the registration happens before the Selector goes
    640              * back to sleep.
    641              */
    642             try {
    643                 wakeup();
    644 
    645                 client.register(mSelector);
    646 
    647                 Debugger dbg = client.getDebugger();
    648                 if (dbg != null) {
    649                     dbg.registerListener(mSelector);
    650                 }
    651             } catch (IOException ioe) {
    652                 // not really expecting this to happen
    653                 ioe.printStackTrace();
    654             }
    655         }
    656     }
    657 
    658     /*
    659      * Broadcast an event to all message handlers.
    660      */
    661     private void broadcast(int event, Client client) {
    662         Log.d("ddms", "broadcast " + event + ": " + client);
    663 
    664         /*
    665          * The handler objects appear once in mHandlerMap for each message they
    666          * handle. We want to notify them once each, so we convert the HashMap
    667          * to a HashSet before we iterate.
    668          */
    669         HashSet<ChunkHandler> set;
    670         synchronized (mHandlerMap) {
    671             Collection<ChunkHandler> values = mHandlerMap.values();
    672             set = new HashSet<ChunkHandler>(values);
    673         }
    674 
    675         Iterator<ChunkHandler> iter = set.iterator();
    676         while (iter.hasNext()) {
    677             ChunkHandler handler = iter.next();
    678             switch (event) {
    679                 case CLIENT_READY:
    680                     try {
    681                         handler.clientReady(client);
    682                     } catch (IOException ioe) {
    683                         // Something failed with the client. It should
    684                         // fall out of the list the next time we try to
    685                         // do something with it, so we discard the
    686                         // exception here and assume cleanup will happen
    687                         // later. May need to propagate farther. The
    688                         // trouble is that not all values for "event" may
    689                         // actually throw an exception.
    690                         Log.w("ddms",
    691                                 "Got exception while broadcasting 'ready'");
    692                         return;
    693                     }
    694                     break;
    695                 case CLIENT_DISCONNECTED:
    696                     handler.clientDisconnected(client);
    697                     break;
    698                 default:
    699                     throw new UnsupportedOperationException();
    700             }
    701         }
    702 
    703     }
    704 
    705     /**
    706      * Opens (or reopens) the "debug selected" port and listen for connections.
    707      * @return true if the port was opened successfully.
    708      * @throws IOException
    709      */
    710     private boolean reopenDebugSelectedPort() throws IOException {
    711 
    712         Log.d("ddms", "reopen debug-selected port: " + mNewDebugSelectedPort);
    713         if (mDebugSelectedChan != null) {
    714             mDebugSelectedChan.close();
    715         }
    716 
    717         mDebugSelectedChan = ServerSocketChannel.open();
    718         mDebugSelectedChan.configureBlocking(false); // required for Selector
    719 
    720         InetSocketAddress addr = new InetSocketAddress(
    721                 InetAddress.getByName("localhost"), //$NON-NLS-1$
    722                 mNewDebugSelectedPort);
    723         mDebugSelectedChan.socket().setReuseAddress(true); // enable SO_REUSEADDR
    724 
    725         try {
    726             mDebugSelectedChan.socket().bind(addr);
    727             if (mSelectedClient != null) {
    728                 mSelectedClient.update(Client.CHANGE_PORT);
    729             }
    730 
    731             mDebugSelectedChan.register(mSelector, SelectionKey.OP_ACCEPT, this);
    732 
    733             return true;
    734         } catch (java.net.BindException e) {
    735             displayDebugSelectedBindError(mNewDebugSelectedPort);
    736 
    737             // do not attempt to reopen it.
    738             mDebugSelectedChan = null;
    739             mNewDebugSelectedPort = -1;
    740 
    741             return false;
    742         }
    743     }
    744 
    745     /*
    746      * We have some activity on the "debug selected" port. Handle it.
    747      */
    748     private void processDebugSelectedActivity(SelectionKey key) {
    749         assert key.isAcceptable();
    750 
    751         ServerSocketChannel acceptChan = (ServerSocketChannel)key.channel();
    752 
    753         /*
    754          * Find the debugger associated with the currently-selected client.
    755          */
    756         if (mSelectedClient != null) {
    757             Debugger dbg = mSelectedClient.getDebugger();
    758 
    759             if (dbg != null) {
    760                 Log.d("ddms", "Accepting connection on 'debug selected' port");
    761                 try {
    762                     acceptNewDebugger(dbg, acceptChan);
    763                 } catch (IOException ioe) {
    764                     // client should be gone, keep going
    765                 }
    766 
    767                 return;
    768             }
    769         }
    770 
    771         Log.w("ddms",
    772                 "Connection on 'debug selected' port, but none selected");
    773         try {
    774             SocketChannel chan = acceptChan.accept();
    775             chan.close();
    776         } catch (IOException ioe) {
    777             // not expected; client should be gone, keep going
    778         } catch (NotYetBoundException e) {
    779             displayDebugSelectedBindError(mDebugSelectedPort);
    780         }
    781     }
    782 
    783     private void displayDebugSelectedBindError(int port) {
    784         String message = String.format(
    785                 "Could not open Selected VM debug port (%1$d). Make sure you do not have another instance of DDMS or of the eclipse plugin running. If it's being used by something else, choose a new port number in the preferences.",
    786                 port);
    787 
    788         Log.logAndDisplay(LogLevel.ERROR, "ddms", message);
    789     }
    790 }
    791