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