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