com.android.ddmlib.Client Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ddmlib Show documentation
Show all versions of ddmlib Show documentation
Library providing APIs to talk to Android devices
/*
* 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.annotations.NonNull;
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;
import java.util.concurrent.TimeUnit;
/**
* This represents a single client, usually a Dalvik VM process.
* This class gives access to basic client information, as well as methods to perform actions
* on the client.
* More detailed information, usually updated in real time, can be access through the
* {@link ClientData} class. Each Client
object has its own ClientData
* 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 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 mHeapInfoUpdateEnabled;
private boolean mHeapSegmentUpdateEnabled;
/*
* 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 = 800*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();
mConnState = ST_INIT;
mClientData = new ClientData(pid);
mThreadUpdateEnabled = DdmPreferences.getInitialThreadUpdate();
mHeapInfoUpdateEnabled = DdmPreferences.getInitialHeapUpdate();
mHeapSegmentUpdateEnabled = 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 true
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 true
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.
*/
@NonNull
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
}
}
/**
* Toggles method profiling state.
* @deprecated Use {@link #startMethodTracer()}, {@link #stopMethodTracer()},
* {@link #startSamplingProfiler(int, java.util.concurrent.TimeUnit)} or
* {@link #stopSamplingProfiler()} instead.
*/
public void toggleMethodProfiling() {
try {
switch (mClientData.getMethodProfilingStatus()) {
case TRACER_ON:
stopMethodTracer();
break;
case SAMPLER_ON:
stopSamplingProfiler();
break;
case OFF:
startMethodTracer();
break;
}
} catch (IOException e) {
Log.w("ddms", "Toggle method profiling failed");
// ignore
}
}
private int getProfileBufferSize() {
return DdmPreferences.getProfilerBufferSizeMb() * 1024 * 1024;
}
public void startMethodTracer() throws IOException {
boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
int bufferSize = getProfileBufferSize();
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*/);
}
}
public void stopMethodTracer() throws IOException {
boolean canStream = mClientData.hasFeature(ClientData.FEATURE_PROFILING_STREAMING);
if (canStream) {
HandleProfiling.sendMPSE(this);
} else {
HandleProfiling.sendMPRE(this);
}
}
public void startSamplingProfiler(int samplingInterval, TimeUnit timeUnit) throws IOException {
int bufferSize = getProfileBufferSize();
HandleProfiling.sendSPSS(this, bufferSize, samplingInterval, timeUnit);
}
public void stopSamplingProfiler() throws IOException {
HandleProfiling.sendSPSE(this);
}
public boolean startOpenGlTracing() {
boolean canTraceOpenGl = mClientData.hasFeature(ClientData.FEATURE_OPENGL_TRACING);
if (!canTraceOpenGl) {
return false;
}
try {
HandleViewDebug.sendStartGlTracing(this);
return true;
} catch (IOException e) {
Log.w("ddms", "Start OpenGL Tracing failed");
return false;
}
}
public boolean stopOpenGlTracing() {
boolean canTraceOpenGl = mClientData.hasFeature(ClientData.FEATURE_OPENGL_TRACING);
if (!canTraceOpenGl) {
return false;
}
try {
HandleViewDebug.sendStopGlTracing(this);
return true;
} catch (IOException e) {
Log.w("ddms", "Stop OpenGL Tracing failed");
return false;
}
}
/**
* Sends a request to the VM to send the enable status of the method profiling.
* This is asynchronous.
* 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 changeMask
* 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.
* If true
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) {
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.
* 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 changeMask
* containing the mask {@link #CHANGE_THREAD_DATA}.
*/
public void requestThreadUpdate() {
HandleThread.requestThreadUpdate(this);
}
/**
* Sends a thread stack trace update request. This is asynchronous.
* The thread info can be accessed by {@link ClientData#getThreads()} and
* {@link ThreadInfo#getStackTrace()}.
* The notification that the new data is available
* will be received through {@link IClientChangeListener#clientChanged(Client, int)}
* with a changeMask
containing the mask {@link #CHANGE_THREAD_STACKTRACE}.
*/
public void requestThreadStackTrace(int threadId) {
HandleThread.requestThreadStackCallRefresh(this, threadId);
}
/**
* Enables or disables the heap update.
* If true
, any GC will cause the client to send its heap information.
* The heap information can be accessed by {@link ClientData#getVmHeapData()}.
* The notification that the new data is available
* will be received through {@link IClientChangeListener#clientChanged(Client, int)}
* with a changeMask
containing the value {@link #CHANGE_HEAP_DATA}.
* @param enabled the enable flag
*/
public void setHeapUpdateEnabled(boolean enabled) {
setHeapInfoUpdateEnabled(enabled);
setHeapSegmentUpdateEnabled(enabled);
}
public void setHeapInfoUpdateEnabled(boolean enabled) {
mHeapInfoUpdateEnabled = enabled;
try {
HandleHeap.sendHPIF(this,
enabled ? HandleHeap.HPIF_WHEN_EVERY_GC : HandleHeap.HPIF_WHEN_NEVER);
} catch (IOException ioe) {
// ignore it here; client will clean up shortly
}
update(CHANGE_HEAP_MODE);
}
public void setHeapSegmentUpdateEnabled(boolean enabled) {
mHeapSegmentUpdateEnabled = enabled;
try {
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);
}
void initializeHeapUpdateStatus() throws IOException {
setHeapInfoUpdateEnabled(mHeapInfoUpdateEnabled);
}
/**
* Fires a single heap update.
*/
public void updateHeapInfo() {
try {
HandleHeap.sendHPIF(this, HandleHeap.HPIF_WHEN_NOW);
} catch (IOException ioe) {
// ignore it here; client will clean up shortly
}
}
/**
* Returns whether any heap update is enabled.
* @see #setHeapUpdateEnabled(boolean)
*/
public boolean isHeapUpdateEnabled() {
return mHeapInfoUpdateEnabled || mHeapSegmentUpdateEnabled;
}
/**
* Sends a native heap update request. this is asynchronous.
* 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 changeMask
* 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.
* If enabled, the VM will start tracking allocation information. 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.
* 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 changeMask
* 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 enable
* set to null
. This is asynchronous.
* 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 changeMask
* 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);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy