aboutsummaryrefslogtreecommitdiffstats
path: root/ddms/libs/ddmlib/src/com/android/ddmlib/Client.java
blob: 5b03462a2efb5a703190a216b7f93d4f0d2a131b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ddmlib;

import com.android.ddmlib.ClientData.MethodProfilingStatus;
import com.android.ddmlib.DebugPortManager.IDebugPortProvider;
import com.android.ddmlib.AndroidDebugBridge.IClientChangeListener;

import java.io.IOException;
import java.nio.BufferOverflowException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.HashMap;

/**
 * This represents a single client, usually a DAlvik VM process.
 * <p/>This class gives access to basic client information, as well as methods to perform actions
 * on the client.
 * <p/>More detailed information, usually updated in real time, can be access through the
 * {@link ClientData} class. Each <code>Client</code> object has its own <code>ClientData</code>
 * accessed through {@link #getClientData()}.
 */
public class Client {

    private static final int SERVER_PROTOCOL_VERSION = 1;

    /** Client change bit mask: application name change */
    public static final int CHANGE_NAME                       = 0x0001;
    /** Client change bit mask: debugger status change */
    public static final int CHANGE_DEBUGGER_STATUS            = 0x0002;
    /** Client change bit mask: debugger port change */
    public static final int CHANGE_PORT                       = 0x0004;
    /** Client change bit mask: thread update flag change */
    public static final int CHANGE_THREAD_MODE                = 0x0008;
    /** Client change bit mask: thread data updated */
    public static final int CHANGE_THREAD_DATA                = 0x0010;
    /** Client change bit mask: heap update flag change */
    public static final int CHANGE_HEAP_MODE                  = 0x0020;
    /** Client change bit mask: head data updated */
    public static final int CHANGE_HEAP_DATA                  = 0x0040;
    /** Client change bit mask: native heap data updated */
    public static final int CHANGE_NATIVE_HEAP_DATA           = 0x0080;
    /** Client change bit mask: thread stack trace updated */
    public static final int CHANGE_THREAD_STACKTRACE          = 0x0100;
    /** Client change bit mask: allocation information updated */
    public static final int CHANGE_HEAP_ALLOCATIONS           = 0x0200;
    /** Client change bit mask: allocation information updated */
    public static final int CHANGE_HEAP_ALLOCATION_STATUS     = 0x0400;
    /** Client change bit mask: allocation information updated */
    public static final int CHANGE_METHOD_PROFILING_STATUS    = 0x0800;

    /** Client change bit mask: combination of {@link Client#CHANGE_NAME},
     * {@link Client#CHANGE_DEBUGGER_STATUS}, and {@link Client#CHANGE_PORT}.
     */
    public static final int CHANGE_INFO = CHANGE_NAME | CHANGE_DEBUGGER_STATUS | CHANGE_PORT;

    private SocketChannel mChan;

    // debugger we're associated with, if any
    private Debugger mDebugger;
    private int mDebuggerListenPort;

    // list of IDs for requests we have sent to the client
    private HashMap<Integer,ChunkHandler> mOutstandingReqs;

    // chunk handlers stash state data in here
    private ClientData mClientData;

    // User interface state.  Changing the value causes a message to be
    // sent to the client.
    private boolean mThreadUpdateEnabled;
    private boolean mHeapUpdateEnabled;

    /*
     * Read/write buffers.  We can get large quantities of data from the
     * client, e.g. the response to a "give me the list of all known classes"
     * request from the debugger.  Requests from the debugger, and from us,
     * are much smaller.
     *
     * Pass-through debugger traffic is sent without copying.  "mWriteBuffer"
     * is only used for data generated within Client.
     */
    private static final int INITIAL_BUF_SIZE = 2*1024;
    private static final int MAX_BUF_SIZE = 200*1024*1024;
    private ByteBuffer mReadBuffer;

    private static final int WRITE_BUF_SIZE = 256;
    private ByteBuffer mWriteBuffer;

    private Device mDevice;

    private int mConnState;

    private static final int ST_INIT         = 1;
    private static final int ST_NOT_JDWP     = 2;
    private static final int ST_AWAIT_SHAKE  = 10;
    private static final int ST_NEED_DDM_PKT = 11;
    private static final int ST_NOT_DDM      = 12;
    private static final int ST_READY        = 13;
    private static final int ST_ERROR        = 20;
    private static final int ST_DISCONNECTED = 21;


