FileDocCategorySizeDatePackage
Client.javaAPI DocAndroid 1.5 API26359Wed May 06 22:41:08 BST 2009com.android.ddmlib

Client

public class Client extends Object
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()}.

Fields Summary
private static final int
SERVER_PROTOCOL_VERSION
public static final int
CHANGE_NAME
Client change bit mask: application name change
public static final int
CHANGE_DEBUGGER_INTEREST
Client change bit mask: debugger interest change
public static final int
CHANGE_PORT
Client change bit mask: debugger port change
public static final int
CHANGE_THREAD_MODE
Client change bit mask: thread update flag change
public static final int
CHANGE_THREAD_DATA
Client change bit mask: thread data updated
public static final int
CHANGE_HEAP_MODE
Client change bit mask: heap update flag change
public static final int
CHANGE_HEAP_DATA
Client change bit mask: head data updated
public static final int
CHANGE_NATIVE_HEAP_DATA
Client change bit mask: native heap data updated
public static final int
CHANGE_THREAD_STACKTRACE
Client change bit mask: thread stack trace updated
public static final int
CHANGE_HEAP_ALLOCATIONS
Client change bit mask: allocation information updated
public static final int
CHANGE_HEAP_ALLOCATION_STATUS
Client change bit mask: allocation information updated
public static final int
CHANGE_INFO
Client change bit mask: combination of {@link Client#CHANGE_NAME}, {@link Client#CHANGE_DEBUGGER_INTEREST}, and {@link Client#CHANGE_PORT}.
private SocketChannel
mChan
private Debugger
mDebugger
private int
mDebuggerListenPort
private HashMap
mOutstandingReqs
private ClientData
mClientData
private boolean
mThreadUpdateEnabled
private boolean
mHeapUpdateEnabled
private static final int
INITIAL_BUF_SIZE
private static final int
MAX_BUF_SIZE
private ByteBuffer
mReadBuffer
private static final int
WRITE_BUF_SIZE
private ByteBuffer
mWriteBuffer
private Device
mDevice
private int
mConnState
private static final int
ST_INIT
private static final int
ST_NOT_JDWP
private static final int
ST_AWAIT_SHAKE
private static final int
ST_NEED_DDM_PKT
private static final int
ST_NOT_DDM
private static final int
ST_READY
private static final int
ST_ERROR
private static final int
ST_DISCONNECTED
Constructors Summary
Client(Device device, SocketChannel chan, int pid)
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.



                                    
          
        mDevice = device;
        mChan = chan;

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

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

        mConnState = ST_INIT;

        mClientData = new ClientData(pid);
        
        mThreadUpdateEnabled = DdmPreferences.getInitialThreadUpdate();
        mHeapUpdateEnabled = DdmPreferences.getInitialHeapUpdate();
    
Methods Summary
private voidaddRequestId(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);
        }
    
