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