    /**
     * Create an object for a new client connection.
     *
     * @param device the device this client belongs to
     * @param chan the connected {@link SocketChannel}.
     * @param pid the client pid.
     */
    Client(Device device, SocketChannel chan, int pid) {
        mDevice = device;
        mChan = chan;

        mReadBuffer = ByteBuffer.allocate(INITIAL_BUF_SIZE);
        mWriteBuffer = ByteBuffer.allocate(WRITE_BUF_SIZE);

        mOutstandingReqs = new HashMap<Integer,ChunkHandler>();

        mConnState = ST_INIT;

        mClientData = new ClientData(pid);

        mThreadUpdateEnabled = DdmPreferences.getInitialThreadUpdate();
        mHeapUpdateEnabled = DdmPreferences.getInitialHeapUpdate();
    }

    /**
     * Returns a string representation of the {@link Client} object.
     */
    @Override
    public String toString() {
        return "[Client pid: " + mClientData.getPid() + "]";
    }

    /**
     * Returns the {@link IDevice} on which this Client is running.
     */
    public IDevice getDevice() {
        return mDevice;
    }

    /** Returns the {@link Device} on which this Client is running.
     */
    Device getDeviceImpl() {
        return mDevice;
    }

    /**
     * Returns the debugger port for this client.
     */
    public int getDebuggerListenPort() {
        return mDebuggerListenPort;
    }

    /**
     * Returns <code>true</code> if the client VM is DDM-aware.
     *
     * Calling here is only allowed after the connection has been
     * established.
     */
    public boolean isDdmAware() {
        switch (mConnState) {
            case ST_INIT:
            case ST_NOT_JDWP:
            case ST_AWAIT_SHAKE:
            case ST_NEED_DDM_PKT:
            case ST_NOT_DDM:
            case ST_ERROR:
            case ST_DISCONNECTED:
                return false;
            case ST_READY:
                return true;
            default:
                assert false;
                return false;
        }
    }

    /**
     * Returns <code>true</code> if a debugger is currently attached to the client.
     */
    public boolean isDebuggerAttached() {
        return mDebugger.isDebuggerAttached();
    }

    /**
     * Return the Debugger object associated with this client.
     */
    Debugger getDebugger() {
        return mDebugger;
    }

    /**
     * Returns the {@link ClientData} object containing this client information.
     */
    public ClientData getClientData() {
        return mClientData;
    }

    /**
     * Forces the client to execute its garbage collector.
     */
    public void executeGarbageCollector() {
        try {
            HandleHeap.sendHPGC(this);
        } catch (IOException ioe) {
            Log.w("ddms", "Send of HPGC message failed");
            // ignore
        }
    }

    /**
     * Makes the VM dump an HPROF file
     */
    public void dumpHprof() {
        boolean canStream = mClientData.hasFeature(ClientData.FEATURE_HPROF_STREAMING);
        try {
            if (canStream) {
                HandleHeap.sendHPDS(this);
            } else {
                String file = "/sdcard/" + mClientData.getClientDescription().replaceAll(
                        "\\:.*", "") + ".hprof";
                HandleHeap.sendHPDU(this, file);
            }
        } catch (IOException e) {
            Log.w("ddms", "Send of HPDU message failed");
            // ignore
        }
    }

    public void toggleMethodProfiling() {
        boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
        try {
            if (mClientData.getMethodProfilingStatus() == MethodProfilingStatus.ON) {
                if (canStream) {
                    HandleProfiling.sendMPSE(this);
                } else {
                    HandleProfiling.sendMPRE(this);
                }
            } else {
                int bufferSize = DdmPreferences.getProfilerBufferSizeMb() * 1024 * 1024;
                if (canStream) {
                    HandleProfiling.sendMPSS(this, bufferSize, 0 /*flags*/);
                } else {
                    String file = "/sdcard/" +
                        mClientData.getClientDescription().replaceAll("\\:.*", "") +
                        DdmConstants.DOT_TRACE;
                    HandleProfiling.sendMPRS(this, file, bufferSize, 0 /*flags*/);
                }
            }
        } catch (IOException e) {
            Log.w("ddms", "Toggle method profiling failed");
            // ignore
        }
    }

