• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
MonitorThread()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      */
createInstance()93     static MonitorThread createInstance() {
94         return mInstance = new MonitorThread();
95     }
96 
97     /**
98      * Get singleton instance of the client monitor thread.
99      */
getInstance()100     static MonitorThread getInstance() {
101         return mInstance;
102     }
103 
104 
105     /**
106      * Sets or changes the port number for "debug selected".
107      */
setDebugSelectedPort(int port)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      */
setSelectedClient(Client selectedClient)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      */
getSelectedClient()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      */
getRetryOnBadHandshake()164     boolean getRetryOnBadHandshake() {
165         return true; // TODO? make configurable
166     }
167 
168     /**
169      * Get an array of known clients.
170      */
getClients()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      */
registerChunkHandler(int type, ChunkHandler handler)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
run()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      */
getDebugSelectedPort()294     int getDebugSelectedPort() {
295         return mDebugSelectedPort;
296     }
297 
298     /*
299      * Something happened. Figure out what.
300      */
processClientActivity(SelectionKey key)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      */
callHandler(Client client, JdwpPacket packet, ChunkHandler handler)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      */
dropClient(Client client, boolean notify)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      */
dropClients(Collection<? extends Client> clients, boolean notify)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      */
processDebuggerActivity(SelectionKey key)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      */
acceptNewDebugger(Debugger dbg, ServerSocketChannel acceptChan)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      */
processDebuggerData(SelectionKey key)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      */
wakeup()579     private void wakeup() {
580         mSelector.wakeup();
581     }
582 
583     /**
584      * Tell the thread to stop. Called from UI thread.
585      */
quit()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      */
addClient(Client client)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      */
broadcast(int event, Client client)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      */
reopenDebugSelectedPort()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      */
processDebugSelectedActivity(SelectionKey key)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 
displayDebugSelectedBindError(int port)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