• 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 import com.android.ddmlib.ClientData.MethodProfilingStatus;
20 import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
21 import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;
22 
23 import java.io.IOException;
24 import java.nio.BufferOverflowException;
25 import java.nio.ByteBuffer;
26 import java.nio.channels.SelectionKey;
27 import java.nio.channels.Selector;
28 import java.nio.channels.SocketChannel;
29 import java.util.HashMap;
30 
31 /**
32  * This represents a single client, usually a DAlvik VM process.
33  * <p/>This class gives access to basic client information, as well as methods to perform actions
34  * on the client.
35  * <p/>More detailed information, usually updated in real time, can be access through the
36  * {@link ClientData} class. Each <code>Client</code> object has its own <code>ClientData</code>
37  * accessed through {@link #getClientData()}.
38  */
39 public class Client {
40 
41     private static final int SERVER_PROTOCOL_VERSION = 1;
42 
43     /** Client change bit mask: application name change */
44     public static final int CHANGE_NAME                       = 0x0001;
45     /** Client change bit mask: debugger status change */
46     public static final int CHANGE_DEBUGGER_STATUS            = 0x0002;
47     /** Client change bit mask: debugger port change */
48     public static final int CHANGE_PORT                       = 0x0004;
49     /** Client change bit mask: thread update flag change */
50     public static final int CHANGE_THREAD_MODE                = 0x0008;
51     /** Client change bit mask: thread data updated */
52     public static final int CHANGE_THREAD_DATA                = 0x0010;
53     /** Client change bit mask: heap update flag change */
54     public static final int CHANGE_HEAP_MODE                  = 0x0020;
55     /** Client change bit mask: head data updated */
56     public static final int CHANGE_HEAP_DATA                  = 0x0040;
57     /** Client change bit mask: native heap data updated */
58     public static final int CHANGE_NATIVE_HEAP_DATA           = 0x0080;
59     /** Client change bit mask: thread stack trace updated */
60     public static final int CHANGE_THREAD_STACKTRACE          = 0x0100;
61     /** Client change bit mask: allocation information updated */
62     public static final int CHANGE_HEAP_ALLOCATIONS           = 0x0200;
63     /** Client change bit mask: allocation information updated */
64     public static final int CHANGE_HEAP_ALLOCATION_STATUS     = 0x0400;
65     /** Client change bit mask: allocation information updated */
66     public static final int CHANGE_METHOD_PROFILING_STATUS    = 0x0800;
67 
68     /** Client change bit mask: combination of {@link Client#CHANGE_NAME},
69      * {@link Client#CHANGE_DEBUGGER_STATUS}, and {@link Client#CHANGE_PORT}.
70      */
71     public static final int CHANGE_INFO = CHANGE_NAME | CHANGE_DEBUGGER_STATUS | CHANGE_PORT;
72 
73     private SocketChannel mChan;
74 
75     // debugger we're associated with, if any
76     private Debugger mDebugger;
77     private int mDebuggerListenPort;
78 
79     // list of IDs for requests we have sent to the client
80     private HashMap<Integer,ChunkHandler> mOutstandingReqs;
81 
82     // chunk handlers stash state data in here
83     private ClientData mClientData;
84 
85     // User interface state.  Changing the value causes a message to be
86     // sent to the client.
87     private boolean mThreadUpdateEnabled;
88     private boolean mHeapUpdateEnabled;
89 
90     /*
91      * Read/write buffers.  We can get large quantities of data from the
92      * client, e.g. the response to a "give me the list of all known classes"
93      * request from the debugger.  Requests from the debugger, and from us,
94      * are much smaller.
95      *
96      * Pass-through debugger traffic is sent without copying.  "mWriteBuffer"
97      * is only used for data generated within Client.
98      */
99     private static final int INITIAL_BUF_SIZE = 2*1024;
100     private static final int MAX_BUF_SIZE = 200*1024*1024;
101     private ByteBuffer mReadBuffer;
102 
103     private static final int WRITE_BUF_SIZE = 256;
104     private ByteBuffer mWriteBuffer;
105 
106     private Device mDevice;
107 
108     private int mConnState;
109 
110     private static final int ST_INIT         = 1;
111     private static final int ST_NOT_JDWP     = 2;
112     private static final int ST_AWAIT_SHAKE  = 10;
113     private static final int ST_NEED_DDM_PKT = 11;
114     private static final int ST_NOT_DDM      = 12;
115     private static final int ST_READY        = 13;
116     private static final int ST_ERROR        = 20;
117     private static final int ST_DISCONNECTED = 21;
118 
119 
120     /**
121      * Create an object for a new client connection.
122      *
123      * @param device the device this client belongs to
124      * @param chan the connected {@link SocketChannel}.
125      * @param pid the client pid.
126      */
Client(Device device, SocketChannel chan, int pid)127     Client(Device device, SocketChannel chan, int pid) {
128         mDevice = device;
129         mChan = chan;
130 
131         mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
132         mWriteBuffer = ByteBuffer.allocate(WRITE_BUF_SIZE);
133 
134         mOutstandingReqs = new HashMap<Integer,ChunkHandler>();
135 
136         mConnState = ST_INIT;
137 
138         mClientData = new ClientData(pid);
139 
140         mThreadUpdateEnabled = DdmPreferences.getInitialThreadUpdate();
141         mHeapUpdateEnabled = DdmPreferences.getInitialHeapUpdate();
142     }
143 
144     /**
145      * Returns a string representation of the {@link Client} object.
146      */
147     @Override
toString()148     public String toString() {
149         return "[Client pid: " + mClientData.getPid() + "]";
150     }
151 
152     /**
153      * Returns the {@link IDevice} on which this Client is running.
154      */
getDevice()155     public IDevice getDevice() {
156         return mDevice;
157     }
158 
159     /** Returns the {@link Device} on which this Client is running.
160      */
getDeviceImpl()161     Device getDeviceImpl() {
162         return mDevice;
163     }
164 
165     /**
166      * Returns the debugger port for this client.
167      */
getDebuggerListenPort()168     public int getDebuggerListenPort() {
169         return mDebuggerListenPort;
170     }
171 
172     /**
173      * Returns <code>true</code> if the client VM is DDM-aware.
174      *
175      * Calling here is only allowed after the connection has been
176      * established.
177      */
isDdmAware()178     public boolean isDdmAware() {
179         switch (mConnState) {
180             case ST_INIT:
181             case ST_NOT_JDWP:
182             case ST_AWAIT_SHAKE:
183             case ST_NEED_DDM_PKT:
184             case ST_NOT_DDM:
185             case ST_ERROR:
186             case ST_DISCONNECTED:
187                 return false;
188             case ST_READY:
189                 return true;
190             default:
191                 assert false;
192                 return false;
193         }
194     }
195 
196     /**
197      * Returns <code>true</code> if a debugger is currently attached to the client.
198      */
isDebuggerAttached()199     public boolean isDebuggerAttached() {
200         return mDebugger.isDebuggerAttached();
201     }
202 
203     /**
204      * Return the Debugger object associated with this client.
205      */
getDebugger()206     Debugger getDebugger() {
207         return mDebugger;
208     }
209 
210     /**
211      * Returns the {@link ClientData} object containing this client information.
212      */
getClientData()213     public ClientData getClientData() {
214         return mClientData;
215     }
216 
217     /**
218      * Forces the client to execute its garbage collector.
219      */
executeGarbageCollector()220     public void executeGarbageCollector() {
221         try {
222             HandleHeap.sendHPGC(this);
223         } catch (IOException ioe) {
224             Log.w("ddms", "Send of HPGC message failed");
225             // ignore
226         }
227     }
228 
229     /**
230      * Makes the VM dump an HPROF file
231      */
dumpHprof()232     public void dumpHprof() {
233         boolean canStream = mClientData.hasFeature(ClientData.FEATURE_HPROF_STREAMING);
234         try {
235             if (canStream) {
236                 HandleHeap.sendHPDS(this);
237             } else {
238                 String file = "/sdcard/" + mClientData.getClientDescription().replaceAll(
239                         "\\:.*", "") + ".hprof";
240                 HandleHeap.sendHPDU(this, file);
241             }
242         } catch (IOException e) {
243             Log.w("ddms", "Send of HPDU message failed");
244             // ignore
245         }
246     }
247 
toggleMethodProfiling()248     public void toggleMethodProfiling() {
249         boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
250         try {
251             if (mClientData.getMethodProfilingStatus() == MethodProfilingStatus.ON) {
252                 if (canStream) {
253                     HandleProfiling.sendMPSE(this);
254                 } else {
255                     HandleProfiling.sendMPRE(this);
256                 }
257             } else {
258                 int bufferSize = DdmPreferences.getProfilerBufferSizeMb() * 1024 * 1024;
259                 if (canStream) {
260                     HandleProfiling.sendMPSS(this, bufferSize, 0 /*flags*/);
261                 } else {
262                     String file = "/sdcard/" +
263                         mClientData.getClientDescription().replaceAll("\\:.*", "") +
264                         DdmConstants.DOT_TRACE;
265                     HandleProfiling.sendMPRS(this, file, bufferSize, 0 /*flags*/);
266                 }
267             }
268         } catch (IOException e) {
269             Log.w("ddms", "Toggle method profiling failed");
270             // ignore
271         }
272     }
273 
274     /**
275      * Sends a request to the VM to send the enable status of the method profiling.
276      * This is asynchronous.
277      * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
278      * The notification that the new status is available will be received through
279      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
280      * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
281      */
requestMethodProfilingStatus()282     public void requestMethodProfilingStatus() {
283         try {
284             HandleHeap.sendREAQ(this);
285         } catch (IOException e) {
286             Log.e("ddmlib", e);
287         }
288     }
289 
290 
291     /**
292      * Enables or disables the thread update.
293      * <p/>If <code>true</code> the VM will be able to send thread information. Thread information
294      * must be requested with {@link #requestThreadUpdate()}.
295      * @param enabled the enable flag.
296      */
setThreadUpdateEnabled(boolean enabled)297     public void setThreadUpdateEnabled(boolean enabled) {
298         mThreadUpdateEnabled = enabled;
299         if (enabled == false) {
300             mClientData.clearThreads();
301         }
302 
303         try {
304             HandleThread.sendTHEN(this, enabled);
305         } catch (IOException ioe) {
306             // ignore it here; client will clean up shortly
307             ioe.printStackTrace();
308         }
309 
310         update(CHANGE_THREAD_MODE);
311     }
312 
313     /**
314      * Returns whether the thread update is enabled.
315      */
isThreadUpdateEnabled()316     public boolean isThreadUpdateEnabled() {
317         return mThreadUpdateEnabled;
318     }
319 
320     /**
321      * Sends a thread update request. This is asynchronous.
322      * <p/>The thread info can be accessed by {@link ClientData#getThreads()}. The notification
323      * that the new data is available will be received through
324      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
325      * containing the mask {@link #CHANGE_THREAD_DATA}.
326      */
requestThreadUpdate()327     public void requestThreadUpdate() {
328         HandleThread.requestThreadUpdate(this);
329     }
330 
331     /**
332      * Sends a thread stack trace update request. This is asynchronous.
333      * <p/>The thread info can be accessed by {@link ClientData#getThreads()} and
334      * {@link ThreadInfo#getStackTrace()}.
335      * <p/>The notification that the new data is available
336      * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
337      * with a <code>changeMask</code> containing the mask {@link #CHANGE_THREAD_STACKTRACE}.
338      */
requestThreadStackTrace(int threadId)339     public void requestThreadStackTrace(int threadId) {
340         HandleThread.requestThreadStackCallRefresh(this, threadId);
341     }
342 
343     /**
344      * Enables or disables the heap update.
345      * <p/>If <code>true</code>, any GC will cause the client to send its heap information.
346      * <p/>The heap information can be accessed by {@link ClientData#getVmHeapData()}.
347      * <p/>The notification that the new data is available
348      * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
349      * with a <code>changeMask</code> containing the value {@link #CHANGE_HEAP_DATA}.
350      * @param enabled the enable flag
351      */
setHeapUpdateEnabled(boolean enabled)352     public void setHeapUpdateEnabled(boolean enabled) {
353         mHeapUpdateEnabled = enabled;
354 
355         try {
356             HandleHeap.sendHPIF(this,
357                     enabled ? HandleHeap.HPIF_WHEN_EVERY_GC : HandleHeap.HPIF_WHEN_NEVER);
358 
359             HandleHeap.sendHPSG(this,
360                     enabled ? HandleHeap.WHEN_GC : HandleHeap.WHEN_DISABLE,
361                     HandleHeap.WHAT_MERGE);
362         } catch (IOException ioe) {
363             // ignore it here; client will clean up shortly
364         }
365 
366         update(CHANGE_HEAP_MODE);
367     }
368 
369     /**
370      * Returns whether the heap update is enabled.
371      * @see #setHeapUpdateEnabled(boolean)
372      */
isHeapUpdateEnabled()373     public boolean isHeapUpdateEnabled() {
374         return mHeapUpdateEnabled;
375     }
376 
377     /**
378      * Sends a native heap update request. this is asynchronous.
379      * <p/>The native heap info can be accessed by {@link ClientData#getNativeAllocationList()}.
380      * The notification that the new data is available will be received through
381      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
382      * containing the mask {@link #CHANGE_NATIVE_HEAP_DATA}.
383      */
requestNativeHeapInformation()384     public boolean requestNativeHeapInformation() {
385         try {
386             HandleNativeHeap.sendNHGT(this);
387             return true;
388         } catch (IOException e) {
389             Log.e("ddmlib", e);
390         }
391 
392         return false;
393     }
394 
395     /**
396      * Enables or disables the Allocation tracker for this client.
397      * <p/>If enabled, the VM will start tracking allocation informations. A call to
398      * {@link #requestAllocationDetails()} will make the VM sends the information about all the
399      * allocations that happened between the enabling and the request.
400      * @param enable
401      * @see #requestAllocationDetails()
402      */
enableAllocationTracker(boolean enable)403     public void enableAllocationTracker(boolean enable) {
404         try {
405             HandleHeap.sendREAE(this, enable);
406         } catch (IOException e) {
407             Log.e("ddmlib", e);
408         }
409     }
410 
411     /**
412      * Sends a request to the VM to send the enable status of the allocation tracking.
413      * This is asynchronous.
414      * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
415      * The notification that the new status is available will be received through
416      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
417      * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
418      */
requestAllocationStatus()419     public void requestAllocationStatus() {
420         try {
421             HandleHeap.sendREAQ(this);
422         } catch (IOException e) {
423             Log.e("ddmlib", e);
424         }
425     }
426 
427     /**
428      * Sends a request to the VM to send the information about all the allocations that have
429      * happened since the call to {@link #enableAllocationTracker(boolean)} with <var>enable</var>
430      * set to <code>null</code>. This is asynchronous.
431      * <p/>The allocation information can be accessed by {@link ClientData#getAllocations()}.
432      * The notification that the new data is available will be received through
433      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
434      * containing the mask {@link #CHANGE_HEAP_ALLOCATIONS}.
435      */
requestAllocationDetails()436     public void requestAllocationDetails() {
437         try {
438             HandleHeap.sendREAL(this);
439         } catch (IOException e) {
440             Log.e("ddmlib", e);
441         }
442     }
443 
444     /**
445      * Sends a kill message to the VM.
446      */
kill()447     public void kill() {
448         try {
449             HandleExit.sendEXIT(this, 1);
450         } catch (IOException ioe) {
451             Log.w("ddms", "Send of EXIT message failed");
452             // ignore
453         }
454     }
455 
456     /**
457      * Registers the client with a Selector.
458      */
register(Selector sel)459     void register(Selector sel) throws IOException {
460         if (mChan != null) {
461             mChan.register(sel, SelectionKey.OP_READ, this);
462         }
463     }
464 
465     /**
466      * Sets the client to accept debugger connection on the "selected debugger port".
467      *
468      * @see AndroidDebugBridge#setSelectedClient(Client)
469      * @see DdmPreferences#setSelectedDebugPort(int)
470      */
setAsSelectedClient()471     public void setAsSelectedClient() {
472         MonitorThread monitorThread = MonitorThread.getInstance();
473         if (monitorThread != null) {
474             monitorThread.setSelectedClient(this);
475         }
476     }
477 
478     /**
479      * Returns whether this client is the current selected client, accepting debugger connection
480      * on the "selected debugger port".
481      *
482      * @see #setAsSelectedClient()
483      * @see AndroidDebugBridge#setSelectedClient(Client)
484      * @see DdmPreferences#setSelectedDebugPort(int)
485      */
isSelectedClient()486     public boolean isSelectedClient() {
487         MonitorThread monitorThread = MonitorThread.getInstance();
488         if (monitorThread != null) {
489             return monitorThread.getSelectedClient() == this;
490         }
491 
492         return false;
493     }
494 
495     /**
496      * Tell the client to open a server socket channel and listen for
497      * connections on the specified port.
498      */
listenForDebugger(int listenPort)499     void listenForDebugger(int listenPort) throws IOException {
500         mDebuggerListenPort = listenPort;
501         mDebugger = new Debugger(this, listenPort);
502     }
503 
504     /**
505      * Initiate the JDWP handshake.
506      *
507      * On failure, closes the socket and returns false.
508      */
sendHandshake()509     boolean sendHandshake() {
510         assert mWriteBuffer.position() == 0;
511 
512         try {
513             // assume write buffer can hold 14 bytes
514             JdwpPacket.putHandshake(mWriteBuffer);
515             int expectedLen = mWriteBuffer.position();
516             mWriteBuffer.flip();
517             if (mChan.write(mWriteBuffer) != expectedLen)
518                 throw new IOException("partial handshake write");
519         }
520         catch (IOException ioe) {
521             Log.e("ddms-client", "IO error during handshake: " + ioe.getMessage());
522             mConnState = ST_ERROR;
523             close(true /* notify */);
524             return false;
525         }
526         finally {
527             mWriteBuffer.clear();
528         }
529 
530         mConnState = ST_AWAIT_SHAKE;
531 
532         return true;
533     }
534 
535 
536     /**
537      * Send a non-DDM packet to the client.
538      *
539      * Equivalent to sendAndConsume(packet, null).
540      */
sendAndConsume(JdwpPacket packet)541     void sendAndConsume(JdwpPacket packet) throws IOException {
542         sendAndConsume(packet, null);
543     }
544 
545     /**
546      * Send a DDM packet to the client.
547      *
548      * Ideally, we can do this with a single channel write.  If that doesn't
549      * happen, we have to prevent anybody else from writing to the channel
550      * until this packet completes, so we synchronize on the channel.
551      *
552      * Another goal is to avoid unnecessary buffer copies, so we write
553      * directly out of the JdwpPacket's ByteBuffer.
554      */
sendAndConsume(JdwpPacket packet, ChunkHandler replyHandler)555     void sendAndConsume(JdwpPacket packet, ChunkHandler replyHandler)
556         throws IOException {
557 
558         if (mChan == null) {
559             // can happen for e.g. THST packets
560             Log.v("ddms", "Not sending packet -- client is closed");
561             return;
562         }
563 
564         if (replyHandler != null) {
565             /*
566              * Add the ID to the list of outstanding requests.  We have to do
567              * this before sending the packet, in case the response comes back
568              * before our thread returns from the packet-send function.
569              */
570             addRequestId(packet.getId(), replyHandler);
571         }
572 
573         synchronized (mChan) {
574             try {
575                 packet.writeAndConsume(mChan);
576             }
577             catch (IOException ioe) {
578                 removeRequestId(packet.getId());
579                 throw ioe;
580             }
581         }
582     }
583 
584     /**
585      * Forward the packet to the debugger (if still connected to one).
586      *
587      * Consumes the packet.
588      */
forwardPacketToDebugger(JdwpPacket packet)589     void forwardPacketToDebugger(JdwpPacket packet)
590         throws IOException {
591 
592         Debugger dbg = mDebugger;
593 
594         if (dbg == null) {
595             Log.d("ddms", "Discarding packet");
596             packet.consume();
597         } else {
598             dbg.sendAndConsume(packet);
599         }
600     }
601 
602     /**
603      * Read data from our channel.
604      *
605      * This is called when data is known to be available, and we don't yet
606      * have a full packet in the buffer.  If the buffer is at capacity,
607      * expand it.
608      */
read()609     void read()
610         throws IOException, BufferOverflowException {
611 
612         int count;
613 
614         if (mReadBuffer.position() == mReadBuffer.capacity()) {
615             if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
616                 Log.e("ddms", "Exceeded MAX_BUF_SIZE!");
617                 throw new BufferOverflowException();
618             }
619             Log.d("ddms", "Expanding read buffer to "
620                 + mReadBuffer.capacity() * 2);
621 
622             ByteBuffer newBuffer = ByteBuffer.allocate(mReadBuffer.capacity() * 2);
623 
624             // copy entire buffer to new buffer
625             mReadBuffer.position(0);
626             newBuffer.put(mReadBuffer);  // leaves "position" at end of copied
627 
628             mReadBuffer = newBuffer;
629         }
630 
631         count = mChan.read(mReadBuffer);
632         if (count < 0)
633             throw new IOException("read failed");
634 
635         if (Log.Config.LOGV) Log.v("ddms", "Read " + count + " bytes from " + this);
636         //Log.hexDump("ddms", Log.DEBUG, mReadBuffer.array(),
637         //    mReadBuffer.arrayOffset(), mReadBuffer.position());
638     }
639 
640     /**
641      * Return information for the first full JDWP packet in the buffer.
642      *
643      * If we don't yet have a full packet, return null.
644      *
645      * If we haven't yet received the JDWP handshake, we watch for it here
646      * and consume it without admitting to have done so.  Upon receipt
647      * we send out the "HELO" message, which is why this can throw an
648      * IOException.
649      */
getJdwpPacket()650     JdwpPacket getJdwpPacket() throws IOException {
651 
652         /*
653          * On entry, the data starts at offset 0 and ends at "position".
654          * "limit" is set to the buffer capacity.
655          */
656         if (mConnState == ST_AWAIT_SHAKE) {
657             /*
658              * The first thing we get from the client is a response to our
659              * handshake.  It doesn't look like a packet, so we have to
660              * handle it specially.
661              */
662             int result;
663 
664             result = JdwpPacket.findHandshake(mReadBuffer);
665             //Log.v("ddms", "findHand: " + result);
666             switch (result) {
667                 case JdwpPacket.HANDSHAKE_GOOD:
668                     Log.d("ddms",
669                         "Good handshake from client, sending HELO to " + mClientData.getPid());
670                     JdwpPacket.consumeHandshake(mReadBuffer);
671                     mConnState = ST_NEED_DDM_PKT;
672                     HandleHello.sendHelloCommands(this, SERVER_PROTOCOL_VERSION);
673                     // see if we have another packet in the buffer
674                     return getJdwpPacket();
675                 case JdwpPacket.HANDSHAKE_BAD:
676                     Log.d("ddms", "Bad handshake from client");
677                     if (MonitorThread.getInstance().getRetryOnBadHandshake()) {
678                         // we should drop the client, but also attempt to reopen it.
679                         // This is done by the DeviceMonitor.
680                         mDevice.getMonitor().addClientToDropAndReopen(this,
681                                 IDebugPortProvider.NO_STATIC_PORT);
682                     } else {
683                         // mark it as bad, close the socket, and don't retry
684                         mConnState = ST_NOT_JDWP;
685                         close(true /* notify */);
686                     }
687                     break;
688                 case JdwpPacket.HANDSHAKE_NOTYET:
689                     Log.d("ddms", "No handshake from client yet.");
690                     break;
691                 default:
692                     Log.e("ddms", "Unknown packet while waiting for client handshake");
693             }
694             return null;
695         } else if (mConnState == ST_NEED_DDM_PKT ||
696             mConnState == ST_NOT_DDM ||
697             mConnState == ST_READY) {
698             /*
699              * Normal packet traffic.
700              */
701             if (mReadBuffer.position() != 0) {
702                 if (Log.Config.LOGV) Log.v("ddms",
703                     "Checking " + mReadBuffer.position() + " bytes");
704             }
705             return JdwpPacket.findPacket(mReadBuffer);
706         } else {
707             /*
708              * Not expecting data when in this state.
709              */
710             Log.e("ddms", "Receiving data in state = " + mConnState);
711         }
712 
713         return null;
714     }
715 
716     /*
717      * Add the specified ID to the list of request IDs for which we await
718      * a response.
719      */
addRequestId(int id, ChunkHandler handler)720     private void addRequestId(int id, ChunkHandler handler) {
721         synchronized (mOutstandingReqs) {
722             if (Log.Config.LOGV) Log.v("ddms",
723                 "Adding req 0x" + Integer.toHexString(id) +" to set");
724             mOutstandingReqs.put(id, handler);
725         }
726     }
727 
728     /*
729      * Remove the specified ID from the list, if present.
730      */
removeRequestId(int id)731     void removeRequestId(int id) {
732         synchronized (mOutstandingReqs) {
733             if (Log.Config.LOGV) Log.v("ddms",
734                 "Removing req 0x" + Integer.toHexString(id) + " from set");
735             mOutstandingReqs.remove(id);
736         }
737 
738         //Log.w("ddms", "Request " + Integer.toHexString(id)
739         //    + " could not be removed from " + this);
740     }
741 
742     /**
743      * Determine whether this is a response to a request we sent earlier.
744      * If so, return the ChunkHandler responsible.
745      */
isResponseToUs(int id)746     ChunkHandler isResponseToUs(int id) {
747 
748         synchronized (mOutstandingReqs) {
749             ChunkHandler handler = mOutstandingReqs.get(id);
750             if (handler != null) {
751                 if (Log.Config.LOGV) Log.v("ddms",
752                     "Found 0x" + Integer.toHexString(id)
753                     + " in request set - " + handler);
754                 return handler;
755             }
756         }
757 
758         return null;
759     }
760 
761     /**
762      * An earlier request resulted in a failure.  This is the expected
763      * response to a HELO message when talking to a non-DDM client.
764      */
packetFailed(JdwpPacket reply)765     void packetFailed(JdwpPacket reply) {
766         if (mConnState == ST_NEED_DDM_PKT) {
767             Log.d("ddms", "Marking " + this + " as non-DDM client");
768             mConnState = ST_NOT_DDM;
769         } else if (mConnState != ST_NOT_DDM) {
770             Log.w("ddms", "WEIRD: got JDWP failure packet on DDM req");
771         }
772     }
773 
774     /**
775      * The MonitorThread calls this when it sees a DDM request or reply.
776      * If we haven't seen a DDM packet before, we advance the state to
777      * ST_READY and return "false".  Otherwise, just return true.
778      *
779      * The idea is to let the MonitorThread know when we first see a DDM
780      * packet, so we can send a broadcast to the handlers when a client
781      * connection is made.  This method is synchronized so that we only
782      * send the broadcast once.
783      */
ddmSeen()784     synchronized boolean ddmSeen() {
785         if (mConnState == ST_NEED_DDM_PKT) {
786             mConnState = ST_READY;
787             return false;
788         } else if (mConnState != ST_READY) {
789             Log.w("ddms", "WEIRD: in ddmSeen with state=" + mConnState);
790         }
791         return true;
792     }
793 
794     /**
795      * Close the client socket channel.  If there is a debugger associated
796      * with us, close that too.
797      *
798      * Closing a channel automatically unregisters it from the selector.
799      * However, we have to iterate through the selector loop before it
800      * actually lets them go and allows the file descriptors to close.
801      * The caller is expected to manage that.
802      * @param notify Whether or not to notify the listeners of a change.
803      */
close(boolean notify)804     void close(boolean notify) {
805         Log.d("ddms", "Closing " + this.toString());
806 
807         mOutstandingReqs.clear();
808 
809         try {
810             if (mChan != null) {
811                 mChan.close();
812                 mChan = null;
813             }
814 
815             if (mDebugger != null) {
816                 mDebugger.close();
817                 mDebugger = null;
818             }
819         }
820         catch (IOException ioe) {
821             Log.w("ddms", "failed to close " + this);
822             // swallow it -- not much else to do
823         }
824 
825         mDevice.removeClient(this, notify);
826     }
827 
828     /**
829      * Returns whether this {@link Client} has a valid connection to the application VM.
830      */
isValid()831     public boolean isValid() {
832         return mChan != null;
833     }
834 
update(int changeMask)835     void update(int changeMask) {
836         mDevice.update(this, changeMask);
837     }
838 }
839 
840