    /**
     * Sends a request to the VM to send the enable status of the method profiling.
     * This is asynchronous.
     * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
     * The notification that the new status is available will be received through
     * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
     * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
     */
    public void requestMethodProfilingStatus() {
        try {
            HandleHeap.sendREAQ(this);
        } catch (IOException e) {
            Log.e("ddmlib", e);
        }
    }


    /**
     * Enables or disables the thread update.
     * <p/>If <code>true</code> the VM will be able to send thread information. Thread information
     * must be requested with {@link #requestThreadUpdate()}.
     * @param enabled the enable flag.
     */
    public void setThreadUpdateEnabled(boolean enabled) {
        mThreadUpdateEnabled = enabled;
        if (enabled == false) {
            mClientData.clearThreads();
        }

        try {
            HandleThread.sendTHEN(this, enabled);
        } catch (IOException ioe) {
            // ignore it here; client will clean up shortly
            ioe.printStackTrace();
        }

        update(CHANGE_THREAD_MODE);
    }

    /**
     * Returns whether the thread update is enabled.
     */
    public boolean isThreadUpdateEnabled() {
        return mThreadUpdateEnabled;
    }

    /**
     * Sends a thread update request. This is asynchronous.
     * <p/>The thread info can be accessed by {@link ClientData#getThreads()}. The notification
     * that the new data is available will be received through
     * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
     * containing the mask {@link #CHANGE_THREAD_DATA}.
     */
    public void requestThreadUpdate() {
        HandleThread.requestThreadUpdate(this);
    }

    /**
     * Sends a thread stack trace update request. This is asynchronous.
     * <p/>The thread info can be accessed by {@link ClientData#getThreads()} and
     * {@link ThreadInfo#getStackTrace()}.
     * <p/>The notification that the new data is available
     * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
     * with a <code>changeMask</code> containing the mask {@link #CHANGE_THREAD_STACKTRACE}.
     */
    public void requestThreadStackTrace(int threadId) {
        HandleThread.requestThreadStackCallRefresh(this, threadId);
    }

    /**
     * Enables or disables the heap update.
     * <p/>If <code>true</code>, any GC will cause the client to send its heap information.
     * <p/>The heap information can be accessed by {@link ClientData#getVmHeapData()}.
     * <p/>The notification that the new data is available
     * will be received through {@link IClientChangeListener#clientChanged(Client, int)}
     * with a <code>changeMask</code> containing the value {@link #CHANGE_HEAP_DATA}.
     * @param enabled the enable flag
     */
    public void setHeapUpdateEnabled(boolean enabled) {
        mHeapUpdateEnabled = enabled;

        try {
            HandleHeap.sendHPIF(this,
                    enabled ? HandleHeap.HPIF_WHEN_EVERY_GC : HandleHeap.HPIF_WHEN_NEVER);

            HandleHeap.sendHPSG(this,
                    enabled ? HandleHeap.WHEN_GC : HandleHeap.WHEN_DISABLE,
                    HandleHeap.WHAT_MERGE);
        } catch (IOException ioe) {
            // ignore it here; client will clean up shortly
        }

        update(CHANGE_HEAP_MODE);
    }

    /**
     * Returns whether the heap update is enabled.
     * @see #setHeapUpdateEnabled(boolean)
     */
    public boolean isHeapUpdateEnabled() {
        return mHeapUpdateEnabled;
    }

    /**
     * Sends a native heap update request. this is asynchronous.
     * <p/>The native heap info can be accessed by {@link ClientData#getNativeAllocationList()}.
     * The notification that the new data is available will be received through
     * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
     * containing the mask {@link #CHANGE_NATIVE_HEAP_DATA}.
     */
    public boolean requestNativeHeapInformation() {
        try {
            HandleNativeHeap.sendNHGT(this);
            return true;
        } catch (IOException e) {
            Log.e("ddmlib", e);
        }

        return false;
    }

    /**
     * Enables or disables the Allocation tracker for this client.
     * <p/>If enabled, the VM will start tracking allocation informations. A call to
     * {@link #requestAllocationDetails()} will make the VM sends the information about all the
     * allocations that happened between the enabling and the request.
     * @param enable
     * @see #requestAllocationDetails()
     */
    public void enableAllocationTracker(boolean enable) {
        try {
            HandleHeap.sendREAE(this, enable);
        } catch (IOException e) {
            Log.e("ddmlib", e);
        }
    }

