• 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                 if (canStream) {
259                     HandleProfiling.sendMPSS(this, 8*1024*1024, 0 /*flags*/);
260                 } else {
261                     String file = "/sdcard/" + mClientData.getClientDescription().replaceAll("\\:.*", "") +
262                     ".trace";
263                     HandleProfiling.sendMPRS(this, file, 8*1024*1024, 0 /*flags*/);
264                 }
265             }
266         } catch (IOException e) {
267             Log.w("ddms", "Toggle method profiling failed");
268             // ignore
269         }
270     }
271 
272     /**
273      * Sends a request to the VM to send the enable status of the method profiling.
274      * This is asynchronous.
275      * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
276      * The notification that the new status is available will be received through
277      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
278      * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
279      */
requestMethodProfilingStatus()280     public void requestMethodProfilingStatus() {
281         try {
282             HandleHeap.sendREAQ(this);
283         } catch (IOException e) {
284             Log.e("ddmlib", e);
285         }
286     }
287 
288 
289     /**
290      * Enables or disables the thread update.
291      * <p/>If <code>true</code> the VM will be able to send thread information. Thread information
292      * must be requested with {@link #requestThreadUpdate()}.
293      * @param enabled the enable flag.
294      */
setThreadUpdateEnabled(boolean enabled)295     public void setThreadUpdateEnabled(boolean enabled) {
296         mThreadUpdateEnabled = enabled;
297         if (enabled == false) {
298             mClientData.clearThreads();
299         }
300 
301         try {
302             HandleThread.sendTHEN(this, enabled);
303         } catch (IOException ioe) {
304             // ignore it here; client will clean up shortly
305             ioe.printStackTrace();
306         }
307 
308         update(CHANGE_THREAD_MODE);
309     }
310 
311     /**
312      * Returns whether the thread update is enabled.
313      */
isThreadUpdateEnabled()314     public boolean isThreadUpdateEnabled() {
315         return mThreadUpdateEnabled;
316     }
317 
318     /**
319      * Sends a thread update request. This is asynchronous.
320      * <p/>The thread info can be accessed by {@link ClientData#getThreads()}. The notification
321      * that the new data is available will be received through
322      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
323      * containing the mask {@link #CHANGE_THREAD_DATA}.
324      */
requestThreadUpdate()325     public void requestThreadUpdate() {
326         HandleThread.requestThreadUpdate(this);
327     }
328 
329     /**
330      * Sends a thread stack trace update request. This is asynchronous.
331      * <p/>The thread info can be accessed by {@link ClientData#getThreads()} and
332      * {@link ThreadInfo#getStackTrace()}.
333      * <p/>The notification that the new data is available
334      * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
335      * with a <code>changeMask</code> containing the mask {@link #CHANGE_THREAD_STACKTRACE}.
336      */
requestThreadStackTrace(int threadId)337     public void requestThreadStackTrace(int threadId) {
338         HandleThread.requestThreadStackCallRefresh(this, threadId);
339     }
340 
341     /**
342      * Enables or disables the heap update.
343      * <p/>If <code>true</code>, any GC will cause the client to send its heap information.
344      * <p/>The heap information can be accessed by {@link ClientData#getVmHeapData()}.
345      * <p/>The notification that the new data is available
346      * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
347      * with a <code>changeMask</code> containing the value {@link #CHANGE_HEAP_DATA}.
348      * @param enabled the enable flag
349      */
setHeapUpdateEnabled(boolean enabled)350     public void setHeapUpdateEnabled(boolean enabled) {
351         mHeapUpdateEnabled = enabled;
352 
353         try {
354             HandleHeap.sendHPIF(this,
355                     enabled ? HandleHeap.HPIF_WHEN_EVERY_GC : HandleHeap.HPIF_WHEN_NEVER);
356 
357             HandleHeap.sendHPSG(this,
358                     enabled ? HandleHeap.WHEN_GC : HandleHeap.WHEN_DISABLE,
359                     HandleHeap.WHAT_MERGE);
360         } catch (IOException ioe) {
361             // ignore it here; client will clean up shortly
362         }
363 
364         update(CHANGE_HEAP_MODE);
365     }
366 
367     /**
368      * Returns whether the heap update is enabled.
369      * @see #setHeapUpdateEnabled(boolean)
370      */
isHeapUpdateEnabled()371     public boolean isHeapUpdateEnabled() {
372         return mHeapUpdateEnabled;
373     }
374 
375     /**
376      * Sends a native heap update request. this is asynchronous.
377      * <p/>The native heap info can be accessed by {@link ClientData#getNativeAllocationList()}.
378      * The notification that the new data is available will be received through
379      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
380      * containing the mask {@link #CHANGE_NATIVE_HEAP_DATA}.
381      */
requestNativeHeapInformation()382     public boolean requestNativeHeapInformation() {
383         try {
384             HandleNativeHeap.sendNHGT(this);
385             return true;
386         } catch (IOException e) {
387             Log.e("ddmlib", e);
388         }
389 
390         return false;
391     }
392 
393     /**
394      * Enables or disables the Allocation tracker for this client.
395      * <p/>If enabled, the VM will start tracking allocation informations. A call to
396      * {@link #requestAllocationDetails()} will make the VM sends the information about all the
397      * allocations that happened between the enabling and the request.
398      * @param enable
399      * @see #requestAllocationDetails()
400      */
enableAllocationTracker(boolean enable)401     public void enableAllocationTracker(boolean enable) {
402         try {
403             HandleHeap.sendREAE(this, enable);
404         } catch (IOException e) {
405             Log.e("ddmlib", e);
406         }
407     }
408 
409     /**
410      * Sends a request to the VM to send the enable status of the allocation tracking.
411      * This is asynchronous.
412      * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
413      * The notification that the new status is available will be received through
414      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
415      * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
416      */
requestAllocationStatus()417     public void requestAllocationStatus() {
418         try {
419             HandleHeap.sendREAQ(this);
420         } catch (IOException e) {
421             Log.e("ddmlib", e);
422         }
423     }
424 
425     /**
426      * Sends a request to the VM to send the information about all the allocations that have
427      * happened since the call to {@link #enableAllocationTracker(boolean)} with <var>enable</var>
428      * set to <code>null</code>. This is asynchronous.
429      * <p/>The allocation information can be accessed by {@link ClientData#getAllocations()}.
430      * The notification that the new data is available will be received through
431      * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
432      * containing the mask {@link #CHANGE_HEAP_ALLOCATIONS}.
433      */
requestAllocationDetails()434     public void requestAllocationDetails() {
435         try {
436             HandleHeap.sendREAL(this);
437         } catch (IOException e) {
438             Log.e("ddmlib", e);
439         }
440     }
441 
442     /**
443      * Sends a kill message to the VM.
444      */
kill()445     public void kill() {
446         try {
447             HandleExit.sendEXIT(this, 1);
448         } catch (IOException ioe) {
449             Log.w("ddms", "Send of EXIT message failed");
450             // ignore
451         }
452     }
453 
454     /**
455      * Registers the client with a Selector.
456      */
register(Selector sel)457     void register(Selector sel) throws IOException {
458         if (mChan != null) {
459             mChan.register(sel, SelectionKey.OP_READ, this);
460         }
461     }
462 
463     /**
464      * Sets the client to accept debugger connection on the "selected debugger port".
465      *
466      * @see AndroidDebugBridge#setSelectedClient(Client)
467      * @see DdmPreferences#setSelectedDebugPort(int)
468      */
setAsSelectedClient()469     public void setAsSelectedClient() {
470         MonitorThread monitorThread = MonitorThread.getInstance();
471         if (monitorThread != null) {
472             monitorThread.setSelectedClient(this);
473         }
474     }
475 
476     /**
477      * Returns whether this client is the current selected client, accepting debugger connection
478      * on the "selected debugger port".
479      *
480      * @see #setAsSelectedClient()
481      * @see AndroidDebugBridge#setSelectedClient(Client)
482      * @see DdmPreferences#setSelectedDebugPort(int)
483      */
isSelectedClient()484     public boolean isSelectedClient() {
485         MonitorThread monitorThread = MonitorThread.getInstance();
486         if (monitorThread != null) {
487             return monitorThread.getSelectedClient() == this;
488         }
489 
490         return false;
491     }
492 
493     /**
494      * Tell the client to open a server socket channel and listen for
495      * connections on the specified port.
496      */
listenForDebugger(int listenPort)497     void listenForDebugger(int listenPort) throws IOException {
498         mDebuggerListenPort = listenPort;
499         mDebugger = new Debugger(this, listenPort);
500     }
501 
502     /**
503      * Initiate the JDWP handshake.
504      *
505      * On failure, closes the socket and returns false.
506      */
sendHandshake()507     boolean sendHandshake() {
508         assert mWriteBuffer.position() == 0;
509 
510         try {
511             // assume write buffer can hold 14 bytes
512             JdwpPacket.putHandshake(mWriteBuffer);
513             int expectedLen = mWriteBuffer.position();
514             mWriteBuffer.flip();
515             if (mChan.write(mWriteBuffer) != expectedLen)
516                 throw new IOException("partial handshake write");
517         }
518         catch (IOException ioe) {
519             Log.e("ddms-client", "IO error during handshake: " + ioe.getMessage());
520             mConnState = ST_ERROR;
521             close(true /* notify */);
522             return false;
523         }
524         finally {
525             mWriteBuffer.clear();
526         }
527 
528         mConnState = ST_AWAIT_SHAKE;
529 
530         return true;
531     }
532 
533 
534     /**
535      * Send a non-DDM packet to the client.
536      *
537      * Equivalent to sendAndConsume(packet, null).
538      */
sendAndConsume(JdwpPacket packet)539     void sendAndConsume(JdwpPacket packet) throws IOException {
540         sendAndConsume(packet, null);
541     }
542 
543     /**
544      * Send a DDM packet to the client.
545      *
546      * Ideally, we can do this with a single channel write.  If that doesn't
547      * happen, we have to prevent anybody else from writing to the channel
548      * until this packet completes, so we synchronize on the channel.
549      *
550      * Another goal is to avoid unnecessary buffer copies, so we write
551      * directly out of the JdwpPacket's ByteBuffer.
552      */
sendAndConsume(JdwpPacket packet, ChunkHandler replyHandler)553     void sendAndConsume(JdwpPacket packet, ChunkHandler replyHandler)
554         throws IOException {
555 
556         if (mChan == null) {
557             // can happen for e.g. THST packets
558             Log.v("ddms", "Not sending packet -- client is closed");
559             return;
560         }
561 
562         if (replyHandler != null) {
563             /*
564              * Add the ID to the list of outstanding requests.  We have to do
565              * this before sending the packet, in case the response comes back
566              * before our thread returns from the packet-send function.
567              */
568             addRequestId(packet.getId(), replyHandler);
569         }
570 
571         synchronized (mChan) {
572             try {
573                 packet.writeAndConsume(mChan);
574             }
575             catch (IOException ioe) {
576                 removeRequestId(packet.getId());
577                 throw ioe;
578             }
579         }
580     }
581 
582     /**
583      * Forward the packet to the debugger (if still connected to one).
584      *
585      * Consumes the packet.
586      */
forwardPacketToDebugger(JdwpPacket packet)587     void forwardPacketToDebugger(JdwpPacket packet)
588         throws IOException {
589 
590         Debugger dbg = mDebugger;
591 
592         if (dbg == null) {
593             Log.d("ddms", "Discarding packet");
594             packet.consume();
595         } else {
596             dbg.sendAndConsume(packet);
597         }
598     }
599 
600     /**
601      * Read data from our channel.
602      *
603      * This is called when data is known to be available, and we don't yet
604      * have a full packet in the buffer.  If the buffer is at capacity,
605      * expand it.
606      */
read()607     void read()
608         throws IOException, BufferOverflowException {
609 
610         int count;
611 
612         if (mReadBuffer.position() == mReadBuffer.capacity()) {
613             if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
614                 Log.e("ddms", "Exceeded MAX_BUF_SIZE!");
615                 throw new BufferOverflowException();
616             }
617             Log.d("ddms", "Expanding read buffer to "
618                 + mReadBuffer.capacity() * 2);
619 
620             ByteBuffer newBuffer = ByteBuffer.allocate(mReadBuffer.capacity() * 2);
621 
622             // copy entire buffer to new buffer
623             mReadBuffer.position(0);
624             newBuffer.put(mReadBuffer);  // leaves "position" at end of copied
625 
626             mReadBuffer = newBuffer;
627         }
628 
629         count = mChan.read(mReadBuffer);
630         if (count < 0)
631             throw new IOException("read failed");
632 
633         if (Log.Config.LOGV) Log.v("ddms", "Read " + count + " bytes from " + this);
634         //Log.hexDump("ddms", Log.DEBUG, mReadBuffer.array(),
635         //    mReadBuffer.arrayOffset(), mReadBuffer.position());
636     }
637 
638     /**
639      * Return information for the first full JDWP packet in the buffer.
640      *
641      * If we don't yet have a full packet, return null.
642      *
643      * If we haven't yet received the JDWP handshake, we watch for it here
644      * and consume it without admitting to have done so.  Upon receipt
645      * we send out the "HELO" message, which is why this can throw an
646      * IOException.
647      */
getJdwpPacket()648     JdwpPacket getJdwpPacket() throws IOException {
649 
650         /*
651          * On entry, the data starts at offset 0 and ends at "position".
652          * "limit" is set to the buffer capacity.
653          */
654         if (mConnState == ST_AWAIT_SHAKE) {
655             /*
656              * The first thing we get from the client is a response to our
657              * handshake.  It doesn't look like a packet, so we have to
658              * handle it specially.
659              */
660             int result;
661 
662             result = JdwpPacket.findHandshake(mReadBuffer);
663             //Log.v("ddms", "findHand: " + result);
664             switch (result) {
665                 case JdwpPacket.HANDSHAKE_GOOD:
666                     Log.d("ddms",
667                         "Good handshake from client, sending HELO to " + mClientData.getPid());
668                     JdwpPacket.consumeHandshake(mReadBuffer);
669                     mConnState = ST_NEED_DDM_PKT;
670                     HandleHello.sendHelloCommands(this, SERVER_PROTOCOL_VERSION);
671                     // see if we have another packet in the buffer
672                     return getJdwpPacket();
673                 case JdwpPacket.HANDSHAKE_BAD:
674                     Log.d("ddms", "Bad handshake from client");
675                     if (MonitorThread.getInstance().getRetryOnBadHandshake()) {
676                         // we should drop the client, but also attempt to reopen it.
677                         // This is done by the DeviceMonitor.
678                         mDevice.getMonitor().addClientToDropAndReopen(this,
679                                 IDebugPortProvider.NO_STATIC_PORT);
680                     } else {
681                         // mark it as bad, close the socket, and don't retry
682                         mConnState = ST_NOT_JDWP;
683                         close(true /* notify */);
684                     }
685                     break;
686                 case JdwpPacket.HANDSHAKE_NOTYET:
687                     Log.d("ddms", "No handshake from client yet.");
688                     break;
689                 default:
690                     Log.e("ddms", "Unknown packet while waiting for client handshake");
691             }
692             return null;
693         } else if (mConnState == ST_NEED_DDM_PKT ||
694             mConnState == ST_NOT_DDM ||
695             mConnState == ST_READY) {
696             /*
697              * Normal packet traffic.
698              */
699             if (mReadBuffer.position() != 0) {
700                 if (Log.Config.LOGV) Log.v("ddms",
701                     "Checking " + mReadBuffer.position() + " bytes");
702             }
703             return JdwpPacket.findPacket(mReadBuffer);
704         } else {
705             /*
706              * Not expecting data when in this state.
707              */
708             Log.e("ddms", "Receiving data in state = " + mConnState);
709         }
710 
711         return null;
712     }
713 
714     /*
715      * Add the specified ID to the list of request IDs for which we await
716      * a response.
717      */
addRequestId(int id, ChunkHandler handler)718     private void addRequestId(int id, ChunkHandler handler) {
719         synchronized (mOutstandingReqs) {
720             if (Log.Config.LOGV) Log.v("ddms",
721                 "Adding req 0x" + Integer.toHexString(id) +" to set");
722             mOutstandingReqs.put(id, handler);
723         }
724     }
725 
726     /*
727      * Remove the specified ID from the list, if present.
728      */
removeRequestId(int id)729     void removeRequestId(int id) {
730         synchronized (mOutstandingReqs) {
731             if (Log.Config.LOGV) Log.v("ddms",
732                 "Removing req 0x" + Integer.toHexString(id) + " from set");
733             mOutstandingReqs.remove(id);
734         }
735 
736         //Log.w("ddms", "Request " + Integer.toHexString(id)
737         //    + " could not be removed from " + this);
738     }
739 
740     /**
741      * Determine whether this is a response to a request we sent earlier.
742      * If so, return the ChunkHandler responsible.
743      */
isResponseToUs(int id)744     ChunkHandler isResponseToUs(int id) {
745 
746         synchronized (mOutstandingReqs) {
747             ChunkHandler handler = mOutstandingReqs.get(id);
748             if (handler != null) {
749                 if (Log.Config.LOGV) Log.v("ddms",
750                     "Found 0x" + Integer.toHexString(id)
751                     + " in request set - " + handler);
752                 return handler;
753             }
754         }
755 
756         return null;
757     }
758 
759     /**
760      * An earlier request resulted in a failure.  This is the expected
761      * response to a HELO message when talking to a non-DDM client.
762      */
packetFailed(JdwpPacket reply)763     void packetFailed(JdwpPacket reply) {
764         if (mConnState == ST_NEED_DDM_PKT) {
765             Log.d("ddms", "Marking " + this + " as non-DDM client");
766             mConnState = ST_NOT_DDM;
767         } else if (mConnState != ST_NOT_DDM) {
768             Log.w("ddms", "WEIRD: got JDWP failure packet on DDM req");
769         }
770     }
771 
772     /**
773      * The MonitorThread calls this when it sees a DDM request or reply.
774      * If we haven't seen a DDM packet before, we advance the state to
775      * ST_READY and return "false".  Otherwise, just return true.
776      *
777      * The idea is to let the MonitorThread know when we first see a DDM
778      * packet, so we can send a broadcast to the handlers when a client
779      * connection is made.  This method is synchronized so that we only
780      * send the broadcast once.
781      */
ddmSeen()782     synchronized boolean ddmSeen() {
783         if (mConnState == ST_NEED_DDM_PKT) {
784             mConnState = ST_READY;
785             return false;
786         } else if (mConnState != ST_READY) {
787             Log.w("ddms", "WEIRD: in ddmSeen with state=" + mConnState);
788         }
789         return true;
790     }
791 
792     /**
793      * Close the client socket channel.  If there is a debugger associated
794      * with us, close that too.
795      *
796      * Closing a channel automatically unregisters it from the selector.
797      * However, we have to iterate through the selector loop before it
798      * actually lets them go and allows the file descriptors to close.
799      * The caller is expected to manage that.
800      * @param notify Whether or not to notify the listeners of a change.
801      */
close(boolean notify)802     void close(boolean notify) {
803         Log.d("ddms", "Closing " + this.toString());
804 
805         mOutstandingReqs.clear();
806 
807         try {
808             if (mChan != null) {
809                 mChan.close();
810                 mChan = null;
811             }
812 
813             if (mDebugger != null) {
814                 mDebugger.close();
815                 mDebugger = null;
816             }
817         }
818         catch (IOException ioe) {
819             Log.w("ddms", "failed to close " + this);
820             // swallow it -- not much else to do
821         }
822 
823         mDevice.removeClient(this, notify);
824     }
825 
826     /**
827      * Returns whether this {@link Client} has a valid connection to the application VM.
828      */
isValid()829     public boolean isValid() {
830         return mChan != null;
831     }
832 
update(int changeMask)833     void update(int changeMask) {
834         mDevice.update(this, changeMask);
835     }
836 }
837 
838