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