    /**
     * Sends a request to the VM to send the enable status of the allocation tracking.
     * This is asynchronous.
     * <p/>The allocation status can be accessed by {@link ClientData#getAllocationStatus()}.
     * The notification that the new status is available will be received through
     * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
     * containing the mask {@link #CHANGE_HEAP_ALLOCATION_STATUS}.
     */
    public void requestAllocationStatus() {
        try {
            HandleHeap.sendREAQ(this);
        } catch (IOException e) {
            Log.e("ddmlib", e);
        }
    }

    /**
     * Sends a request to the VM to send the information about all the allocations that have
     * happened since the call to {@link #enableAllocationTracker(boolean)} with <var>enable</var>
     * set to <code>null</code>. This is asynchronous.
     * <p/>The allocation information can be accessed by {@link ClientData#getAllocations()}.
     * The notification that the new data is available will be received through
     * {@link IClientChangeListener#clientChanged(Client, int)} with a <code>changeMask</code>
     * containing the mask {@link #CHANGE_HEAP_ALLOCATIONS}.
     */
    public void requestAllocationDetails() {
        try {
            HandleHeap.sendREAL(this);
        } catch (IOException e) {
            Log.e("ddmlib", e);
        }
    }

    /**
     * Sends a kill message to the VM.
     */
    public void kill() {
        try {
            HandleExit.sendEXIT(this, 1);
        } catch (IOException ioe) {
            Log.w("ddms", "Send of EXIT message failed");
            // ignore
        }
    }

    /**
     * Registers the client with a Selector.
     */
    void register(Selector sel) throws IOException {
        if (mChan != null) {
            mChan.register(sel, SelectionKey.OP_READ, this);
        }
    }

    /**
     * Sets the client to accept debugger connection on the "selected debugger port".
     *
     * @see AndroidDebugBridge#setSelectedClient(Client)
     * @see DdmPreferences#setSelectedDebugPort(int)
     */
    public void setAsSelectedClient() {
        MonitorThread monitorThread = MonitorThread.getInstance();
        if (monitorThread != null) {
            monitorThread.setSelectedClient(this);
        }
    }

    /**
     * Returns whether this client is the current selected client, accepting debugger connection
     * on the "selected debugger port".
     *
     * @see #setAsSelectedClient()
     * @see AndroidDebugBridge#setSelectedClient(Client)
     * @see DdmPreferences#setSelectedDebugPort(int)
     */
    public boolean isSelectedClient() {
        MonitorThread monitorThread = MonitorThread.getInstance();
        if (monitorThread != null) {
            return monitorThread.getSelectedClient() == this;
        }

        return false;
    }

    /**
     * Tell the client to open a server socket channel and listen for
     * connections on the specified port.
     */
    void listenForDebugger(int listenPort) throws IOException {
        mDebuggerListenPort = listenPort;
        mDebugger = new Debugger(this, listenPort);
    }

    /**
     * Initiate the JDWP handshake.
     *
     * On failure, closes the socket and returns false.
     */
    boolean sendHandshake() {
        assert mWriteBuffer.position() == 0;

        try {
            // assume write buffer can hold 14 bytes
            JdwpPacket.putHandshake(mWriteBuffer);
            int expectedLen = mWriteBuffer.position();
            mWriteBuffer.flip();
            if (mChan.write(mWriteBuffer) != expectedLen)
                throw new IOException("partial handshake write");
        }
        catch (IOException ioe) {
            Log.e("ddms-client", "IO error during handshake: " + ioe.getMessage());
            mConnState = ST_ERROR;
            close(true /* notify */);
            return false;
        }
        finally {
            mWriteBuffer.clear();
        }

        mConnState = ST_AWAIT_SHAKE;

        return true;
    }


    /**
     * Send a non-DDM packet to the client.
     *
     * Equivalent to sendAndConsume(packet, null).
     */
    void sendAndConsume(JdwpPacket packet) throws IOException {
        sendAndConsume(packet, null);
    }