voidclose(boolean notify)
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.

        Log.i("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);
    
synchronized booleanddmSeen()
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.

        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;
    
public voidenableAllocationTracker(boolean enable)
Enables or disables the Allocation tracker for this client.

If enabled, the VM will start tracking allocation informations. A call to {@link #requestAllocationDetails()} will make the VM sends the information about all the allocations that happened between the enabling and the request.

param
enable
see
#requestAllocationDetails()

        try {
            HandleHeap.sendREAE(this, enable);
        } catch (IOException e) {
            Log.e("ddmlib", e);
        }
    
public voidexecuteGarbageCollector()
Forces the client to execute its garbage collector.

        try {
            HandleHeap.sendHPGC(this);
        } catch (IOException ioe) {
            Log.w("ddms", "Send of HPGC message failed");
            // ignore
        }
    
voidforwardPacketToDebugger(JdwpPacket packet)
Forward the packet to the debugger (if still connected to one). Consumes the packet.


        Debugger dbg = mDebugger;

        if (dbg == null) {
            Log.i("ddms", "Discarding packet");
            packet.consume();
        } else {
            dbg.sendAndConsume(packet);
        }
    
public ClientDatagetClientData()
Returns the {@link ClientData} object containing this client information.

        return mClientData;
    
DebuggergetDebugger()
Return the Debugger object associated with this client.

        return mDebugger;
    
public intgetDebuggerListenPort()
Returns the debugger port for this client.

        return mDebuggerListenPort;
    
public DevicegetDevice()
Returns the {@link Device} on which this Client is running.

        return mDevice;
    
JdwpPacketgetJdwpPacket()
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.


        /*
         * 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.i("ddms",
                        "Good handshake from client, sending HELO to " + mClientData.getPid());
                    JdwpPacket.consumeHandshake(mReadBuffer);
                    mConnState = ST_NEED_DDM_PKT;
                    HandleHello.sendHELO(this, SERVER_PROTOCOL_VERSION);
                    // see if we have another packet in the buffer
                    return getJdwpPacket();
                case JdwpPacket.HANDSHAKE_BAD:
                    Log.i("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.i("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;
    
public booleanisDdmAware()
Returns true if the client VM is DDM-aware. Calling here is only allowed after the connection has been established.

        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;
        }
    
public booleanisDebuggerAttached()
Returns true if a debugger is currently attached to the client.

        return mDebugger.isDebuggerAttached();
    
public booleanisHeapUpdateEnabled()
Returns whether the heap update is enabled.

see
#setHeapUpdateEnabled(boolean)

        return mHeapUpdateEnabled;
    
ChunkHandlerisResponseToUs(int id)
Determine whether this is a response to a request we sent earlier. If so, return the ChunkHandler responsible.


        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;
    
public booleanisSelectedClient()
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)

        MonitorThread monitorThread = MonitorThread.getInstance();
        if (monitorThread != null) {
            return monitorThread.getSelectedClient() == this;
        }

        return false;
    
public booleanisThreadUpdateEnabled()
Returns whether the thread update is enabled.

        return mThreadUpdateEnabled;
    
public booleanisValid()
Returns whether this {@link Client} has a valid connection to the application VM.

        return mChan != null;
    
public voidkill()
Sends a kill message to the VM.

        try {
            HandleExit.sendEXIT(this, 1);
        } catch (IOException ioe) {
            Log.w("ddms", "Send of EXIT message failed");
            // ignore
        }
    
voidlistenForDebugger(int listenPort)
Tell the client to open a server socket channel and listen for connections on the specified port.

        mDebuggerListenPort = listenPort;
        mDebugger = new Debugger(this, listenPort);
    
voidpacketFailed(JdwpPacket reply)
An earlier request resulted in a failure. This is the expected response to a HELO message when talking to a non-DDM client.

        if (mConnState == ST_NEED_DDM_PKT) {
            Log.i("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");
        }
    
voidread()
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.


        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());
    
voidregister(java.nio.channels.Selector sel)
Registers the client with a Selector.

        if (mChan != null) {
            mChan.register(sel, SelectionKey.OP_READ, this);
        }
    
voidremoveRequestId(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);
    
public voidrequestAllocationDetails()
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}.

        try {
            HandleHeap.sendREAL(this);
        } catch (IOException e) {
            Log.e("ddmlib", e);
        }
    
public voidrequestAllocationStatus()
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}.

        try {
            HandleHeap.sendREAQ(this);
        } catch (IOException e) {
            Log.e("ddmlib", e);
        }   
    
public booleanrequestNativeHeapInformation()
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}.

        try {
            HandleNativeHeap.sendNHGT(this);
            return true;
        } catch (IOException e) {
            Log.e("ddmlib", e);
        }

        return false;
    
public voidrequestThreadStackTrace(int threadId)
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}.

        HandleThread.requestThreadStackCallRefresh(this, threadId);
    
public voidrequestThreadUpdate()
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}.

        HandleThread.requestThreadUpdate(this);
    
voidsendAndConsume(JdwpPacket packet)
Send a non-DDM packet to the client. Equivalent to sendAndConsume(packet, null).

        sendAndConsume(packet, null);
    
voidsendAndConsume(JdwpPacket packet, ChunkHandler replyHandler)
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.


        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;
            }
        }
    
booleansendHandshake()
Initiate the JDWP handshake. On failure, closes the socket and returns false.

        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;
    
public voidsetAsSelectedClient()
Sets the client to accept debugger connection on the "selected debugger port".

see
AndroidDebugBridge#setSelectedClient(Client)
see
DdmPreferences#setSelectedDebugPort(int)

        MonitorThread monitorThread = MonitorThread.getInstance();
        if (monitorThread != null) {
            monitorThread.setSelectedClient(this);
        }
    
public voidsetHeapUpdateEnabled(boolean enabled)
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

        mHeapUpdateEnabled = enabled;

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

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

        update(CHANGE_HEAP_MODE);
    
public voidsetThreadUpdateEnabled(boolean enabled)
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.

        mThreadUpdateEnabled = enabled;
        if (enabled == false) {
            mClientData.clearThreads();
        }

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

        update(CHANGE_THREAD_MODE);
    
public java.lang.StringtoString()
Returns a string representation of the {@link Client} object.

        return "[Client pid: " + mClientData.getPid() + "]";
    
voidupdate(int changeMask)

        mDevice.update(this, changeMask);