am 3385faf4: Add AdbTest sample program

* commit '3385faf4e60e1ac4f4cf387702ef3be211374ef2':
  Add AdbTest sample program
diff --git a/samples/USB/AdbTest/Android.mk b/samples/USB/AdbTest/Android.mk
new file mode 100644
index 0000000..bfb9fa8
--- /dev/null
+++ b/samples/USB/AdbTest/Android.mk
@@ -0,0 +1,12 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := samples
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := AdbTest
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_PACKAGE)
diff --git a/samples/USB/AdbTest/AndroidManifest.xml b/samples/USB/AdbTest/AndroidManifest.xml
new file mode 100644
index 0000000..9690ccf
--- /dev/null
+++ b/samples/USB/AdbTest/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.adb">
+
+    <uses-feature android:name="android.hardware.usb.host" />
+    <uses-sdk android:minSdkVersion="12" />
+
+    <application>
+        <activity android:name="AdbTestActivity" android:label="ADB Test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+
+            <intent-filter>
+                <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+            </intent-filter>
+
+            <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
+                android:resource="@xml/device_filter" />
+        </activity>
+    </application>
+
+</manifest>
diff --git a/samples/USB/AdbTest/README.txt b/samples/USB/AdbTest/README.txt
new file mode 100644
index 0000000..3fd9cbb
--- /dev/null
+++ b/samples/USB/AdbTest/README.txt
@@ -0,0 +1,11 @@
+AdbTest is a sample program that implements a subset of the adb USB protocol.
+Currently it only implements the "adb logcat" command and displays the log
+output in a text view and only allows connecting to one device at a time.
+However the support classes are structured in a way that would allow
+connecting to multiple devices and running multiple adb commands simultaneously.
+
+This program serves as an example of the following USB host features:
+
+- Matching devices based on interface class, subclass and protocol (see device_filter.xml)
+
+- Asynchronous IO on bulk endpoints
\ No newline at end of file
diff --git a/samples/USB/AdbTest/_index.html b/samples/USB/AdbTest/_index.html
new file mode 100644
index 0000000..424ee5b
--- /dev/null
+++ b/samples/USB/AdbTest/_index.html
@@ -0,0 +1,13 @@
+<p>AdbTest is a sample program that implements a subset of the <code>adb</code> USB protocol.
+Currently it only implements the <code>adb logcat</code> command and displays the log
+output in a text view and only allows connecting to one device at a time.
+However, the support classes are structured in a way that would allow
+connecting to multiple devices and running multiple <code>adb</code> commands simultaneously.</p>
+
+<p>This program serves as an example of the following USB host features:</p>
+
+<ul> 
+<li>Matching devices based on interface class, subclass and protocol (see <code>device_filter.xml</code>)</li>
+
+<li>Asynchronous IO on bulk endpoints</li>
+</ul>
\ No newline at end of file
diff --git a/samples/USB/AdbTest/default.properties b/samples/USB/AdbTest/default.properties
new file mode 100644
index 0000000..3ac2523
--- /dev/null
+++ b/samples/USB/AdbTest/default.properties
@@ -0,0 +1,11 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-12
diff --git a/samples/USB/AdbTest/res/layout/adb.xml b/samples/USB/AdbTest/res/layout/adb.xml
new file mode 100644
index 0000000..10994d5
--- /dev/null
+++ b/samples/USB/AdbTest/res/layout/adb.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical"
+    >
+
+    <TextView android:id="@+id/log"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginTop="25dp"
+        android:textSize="18sp"
+        android:textColor="#ffffffff"
+        />
+
+</LinearLayout>
+
+
diff --git a/samples/USB/AdbTest/res/xml/device_filter.xml b/samples/USB/AdbTest/res/xml/device_filter.xml
new file mode 100644
index 0000000..bc6fa04
--- /dev/null
+++ b/samples/USB/AdbTest/res/xml/device_filter.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<resources>
+    <usb-device class="255" subclass="66" protocol="1" />
+</resources>
diff --git a/samples/USB/AdbTest/src/com/android/adb/AdbDevice.java b/samples/USB/AdbTest/src/com/android/adb/AdbDevice.java
new file mode 100644
index 0000000..f793ce4
--- /dev/null
+++ b/samples/USB/AdbTest/src/com/android/adb/AdbDevice.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2011 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.adb;
+
+import android.hardware.usb.UsbConstants;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbRequest;
+import android.util.SparseArray;
+
+import java.util.LinkedList;
+
+/* This class represents a USB device that supports the adb protocol. */
+public class AdbDevice {
+
+    private final AdbTestActivity mActivity;
+    private final UsbDeviceConnection mDeviceConnection;
+    private final UsbEndpoint mEndpointOut;
+    private final UsbEndpoint mEndpointIn;
+
+    private String mSerial;
+
+    // pool of requests for the OUT endpoint
+    private final LinkedList<UsbRequest> mOutRequestPool = new LinkedList<UsbRequest>();
+    // pool of requests for the IN endpoint
+    private final LinkedList<UsbRequest> mInRequestPool = new LinkedList<UsbRequest>();
+    // list of currently opened sockets
+    private final SparseArray<AdbSocket> mSockets = new SparseArray<AdbSocket>();
+    private int mNextSocketId = 1;
+
+    private final WaiterThread mWaiterThread = new WaiterThread();
+
+    public AdbDevice(AdbTestActivity activity, UsbDeviceConnection connection,
+            UsbInterface intf) {
+        mActivity = activity;
+        mDeviceConnection = connection;
+        mSerial = connection.getSerial();
+
+        UsbEndpoint epOut = null;
+        UsbEndpoint epIn = null;
+        // look for our bulk endpoints
+        for (int i = 0; i < intf.getEndpointCount(); i++) {
+            UsbEndpoint ep = intf.getEndpoint(i);
+            if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
+                if (ep.getDirection() == UsbConstants.USB_DIR_OUT) {
+                    epOut = ep;
+                } else {
+                    epIn = ep;
+                }
+            }
+        }
+        if (epOut == null || epIn == null) {
+            throw new IllegalArgumentException("not all endpoints found");
+        }
+        mEndpointOut = epOut;
+        mEndpointIn = epIn;
+    }
+
+    // return device serial number
+    public String getSerial() {
+        return mSerial;
+    }
+
+    // get an OUT request from our pool
+    public UsbRequest getOutRequest() {
+        synchronized(mOutRequestPool) {
+            if (mOutRequestPool.isEmpty()) {
+                UsbRequest request = new UsbRequest();
+                request.initialize(mDeviceConnection, mEndpointOut);
+                return request;
+            } else {
+                return mOutRequestPool.removeFirst();
+            }
+        }
+    }
+
+    // return an OUT request to the pool
+    public void releaseOutRequest(UsbRequest request) {
+        synchronized (mOutRequestPool) {
+            mOutRequestPool.add(request);
+        }
+    }
+
+    // get an IN request from the pool
+    public UsbRequest getInRequest() {
+        synchronized(mInRequestPool) {
+            if (mInRequestPool.isEmpty()) {
+                UsbRequest request = new UsbRequest();
+                request.initialize(mDeviceConnection, mEndpointIn);
+                return request;
+            } else {
+                return mInRequestPool.removeFirst();
+            }
+        }
+    }
+
+    public void start() {
+        mWaiterThread.start();
+        connect();
+    }
+
+    public AdbSocket openSocket(String destination) {
+        AdbSocket socket;
+        synchronized (mSockets) {
+            int id = mNextSocketId++;
+            socket = new AdbSocket(this, id);
+            mSockets.put(id, socket);
+        }
+        if (socket.open(destination)) {
+            return socket;
+        } else {
+            return null;
+        }
+    }
+
+    private AdbSocket getSocket(int id) {
+        synchronized (mSockets) {
+            return mSockets.get(id);
+        }
+    }
+
+    public void socketClosed(AdbSocket socket) {
+        synchronized (mSockets) {
+            mSockets.remove(socket.getId());
+        }
+    }
+
+    // send a connect command
+    private void connect() {
+        AdbMessage message = new AdbMessage();
+        message.set(AdbMessage.A_CNXN, AdbMessage.A_VERSION, AdbMessage.MAX_PAYLOAD, "host::\0");
+        message.write(this);
+    }
+
+    // handle connect response
+    private void handleConnect(AdbMessage message) {
+        if (message.getDataString().startsWith("device:")) {
+            log("connected");
+            mActivity.deviceOnline(this);
+        }
+    }
+
+    public void stop() {
+        synchronized (mWaiterThread) {
+            mWaiterThread.mStop = true;
+        }
+    }
+
+    // dispatch a message from the device
+    void dispatchMessage(AdbMessage message) {
+        int command = message.getCommand();
+        switch (command) {
+            case AdbMessage.A_SYNC:
+                log("got A_SYNC");
+                break;
+            case AdbMessage.A_CNXN:
+                handleConnect(message);
+                break;
+            case AdbMessage.A_OPEN:
+            case AdbMessage.A_OKAY:
+            case AdbMessage.A_CLSE:
+            case AdbMessage.A_WRTE:
+                AdbSocket socket = getSocket(message.getArg1());
+                if (socket == null) {
+                    log("ERROR socket not found");
+                } else {
+                    socket.handleMessage(message);
+                }
+                break;
+        }
+    }
+
+    void log(String s) {
+        mActivity.log(s);
+    }
+
+
+    private class WaiterThread extends Thread {
+        public boolean mStop;
+
+        public void run() {
+            // start out with a command read
+            AdbMessage currentCommand = new AdbMessage();
+            AdbMessage currentData = null;
+            // FIXME error checking
+            currentCommand.readCommand(getInRequest());
+
+            while (true) {
+                synchronized (this) {
+                    if (mStop) {
+                        return;
+                    }
+                }
+                UsbRequest request = mDeviceConnection.requestWait();
+                if (request == null) {
+                    break;
+                }
+
+                AdbMessage message = (AdbMessage)request.getClientData();
+                request.setClientData(null);
+                AdbMessage messageToDispatch = null;
+
+                if (message == currentCommand) {
+                    int dataLength = message.getDataLength();
+                    // read data if length > 0
+                    if (dataLength > 0) {
+                        message.readData(getInRequest(), dataLength);
+                        currentData = message;
+                    } else {
+                        messageToDispatch = message;
+                    }
+                    currentCommand = null;
+                } else if (message == currentData) {
+                    messageToDispatch = message;
+                    currentData = null;
+                }
+
+                if (messageToDispatch != null) {
+                    // queue another read first
+                    currentCommand = new AdbMessage();
+                    currentCommand.readCommand(getInRequest());
+
+                    // then dispatch the current message
+                    dispatchMessage(messageToDispatch);
+                }
+
+                // put request back into the appropriate pool
+                if (request.getEndpoint() == mEndpointOut) {
+                    releaseOutRequest(request);
+                } else {
+                    synchronized (mInRequestPool) {
+                        mInRequestPool.add(request);
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/samples/USB/AdbTest/src/com/android/adb/AdbMessage.java b/samples/USB/AdbTest/src/com/android/adb/AdbMessage.java
new file mode 100644
index 0000000..1dbb136
--- /dev/null
+++ b/samples/USB/AdbTest/src/com/android/adb/AdbMessage.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011 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.adb;
+
+import android.hardware.usb.UsbRequest;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+
+/* This class encapsulates and adb command packet */
+public class AdbMessage {
+
+    // command names
+    public static final int A_SYNC = 0x434e5953;
+    public static final int A_CNXN = 0x4e584e43;
+    public static final int A_OPEN = 0x4e45504f;
+    public static final int A_OKAY = 0x59414b4f;
+    public static final int A_CLSE = 0x45534c43;
+    public static final int A_WRTE = 0x45545257;
+
+    // ADB protocol version
+    public static final int A_VERSION = 0x01000000;
+
+    public static final int MAX_PAYLOAD = 4096;
+
+    private final ByteBuffer mMessageBuffer;
+    private final ByteBuffer mDataBuffer;
+
+    public AdbMessage() {
+        mMessageBuffer = ByteBuffer.allocate(24);
+        mDataBuffer = ByteBuffer.allocate(MAX_PAYLOAD);
+        mMessageBuffer.order(ByteOrder.LITTLE_ENDIAN);
+        mDataBuffer.order(ByteOrder.LITTLE_ENDIAN);
+    }
+
+    // sets the fields in the command header
+    public void set(int command, int arg0, int arg1, byte[] data) {
+        mMessageBuffer.putInt(0, command);
+        mMessageBuffer.putInt(4, arg0);
+        mMessageBuffer.putInt(8, arg1);
+        mMessageBuffer.putInt(12, (data == null ? 0 : data.length));
+        mMessageBuffer.putInt(16, (data == null ? 0 : checksum(data)));
+        mMessageBuffer.putInt(20, command ^ 0xFFFFFFFF);
+        if (data != null) {
+            mDataBuffer.put(data, 0, data.length);
+        }
+    }
+
+    public void set(int command, int arg0, int arg1) {
+        set(command, arg0, arg1, (byte[])null);
+    }
+    public void set(int command, int arg0, int arg1, String data) {
+        // add trailing zero
+        data += "\0";
+        set(command, arg0, arg1, data.getBytes());
+    }
+
+    // returns the command's message ID
+    public int getCommand() {
+        return mMessageBuffer.getInt(0);
+    }
+
+    // returns command's first argument
+    public int getArg0() {
+        return mMessageBuffer.getInt(4);
+    }
+
+    // returns command's second argument
+    public int getArg1() {
+        return mMessageBuffer.getInt(8);
+    }
+
+    // returns command's data buffer
+    public ByteBuffer getData() {
+        return mDataBuffer;
+    }
+
+    // returns command's data length
+    public int getDataLength() {
+        return mMessageBuffer.getInt(12);
+    }
+
+    // returns command's data as a string
+    public String getDataString() {
+        int length = getDataLength();
+        if (length == 0) return null;
+        // trim trailing zero
+        return new String(mDataBuffer.array(), 0, length - 1);
+    }
+
+
+    public boolean write(AdbDevice device) {
+        synchronized (device) {
+            UsbRequest request = device.getOutRequest();
+            request.setClientData(this);
+            if (request.queue(mMessageBuffer, 24)) {
+                int length = getDataLength();
+                if (length > 0) {
+                    request = device.getOutRequest();
+                    request.setClientData(this);
+                    if (request.queue(mDataBuffer, length)) {
+                        return true;
+                    } else {
+                        device.releaseOutRequest(request);
+                        return false;
+                    }
+                }
+                return true;
+            } else {
+                device.releaseOutRequest(request);
+                return false;
+            }
+        }
+    }
+
+    public boolean readCommand(UsbRequest request) {
+        request.setClientData(this);
+        return request.queue(mMessageBuffer, 24);
+    }
+
+    public boolean readData(UsbRequest request, int length) {
+        request.setClientData(this);
+        return request.queue(mDataBuffer, length);
+    }
+
+    private static String extractString(ByteBuffer buffer, int offset, int length) {
+        byte[] bytes = new byte[length];
+        for (int i = 0; i < length; i++) {
+            bytes[i] = buffer.get(offset++);
+        }
+        return new String(bytes);
+    }
+
+    @Override
+    public String toString() {
+        String commandName = extractString(mMessageBuffer, 0, 4);
+        int dataLength = getDataLength();
+        String result = "Adb Message: " + commandName + " arg0: " + getArg0() +
+             " arg1: " + getArg1() + " dataLength: " + dataLength;
+        if (dataLength > 0) {
+            result += (" data: \"" + getDataString() + "\"");
+        }
+        return result;
+    }
+
+    private static int checksum(byte[] data) {
+        int result = 0;
+        for (int i = 0; i < data.length; i++) {
+            int x = data[i];
+            // dang, no unsigned ints in java
+            if (x < 0) x += 256;
+            result += x;
+        }
+        return result;
+    }
+}
\ No newline at end of file
diff --git a/samples/USB/AdbTest/src/com/android/adb/AdbSocket.java b/samples/USB/AdbTest/src/com/android/adb/AdbSocket.java
new file mode 100644
index 0000000..deb5980
--- /dev/null
+++ b/samples/USB/AdbTest/src/com/android/adb/AdbSocket.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 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.adb;
+
+/* This class represents an adb socket.  adb supports multiple independent
+ * socket connections to a single device.  Typically a socket is created
+ * for each adb command that is executed.
+ */
+public class AdbSocket {
+
+    private final AdbDevice mDevice;
+    private final int mId;
+    private int mPeerId;
+
+    public AdbSocket(AdbDevice device, int id) {
+        mDevice = device;
+        mId = id;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public boolean open(String destination) {
+        AdbMessage message = new AdbMessage();
+        message.set(AdbMessage.A_OPEN, mId, 0, destination);
+        if (! message.write(mDevice)) {
+            return false;
+        }
+
+        synchronized (this) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public void handleMessage(AdbMessage message) {
+        switch (message.getCommand()) {
+            case AdbMessage.A_OKAY:
+                mPeerId = message.getArg0();
+                synchronized (this) {
+                    notify();
+                }
+                break;
+            case AdbMessage.A_WRTE:
+                mDevice.log(message.getDataString());
+                sendReady();
+                break;
+        }
+    }
+
+    private void sendReady() {
+        AdbMessage message = new AdbMessage();
+        message.set(AdbMessage.A_OKAY, mId, mPeerId);
+        message.write(mDevice);
+    }
+}
diff --git a/samples/USB/AdbTest/src/com/android/adb/AdbTestActivity.java b/samples/USB/AdbTest/src/com/android/adb/AdbTestActivity.java
new file mode 100644
index 0000000..bd11977
--- /dev/null
+++ b/samples/USB/AdbTest/src/com/android/adb/AdbTestActivity.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2011 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.adb;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Rect;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbDeviceConnection;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.widget.TextView;
+
+/* Main activity for the adb test program */
+public class AdbTestActivity extends Activity {
+
+    private static final String TAG = "AdbTestActivity";
+
+    private TextView mLog;
+    private UsbManager mManager;
+    private UsbDevice mDevice;
+    private UsbDeviceConnection mDeviceConnection;
+    private UsbInterface mInterface;
+    private AdbDevice mAdbDevice;
+
+    private static final int MESSAGE_LOG = 1;
+    private static final int MESSAGE_DEVICE_ONLINE = 2;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.adb);
+        mLog = (TextView)findViewById(R.id.log);
+
+        mManager = (UsbManager)getSystemService(Context.USB_SERVICE);
+
+        // check for existing devices
+        for (UsbDevice device :  mManager.getDeviceList().values()) {
+            UsbInterface intf = findAdbInterface(device);
+            if (setAdbInterface(device, intf)) {
+                break;
+            }
+        }
+
+        // listen for new devices
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+        registerReceiver(mUsbReceiver, filter);
+    }
+
+    @Override
+    public void onDestroy() {
+        unregisterReceiver(mUsbReceiver);
+        setAdbInterface(null, null);
+        super.onDestroy();
+    }
+
+    public void log(String s) {
+        Message m = Message.obtain(mHandler, MESSAGE_LOG);
+        m.obj = s;
+        mHandler.sendMessage(m);
+    }
+
+    private void appendLog(String text) {
+        Rect r = new Rect();
+        mLog.getDrawingRect(r);
+        int maxLines = r.height() / mLog.getLineHeight() - 1;
+        text = mLog.getText() + "\n" + text;
+
+        // see how many lines we have
+        int index = text.lastIndexOf('\n');
+        int count = 0;
+        while (index > 0 && count <= maxLines) {
+            count++;
+            index = text.lastIndexOf('\n', index - 1);
+        }
+
+        // truncate to maxLines
+        if (index > 0) {
+            text = text.substring(index + 1);
+        }
+        mLog.setText(text);
+    }
+
+    public void deviceOnline(AdbDevice device) {
+        Message m = Message.obtain(mHandler, MESSAGE_DEVICE_ONLINE);
+        m.obj = device;
+        mHandler.sendMessage(m);
+    }
+
+    private void handleDeviceOnline(AdbDevice device) {
+        log("device online: " + device.getSerial());
+        device.openSocket("shell:exec logcat");
+    }
+
+    // Sets the current USB device and interface
+    private boolean setAdbInterface(UsbDevice device, UsbInterface intf) {
+        if (mDeviceConnection != null) {
+            if (mInterface != null) {
+                mDeviceConnection.releaseInterface(mInterface);
+                mInterface = null;
+            }
+            mDeviceConnection.close();
+            mDevice = null;
+            mDeviceConnection = null;
+        }
+
+        if (device != null && intf != null) {
+            UsbDeviceConnection connection = mManager.openDevice(device);
+            if (connection != null) {
+                log("open succeeded");
+                if (connection.claimInterface(intf, false)) {
+                    log("claim interface succeeded");
+                    mDevice = device;
+                    mDeviceConnection = connection;
+                    mInterface = intf;
+                    mAdbDevice = new AdbDevice(this, mDeviceConnection, intf);
+                    log("call start");
+                    mAdbDevice.start();
+                    return true;
+                } else {
+                    log("claim interface failed");
+                    connection.close();
+                }
+            } else {
+                log("open failed");
+            }
+        }
+
+        if (mDeviceConnection == null && mAdbDevice != null) {
+            mAdbDevice.stop();
+            mAdbDevice = null;
+        }
+        return false;
+    }
+
+    // searches for an adb interface on the given USB device
+    static private UsbInterface findAdbInterface(UsbDevice device) {
+        Log.d(TAG, "findAdbInterface " + device);
+        int count = device.getInterfaceCount();
+        for (int i = 0; i < count; i++) {
+            UsbInterface intf = device.getInterface(i);
+            if (intf.getInterfaceClass() == 255 && intf.getInterfaceSubclass() == 66 &&
+                    intf.getInterfaceProtocol() == 1) {
+                return intf;
+            }
+        }
+        return null;
+    }
+
+    BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+
+            if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
+                UsbDevice device = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+                UsbInterface intf = findAdbInterface(device);
+                if (intf != null) {
+                    log("Found adb interface " + intf);
+                    setAdbInterface(device, intf);
+                }
+            } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
+                UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+                String deviceName = device.getDeviceName();
+                if (mDevice != null && mDevice.equals(deviceName)) {
+                    log("adb interface removed");
+                    setAdbInterface(null, null);
+                }
+            }
+        }
+    };
+
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_LOG:
+                    appendLog((String)msg.obj);
+                    break;
+                case MESSAGE_DEVICE_ONLINE:
+                    handleDeviceOnline((AdbDevice)msg.obj);
+                    break;
+            }
+        }
+    };
+}
+
+