    /**
     * Send a DDM packet to the client.
     *
     * Ideally, we can do this with a single channel write.  If that doesn't
     * happen, we have to prevent anybody else from writing to the channel
     * until this packet completes, so we synchronize on the channel.
     *
     * Another goal is to avoid unnecessary buffer copies, so we write
     * directly out of the JdwpPacket's ByteBuffer.
     */
    void sendAndConsume(JdwpPacket packet, ChunkHandler replyHandler)
        throws IOException {

        if (mChan == null) {
            // can happen for e.g. THST packets
            Log.v("ddms", "Not sending packet -- client is closed");
            return;
        }

        if (replyHandler != null) {
            /*
             * Add the ID to the list of outstanding requests.  We have to do
             * this before sending the packet, in case the response comes back
             * before our thread returns from the packet-send function.
             */
            addRequestId(packet.getId(), replyHandler);
        }

        synchronized (mChan) {
            try {
                packet.writeAndConsume(mChan);
            }
            catch (IOException ioe) {
                removeRequestId(packet.getId());
                throw ioe;
            }
        }
    }

    /**
     * Forward the packet to the debugger (if still connected to one).
     *
     * Consumes the packet.
     */
    void forwardPacketToDebugger(JdwpPacket packet)
        throws IOException {

        Debugger dbg = mDebugger;

        if (dbg == null) {
            Log.d("ddms", "Discarding packet");
            packet.consume();
        } else {
            dbg.sendAndConsume(packet);
        }
    }

    /**
     * Read data from our channel.
     *
     * This is called when data is known to be available, and we don't yet
     * have a full packet in the buffer.  If the buffer is at capacity,
     * expand it.
     */
    void read()
        throws IOException, BufferOverflowException {

        int count;

        if (mReadBuffer.position() == mReadBuffer.capacity()) {
            if (mReadBuffer.capacity() * 2 > MAX_BUF_SIZE) {
                Log.e("ddms", "Exceeded MAX_BUF_SIZE!");
                throw new BufferOverflowException();
            }
            Log.d("ddms", "Expanding read buffer to "
                + mReadBuffer.capacity() * 2);

            ByteBuffer newBuffer = ByteBuffer.allocate(mReadBuffer.capacity() * 2);

            // copy entire buffer to new buffer
            mReadBuffer.position(0);
            newBuffer.put(mReadBuffer);  // leaves "position" at end of copied

            mReadBuffer = newBuffer;
        }

        count = mChan.read(mReadBuffer);
        if (count < 0)
            throw new IOException("read failed");

        if (Log.Config.LOGV) Log.v("ddms", "Read " + count + " bytes from " + this);
        //Log.hexDump("ddms", Log.DEBUG, mReadBuffer.array(),
        //    mReadBuffer.arrayOffset(), mReadBuffer.position());
    }

    /**
     * Return information for the first full JDWP packet in the buffer.
     *
     * If we don't yet have a full packet, return null.
     *
     * If we haven't yet received the JDWP handshake, we watch for it here
     * and consume it without admitting to have done so.  Upon receipt
     * we send out the "HELO" message, which is why this can throw an
     * IOException.
     */
    JdwpPacket getJdwpPacket() throws IOException {

        /*
         * On entry, the data starts at offset 0 and ends at "position".
         * "limit" is set to the buffer capacity.
         */
        if (mConnState == ST_AWAIT_SHAKE) {
            /*
             * The first thing we get from the client is a response to our
             * handshake.  It doesn't look like a packet, so we have to
             * handle it specially.
             */
            int result;

            result = JdwpPacket.findHandshake(mReadBuffer);
            //Log.v("ddms", "findHand: " + result);
            switch (result) {
                case JdwpPacket.HANDSHAKE_GOOD:
                    Log.d("ddms",
                        "Good handshake from client, sending HELO to " + mClientData.getPid());
                    JdwpPacket.consumeHandshake(mReadBuffer);
                    mConnState = ST_NEED_DDM_PKT;
                    HandleHello.sendHelloCommands(this, SERVER_PROTOCOL_VERSION);
                    // see if we have another packet in the buffer
                    return getJdwpPacket();
                case JdwpPacket.HANDSHAKE_BAD:
                    Log.d("ddms", "Bad handshake from client");
                    if (MonitorThread.getInstance().getRetryOnBadHandshake()) {
                        // we should drop the client, but also attempt to reopen it.
                        // This is done by the DeviceMonitor.
                        mDevice.getMonitor().addClientToDropAndReopen(this,
                                IDebugPortProvider.NO_STATIC_PORT);
                    } else {
                        // mark it as bad, close the socket, and don't retry
                        mConnState = ST_NOT_JDWP;
                        close(true /* notify */);
                    }
                    break;
                case JdwpPacket.HANDSHAKE_NOTYET:
                    Log.d("ddms", "No handshake from client yet.");
                    break;
                default:
                    Log.e("ddms", "Unknown packet while waiting for client handshake");
            }
            return null;
        } else if (mConnState == ST_NEED_DDM_PKT ||
            mConnState == ST_NOT_DDM ||
            mConnState == ST_READY) {
            /*
             * Normal packet traffic.
             */
            if (mReadBuffer.position() != 0) {
                if (Log.Config.LOGV) Log.v("ddms",
                    "Checking " + mReadBuffer.position() + " bytes");
            }
            return JdwpPacket.findPacket(mReadBuffer);
        } else {
            /*
             * Not expecting data when in this state.
             */
            Log.e("ddms", "Receiving data in state = " + mConnState);
        }

        return null;
    }

    /*
     * Add the specified ID to the list of request IDs for which we await
     * a response.
     */
    private void addRequestId(int id, ChunkHandler handler) {
        synchronized (mOutstandingReqs) {
            if (Log.Config.LOGV) Log.v("ddms",
                "Adding req 0x" + Integer.toHexString(id) +" to set");
            mOutstandingReqs.put(id, handler);
        }
    }

    /*
     * Remove the specified ID from the list, if present.
     */
    void removeRequestId(int id) {
        synchronized (mOutstandingReqs) {
            if (Log.Config.LOGV) Log.v("ddms",
                "Removing req 0x" + Integer.toHexString(id) + " from set");
            mOutstandingReqs.remove(id);
        }

        //Log.w("ddms", "Request " + Integer.toHexString(id)
        //    + " could not be removed from " + this);
    }

    /**
     * Determine whether this is a response to a request we sent earlier.
     * If so, return the ChunkHandler responsible.
     */
    ChunkHandler isResponseToUs(int id) {

        synchronized (mOutstandingReqs) {
            ChunkHandler handler = mOutstandingReqs.get(id);
            if (handler != null) {
                if (Log.Config.LOGV) Log.v("ddms",
                    "Found 0x" + Integer.toHexString(id)
                    + " in request set - " + handler);
                return handler;
            }
        }

        return null;
    }

    /**
     * An earlier request resulted in a failure.  This is the expected
     * response to a HELO message when talking to a non-DDM client.
     */
    void packetFailed(JdwpPacket reply) {
        if (mConnState == ST_NEED_DDM_PKT) {
            Log.d("ddms", "Marking " + this + " as non-DDM client");
            mConnState = ST_NOT_DDM;
        } else if (mConnState != ST_NOT_DDM) {
            Log.w("ddms", "WEIRD: got JDWP failure packet on DDM req");
        }
    }

    /**
     * The MonitorThread calls this when it sees a DDM request or reply.
     * If we haven't seen a DDM packet before, we advance the state to
     * ST_READY and return "false".  Otherwise, just return true.
     *
     * The idea is to let the MonitorThread know when we first see a DDM
     * packet, so we can send a broadcast to the handlers when a client
     * connection is made.  This method is synchronized so that we only
     * send the broadcast once.
     */
    synchronized boolean ddmSeen() {
        if (mConnState == ST_NEED_DDM_PKT) {
            mConnState = ST_READY;
            return false;
        } else if (mConnState != ST_READY) {
            Log.w("ddms", "WEIRD: in ddmSeen with state=" + mConnState);
        }
        return true;
    }

    /**
     * Close the client socket channel.  If there is a debugger associated
     * with us, close that too.
     *
     * Closing a channel automatically unregisters it from the selector.
     * However, we have to iterate through the selector loop before it
     * actually lets them go and allows the file descriptors to close.
     * The caller is expected to manage that.
     * @param notify Whether or not to notify the listeners of a change.
     */
    void close(boolean notify) {
        Log.d("ddms", "Closing " + this.toString());

        mOutstandingReqs.clear();

        try {
            if (mChan != null) {
                mChan.close();
                mChan = null;
            }

            if (mDebugger != null) {
                mDebugger.close();
                mDebugger = null;
            }
        }
        catch (IOException ioe) {
            Log.w("ddms", "failed to close " + this);
            // swallow it -- not much else to do
        }

        mDevice.removeClient(this, notify);
    }

    /**
     * Returns whether this {@link Client} has a valid connection to the application VM.
     */
    public boolean isValid() {
        return mChan != null;
    }

    void update(int changeMask) {
        mDevice.update(this, changeMask);
    }
}