From 7b2014678ef492d756ec117467aa239cfd03c7da Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koushd@gmail.com>
Date: Fri, 4 May 2012 00:01:26 -0700
Subject: [PATCH 01/56] Add initial Socket.IO support.

---
 .gitignore                                    |   6 +-
 project.properties                            |   2 +-
 .../android_websockets/HybiParser.java        |  35 ++-
 .../android_websockets/SocketIOClient.java    | 226 ++++++++++++++++++
 4 files changed, 265 insertions(+), 4 deletions(-)
 create mode 100644 src/com/codebutler/android_websockets/SocketIOClient.java

diff --git a/.gitignore b/.gitignore
index 81d81b4..bd55530 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,4 @@
-gen/
-local.properties
+bin
+gen
+.classpath
+.project
diff --git a/project.properties b/project.properties
index 1d35d2d..cd0ca12 100644
--- a/project.properties
+++ b/project.properties
@@ -12,4 +12,4 @@
 
 android.library=true
 # Project target.
-target=android-14
+target=android-8
diff --git a/src/com/codebutler/android_websockets/HybiParser.java b/src/com/codebutler/android_websockets/HybiParser.java
index 5565c1a..44eb1c7 100644
--- a/src/com/codebutler/android_websockets/HybiParser.java
+++ b/src/com/codebutler/android_websockets/HybiParser.java
@@ -332,9 +332,42 @@ private int getInteger(byte[] bytes) throws ProtocolError {
         }
         return (int) i;
     }
+    
+    /**
+     * Copied from AOSP Arrays.java.
+     */
+    /**
+     * Copies elements from {@code original} into a new array, from indexes start (inclusive) to
+     * end (exclusive). The original order of elements is preserved.
+     * If {@code end} is greater than {@code original.length}, the result is padded
+     * with the value {@code (byte) 0}.
+     *
+     * @param original the original array
+     * @param start the start index, inclusive
+     * @param end the end index, exclusive
+     * @return the new array
+     * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length}
+     * @throws IllegalArgumentException if {@code start > end}
+     * @throws NullPointerException if {@code original == null}
+     * @since 1.6
+     */
+    private static byte[] copyOfRange(byte[] original, int start, int end) {
+        if (start > end) {
+            throw new IllegalArgumentException();
+        }
+        int originalLength = original.length;
+        if (start < 0 || start > originalLength) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        int resultLength = end - start;
+        int copyLength = Math.min(resultLength, originalLength - start);
+        byte[] result = new byte[resultLength];
+        System.arraycopy(original, start, result, 0, copyLength);
+        return result;
+    }
 
     private byte[] slice(byte[] array, int start) {
-        return Arrays.copyOfRange(array, start, array.length);
+        return copyOfRange(array, start, array.length);
     }
 
     public static class ProtocolError extends IOException {
diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
new file mode 100644
index 0000000..3767444
--- /dev/null
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -0,0 +1,226 @@
+package com.codebutler.android_websockets;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.HashSet;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import android.net.http.AndroidHttpClient;
+import android.os.Looper;
+
+public class SocketIOClient {
+    public static interface Handler {
+        public void onConnect();
+
+        public void on(String event, JSONArray arguments);
+
+        public void onDisconnect(int code, String reason);
+
+        public void onError(Exception error);
+    }
+
+    URI mURI;
+    Handler mHandler;
+    String mSession;
+    int mHeartbeat;
+    int mClosingTimeout;
+    WebSocketClient mClient;
+
+    public SocketIOClient(URI uri, Handler handler) {
+        mURI = uri;
+        mHandler = handler;
+    }
+
+    private static String downloadUriAsString(final HttpUriRequest req) throws IOException {
+        AndroidHttpClient client = AndroidHttpClient.newInstance("android-websockets");
+        try {
+            HttpResponse res = client.execute(req);
+            return readToEnd(res.getEntity().getContent());
+        }
+        finally {
+            client.close();
+        }
+    }
+
+    private static byte[] readToEndAsArray(InputStream input) throws IOException {
+        DataInputStream dis = new DataInputStream(input);
+        byte[] stuff = new byte[1024];
+        ByteArrayOutputStream buff = new ByteArrayOutputStream();
+        int read = 0;
+        while ((read = dis.read(stuff)) != -1) {
+            buff.write(stuff, 0, read);
+        }
+
+        return buff.toByteArray();
+    }
+
+    private static String readToEnd(InputStream input) throws IOException {
+        return new String(readToEndAsArray(input));
+    }
+
+    android.os.Handler mSendHandler;
+    Looper mSendLooper;
+
+    public void send(String name, JSONArray args) throws JSONException {
+        final JSONObject event = new JSONObject();
+        event.put("name", name);
+        event.put("args", args);
+        System.out.println(event.toString());
+        mSendHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mClient.send(String.format("5:::%s", event.toString()));
+            }
+        });
+    }
+
+    private void connectSession() throws URISyntaxException {
+        mClient = new WebSocketClient(new URI(mURI.toString() + "/socket.io/1/websocket/" + mSession), new WebSocketClient.Handler() {
+            @Override
+            public void onMessage(byte[] data) {
+                try {
+                    mClient.disconnect();
+                }
+                catch (IOException e) {
+                }
+                mHandler.onError(new Exception("Unexpected binary data"));
+            }
+
+            @Override
+            public void onMessage(String message) {
+                try {
+                    System.out.println(message);
+                    String[] parts = message.split(":", 4);
+                    int code = Integer.parseInt(parts[0]);
+                    switch (code) {
+                    case 1:
+                        onConnect();
+                        break;
+                    case 2:
+                        // heartbeat
+                        break;
+                    case 3:
+                        // message
+                    case 4:
+                        // json message
+                        throw new Exception("message type not supported");
+                    case 5: {
+                        final String messageId = parts[1];
+                        final String dataString = parts[3];
+                        JSONObject data = new JSONObject(dataString);
+                        String event = data.getString("name");
+                        JSONArray args = data.getJSONArray("args");
+                        if (!"".equals(messageId)) {
+                            mSendHandler.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    mClient.send(String.format("6:::%s", messageId));
+                                }
+                            });
+                        }
+                        mHandler.on(event, args);
+                        break;
+                    }
+                    case 6:
+                        // ACK
+                        break;
+                    case 7:
+                        // error
+                        throw new Exception(message);
+                    case 8:
+                        // noop
+                        break;
+                    default:
+                        throw new Exception("unknown code");
+                    }
+                }
+                catch (Exception ex) {
+                    onError(ex);
+                }
+            }
+
+            @Override
+            public void onError(Exception error) {
+                cleanup();
+                mHandler.onError(error);
+            }
+
+            @Override
+            public void onDisconnect(int code, String reason) {
+                cleanup();
+                // attempt reconnect?
+                mHandler.onDisconnect(code, reason);
+            }
+
+            @Override
+            public void onConnect() {
+                mSendHandler.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        mSendHandler.postDelayed(this, mHeartbeat);
+                        mClient.send("2:::");
+                    }
+                }, mHeartbeat);
+            }
+        }, null);
+        mClient.connect();
+    }
+
+    public void disconnect() throws IOException {
+        cleanup();
+    }
+
+    private void cleanup() {
+        try {
+            mClient.disconnect();
+        }
+        catch (IOException e) {
+        }
+        mSendLooper.quit();
+        mSendLooper = null;
+        mSendHandler = null;
+    }
+
+    public void connect() {
+        new Thread() {
+            public void run() {
+                HttpPost post = new HttpPost(mURI.toString() + "/socket.io/1/");
+                try {
+                    String line = downloadUriAsString(post);
+                    String[] parts = line.split(":");
+                    mSession = parts[0];
+                    String heartbeat = parts[1];
+                    if (!"".equals(heartbeat))
+                        mHeartbeat = Integer.parseInt(heartbeat) / 2 * 1000;
+                    String transportsLine = parts[3];
+                    String[] transports = transportsLine.split(",");
+                    HashSet<String> set = new HashSet<String>(Arrays.asList(transports));
+                    if (!set.contains("websocket"))
+                        throw new Exception("websocket not supported");
+
+                    Looper.prepare();
+                    mSendLooper = Looper.myLooper();
+                    mSendHandler = new android.os.Handler();
+
+                    connectSession();
+
+                    Looper.loop();
+                }
+                catch (Exception e) {
+                    mHandler.onError(e);
+                }
+            };
+        }.start();
+    }
+}

From 47a7822850ebe347ab724f475b9be848dbbfa4fa Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koushd@gmail.com>
Date: Fri, 4 May 2012 00:08:57 -0700
Subject: [PATCH 02/56] Add proper cleanup for errors.

---
 src/com/codebutler/android_websockets/SocketIOClient.java | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 3767444..d568410 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -89,11 +89,7 @@ private void connectSession() throws URISyntaxException {
         mClient = new WebSocketClient(new URI(mURI.toString() + "/socket.io/1/websocket/" + mSession), new WebSocketClient.Handler() {
             @Override
             public void onMessage(byte[] data) {
-                try {
-                    mClient.disconnect();
-                }
-                catch (IOException e) {
-                }
+                cleanup();
                 mHandler.onError(new Exception("Unexpected binary data"));
             }
 
@@ -146,6 +142,7 @@ public void run() {
                     }
                 }
                 catch (Exception ex) {
+                    cleanup();
                     onError(ex);
                 }
             }

From 3e4a15972781ab98bbaeb98c2475720cc2ed3069 Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koushd@gmail.com>
Date: Fri, 4 May 2012 10:05:43 -0700
Subject: [PATCH 03/56] Fix cleanup to null out the client. Prevent multiple
 connections while already connected.

---
 src/com/codebutler/android_websockets/SocketIOClient.java | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index d568410..a603907 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -156,7 +156,7 @@ public void onError(Exception error) {
             @Override
             public void onDisconnect(int code, String reason) {
                 cleanup();
-                // attempt reconnect?
+                // attempt reconnect with same session?
                 mHandler.onDisconnect(code, reason);
             }
 
@@ -181,6 +181,7 @@ public void disconnect() throws IOException {
     private void cleanup() {
         try {
             mClient.disconnect();
+            mClient = null;
         }
         catch (IOException e) {
         }
@@ -190,6 +191,8 @@ private void cleanup() {
     }
 
     public void connect() {
+        if (mClient != null)
+            return;
         new Thread() {
             public void run() {
                 HttpPost post = new HttpPost(mURI.toString() + "/socket.io/1/");

From 2c88ddbdf0994cc8d83e394b3264d068651b620f Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koush@koushikdutta.com>
Date: Fri, 4 May 2012 10:12:01 -0700
Subject: [PATCH 04/56] Documentation for Socket.IO support.

---
 README.md | 45 ++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 44 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 17a934d..eeb13d6 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@ Ported from JavaScript to Java by [Eric Butler](https://twitter.com/codebutler)
 
 ## Usage
 
-Here's the entire API:
+Here's the entire WebSocket API:
 
 ```java
 List<BasicNameValuePair> extraHeaders = Arrays.asList(
@@ -52,6 +52,49 @@ client.send(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF });
 client.disconnect();
 ```
 
+And here's the Socket.IO API built on top of that:
+
+
+```java
+List<BasicNameValuePair> extraHeaders = Arrays.asList(
+    new BasicNameValuePair("Cookie", "session=abcd");
+);
+
+WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), new WebSocketClient.Handler() {
+    @Override
+    public void onConnect() {
+        Log.d(TAG, "Connected!");
+    }
+
+    @Override
+    public void on(String event, JSONArray arguments) {
+        Log.d(TAG, String.format("Got event %s: %s", event, arguments.toString()));
+    }
+
+    @Override
+    public void onDisconnect(int code, String reason) {
+        Log.d(TAG, String.format("Disconnected! Code: %d Reason: %s", code, reason));
+    }
+
+    @Override
+    public void onError(Exception error) {
+        Log.e(TAG, "Error!", error);
+    }
+}, extraHeaders);
+
+client.connect();
+
+// Later… 
+JSONArray arguments = new JSONArray();
+arguments.put("first argument");
+JSONObject second = new JSONObject();
+second.put("dictionary", true);
+arguments.put(second)
+client.send("hello", arguments);
+client.disconnect();
+```
+
+
 
 ## TODO
 

From 76d8cf5b6b640f4cd65e8868a6f7dc085ab525df Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koush@koushikdutta.com>
Date: Fri, 4 May 2012 10:12:25 -0700
Subject: [PATCH 05/56] Remove extraHeaders for Socket.IO connections.

---
 README.md | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/README.md b/README.md
index eeb13d6..f518aac 100644
--- a/README.md
+++ b/README.md
@@ -56,10 +56,6 @@ And here's the Socket.IO API built on top of that:
 
 
 ```java
-List<BasicNameValuePair> extraHeaders = Arrays.asList(
-    new BasicNameValuePair("Cookie", "session=abcd");
-);
-
 WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), new WebSocketClient.Handler() {
     @Override
     public void onConnect() {
@@ -80,7 +76,7 @@ WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), ne
     public void onError(Exception error) {
         Log.e(TAG, "Error!", error);
     }
-}, extraHeaders);
+});
 
 client.connect();
 

From b0c10ccab25a43499f78e18c06110fa1c1ed1903 Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koush@koushikdutta.com>
Date: Fri, 4 May 2012 10:13:19 -0700
Subject: [PATCH 06/56] Copyright for my changes.

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index f518aac..78533bc 100644
--- a/README.md
+++ b/README.md
@@ -103,6 +103,7 @@ client.disconnect();
 	
 	Copyright (c) 2009-2012 James Coglan
 	Copyright (c) 2012 Eric Butler 
+	Copyright (c) 2012 Koushik Dutta 
 	
 	Permission is hereby granted, free of charge, to any person obtaining a copy of
 	this software and associated documentation files (the 'Software'), to deal in

From 44caf8e6fb8e080cc2ee4fb60c527f4f9dbf4723 Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koush@koushikdutta.com>
Date: Fri, 4 May 2012 10:15:43 -0700
Subject: [PATCH 07/56] Update the title and credits.

---
 README.md | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 78533bc..e05556b 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,16 @@
-# WebSocket client for Android
+# WebSocket and Socket.IO client for Android
 
-A very simple bare-minimum WebSocket client for Android.
+A very simple bare-minimum WebSocket and Socket.IO client for Android.
 
 ## Credits
 
 The hybi parser is based on code from the [faye project](https://github.com/faye/faye-websocket-node). Faye is Copyright (c) 2009-2012 James Coglan. Many thanks for the great open-source library!
 
-Ported from JavaScript to Java by [Eric Butler](https://twitter.com/codebutler) <eric@codebutler.com>.
+The hybi parser was ported from JavaScript to Java by [Eric Butler](https://twitter.com/codebutler) <eric@codebutler.com>.
+
+The WebSocket client was written by [Eric Butler](https://twitter.com/codebutler) <eric@codebutler.com>.
+
+The Socket.IO client was written by [Koushik Dutta](https://twitter.com/koush).
 
 ## Usage
 

From c511e7329258cbc672bbc493ada35fddc119e7a3 Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koush@koushikdutta.com>
Date: Fri, 4 May 2012 10:16:45 -0700
Subject: [PATCH 08/56] Clean up the titles around usage.

---
 README.md | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/README.md b/README.md
index e05556b..c2260f6 100644
--- a/README.md
+++ b/README.md
@@ -12,9 +12,7 @@ The WebSocket client was written by [Eric Butler](https://twitter.com/codebutler
 
 The Socket.IO client was written by [Koushik Dutta](https://twitter.com/koush).
 
-## Usage
-
-Here's the entire WebSocket API:
+## WebSocket Usage
 
 ```java
 List<BasicNameValuePair> extraHeaders = Arrays.asList(
@@ -56,8 +54,7 @@ client.send(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF });
 client.disconnect();
 ```
 
-And here's the Socket.IO API built on top of that:
-
+## Socket.IO Usage
 
 ```java
 WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), new WebSocketClient.Handler() {

From 6558e4c5e0f173b508f2fd72159f1e70249021d5 Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koush@koushikdutta.com>
Date: Fri, 4 May 2012 10:19:36 -0700
Subject: [PATCH 09/56] Update SocketIOClient to actually use the
 SocketIOClient class.

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index c2260f6..54b898c 100644
--- a/README.md
+++ b/README.md
@@ -57,7 +57,7 @@ client.disconnect();
 ## Socket.IO Usage
 
 ```java
-WebSocketClient client = new WebSocketClient(URI.create("wss://example.com"), new WebSocketClient.Handler() {
+SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new SocketIOClient.Handler() {
     @Override
     public void onConnect() {
         Log.d(TAG, "Connected!");

From 4f49431e16ba37412f7764eacfe36b3b93c25156 Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koushd@gmail.com>
Date: Fri, 4 May 2012 11:17:42 -0700
Subject: [PATCH 10/56] Rename send to emit, similar to how the node/javascript
 code names it.

---
 README.md                                                 | 2 +-
 src/com/codebutler/android_websockets/SocketIOClient.java | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 54b898c..dcc3ce9 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,7 @@ arguments.put("first argument");
 JSONObject second = new JSONObject();
 second.put("dictionary", true);
 arguments.put(second)
-client.send("hello", arguments);
+client.emit("hello", arguments);
 client.disconnect();
 ```
 
diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index a603907..af461fb 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -72,7 +72,7 @@ private static String readToEnd(InputStream input) throws IOException {
     android.os.Handler mSendHandler;
     Looper mSendLooper;
 
-    public void send(String name, JSONArray args) throws JSONException {
+    public void emit(String name, JSONArray args) throws JSONException {
         final JSONObject event = new JSONObject();
         event.put("name", name);
         event.put("args", args);

From 8bce20f2a9fc256c19d5332770dd3dd65ab44b67 Mon Sep 17 00:00:00 2001
From: Filip Zawada <me@filimanjaro.com>
Date: Thu, 17 May 2012 20:56:11 +0200
Subject: [PATCH 11/56] Fixed bug with incorrect URL when base URI with
 trailing slash used.

---
 .../codebutler/android_websockets/SocketIOClient.java | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index af461fb..f4ff3f9 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -30,15 +30,15 @@ public static interface Handler {
         public void onError(Exception error);
     }
 
-    URI mURI;
+    String mURL;
     Handler mHandler;
     String mSession;
     int mHeartbeat;
-    int mClosingTimeout;
     WebSocketClient mClient;
 
     public SocketIOClient(URI uri, Handler handler) {
-        mURI = uri;
+        // remove trailing "/" from URI, in case user provided e.g. http://test.com/
+        mURL = uri.toString().replaceAll("/$", "") + "/socket.io/1/";
         mHandler = handler;
     }
 
@@ -86,7 +86,7 @@ public void run() {
     }
 
     private void connectSession() throws URISyntaxException {
-        mClient = new WebSocketClient(new URI(mURI.toString() + "/socket.io/1/websocket/" + mSession), new WebSocketClient.Handler() {
+        mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Handler() {
             @Override
             public void onMessage(byte[] data) {
                 cleanup();
@@ -195,7 +195,7 @@ public void connect() {
             return;
         new Thread() {
             public void run() {
-                HttpPost post = new HttpPost(mURI.toString() + "/socket.io/1/");
+                HttpPost post = new HttpPost(mURL);
                 try {
                     String line = downloadUriAsString(post);
                     String[] parts = line.split(":");
@@ -224,3 +224,4 @@ public void run() {
         }.start();
     }
 }
+

From 71be505536e4286957d241c74d7eceeca5220211 Mon Sep 17 00:00:00 2001
From: Mike Stewart <mike.stewart@unb.ca>
Date: Thu, 29 Nov 2012 14:04:44 -0400
Subject: [PATCH 12/56] Update README.md

Missing semi-colon in example code.
---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index dcc3ce9..7425c42 100644
--- a/README.md
+++ b/README.md
@@ -86,7 +86,7 @@ JSONArray arguments = new JSONArray();
 arguments.put("first argument");
 JSONObject second = new JSONObject();
 second.put("dictionary", true);
-arguments.put(second)
+arguments.put(second);
 client.emit("hello", arguments);
 client.disconnect();
 ```

From 80322b33031d80a78f80d6244af471eb1c186987 Mon Sep 17 00:00:00 2001
From: Brandon Kase <bkase@cmu.edu>
Date: Wed, 2 Jan 2013 15:56:02 -0500
Subject: [PATCH 13/56] Fire onConnect event when connection established

In the SocketIOClient, `onConnect()` never called `mHandler.onConnect()`
---
 src/com/codebutler/android_websockets/SocketIOClient.java | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index af461fb..cc335ef 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -169,6 +169,7 @@ public void run() {
                         mClient.send("2:::");
                     }
                 }, mHeartbeat);
+                mHandler.onConnect();
             }
         }, null);
         mClient.connect();

From 6adfa2af5ff051575ec70fd2c4fea8bcf6597311 Mon Sep 17 00:00:00 2001
From: Brandon Kase <bkase@cmu.edu>
Date: Wed, 2 Jan 2013 16:05:55 -0500
Subject: [PATCH 14/56] Fixed crash on a no-argument message

If a message from the socket.io server is emitted with no arguments, it
now is properly received
---
 src/com/codebutler/android_websockets/SocketIOClient.java | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index af461fb..615f70a 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -116,7 +116,12 @@ public void onMessage(String message) {
                         final String dataString = parts[3];
                         JSONObject data = new JSONObject(dataString);
                         String event = data.getString("name");
-                        JSONArray args = data.getJSONArray("args");
+                        JSONArray args;
+                        try {
+                            args = data.getJSONArray("args");
+                        } catch (JSONException e) {
+                            args = new JSONArray();
+                        }
                         if (!"".equals(messageId)) {
                             mSendHandler.post(new Runnable() {
                                 @Override

From b45e80c023064fde6ba57e95e53e6368ba519c3f Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koush@koushikdutta.com>
Date: Thu, 3 Jan 2013 13:38:53 -0800
Subject: [PATCH 15/56] Update
 src/com/codebutler/android_websockets/HybiParser.java

Fix logic error from the javascript port.
---
 src/com/codebutler/android_websockets/HybiParser.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/com/codebutler/android_websockets/HybiParser.java b/src/com/codebutler/android_websockets/HybiParser.java
index 44eb1c7..dcbcc87 100644
--- a/src/com/codebutler/android_websockets/HybiParser.java
+++ b/src/com/codebutler/android_websockets/HybiParser.java
@@ -149,7 +149,7 @@ private void parseOpcode(byte data) throws ProtocolError {
             throw new ProtocolError("Bad opcode");
         }
 
-        if (FRAGMENTED_OPCODES.contains(mOpcode) && !mFinal) {
+        if (!FRAGMENTED_OPCODES.contains(mOpcode) && !mFinal) {
             throw new ProtocolError("Expected non-final packet");
         }
 
@@ -413,4 +413,4 @@ public byte[] readBytes(int length) throws IOException {
             return buffer;
         }
     }
-}
\ No newline at end of file
+}

From e33c05bbf7b84e38aed82306d44250caf44f3f62 Mon Sep 17 00:00:00 2001
From: Mike Stewart <mike.stewart@unb.ca>
Date: Thu, 10 Jan 2013 02:45:12 -0400
Subject: [PATCH 16/56] Added heartbeat replies, fixed two bugs, and added
 logging.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Client will now send a heartbeat back to the server when it receives one, so the connection won't time out. 
Removed unreachable catch block in cleanup() method, and changed WebSocketClient.Handler() to a Listener() to match the recent change to to the WebSocketClient interface.
Changed print statements to use Log, to be consistent with the rest of the package.
---
 .../android_websockets/SocketIOClient.java    | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 97ec0b4..45786e6 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -18,6 +18,7 @@
 
 import android.net.http.AndroidHttpClient;
 import android.os.Looper;
+import android.util.Log;
 
 public class SocketIOClient {
     public static interface Handler {
@@ -30,6 +31,8 @@ public static interface Handler {
         public void onError(Exception error);
     }
 
+    private static final String TAG = "SocketIOClient";
+    
     String mURL;
     Handler mHandler;
     String mSession;
@@ -76,7 +79,7 @@ public void emit(String name, JSONArray args) throws JSONException {
         final JSONObject event = new JSONObject();
         event.put("name", name);
         event.put("args", args);
-        System.out.println(event.toString());
+        Log.d(TAG, "Emitting event: " + event.toString());
         mSendHandler.post(new Runnable() {
             @Override
             public void run() {
@@ -86,7 +89,7 @@ public void run() {
     }
 
     private void connectSession() throws URISyntaxException {
-        mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Handler() {
+        mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() {
             @Override
             public void onMessage(byte[] data) {
                 cleanup();
@@ -96,7 +99,7 @@ public void onMessage(byte[] data) {
             @Override
             public void onMessage(String message) {
                 try {
-                    System.out.println(message);
+                    Log.d(TAG, "Message: " + message);
                     String[] parts = message.split(":", 4);
                     int code = Integer.parseInt(parts[0]);
                     switch (code) {
@@ -105,6 +108,7 @@ public void onMessage(String message) {
                         break;
                     case 2:
                         // heartbeat
+                        mClient.send("2::");
                         break;
                     case 3:
                         // message
@@ -185,12 +189,9 @@ public void disconnect() throws IOException {
     }
 
     private void cleanup() {
-        try {
-            mClient.disconnect();
-            mClient = null;
-        }
-        catch (IOException e) {
-        }
+        mClient.disconnect();
+        mClient = null;
+       
         mSendLooper.quit();
         mSendLooper = null;
         mSendHandler = null;

From 86617e55550cb31ad354ff9a07dd8054a82e501d Mon Sep 17 00:00:00 2001
From: Mike Stewart <mike.stewart@unb.ca>
Date: Tue, 5 Feb 2013 00:49:29 -0400
Subject: [PATCH 17/56] Create an SSL socket for https connections.

---
 src/com/codebutler/android_websockets/WebSocketClient.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java
index 7e3343c..3b14a9f 100644
--- a/src/com/codebutler/android_websockets/WebSocketClient.java
+++ b/src/com/codebutler/android_websockets/WebSocketClient.java
@@ -69,7 +69,7 @@ public void connect() {
             @Override
             public void run() {
                 try {
-                    int port = (mURI.getPort() != -1) ? mURI.getPort() : (mURI.getScheme().equals("wss") ? 443 : 80);
+                    int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80);
 
                     String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath();
                     if (!TextUtils.isEmpty(mURI.getQuery())) {
@@ -79,7 +79,7 @@ public void run() {
                     String originScheme = mURI.getScheme().equals("wss") ? "https" : "http";
                     URI origin = new URI(originScheme, "//" + mURI.getHost(), null);
 
-                    SocketFactory factory = mURI.getScheme().equals("wss") ? getSSLSocketFactory() : SocketFactory.getDefault();
+                    SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault();
                     mSocket = factory.createSocket(mURI.getHost(), port);
 
                     PrintWriter out = new PrintWriter(mSocket.getOutputStream());

From 4b2fa51cedb3c432cee85e5a3bab6e8ab9eba32f Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay.shenoy@sourcebits.com>
Date: Fri, 1 Mar 2013 17:55:34 +0530
Subject: [PATCH 18/56] Added manifest target sdk

---
 AndroidManifest.xml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index eb6f10c..80e8295 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -4,4 +4,8 @@
     package="com.codebutler.android_websockets"
     android:versionCode="1"
     android:versionName="0.01">
+    
+    <uses-sdk
+        android:minSdkVersion="8"
+        android:targetSdkVersion="8" />
 </manifest>

From fbd83b55a06062f2a4bf246ed72aa092b036259f Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay.shenoy@sourcebits.com>
Date: Fri, 1 Mar 2013 18:09:28 +0530
Subject: [PATCH 19/56] Added support for JSONMessage and Message to
 SocketIOClient

---
 .../android_websockets/SocketIOClient.java    | 69 +++++++++++++++++--
 1 file changed, 65 insertions(+), 4 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 45786e6..89a275f 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -28,6 +28,10 @@ public static interface Handler {
 
         public void onDisconnect(int code, String reason);
 
+        public void onJSONMessage(JSONObject jsonMessage);
+
+        public void onMessage(String message);
+
         public void onError(Exception error);
     }
 
@@ -87,6 +91,27 @@ public void run() {
             }
         });
     }
+    
+    public void emit(final String message) {
+        mSendHandler.post(new Runnable() {
+            
+            @Override
+            public void run() {
+                mClient.send(String.format("3:::%s", message));
+            }
+        });
+    }
+    
+    public void emit(final JSONObject jsonMessage) {
+        
+        mSendHandler.post(new Runnable() {
+            
+            @Override
+            public void run() {
+                mClient.send(String.format("4:::%s", jsonMessage.toString()));
+            }
+        });
+    }
 
     private void connectSession() throws URISyntaxException {
         mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() {
@@ -110,11 +135,47 @@ public void onMessage(String message) {
                         // heartbeat
                         mClient.send("2::");
                         break;
-                    case 3:
+                    case 3: {
                         // message
-                    case 4:
-                        // json message
-                        throw new Exception("message type not supported");
+                        final String messageId = parts[1];
+                        final String dataString = parts[3];
+                        
+                        if(!"".equals(messageId)) {
+                            mSendHandler.post(new Runnable() {
+                                
+                                @Override
+                                public void run() {
+                                    mClient.send(String.format("6:::%s", messageId));
+                                }
+                            });
+                        }
+                        mHandler.onMessage(dataString);
+                        break;
+                    }
+                    case 4: {
+                        //json message
+                        final String messageId = parts[1];
+                        final String dataString = parts[3];
+                        
+                        JSONObject jsonMessage = null;
+                        
+                        try {
+                            jsonMessage = new JSONObject(dataString);
+                        } catch(JSONException e) {
+                            jsonMessage = new JSONObject();
+                        }
+                        if(!"".equals(messageId)) {
+                            mSendHandler.post(new Runnable() {
+                                
+                                @Override
+                                public void run() {
+                                    mClient.send(String.format("6:::%s", messageId));
+                                }
+                            });
+                        }
+                        mHandler.onJSONMessage(jsonMessage);
+                        break;
+                    }
                     case 5: {
                         final String messageId = parts[1];
                         final String dataString = parts[3];

From acc35521562bb1b3c0d4e682fcca003d0fb05601 Mon Sep 17 00:00:00 2001
From: Vinay Shenoy <vinaysshenoy@gmail.com>
Date: Sat, 2 Mar 2013 07:16:51 +0530
Subject: [PATCH 20/56] Renamed method onJSONMessage to onJSON

---
 src/com/codebutler/android_websockets/SocketIOClient.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 89a275f..8e5ccf9 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -28,7 +28,7 @@ public static interface Handler {
 
         public void onDisconnect(int code, String reason);
 
-        public void onJSONMessage(JSONObject jsonMessage);
+        public void onJSON(JSONObject json);
 
         public void onMessage(String message);
 
@@ -173,7 +173,7 @@ public void run() {
                                 }
                             });
                         }
-                        mHandler.onJSONMessage(jsonMessage);
+                        mHandler.onJSON(jsonMessage);
                         break;
                     }
                     case 5: {

From f440715a972dd260ca5438f3e7e74ab5fcbc0375 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinaysshenoy@gmail.com>
Date: Sat, 2 Mar 2013 09:48:02 +0530
Subject: [PATCH 21/56] Update README.md

---
 README.md | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 7425c42..72ce59a 100644
--- a/README.md
+++ b/README.md
@@ -67,6 +67,19 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new
     public void on(String event, JSONArray arguments) {
         Log.d(TAG, String.format("Got event %s: %s", event, arguments.toString()));
     }
+    
+    @Override
+    public void onJSON(JSONObject json) {
+    	try {
+    		Log.d(TAG, String.format("Got JSON Object: %s", json.toString()));
+    	} catch(JSONException e) {
+    	}
+    }
+
+    @Override
+    public void onMessage(String message) {
+    	Log.d(TAG, String.format("Got message: %s", message));
+    }
 
     @Override
     public void onDisconnect(int code, String reason) {
@@ -82,12 +95,14 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new
 client.connect();
 
 // Later… 
+client.emit("Message"); //Message
 JSONArray arguments = new JSONArray();
 arguments.put("first argument");
 JSONObject second = new JSONObject();
+client.emit(second); //JSON Message
 second.put("dictionary", true);
 arguments.put(second);
-client.emit("hello", arguments);
+client.emit("hello", arguments); //Event
 client.disconnect();
 ```
 

From fc5d02338e63f6c977ec8dae8a019b272232e2f2 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinaysshenoy@gmail.com>
Date: Sat, 2 Mar 2013 09:50:37 +0530
Subject: [PATCH 22/56] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 72ce59a..0667347 100644
--- a/README.md
+++ b/README.md
@@ -99,8 +99,8 @@ client.emit("Message"); //Message
 JSONArray arguments = new JSONArray();
 arguments.put("first argument");
 JSONObject second = new JSONObject();
-client.emit(second); //JSON Message
 second.put("dictionary", true);
+client.emit(second); //JSON Message
 arguments.put(second);
 client.emit("hello", arguments); //Event
 client.disconnect();

From a6a3b99fb032bd2fc96c0d8d0275b4eed830acfd Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koushd@gmail.com>
Date: Wed, 6 Mar 2013 07:34:32 -0800
Subject: [PATCH 23/56] update project properties and build xml

---
 .gitignore           |  1 +
 build.xml            | 13 +++++++++++--
 proguard-project.txt | 20 ++++++++++++++++++++
 3 files changed, 32 insertions(+), 2 deletions(-)
 create mode 100644 proguard-project.txt

diff --git a/.gitignore b/.gitignore
index bd55530..926c55d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,4 @@ bin
 gen
 .classpath
 .project
+local.properties
diff --git a/build.xml b/build.xml
index 7db9217..876bc4b 100644
--- a/build.xml
+++ b/build.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<project name="android_websockets" default="help">
+<project name="android-websockets" default="help">
 
     <!-- The local.properties file is created and updated by the 'android' tool.
          It contains the path to the SDK. It should *NOT* be checked into
@@ -28,6 +28,15 @@
          -->
     <property file="ant.properties" />
 
+    <!-- if sdk.dir was not set from one of the property file, then
+         get it from the ANDROID_HOME env var.
+         This must be done before we load project.properties since
+         the proguard config can use sdk.dir -->
+    <property environment="env" />
+    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
+        <isset property="env.ANDROID_HOME" />
+    </condition>
+
     <!-- The project.properties file is created and updated by the 'android'
          tool, as well as ADT.
 
@@ -41,7 +50,7 @@
 
     <!-- quick check on sdk.dir -->
     <fail
-            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through an env var"
+            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
             unless="sdk.dir"
     />
 
diff --git a/proguard-project.txt b/proguard-project.txt
new file mode 100644
index 0000000..f2fe155
--- /dev/null
+++ b/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

From 3cbe3eefab54c70e6610bf4dd14fc74a8de10365 Mon Sep 17 00:00:00 2001
From: Mike Stewart <mike.stewart@unb.ca>
Date: Sat, 9 Mar 2013 14:09:13 -0400
Subject: [PATCH 24/56] Fixed double firing of onConnect event in
 SocketIOClient handler.

---
 src/com/codebutler/android_websockets/SocketIOClient.java | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 8e5ccf9..21e2593 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -129,7 +129,8 @@ public void onMessage(String message) {
                     int code = Integer.parseInt(parts[0]);
                     switch (code) {
                     case 1:
-                        onConnect();
+                        // connect
+                        mHandler.onConnect();
                         break;
                     case 2:
                         // heartbeat
@@ -239,7 +240,6 @@ public void run() {
                         mClient.send("2:::");
                     }
                 }, mHeartbeat);
-                mHandler.onConnect();
             }
         }, null);
         mClient.connect();

From 1174d7e8751b7b572420b4fbd1ac4e54d1df3a03 Mon Sep 17 00:00:00 2001
From: Peter Foley <pefoley2@verizon.net>
Date: Tue, 2 Apr 2013 10:12:11 -0400
Subject: [PATCH 25/56] add isConnected method

I converted my application from AutobahnAndroid to android-websockets
to take advantage of ssl support.
android-websockets did not have a isConnected method, which is used in
my application, so I implemented one.
---
 .../android_websockets/WebSocketClient.java         | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java
index 3b14a9f..82aadbe 100644
--- a/src/com/codebutler/android_websockets/WebSocketClient.java
+++ b/src/com/codebutler/android_websockets/WebSocketClient.java
@@ -36,6 +36,7 @@ public class WebSocketClient {
     private Handler                  mHandler;
     private List<BasicNameValuePair> mExtraHeaders;
     private HybiParser               mParser;
+    private boolean                  mConnected;
 
     private final Object mSendLock = new Object();
 
@@ -47,8 +48,9 @@ public static void setTrustManagers(TrustManager[] tm) {
 
     public WebSocketClient(URI uri, Listener listener, List<BasicNameValuePair> extraHeaders) {
         mURI          = uri;
-        mListener = listener;
+        mListener     = listener;
         mExtraHeaders = extraHeaders;
+        mConnected    = false;
         mParser       = new HybiParser(this);
 
         mHandlerThread = new HandlerThread("websocket-thread");
@@ -119,17 +121,21 @@ public void run() {
 
                     mListener.onConnect();
 
+                    mConnected = true;
+
                     // Now decode websocket frames.
                     mParser.start(stream);
 
                 } catch (EOFException ex) {
                     Log.d(TAG, "WebSocket EOF!", ex);
                     mListener.onDisconnect(0, "EOF");
+                    mConnected = false;
 
                 } catch (SSLException ex) {
                     // Connection reset by peer
                     Log.d(TAG, "Websocket SSL error!", ex);
                     mListener.onDisconnect(0, "SSL");
+                    mConnected = false;
 
                 } catch (Exception ex) {
                     mListener.onError(ex);
@@ -147,6 +153,7 @@ public void run() {
                     try {
                         mSocket.close();
                         mSocket = null;
+                        mConnected = false;
                     } catch (IOException ex) {
                         Log.d(TAG, "Error while disconnecting", ex);
                         mListener.onError(ex);
@@ -164,6 +171,10 @@ public void send(byte[] data) {
         sendFrame(mParser.frame(data));
     }
 
+    public boolean isConnected() {
+        return mConnected;
+    }
+
     private StatusLine parseStatusLine(String line) {
         if (TextUtils.isEmpty(line)) {
             return null;

From dad5c6fb6e62bb50cfcf83dab2d055ac191eac0f Mon Sep 17 00:00:00 2001
From: orenda <oren.david@gmail.com>
Date: Wed, 8 May 2013 15:00:13 +0300
Subject: [PATCH 26/56] Update SocketIOClient.java

---
 src/com/codebutler/android_websockets/SocketIOClient.java | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 21e2593..a16464a 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -250,7 +250,8 @@ public void disconnect() throws IOException {
     }
 
     private void cleanup() {
-        mClient.disconnect();
+        if (mClient != null)
+            mClient.disconnect();
         mClient = null;
        
         mSendLooper.quit();

From c62edc2ad90be555db49249336c757215b84ac1d Mon Sep 17 00:00:00 2001
From: Chris Peterson <cpeterson@mozilla.com>
Date: Thu, 9 May 2013 14:41:20 -0700
Subject: [PATCH 27/56] Fix syntax errors in README.md's example code

---
 README.md | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 0667347..64d852e 100644
--- a/README.md
+++ b/README.md
@@ -16,10 +16,10 @@ The Socket.IO client was written by [Koushik Dutta](https://twitter.com/koush).
 
 ```java
 List<BasicNameValuePair> extraHeaders = Arrays.asList(
-    new BasicNameValuePair("Cookie", "session=abcd");
+    new BasicNameValuePair("Cookie", "session=abcd")
 );
 
-WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), new WebSocketClient.Handler() {
+WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), new WebSocketClient.Listener() {
     @Override
     public void onConnect() {
         Log.d(TAG, "Connected!");
@@ -32,7 +32,7 @@ WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), n
 
     @Override
     public void onMessage(byte[] data) {
-        Log.d(TAG, String.format("Got binary message! %s", toHexString(data));
+        Log.d(TAG, String.format("Got binary message! %s", toHexString(data)));
     }
 
     @Override

From e1877d697ca25e15392b9012a907e0e96e6034a2 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Mon, 20 May 2013 22:02:27 +0530
Subject: [PATCH 28/56] Added support for Endpoints

---
 .gitignore                                    |   1 +
 .../android_websockets/SocketIOClient.java    | 117 ++++++++++++++----
 2 files changed, 93 insertions(+), 25 deletions(-)

diff --git a/.gitignore b/.gitignore
index 926c55d..e66abab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ gen
 .classpath
 .project
 local.properties
+/.settings
diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 21e2593..58e2268 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -18,6 +18,7 @@
 
 import android.net.http.AndroidHttpClient;
 import android.os.Looper;
+import android.text.TextUtils;
 import android.util.Log;
 
 public class SocketIOClient {
@@ -36,16 +37,28 @@ public static interface Handler {
     }
 
     private static final String TAG = "SocketIOClient";
-    
+
     String mURL;
     Handler mHandler;
     String mSession;
     int mHeartbeat;
     WebSocketClient mClient;
+    String mEndpoint;
 
     public SocketIOClient(URI uri, Handler handler) {
-        // remove trailing "/" from URI, in case user provided e.g. http://test.com/
-        mURL = uri.toString().replaceAll("/$", "") + "/socket.io/1/";
+        this(uri, handler, null);
+    }
+
+    public SocketIOClient(URI uri, Handler handler, String namespace) {
+        mEndpoint = namespace;
+
+        if (TextUtils.isEmpty(namespace)) {
+            mEndpoint = "socket.io";
+        }
+
+        // remove trailing "/" from URI, in case user provided e.g.
+        // http://test.com/
+        mURL = uri.toString().replaceAll("/$", "") + "/" + mEndpoint + "/1/";
         mHandler = handler;
     }
 
@@ -54,8 +67,7 @@ private static String downloadUriAsString(final HttpUriRequest req) throws IOExc
         try {
             HttpResponse res = client.execute(req);
             return readToEnd(res.getEntity().getContent());
-        }
-        finally {
+        } finally {
             client.close();
         }
     }
@@ -91,21 +103,21 @@ public void run() {
             }
         });
     }
-    
+
     public void emit(final String message) {
         mSendHandler.post(new Runnable() {
-            
+
             @Override
             public void run() {
                 mClient.send(String.format("3:::%s", message));
             }
         });
     }
-    
+
     public void emit(final JSONObject jsonMessage) {
-        
+
         mSendHandler.post(new Runnable() {
-            
+
             @Override
             public void run() {
                 mClient.send(String.format("4:::%s", jsonMessage.toString()));
@@ -140,10 +152,10 @@ public void onMessage(String message) {
                         // message
                         final String messageId = parts[1];
                         final String dataString = parts[3];
-                        
-                        if(!"".equals(messageId)) {
+
+                        if (!"".equals(messageId)) {
                             mSendHandler.post(new Runnable() {
-                                
+
                                 @Override
                                 public void run() {
                                     mClient.send(String.format("6:::%s", messageId));
@@ -154,20 +166,20 @@ public void run() {
                         break;
                     }
                     case 4: {
-                        //json message
+                        // json message
                         final String messageId = parts[1];
                         final String dataString = parts[3];
-                        
+
                         JSONObject jsonMessage = null;
-                        
+
                         try {
                             jsonMessage = new JSONObject(dataString);
-                        } catch(JSONException e) {
+                        } catch (JSONException e) {
                             jsonMessage = new JSONObject();
                         }
-                        if(!"".equals(messageId)) {
+                        if (!"".equals(messageId)) {
                             mSendHandler.post(new Runnable() {
-                                
+
                                 @Override
                                 public void run() {
                                     mClient.send(String.format("6:::%s", messageId));
@@ -211,8 +223,7 @@ public void run() {
                     default:
                         throw new Exception("unknown code");
                     }
-                }
-                catch (Exception ex) {
+                } catch (Exception ex) {
                     cleanup();
                     onError(ex);
                 }
@@ -252,7 +263,7 @@ public void disconnect() throws IOException {
     private void cleanup() {
         mClient.disconnect();
         mClient = null;
-       
+
         mSendLooper.quit();
         mSendLooper = null;
         mSendHandler = null;
@@ -284,12 +295,68 @@ public void run() {
                     connectSession();
 
                     Looper.loop();
-                }
-                catch (Exception e) {
+                } catch (Exception e) {
                     mHandler.onError(e);
                 }
             };
         }.start();
     }
-}
 
+    /**
+     * Connect to an endpoint
+     */
+    public void connectToEndpoint(final String endpoint) {
+
+        if (mClient.isConnected() && !TextUtils.isEmpty(endpoint)) {
+            mEndpoint = endpoint;
+            mSendHandler.post(new Runnable() {
+
+                @Override
+                public void run() {
+                    mClient.send("1::" + endpoint);
+                }
+            });
+        }
+    }
+
+    /**
+     * Disconnect from an endpoint or socket
+     * 
+     * @param endpoint
+     *            {@code null} to disconnect the entire socket, otherwise the
+     *            endpoint to disconnect from
+     */
+    public void sendDisconnect(final String endpoint) {
+
+        if (TextUtils.isEmpty(endpoint)) {
+
+            mSendHandler.post(new Runnable() {
+
+                @Override
+                public void run() {
+                    mClient.send("0");
+                }
+            });
+        }
+
+        else {
+            mSendHandler.post(new Runnable() {
+
+                @Override
+                public void run() {
+                    mClient.send("0::" + endpoint);
+                }
+            });
+        }
+    }
+
+    /**
+     * Get the current connected endpoint
+     * 
+     * @return The current connected endpoint, "socket.io" if connected to the
+     *         default endpoint
+     */
+    public String getConnectedEndpoint() {
+        return mEndpoint;
+    }
+}

From b7e9b765c361f0b4a9a2cfab004576eb39b53930 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Mon, 20 May 2013 22:10:39 +0530
Subject: [PATCH 29/56] Added listener event for connecting to endpoints

---
 src/com/codebutler/android_websockets/SocketIOClient.java | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 58e2268..00a08c2 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -25,6 +25,8 @@ public class SocketIOClient {
     public static interface Handler {
         public void onConnect();
 
+        public void onConnectToEndpoint(String endpoint);
+
         public void on(String event, JSONArray arguments);
 
         public void onDisconnect(int code, String reason);
@@ -142,7 +144,11 @@ public void onMessage(String message) {
                     switch (code) {
                     case 1:
                         // connect
-                        mHandler.onConnect();
+                        if (!TextUtils.isEmpty(parts[2])) {
+                            mHandler.onConnectToEndpoint(parts[2]);
+                        } else {
+                            mHandler.onConnect();
+                        }
                         break;
                     case 2:
                         // heartbeat

From 4c28f20bd101a8247fdddd566517d91846627a93 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Mon, 20 May 2013 23:30:54 +0530
Subject: [PATCH 30/56] Added support for Acknowledges

---
 .../android_websockets/SocketIOClient.java    | 78 +++++++++++++++++--
 1 file changed, 70 insertions(+), 8 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index c07ee5a..1a861dd 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -8,6 +8,7 @@
 import java.net.URISyntaxException;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpPost;
@@ -20,6 +21,7 @@
 import android.os.Looper;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 
 public class SocketIOClient {
     public static interface Handler {
@@ -38,6 +40,10 @@ public static interface Handler {
         public void onError(Exception error);
     }
 
+    public static interface Acknowledge {
+        public void acknowledge(String[] args);
+    }
+
     private static final String TAG = "SocketIOClient";
 
     String mURL;
@@ -46,6 +52,8 @@ public static interface Handler {
     int mHeartbeat;
     WebSocketClient mClient;
     String mEndpoint;
+    private AtomicInteger mMessageIdCounter;
+    private SparseArray<Acknowledge> mAcknowledges;
 
     public SocketIOClient(URI uri, Handler handler) {
         this(uri, handler, null);
@@ -53,7 +61,8 @@ public SocketIOClient(URI uri, Handler handler) {
 
     public SocketIOClient(URI uri, Handler handler, String namespace) {
         mEndpoint = namespace;
-
+        mAcknowledges = new SparseArray<SocketIOClient.Acknowledge>();
+        mMessageIdCounter = new AtomicInteger(0);
         if (TextUtils.isEmpty(namespace)) {
             mEndpoint = "socket.io";
         }
@@ -74,6 +83,10 @@ private static String downloadUriAsString(final HttpUriRequest req) throws IOExc
         }
     }
 
+    private int getNextMessageId() {
+        return mMessageIdCounter.incrementAndGet();
+    }
+
     private static byte[] readToEndAsArray(InputStream input) throws IOException {
         DataInputStream dis = new DataInputStream(input);
         byte[] stuff = new byte[1024];
@@ -94,35 +107,60 @@ private static String readToEnd(InputStream input) throws IOException {
     Looper mSendLooper;
 
     public void emit(String name, JSONArray args) throws JSONException {
+        emit(name, args, null);
+    }
+
+    public void emit(String name, JSONArray args, Acknowledge acknowledge) throws JSONException {
         final JSONObject event = new JSONObject();
         event.put("name", name);
         event.put("args", args);
-        Log.d(TAG, "Emitting event: " + event.toString());
+
+        final int nextId = getNextMessageId();
+        if (acknowledge != null) {
+            mAcknowledges.put(nextId, acknowledge);
+        }
         mSendHandler.post(new Runnable() {
             @Override
             public void run() {
-                mClient.send(String.format("5:::%s", event.toString()));
+                mClient.send(String.format("5:" + nextId + "+::%s", event.toString()));
             }
         });
     }
 
-    public void emit(final String message) {
+    public void emit(final JSONObject jsonMessage) throws JSONException {
+        emit(jsonMessage, null);
+    }
+
+    public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) throws JSONException {
+
+        final int nextId = getNextMessageId();
+        if (acknowledge != null) {
+            mAcknowledges.put(nextId, acknowledge);
+        }
         mSendHandler.post(new Runnable() {
 
             @Override
             public void run() {
-                mClient.send(String.format("3:::%s", message));
+                mClient.send(String.format("4:" + nextId + "+::%s", jsonMessage.toString()));
             }
         });
     }
 
-    public void emit(final JSONObject jsonMessage) {
+    public void emit(final String message) {
+        emit(message, (Acknowledge) null);
+    }
 
+    public void emit(final String message, Acknowledge acknowledge) {
+
+        final int nextId = getNextMessageId();
+        if (acknowledge != null) {
+            mAcknowledges.put(nextId, acknowledge);
+        }
         mSendHandler.post(new Runnable() {
 
             @Override
             public void run() {
-                mClient.send(String.format("4:::%s", jsonMessage.toString()));
+                mClient.send(String.format("3:" + nextId + "+::%s", message));
             }
         });
     }
@@ -219,6 +257,29 @@ public void run() {
                     }
                     case 6:
                         // ACK
+                        if (parts[3] != null && parts[3].contains("+")) {
+                            String[] ackParts = parts[3].split("\\+");
+                            int ackId = Integer.valueOf(ackParts[0]);
+
+                            String ackArgs = ackParts[1];
+
+                            int startIndex = ackArgs.indexOf('[') + 1;
+
+                            ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1);
+
+                            Acknowledge acknowledge = mAcknowledges.get(ackId);
+
+                            if (acknowledge != null) {
+
+                                String[] params = ackArgs.split(",");
+                                for (int i = 0; i < params.length; i++) {
+                                    params[i] = params[i].replace("\"", "");
+                                }
+                                acknowledge.acknowledge(params);
+                            }
+
+                            mAcknowledges.remove(ackId);
+                        }
                         break;
                     case 7:
                         // error
@@ -270,7 +331,8 @@ private void cleanup() {
         if (mClient != null)
             mClient.disconnect();
         mClient = null;
-
+        mMessageIdCounter.set(0);
+        mAcknowledges.clear();
         mSendLooper.quit();
         mSendLooper = null;
         mSendHandler = null;

From 2f586e0d26702d9ef4e6ec2a2482a88ed6a16513 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinaysshenoy@gmail.com>
Date: Mon, 20 May 2013 23:39:18 +0530
Subject: [PATCH 31/56] Update README.md

---
 README.md | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/README.md b/README.md
index 64d852e..d720fee 100644
--- a/README.md
+++ b/README.md
@@ -104,6 +104,22 @@ client.emit(second); //JSON Message
 arguments.put(second);
 client.emit("hello", arguments); //Event
 client.disconnect();
+
+/*How to use Acknowledges
+*Call any of the emit() methods 
+*with the Acknowledge parameter.
+*
+*Each message must have a new
+*Acknowledge object as they
+*are matched against the Message Id.
+*/
+client.emit("Message", new Acknowledge {
+
+	@Override
+	public void acknowledge(String[] args) {
+		//Perform your acknowledge handling here
+	}
+});
 ```
 
 

From f7487f263630037949528d6424ec296b4e0f28ed Mon Sep 17 00:00:00 2001
From: Lior Sion <lior.sion@gmail.com>
Date: Tue, 21 May 2013 16:31:19 +0300
Subject: [PATCH 32/56] add end pointed connected override to the handler

the new endpoint connected callback is mandatory in the handler
interface, without it the README example doesn't work
---
 .gitignore |  1 +
 README.md  | 10 ++++++++++
 2 files changed, 11 insertions(+)

diff --git a/.gitignore b/.gitignore
index e66abab..a361a6e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ gen
 .project
 local.properties
 /.settings
+project.properties
diff --git a/README.md b/README.md
index 64d852e..e384ddc 100644
--- a/README.md
+++ b/README.md
@@ -44,6 +44,11 @@ WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), n
     public void onError(Exception error) {
         Log.e(TAG, "Error!", error);
     }
+
+    @Override
+    public void onConnectToEndpoint(String endpoint) {
+        Log.d(TAG, "Connected to endpoint")
+    }
 }, extraHeaders);
 
 client.connect();
@@ -90,6 +95,11 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new
     public void onError(Exception error) {
         Log.e(TAG, "Error!", error);
     }
+
+    @Override
+    public void onConnectToEndpoint(String endpoint) {
+        Log.d(TAG, "Connected to endpoint")
+    }
 });
 
 client.connect();

From 3a2f9d9e5841089c56f0a5508bcbe947b65677be Mon Sep 17 00:00:00 2001
From: Lior Sion <lior.sion@gmail.com>
Date: Tue, 21 May 2013 19:19:33 +0300
Subject: [PATCH 33/56] only socketio client has endpoints, normal websocket
 doesn't

---
 README.md | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/README.md b/README.md
index e384ddc..00957da 100644
--- a/README.md
+++ b/README.md
@@ -45,10 +45,6 @@ WebSocketClient client = new WebSocketClient(URI.create("wss://irccloud.com"), n
         Log.e(TAG, "Error!", error);
     }
 
-    @Override
-    public void onConnectToEndpoint(String endpoint) {
-        Log.d(TAG, "Connected to endpoint")
-    }
 }, extraHeaders);
 
 client.connect();

From 604cfdd96433dda894b7506ab25de101711aac24 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinaysshenoy@gmail.com>
Date: Wed, 22 May 2013 21:11:17 +0530
Subject: [PATCH 34/56] Update README.md

The update for onConnectToEndpoint() got overwritten by my changes for Acknowledges. Adding the method back to the README.
---
 README.md | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/README.md b/README.md
index d720fee..0a17fb3 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,11 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new
     public void onError(Exception error) {
         Log.e(TAG, "Error!", error);
     }
+    
+    @Override
+    public void onConnectToEndpoint(String endpoint) {
+        Log.d(TAG, "Connected to endpoint")
+    }
 });
 
 client.connect();

From 21ef8eeb111f1be4fece260f7ccb16808a4692ab Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Wed, 22 May 2013 22:35:09 +0530
Subject: [PATCH 35/56] Added support for Acknowledges

---
 README.md                                     | 21 +++++
 .../android_websockets/SocketIOClient.java    | 78 +++++++++++++++++--
 2 files changed, 91 insertions(+), 8 deletions(-)

diff --git a/README.md b/README.md
index 64d852e..8c3c870 100644
--- a/README.md
+++ b/README.md
@@ -90,6 +90,11 @@ SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new
     public void onError(Exception error) {
         Log.e(TAG, "Error!", error);
     }
+    
+    @Override
+    public void onConnectToEndpoint(String endpoint) {
+    	Log.d(TAG, "Connected to:" + endpoint);
+    }
 });
 
 client.connect();
@@ -104,6 +109,22 @@ client.emit(second); //JSON Message
 arguments.put(second);
 client.emit("hello", arguments); //Event
 client.disconnect();
+
+/*How to use Acknowledges
+*Call any of the emit() methods 
+*with the Acknowledge parameter.
+*
+*Each message must have a new
+*Acknowledge object as they
+*are matched against the Message Id.
+*/
+client.emit("Message", new Acknowledge {
+
+	@Override
+	public void acknowledge(String[] args) {
+		//Perform your acknowledge handling here
+	}
+});
 ```
 
 
diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index c07ee5a..1a861dd 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -8,6 +8,7 @@
 import java.net.URISyntaxException;
 import java.util.Arrays;
 import java.util.HashSet;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpPost;
@@ -20,6 +21,7 @@
 import android.os.Looper;
 import android.text.TextUtils;
 import android.util.Log;
+import android.util.SparseArray;
 
 public class SocketIOClient {
     public static interface Handler {
@@ -38,6 +40,10 @@ public static interface Handler {
         public void onError(Exception error);
     }
 
+    public static interface Acknowledge {
+        public void acknowledge(String[] args);
+    }
+
     private static final String TAG = "SocketIOClient";
 
     String mURL;
@@ -46,6 +52,8 @@ public static interface Handler {
     int mHeartbeat;
     WebSocketClient mClient;
     String mEndpoint;
+    private AtomicInteger mMessageIdCounter;
+    private SparseArray<Acknowledge> mAcknowledges;
 
     public SocketIOClient(URI uri, Handler handler) {
         this(uri, handler, null);
@@ -53,7 +61,8 @@ public SocketIOClient(URI uri, Handler handler) {
 
     public SocketIOClient(URI uri, Handler handler, String namespace) {
         mEndpoint = namespace;
-
+        mAcknowledges = new SparseArray<SocketIOClient.Acknowledge>();
+        mMessageIdCounter = new AtomicInteger(0);
         if (TextUtils.isEmpty(namespace)) {
             mEndpoint = "socket.io";
         }
@@ -74,6 +83,10 @@ private static String downloadUriAsString(final HttpUriRequest req) throws IOExc
         }
     }
 
+    private int getNextMessageId() {
+        return mMessageIdCounter.incrementAndGet();
+    }
+
     private static byte[] readToEndAsArray(InputStream input) throws IOException {
         DataInputStream dis = new DataInputStream(input);
         byte[] stuff = new byte[1024];
@@ -94,35 +107,60 @@ private static String readToEnd(InputStream input) throws IOException {
     Looper mSendLooper;
 
     public void emit(String name, JSONArray args) throws JSONException {
+        emit(name, args, null);
+    }
+
+    public void emit(String name, JSONArray args, Acknowledge acknowledge) throws JSONException {
         final JSONObject event = new JSONObject();
         event.put("name", name);
         event.put("args", args);
-        Log.d(TAG, "Emitting event: " + event.toString());
+
+        final int nextId = getNextMessageId();
+        if (acknowledge != null) {
+            mAcknowledges.put(nextId, acknowledge);
+        }
         mSendHandler.post(new Runnable() {
             @Override
             public void run() {
-                mClient.send(String.format("5:::%s", event.toString()));
+                mClient.send(String.format("5:" + nextId + "+::%s", event.toString()));
             }
         });
     }
 
-    public void emit(final String message) {
+    public void emit(final JSONObject jsonMessage) throws JSONException {
+        emit(jsonMessage, null);
+    }
+
+    public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) throws JSONException {
+
+        final int nextId = getNextMessageId();
+        if (acknowledge != null) {
+            mAcknowledges.put(nextId, acknowledge);
+        }
         mSendHandler.post(new Runnable() {
 
             @Override
             public void run() {
-                mClient.send(String.format("3:::%s", message));
+                mClient.send(String.format("4:" + nextId + "+::%s", jsonMessage.toString()));
             }
         });
     }
 
-    public void emit(final JSONObject jsonMessage) {
+    public void emit(final String message) {
+        emit(message, (Acknowledge) null);
+    }
 
+    public void emit(final String message, Acknowledge acknowledge) {
+
+        final int nextId = getNextMessageId();
+        if (acknowledge != null) {
+            mAcknowledges.put(nextId, acknowledge);
+        }
         mSendHandler.post(new Runnable() {
 
             @Override
             public void run() {
-                mClient.send(String.format("4:::%s", jsonMessage.toString()));
+                mClient.send(String.format("3:" + nextId + "+::%s", message));
             }
         });
     }
@@ -219,6 +257,29 @@ public void run() {
                     }
                     case 6:
                         // ACK
+                        if (parts[3] != null && parts[3].contains("+")) {
+                            String[] ackParts = parts[3].split("\\+");
+                            int ackId = Integer.valueOf(ackParts[0]);
+
+                            String ackArgs = ackParts[1];
+
+                            int startIndex = ackArgs.indexOf('[') + 1;
+
+                            ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1);
+
+                            Acknowledge acknowledge = mAcknowledges.get(ackId);
+
+                            if (acknowledge != null) {
+
+                                String[] params = ackArgs.split(",");
+                                for (int i = 0; i < params.length; i++) {
+                                    params[i] = params[i].replace("\"", "");
+                                }
+                                acknowledge.acknowledge(params);
+                            }
+
+                            mAcknowledges.remove(ackId);
+                        }
                         break;
                     case 7:
                         // error
@@ -270,7 +331,8 @@ private void cleanup() {
         if (mClient != null)
             mClient.disconnect();
         mClient = null;
-
+        mMessageIdCounter.set(0);
+        mAcknowledges.clear();
         mSendLooper.quit();
         mSendLooper = null;
         mSendHandler = null;

From 36cc20902b49d97c29b28e8db23ebe77c135bb5d Mon Sep 17 00:00:00 2001
From: Chris Peterson <cpeterson@mozilla.com>
Date: Thu, 23 May 2013 12:52:27 -0700
Subject: [PATCH 36/56] Avoid mSocket NPE if WebSocketClient.disconnect() is
 called twice

---
 .../android_websockets/WebSocketClient.java        | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java
index 82aadbe..a75ec64 100644
--- a/src/com/codebutler/android_websockets/WebSocketClient.java
+++ b/src/com/codebutler/android_websockets/WebSocketClient.java
@@ -150,14 +150,16 @@ public void disconnect() {
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
-                    try {
-                        mSocket.close();
+                    if (mSocket != null) {
+                        try {
+                            mSocket.close();
+                        } catch (IOException ex) {
+                            Log.d(TAG, "Error while disconnecting", ex);
+                            mListener.onError(ex);
+                        }
                         mSocket = null;
-                        mConnected = false;
-                    } catch (IOException ex) {
-                        Log.d(TAG, "Error while disconnecting", ex);
-                        mListener.onError(ex);
                     }
+                    mConnected = false;
                 }
             });
         }

From 57be2c6dd240a91b8f16db1e59a858536ccb4734 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?M=C3=A1ximo=20Mussini?= <maximomussini@gmail.com>
Date: Sat, 1 Jun 2013 10:15:57 -0300
Subject: [PATCH 37/56] Avoid calling cleanup twice on error condition

---
 src/com/codebutler/android_websockets/SocketIOClient.java | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 1a861dd..eeef8da 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -291,7 +291,6 @@ public void run() {
                         throw new Exception("unknown code");
                     }
                 } catch (Exception ex) {
-                    cleanup();
                     onError(ex);
                 }
             }

From 61be4e5c55c5d2afb66ef119f6616fb07427c35d Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Thu, 6 Jun 2013 20:35:06 +0530
Subject: [PATCH 38/56] Fixed msgId in emit() messages being appended with '+'
 when Ackowledge not added

---
 .../android_websockets/SocketIOClient.java           | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index eeef8da..c4e638c 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -110,7 +110,7 @@ public void emit(String name, JSONArray args) throws JSONException {
         emit(name, args, null);
     }
 
-    public void emit(String name, JSONArray args, Acknowledge acknowledge) throws JSONException {
+    public void emit(String name, JSONArray args, final Acknowledge acknowledge) throws JSONException {
         final JSONObject event = new JSONObject();
         event.put("name", name);
         event.put("args", args);
@@ -122,7 +122,7 @@ public void emit(String name, JSONArray args, Acknowledge acknowledge) throws JS
         mSendHandler.post(new Runnable() {
             @Override
             public void run() {
-                mClient.send(String.format("5:" + nextId + "+::%s", event.toString()));
+                mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +"::%s", event.toString()));
             }
         });
     }
@@ -131,7 +131,7 @@ public void emit(final JSONObject jsonMessage) throws JSONException {
         emit(jsonMessage, null);
     }
 
-    public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) throws JSONException {
+    public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) throws JSONException {
 
         final int nextId = getNextMessageId();
         if (acknowledge != null) {
@@ -141,7 +141,7 @@ public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) throws J
 
             @Override
             public void run() {
-                mClient.send(String.format("4:" + nextId + "+::%s", jsonMessage.toString()));
+                mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + "::%s", jsonMessage.toString()));
             }
         });
     }
@@ -150,7 +150,7 @@ public void emit(final String message) {
         emit(message, (Acknowledge) null);
     }
 
-    public void emit(final String message, Acknowledge acknowledge) {
+    public void emit(final String message, final Acknowledge acknowledge) {
 
         final int nextId = getNextMessageId();
         if (acknowledge != null) {
@@ -160,7 +160,7 @@ public void emit(final String message, Acknowledge acknowledge) {
 
             @Override
             public void run() {
-                mClient.send(String.format("3:" + nextId + "+::%s", message));
+                mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +"::%s", message));
             }
         });
     }

From 7583bf9c9162ae3b5a118c058bc0111e4def0a00 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Sat, 15 Jun 2013 11:46:08 +0530
Subject: [PATCH 39/56] Implemented auto-reconnect with exponential backoff for
 SocketIOClient

---
 .../android_websockets/SocketIOClient.java    | 342 ++++++++++--------
 1 file changed, 195 insertions(+), 147 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index c4e638c..10ad9b9 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -24,6 +24,12 @@
 import android.util.SparseArray;
 
 public class SocketIOClient {
+
+    private static final String TAG = "SocketIOClient";
+
+    private static final int CONNECT_MULTIPLIER = 2;
+    private static final int MAX_CONNECT_POWER = 10;
+
     public static interface Handler {
         public void onConnect();
 
@@ -44,22 +50,27 @@ public static interface Acknowledge {
         public void acknowledge(String[] args);
     }
 
-    private static final String TAG = "SocketIOClient";
-
-    String mURL;
-    Handler mHandler;
-    String mSession;
-    int mHeartbeat;
-    WebSocketClient mClient;
-    String mEndpoint;
+    private String mURL;
+    private Handler mHandler;
+    private String mSession;
+    private int mHeartbeat;
+    private WebSocketClient mClient;
+    private String mEndpoint;
     private AtomicInteger mMessageIdCounter;
     private SparseArray<Acknowledge> mAcknowledges;
+    private boolean mReconnectOnError;
+    private int mConnectPower;
 
     public SocketIOClient(URI uri, Handler handler) {
-        this(uri, handler, null);
+        this(uri, handler, null, false);
+    }
+
+    public SocketIOClient(URI uri, Handler handler, boolean reconnectOnError) {
+        this(uri, handler, null, reconnectOnError);
     }
 
-    public SocketIOClient(URI uri, Handler handler, String namespace) {
+    public SocketIOClient(URI uri, Handler handler, String namespace,
+            boolean reconnectOnError) {
         mEndpoint = namespace;
         mAcknowledges = new SparseArray<SocketIOClient.Acknowledge>();
         mMessageIdCounter = new AtomicInteger(0);
@@ -71,10 +82,13 @@ public SocketIOClient(URI uri, Handler handler, String namespace) {
         // http://test.com/
         mURL = uri.toString().replaceAll("/$", "") + "/" + mEndpoint + "/1/";
         mHandler = handler;
+        mConnectPower = 1;
+        mReconnectOnError = reconnectOnError;
     }
 
     private static String downloadUriAsString(final HttpUriRequest req) throws IOException {
-        AndroidHttpClient client = AndroidHttpClient.newInstance("android-websockets");
+        AndroidHttpClient client = AndroidHttpClient
+                .newInstance("android-websockets");
         try {
             HttpResponse res = client.execute(req);
             return readToEnd(res.getEntity().getContent());
@@ -122,7 +136,9 @@ public void emit(String name, JSONArray args, final Acknowledge acknowledge) thr
         mSendHandler.post(new Runnable() {
             @Override
             public void run() {
-                mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +"::%s", event.toString()));
+                mClient.send(String.format("5:" + nextId
+                        + (acknowledge == null ? "" : "+") + "::%s",
+                        event.toString()));
             }
         });
     }
@@ -141,7 +157,9 @@ public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) th
 
             @Override
             public void run() {
-                mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + "::%s", jsonMessage.toString()));
+                mClient.send(String.format("4:" + nextId
+                        + (acknowledge == null ? "" : "+") + "::%s",
+                        jsonMessage.toString()));
             }
         });
     }
@@ -160,168 +178,197 @@ public void emit(final String message, final Acknowledge acknowledge) {
 
             @Override
             public void run() {
-                mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +"::%s", message));
+                mClient.send(String.format("3:" + nextId
+                        + (acknowledge == null ? "" : "+") + "::%s", message));
             }
         });
     }
 
     private void connectSession() throws URISyntaxException {
-        mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() {
-            @Override
-            public void onMessage(byte[] data) {
-                cleanup();
-                mHandler.onError(new Exception("Unexpected binary data"));
-            }
-
-            @Override
-            public void onMessage(String message) {
-                try {
-                    Log.d(TAG, "Message: " + message);
-                    String[] parts = message.split(":", 4);
-                    int code = Integer.parseInt(parts[0]);
-                    switch (code) {
-                    case 1:
-                        // connect
-                        if (!TextUtils.isEmpty(parts[2])) {
-                            mHandler.onConnectToEndpoint(parts[2]);
-                        } else {
-                            mHandler.onConnect();
-                        }
-                        break;
-                    case 2:
-                        // heartbeat
-                        mClient.send("2::");
-                        break;
-                    case 3: {
-                        // message
-                        final String messageId = parts[1];
-                        final String dataString = parts[3];
-
-                        if (!"".equals(messageId)) {
-                            mSendHandler.post(new Runnable() {
-
-                                @Override
-                                public void run() {
-                                    mClient.send(String.format("6:::%s", messageId));
-                                }
-                            });
-                        }
-                        mHandler.onMessage(dataString);
-                        break;
+        mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession),
+                new WebSocketClient.Listener() {
+                    @Override
+                    public void onMessage(byte[] data) {
+                        cleanup();
+                        mHandler
+                                .onError(new Exception("Unexpected binary data"));
                     }
-                    case 4: {
-                        // json message
-                        final String messageId = parts[1];
-                        final String dataString = parts[3];
-
-                        JSONObject jsonMessage = null;
 
+                    @Override
+                    public void onMessage(String message) {
                         try {
-                            jsonMessage = new JSONObject(dataString);
-                        } catch (JSONException e) {
-                            jsonMessage = new JSONObject();
-                        }
-                        if (!"".equals(messageId)) {
-                            mSendHandler.post(new Runnable() {
+                            Log.d(TAG, "Message: " + message);
+                            String[] parts = message.split(":", 4);
+                            int code = Integer.parseInt(parts[0]);
+                            switch (code) {
+                            case 1:
+                                // connect
+                                if (!TextUtils.isEmpty(parts[2])) {
+                                    mHandler.onConnectToEndpoint(parts[2]);
+                                } else {
+                                    mHandler.onConnect();
+                                }
+                                break;
+                            case 2:
+                                // heartbeat
+                                mClient.send("2::");
+                                break;
+                            case 3: {
+                                // message
+                                final String messageId = parts[1];
+                                final String dataString = parts[3];
+
+                                if (!"".equals(messageId)) {
+                                    mSendHandler.post(new Runnable() {
+
+                                        @Override
+                                        public void run() {
+                                            mClient.send(String.format(
+                                                    "6:::%s", messageId));
+                                        }
+                                    });
+                                }
+                                mHandler.onMessage(dataString);
+                                break;
+                            }
+                            case 4: {
+                                // json message
+                                final String messageId = parts[1];
+                                final String dataString = parts[3];
 
-                                @Override
-                                public void run() {
-                                    mClient.send(String.format("6:::%s", messageId));
+                                JSONObject jsonMessage = null;
+
+                                try {
+                                    jsonMessage = new JSONObject(dataString);
+                                } catch (JSONException e) {
+                                    jsonMessage = new JSONObject();
                                 }
-                            });
-                        }
-                        mHandler.onJSON(jsonMessage);
-                        break;
-                    }
-                    case 5: {
-                        final String messageId = parts[1];
-                        final String dataString = parts[3];
-                        JSONObject data = new JSONObject(dataString);
-                        String event = data.getString("name");
-                        JSONArray args;
-                        try {
-                            args = data.getJSONArray("args");
-                        } catch (JSONException e) {
-                            args = new JSONArray();
-                        }
-                        if (!"".equals(messageId)) {
-                            mSendHandler.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    mClient.send(String.format("6:::%s", messageId));
+                                if (!"".equals(messageId)) {
+                                    mSendHandler.post(new Runnable() {
+
+                                        @Override
+                                        public void run() {
+                                            mClient.send(String.format(
+                                                    "6:::%s", messageId));
+                                        }
+                                    });
                                 }
-                            });
-                        }
-                        mHandler.on(event, args);
-                        break;
-                    }
-                    case 6:
-                        // ACK
-                        if (parts[3] != null && parts[3].contains("+")) {
-                            String[] ackParts = parts[3].split("\\+");
-                            int ackId = Integer.valueOf(ackParts[0]);
+                                mHandler.onJSON(jsonMessage);
+                                break;
+                            }
+                            case 5: {
+                                final String messageId = parts[1];
+                                final String dataString = parts[3];
+                                JSONObject data = new JSONObject(dataString);
+                                String event = data.getString("name");
+                                JSONArray args;
+                                try {
+                                    args = data.getJSONArray("args");
+                                } catch (JSONException e) {
+                                    args = new JSONArray();
+                                }
+                                if (!"".equals(messageId)) {
+                                    mSendHandler.post(new Runnable() {
+                                        @Override
+                                        public void run() {
+                                            mClient.send(String.format(
+                                                    "6:::%s", messageId));
+                                        }
+                                    });
+                                }
+                                mHandler.on(event, args);
+                                break;
+                            }
+                            case 6:
+                                // ACK
+                                if (parts[3] != null && parts[3].contains("+")) {
+                                    String[] ackParts = parts[3].split("\\+");
+                                    int ackId = Integer.valueOf(ackParts[0]);
 
-                            String ackArgs = ackParts[1];
+                                    String ackArgs = ackParts[1];
 
-                            int startIndex = ackArgs.indexOf('[') + 1;
+                                    int startIndex = ackArgs.indexOf('[') + 1;
 
-                            ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1);
+                                    ackArgs = ackArgs.substring(startIndex,
+                                            ackArgs.length() - 1);
 
-                            Acknowledge acknowledge = mAcknowledges.get(ackId);
+                                    Acknowledge acknowledge = mAcknowledges
+                                            .get(ackId);
 
-                            if (acknowledge != null) {
+                                    if (acknowledge != null) {
 
-                                String[] params = ackArgs.split(",");
-                                for (int i = 0; i < params.length; i++) {
-                                    params[i] = params[i].replace("\"", "");
+                                        String[] params = ackArgs.split(",");
+                                        for (int i = 0; i < params.length; i++) {
+                                            params[i] = params[i].replace("\"",
+                                                    "");
+                                        }
+                                        acknowledge.acknowledge(params);
+                                    }
+
+                                    mAcknowledges.remove(ackId);
                                 }
-                                acknowledge.acknowledge(params);
+                                break;
+                            case 7:
+                                // error
+                                throw new Exception(message);
+                            case 8:
+                                // noop
+                                break;
+                            default:
+                                throw new Exception("unknown code");
                             }
-
-                            mAcknowledges.remove(ackId);
+                        } catch (Exception ex) {
+                            onError(ex);
                         }
-                        break;
-                    case 7:
-                        // error
-                        throw new Exception(message);
-                    case 8:
-                        // noop
-                        break;
-                    default:
-                        throw new Exception("unknown code");
                     }
-                } catch (Exception ex) {
-                    onError(ex);
-                }
-            }
 
-            @Override
-            public void onError(Exception error) {
-                cleanup();
-                mHandler.onError(error);
-            }
+                    @Override
+                    public void onError(Exception error) {
+                        cleanup();
+                        mHandler.onError(error);
 
-            @Override
-            public void onDisconnect(int code, String reason) {
-                cleanup();
-                // attempt reconnect with same session?
-                mHandler.onDisconnect(code, reason);
-            }
+                        if (mReconnectOnError) {
+                            mSendHandler.postDelayed(new Runnable() {
+
+                                @Override
+                                public void run() {
+                                    
+                                    //Doing this check here because the client can be forcibly connected before the Runnable runs
+                                    if (!mClient.isConnected()) {
+                                        connect();
+                                    }
+
+                                }
+                            }, getDelayInMillis());
+                        }
+                    }
 
-            @Override
-            public void onConnect() {
-                mSendHandler.postDelayed(new Runnable() {
                     @Override
-                    public void run() {
-                        mSendHandler.postDelayed(this, mHeartbeat);
-                        mClient.send("2:::");
+                    public void onDisconnect(int code, String reason) {
+                        cleanup();
+                        // attempt reconnect with same session?
+                        mHandler.onDisconnect(code, reason);
                     }
-                }, mHeartbeat);
-            }
-        }, null);
+
+                    @Override
+                    public void onConnect() {
+                        mConnectPower = 1;
+                        mSendHandler.postDelayed(new Runnable() {
+                            @Override
+                            public void run() {
+                                mSendHandler.postDelayed(this, mHeartbeat);
+                                mClient.send("2:::");
+                            }
+                        }, mHeartbeat);
+                    }
+                }, null);
         mClient.connect();
     }
 
+    private long getDelayInMillis() {
+        return (long) (Math.pow(CONNECT_MULTIPLIER, (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER : mConnectPower++) * 1000);
+    }
+
     public void disconnect() throws IOException {
         cleanup();
     }
@@ -352,7 +399,8 @@ public void run() {
                         mHeartbeat = Integer.parseInt(heartbeat) / 2 * 1000;
                     String transportsLine = parts[3];
                     String[] transports = transportsLine.split(",");
-                    HashSet<String> set = new HashSet<String>(Arrays.asList(transports));
+                    HashSet<String> set = new HashSet<String>(
+                            Arrays.asList(transports));
                     if (!set.contains("websocket"))
                         throw new Exception("websocket not supported");
 

From fb3865c87215f4769700bfed21ae167d4a4c0201 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Sat, 15 Jun 2013 11:54:18 +0530
Subject: [PATCH 40/56] Changed cleanup code because we can reconnect on error

---
 .../android_websockets/SocketIOClient.java    | 26 ++++++++++++++++---
 1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 10ad9b9..d205683 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -324,16 +324,28 @@ public void run() {
 
                     @Override
                     public void onError(Exception error) {
-                        cleanup();
+
+                        // Removed call to cleanup because now we can
+                        // reconnect automatically so we need the mSendHandler
+                        // instance
                         mHandler.onError(error);
 
                         if (mReconnectOnError) {
+                            
+                            if (mClient != null)
+                                mClient.disconnect();
+                            mClient = null;
+                            mMessageIdCounter.set(0);
+                            mAcknowledges.clear();
+                            
                             mSendHandler.postDelayed(new Runnable() {
 
                                 @Override
                                 public void run() {
-                                    
-                                    //Doing this check here because the client can be forcibly connected before the Runnable runs
+
+                                    // Doing this check here because the client
+                                    // can be forcibly connected before the
+                                    // Runnable runs
                                     if (!mClient.isConnected()) {
                                         connect();
                                     }
@@ -341,6 +353,10 @@ public void run() {
                                 }
                             }, getDelayInMillis());
                         }
+
+                        else {
+                            cleanup();
+                        }
                     }
 
                     @Override
@@ -366,7 +382,9 @@ public void run() {
     }
 
     private long getDelayInMillis() {
-        return (long) (Math.pow(CONNECT_MULTIPLIER, (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER : mConnectPower++) * 1000);
+        return (long) (Math.pow(CONNECT_MULTIPLIER,
+                (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER
+                        : mConnectPower++) * 1000);
     }
 
     public void disconnect() throws IOException {

From c3d08a1ffa464ee6c90513c25d57065c1fbc782d Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Sat, 15 Jun 2013 11:56:57 +0530
Subject: [PATCH 41/56] Fixed formatting

---
 .../android_websockets/SocketIOClient.java    | 343 +++++++++---------
 1 file changed, 165 insertions(+), 178 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index d205683..0f3557c 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -87,8 +87,7 @@ public SocketIOClient(URI uri, Handler handler, String namespace,
     }
 
     private static String downloadUriAsString(final HttpUriRequest req) throws IOException {
-        AndroidHttpClient client = AndroidHttpClient
-                .newInstance("android-websockets");
+        AndroidHttpClient client = AndroidHttpClient.newInstance("android-websockets");
         try {
             HttpResponse res = client.execute(req);
             return readToEnd(res.getEntity().getContent());
@@ -137,8 +136,7 @@ public void emit(String name, JSONArray args, final Acknowledge acknowledge) thr
             @Override
             public void run() {
                 mClient.send(String.format("5:" + nextId
-                        + (acknowledge == null ? "" : "+") + "::%s",
-                        event.toString()));
+                        + (acknowledge == null ? "" : "+") + "::%s", event.toString()));
             }
         });
     }
@@ -158,8 +156,7 @@ public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) th
             @Override
             public void run() {
                 mClient.send(String.format("4:" + nextId
-                        + (acknowledge == null ? "" : "+") + "::%s",
-                        jsonMessage.toString()));
+                        + (acknowledge == null ? "" : "+") + "::%s", jsonMessage.toString()));
             }
         });
     }
@@ -185,206 +182,197 @@ public void run() {
     }
 
     private void connectSession() throws URISyntaxException {
-        mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession),
-                new WebSocketClient.Listener() {
-                    @Override
-                    public void onMessage(byte[] data) {
-                        cleanup();
-                        mHandler
-                                .onError(new Exception("Unexpected binary data"));
-                    }
+        mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() {
+            @Override
+            public void onMessage(byte[] data) {
+                cleanup();
+                mHandler.onError(new Exception("Unexpected binary data"));
+            }
 
-                    @Override
-                    public void onMessage(String message) {
-                        try {
-                            Log.d(TAG, "Message: " + message);
-                            String[] parts = message.split(":", 4);
-                            int code = Integer.parseInt(parts[0]);
-                            switch (code) {
-                            case 1:
-                                // connect
-                                if (!TextUtils.isEmpty(parts[2])) {
-                                    mHandler.onConnectToEndpoint(parts[2]);
-                                } else {
-                                    mHandler.onConnect();
-                                }
-                                break;
-                            case 2:
-                                // heartbeat
-                                mClient.send("2::");
-                                break;
-                            case 3: {
-                                // message
-                                final String messageId = parts[1];
-                                final String dataString = parts[3];
-
-                                if (!"".equals(messageId)) {
-                                    mSendHandler.post(new Runnable() {
-
-                                        @Override
-                                        public void run() {
-                                            mClient.send(String.format(
-                                                    "6:::%s", messageId));
-                                        }
-                                    });
+            @Override
+            public void onMessage(String message) {
+                try {
+                    Log.d(TAG, "Message: " + message);
+                    String[] parts = message.split(":", 4);
+                    int code = Integer.parseInt(parts[0]);
+                    switch (code) {
+                    case 1:
+                        // connect
+                        if (!TextUtils.isEmpty(parts[2])) {
+                            mHandler.onConnectToEndpoint(parts[2]);
+                        } else {
+                            mHandler.onConnect();
+                        }
+                        break;
+                    case 2:
+                        // heartbeat
+                        mClient.send("2::");
+                        break;
+                    case 3: {
+                        // message
+                        final String messageId = parts[1];
+                        final String dataString = parts[3];
+
+                        if (!"".equals(messageId)) {
+                            mSendHandler.post(new Runnable() {
+
+                                @Override
+                                public void run() {
+                                    mClient.send(String.format("6:::%s", messageId));
                                 }
-                                mHandler.onMessage(dataString);
-                                break;
-                            }
-                            case 4: {
-                                // json message
-                                final String messageId = parts[1];
-                                final String dataString = parts[3];
+                            });
+                        }
+                        mHandler.onMessage(dataString);
+                        break;
+                    }
+                    case 4: {
+                        // json message
+                        final String messageId = parts[1];
+                        final String dataString = parts[3];
 
-                                JSONObject jsonMessage = null;
+                        JSONObject jsonMessage = null;
 
-                                try {
-                                    jsonMessage = new JSONObject(dataString);
-                                } catch (JSONException e) {
-                                    jsonMessage = new JSONObject();
-                                }
-                                if (!"".equals(messageId)) {
-                                    mSendHandler.post(new Runnable() {
-
-                                        @Override
-                                        public void run() {
-                                            mClient.send(String.format(
-                                                    "6:::%s", messageId));
-                                        }
-                                    });
-                                }
-                                mHandler.onJSON(jsonMessage);
-                                break;
-                            }
-                            case 5: {
-                                final String messageId = parts[1];
-                                final String dataString = parts[3];
-                                JSONObject data = new JSONObject(dataString);
-                                String event = data.getString("name");
-                                JSONArray args;
-                                try {
-                                    args = data.getJSONArray("args");
-                                } catch (JSONException e) {
-                                    args = new JSONArray();
+                        try {
+                            jsonMessage = new JSONObject(dataString);
+                        } catch (JSONException e) {
+                            jsonMessage = new JSONObject();
+                        }
+                        if (!"".equals(messageId)) {
+                            mSendHandler.post(new Runnable() {
+
+                                @Override
+                                public void run() {
+                                    mClient.send(String.format("6:::%s", messageId));
                                 }
-                                if (!"".equals(messageId)) {
-                                    mSendHandler.post(new Runnable() {
-                                        @Override
-                                        public void run() {
-                                            mClient.send(String.format(
-                                                    "6:::%s", messageId));
-                                        }
-                                    });
+                            });
+                        }
+                        mHandler.onJSON(jsonMessage);
+                        break;
+                    }
+                    case 5: {
+                        final String messageId = parts[1];
+                        final String dataString = parts[3];
+                        JSONObject data = new JSONObject(dataString);
+                        String event = data.getString("name");
+                        JSONArray args;
+                        try {
+                            args = data.getJSONArray("args");
+                        } catch (JSONException e) {
+                            args = new JSONArray();
+                        }
+                        if (!"".equals(messageId)) {
+                            mSendHandler.post(new Runnable() {
+                                @Override
+                                public void run() {
+                                    mClient.send(String.format("6:::%s", messageId));
                                 }
-                                mHandler.on(event, args);
-                                break;
-                            }
-                            case 6:
-                                // ACK
-                                if (parts[3] != null && parts[3].contains("+")) {
-                                    String[] ackParts = parts[3].split("\\+");
-                                    int ackId = Integer.valueOf(ackParts[0]);
-
-                                    String ackArgs = ackParts[1];
+                            });
+                        }
+                        mHandler.on(event, args);
+                        break;
+                    }
+                    case 6:
+                        // ACK
+                        if (parts[3] != null && parts[3].contains("+")) {
+                            String[] ackParts = parts[3].split("\\+");
+                            int ackId = Integer.valueOf(ackParts[0]);
 
-                                    int startIndex = ackArgs.indexOf('[') + 1;
+                            String ackArgs = ackParts[1];
 
-                                    ackArgs = ackArgs.substring(startIndex,
-                                            ackArgs.length() - 1);
+                            int startIndex = ackArgs.indexOf('[') + 1;
 
-                                    Acknowledge acknowledge = mAcknowledges
-                                            .get(ackId);
+                            ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1);
 
-                                    if (acknowledge != null) {
+                            Acknowledge acknowledge = mAcknowledges.get(ackId);
 
-                                        String[] params = ackArgs.split(",");
-                                        for (int i = 0; i < params.length; i++) {
-                                            params[i] = params[i].replace("\"",
-                                                    "");
-                                        }
-                                        acknowledge.acknowledge(params);
-                                    }
+                            if (acknowledge != null) {
 
-                                    mAcknowledges.remove(ackId);
+                                String[] params = ackArgs.split(",");
+                                for (int i = 0; i < params.length; i++) {
+                                    params[i] = params[i].replace("\"", "");
                                 }
-                                break;
-                            case 7:
-                                // error
-                                throw new Exception(message);
-                            case 8:
-                                // noop
-                                break;
-                            default:
-                                throw new Exception("unknown code");
+                                acknowledge.acknowledge(params);
                             }
-                        } catch (Exception ex) {
-                            onError(ex);
+
+                            mAcknowledges.remove(ackId);
                         }
+                        break;
+                    case 7:
+                        // error
+                        throw new Exception(message);
+                    case 8:
+                        // noop
+                        break;
+                    default:
+                        throw new Exception("unknown code");
                     }
+                } catch (Exception ex) {
+                    onError(ex);
+                }
+            }
 
-                    @Override
-                    public void onError(Exception error) {
-
-                        // Removed call to cleanup because now we can
-                        // reconnect automatically so we need the mSendHandler
-                        // instance
-                        mHandler.onError(error);
-
-                        if (mReconnectOnError) {
-                            
-                            if (mClient != null)
-                                mClient.disconnect();
-                            mClient = null;
-                            mMessageIdCounter.set(0);
-                            mAcknowledges.clear();
-                            
-                            mSendHandler.postDelayed(new Runnable() {
+            @Override
+            public void onError(Exception error) {
 
-                                @Override
-                                public void run() {
+                // Removed call to cleanup because now we can
+                // reconnect automatically so we need the mSendHandler
+                // instance
+                mHandler.onError(error);
 
-                                    // Doing this check here because the client
-                                    // can be forcibly connected before the
-                                    // Runnable runs
-                                    if (!mClient.isConnected()) {
-                                        connect();
-                                    }
+                if (mReconnectOnError) {
 
-                                }
-                            }, getDelayInMillis());
-                        }
+                    if (mClient != null)
+                        mClient.disconnect();
+                    mClient = null;
+                    mMessageIdCounter.set(0);
+                    mAcknowledges.clear();
+
+                    mSendHandler.postDelayed(new Runnable() {
+
+                        @Override
+                        public void run() {
+
+                            // Doing this check here because the client
+                            // can be forcibly connected before the
+                            // Runnable runs
+                            if (!mClient.isConnected()) {
+                                connect();
+                            }
 
-                        else {
-                            cleanup();
                         }
-                    }
+                    }, getDelayInMillis());
+                }
 
-                    @Override
-                    public void onDisconnect(int code, String reason) {
-                        cleanup();
-                        // attempt reconnect with same session?
-                        mHandler.onDisconnect(code, reason);
-                    }
+                else {
+                    cleanup();
+                }
+            }
+
+            @Override
+            public void onDisconnect(int code, String reason) {
+                cleanup();
+                // attempt reconnect with same session?
+                mHandler.onDisconnect(code, reason);
+            }
 
+            @Override
+            public void onConnect() {
+                mConnectPower = 1;
+                mSendHandler.postDelayed(new Runnable() {
                     @Override
-                    public void onConnect() {
-                        mConnectPower = 1;
-                        mSendHandler.postDelayed(new Runnable() {
-                            @Override
-                            public void run() {
-                                mSendHandler.postDelayed(this, mHeartbeat);
-                                mClient.send("2:::");
-                            }
-                        }, mHeartbeat);
+                    public void run() {
+                        mSendHandler.postDelayed(this, mHeartbeat);
+                        mClient.send("2:::");
                     }
-                }, null);
+                }, mHeartbeat);
+            }
+        }, null);
         mClient.connect();
     }
 
     private long getDelayInMillis() {
-        return (long) (Math.pow(CONNECT_MULTIPLIER,
-                (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER
-                        : mConnectPower++) * 1000);
+        return (long) (Math.pow(CONNECT_MULTIPLIER, (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER
+                : mConnectPower++) * 1000);
     }
 
     public void disconnect() throws IOException {
@@ -417,8 +405,7 @@ public void run() {
                         mHeartbeat = Integer.parseInt(heartbeat) / 2 * 1000;
                     String transportsLine = parts[3];
                     String[] transports = transportsLine.split(",");
-                    HashSet<String> set = new HashSet<String>(
-                            Arrays.asList(transports));
+                    HashSet<String> set = new HashSet<String>(Arrays.asList(transports));
                     if (!set.contains("websocket"))
                         throw new Exception("websocket not supported");
 

From 17dbf039cbf4575ecef151052815e65da187db37 Mon Sep 17 00:00:00 2001
From: Nicolas <nicolas.barranger@zi-apps.fr>
Date: Mon, 1 Jul 2013 16:17:01 +0200
Subject: [PATCH 42/56] Add support of message endPoint for types : message,
 message JSON and event

---
 src/com/codebutler/android_websockets/SocketIOClient.java | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index c4e638c..482bc3b 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -122,7 +122,7 @@ public void emit(String name, JSONArray args, final Acknowledge acknowledge) thr
         mSendHandler.post(new Runnable() {
             @Override
             public void run() {
-                mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +"::%s", event.toString()));
+                mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +":%s:%s", (mEndpoint == null ? "" : mEndpoint), event.toString()));
             }
         });
     }
@@ -141,7 +141,7 @@ public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) th
 
             @Override
             public void run() {
-                mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + "::%s", jsonMessage.toString()));
+                mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (mEndpoint == null ? "" : mEndpoint), jsonMessage.toString()));
             }
         });
     }
@@ -160,7 +160,7 @@ public void emit(final String message, final Acknowledge acknowledge) {
 
             @Override
             public void run() {
-                mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +"::%s", message));
+                mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +":%s:%s", (mEndpoint == null ? "" : mEndpoint), message));
             }
         });
     }

From 386dfed30ab10a47948b3265291f8bc8304d29a7 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Fri, 5 Jul 2013 21:22:47 +0530
Subject: [PATCH 43/56] Changed endpoints to be optional when emitting messages

---
 .../android_websockets/SocketIOClient.java    | 22 ++++++++++++++++---
 1 file changed, 19 insertions(+), 3 deletions(-)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
index 102bb67..f3de7a3 100644
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ b/src/com/codebutler/android_websockets/SocketIOClient.java
@@ -124,6 +124,12 @@ public void emit(String name, JSONArray args) throws JSONException {
     }
 
     public void emit(String name, JSONArray args, final Acknowledge acknowledge) throws JSONException {
+
+        emit(name, args, acknowledge, null);
+    }
+
+    public void emit(String name, JSONArray args, final Acknowledge acknowledge, final String endpoint) throws JSONException {
+
         final JSONObject event = new JSONObject();
         event.put("name", name);
         event.put("args", args);
@@ -135,7 +141,7 @@ public void emit(String name, JSONArray args, final Acknowledge acknowledge) thr
         mSendHandler.post(new Runnable() {
             @Override
             public void run() {
-                mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") +":%s:%s", (mEndpoint == null ? "" : mEndpoint), event.toString()));
+                mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), event.toString()));
             }
         });
     }
@@ -146,6 +152,11 @@ public void emit(final JSONObject jsonMessage) throws JSONException {
 
     public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) throws JSONException {
 
+        emit(jsonMessage, acknowledge, null);
+    }
+
+    public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge, final String endpoint) throws JSONException {
+
         final int nextId = getNextMessageId();
         if (acknowledge != null) {
             mAcknowledges.put(nextId, acknowledge);
@@ -154,7 +165,7 @@ public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) th
 
             @Override
             public void run() {
-                mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (mEndpoint == null ? "" : mEndpoint), jsonMessage.toString()));
+                mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), jsonMessage.toString()));
             }
         });
     }
@@ -165,6 +176,11 @@ public void emit(final String message) {
 
     public void emit(final String message, final Acknowledge acknowledge) {
 
+        emit(message, acknowledge, null);
+    }
+    
+    public void emit(final String message, final Acknowledge acknowledge, final String endpoint) {
+
         final int nextId = getNextMessageId();
         if (acknowledge != null) {
             mAcknowledges.put(nextId, acknowledge);
@@ -173,7 +189,7 @@ public void emit(final String message, final Acknowledge acknowledge) {
 
             @Override
             public void run() {
-                mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") +":%s:%s", (mEndpoint == null ? "" : mEndpoint), message));
+                mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), message));
             }
         });
     }

From ad5a9f315695e48db27591f563957c3674357a41 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Tue, 9 Jul 2013 23:26:45 +0530
Subject: [PATCH 44/56] Ported AndroidAsync SocketIO framework

---
 .../android_websockets/SocketIOClient.java    | 496 ------------------
 .../async/http/socketio/Acknowledge.java      |   7 +
 .../async/http/socketio/ConnectCallback.java  |   5 +
 .../http/socketio/DisconnectCallback.java     |   8 +
 .../async/http/socketio/ErrorCallback.java    |   8 +
 .../async/http/socketio/EventCallback.java    |   7 +
 .../async/http/socketio/EventEmitter.java     |  54 ++
 .../async/http/socketio/JSONCallback.java     |   8 +
 .../http/socketio/ReconnectCallback.java      |   5 +
 .../async/http/socketio/SocketIOClient.java   | 179 +++++++
 .../http/socketio/SocketIOConnection.java     | 396 ++++++++++++++
 .../async/http/socketio/StringCallback.java   |   5 +
 src/com/koushikdutta/async/util/HashList.java |  26 +
 .../koushikdutta/http/AsyncHttpClient.java    | 126 +++++
 .../http}/HybiParser.java                     |  36 +-
 .../http/WebSocket.java}                      | 166 ++++--
 16 files changed, 971 insertions(+), 561 deletions(-)
 delete mode 100644 src/com/codebutler/android_websockets/SocketIOClient.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/Acknowledge.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/ConnectCallback.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/DisconnectCallback.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/ErrorCallback.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/EventCallback.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/EventEmitter.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/JSONCallback.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/ReconnectCallback.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/SocketIOClient.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
 create mode 100644 src/com/koushikdutta/async/http/socketio/StringCallback.java
 create mode 100644 src/com/koushikdutta/async/util/HashList.java
 create mode 100644 src/com/koushikdutta/http/AsyncHttpClient.java
 rename src/com/{codebutler/android_websockets => koushikdutta/http}/HybiParser.java (93%)
 rename src/com/{codebutler/android_websockets/WebSocketClient.java => koushikdutta/http/WebSocket.java} (70%)

diff --git a/src/com/codebutler/android_websockets/SocketIOClient.java b/src/com/codebutler/android_websockets/SocketIOClient.java
deleted file mode 100644
index f3de7a3..0000000
--- a/src/com/codebutler/android_websockets/SocketIOClient.java
+++ /dev/null
@@ -1,496 +0,0 @@
-package com.codebutler.android_websockets;
-
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import org.apache.http.HttpResponse;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import android.net.http.AndroidHttpClient;
-import android.os.Looper;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.SparseArray;
-
-public class SocketIOClient {
-
-    private static final String TAG = "SocketIOClient";
-
-    private static final int CONNECT_MULTIPLIER = 2;
-    private static final int MAX_CONNECT_POWER = 10;
-
-    public static interface Handler {
-        public void onConnect();
-
-        public void onConnectToEndpoint(String endpoint);
-
-        public void on(String event, JSONArray arguments);
-
-        public void onDisconnect(int code, String reason);
-
-        public void onJSON(JSONObject json);
-
-        public void onMessage(String message);
-
-        public void onError(Exception error);
-    }
-
-    public static interface Acknowledge {
-        public void acknowledge(String[] args);
-    }
-
-    private String mURL;
-    private Handler mHandler;
-    private String mSession;
-    private int mHeartbeat;
-    private WebSocketClient mClient;
-    private String mEndpoint;
-    private AtomicInteger mMessageIdCounter;
-    private SparseArray<Acknowledge> mAcknowledges;
-    private boolean mReconnectOnError;
-    private int mConnectPower;
-
-    public SocketIOClient(URI uri, Handler handler) {
-        this(uri, handler, null, false);
-    }
-
-    public SocketIOClient(URI uri, Handler handler, boolean reconnectOnError) {
-        this(uri, handler, null, reconnectOnError);
-    }
-
-    public SocketIOClient(URI uri, Handler handler, String namespace,
-            boolean reconnectOnError) {
-        mEndpoint = namespace;
-        mAcknowledges = new SparseArray<SocketIOClient.Acknowledge>();
-        mMessageIdCounter = new AtomicInteger(0);
-        if (TextUtils.isEmpty(namespace)) {
-            mEndpoint = "socket.io";
-        }
-
-        // remove trailing "/" from URI, in case user provided e.g.
-        // http://test.com/
-        mURL = uri.toString().replaceAll("/$", "") + "/" + mEndpoint + "/1/";
-        mHandler = handler;
-        mConnectPower = 1;
-        mReconnectOnError = reconnectOnError;
-    }
-
-    private static String downloadUriAsString(final HttpUriRequest req) throws IOException {
-        AndroidHttpClient client = AndroidHttpClient.newInstance("android-websockets");
-        try {
-            HttpResponse res = client.execute(req);
-            return readToEnd(res.getEntity().getContent());
-        } finally {
-            client.close();
-        }
-    }
-
-    private int getNextMessageId() {
-        return mMessageIdCounter.incrementAndGet();
-    }
-
-    private static byte[] readToEndAsArray(InputStream input) throws IOException {
-        DataInputStream dis = new DataInputStream(input);
-        byte[] stuff = new byte[1024];
-        ByteArrayOutputStream buff = new ByteArrayOutputStream();
-        int read = 0;
-        while ((read = dis.read(stuff)) != -1) {
-            buff.write(stuff, 0, read);
-        }
-
-        return buff.toByteArray();
-    }
-
-    private static String readToEnd(InputStream input) throws IOException {
-        return new String(readToEndAsArray(input));
-    }
-
-    android.os.Handler mSendHandler;
-    Looper mSendLooper;
-
-    public void emit(String name, JSONArray args) throws JSONException {
-        emit(name, args, null);
-    }
-
-    public void emit(String name, JSONArray args, final Acknowledge acknowledge) throws JSONException {
-
-        emit(name, args, acknowledge, null);
-    }
-
-    public void emit(String name, JSONArray args, final Acknowledge acknowledge, final String endpoint) throws JSONException {
-
-        final JSONObject event = new JSONObject();
-        event.put("name", name);
-        event.put("args", args);
-
-        final int nextId = getNextMessageId();
-        if (acknowledge != null) {
-            mAcknowledges.put(nextId, acknowledge);
-        }
-        mSendHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mClient.send(String.format("5:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), event.toString()));
-            }
-        });
-    }
-
-    public void emit(final JSONObject jsonMessage) throws JSONException {
-        emit(jsonMessage, null);
-    }
-
-    public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge) throws JSONException {
-
-        emit(jsonMessage, acknowledge, null);
-    }
-
-    public void emit(final JSONObject jsonMessage, final Acknowledge acknowledge, final String endpoint) throws JSONException {
-
-        final int nextId = getNextMessageId();
-        if (acknowledge != null) {
-            mAcknowledges.put(nextId, acknowledge);
-        }
-        mSendHandler.post(new Runnable() {
-
-            @Override
-            public void run() {
-                mClient.send(String.format("4:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), jsonMessage.toString()));
-            }
-        });
-    }
-
-    public void emit(final String message) {
-        emit(message, (Acknowledge) null);
-    }
-
-    public void emit(final String message, final Acknowledge acknowledge) {
-
-        emit(message, acknowledge, null);
-    }
-    
-    public void emit(final String message, final Acknowledge acknowledge, final String endpoint) {
-
-        final int nextId = getNextMessageId();
-        if (acknowledge != null) {
-            mAcknowledges.put(nextId, acknowledge);
-        }
-        mSendHandler.post(new Runnable() {
-
-            @Override
-            public void run() {
-                mClient.send(String.format("3:" + nextId + (acknowledge == null ? "" : "+") + ":%s:%s", (endpoint == null ? "" : endpoint), message));
-            }
-        });
-    }
-
-    private void connectSession() throws URISyntaxException {
-        mClient = new WebSocketClient(new URI(mURL + "websocket/" + mSession), new WebSocketClient.Listener() {
-            @Override
-            public void onMessage(byte[] data) {
-                cleanup();
-                mHandler.onError(new Exception("Unexpected binary data"));
-            }
-
-            @Override
-            public void onMessage(String message) {
-                try {
-                    Log.d(TAG, "Message: " + message);
-                    String[] parts = message.split(":", 4);
-                    int code = Integer.parseInt(parts[0]);
-                    switch (code) {
-                    case 1:
-                        // connect
-                        if (!TextUtils.isEmpty(parts[2])) {
-                            mHandler.onConnectToEndpoint(parts[2]);
-                        } else {
-                            mHandler.onConnect();
-                        }
-                        break;
-                    case 2:
-                        // heartbeat
-                        mClient.send("2::");
-                        break;
-                    case 3: {
-                        // message
-                        final String messageId = parts[1];
-                        final String dataString = parts[3];
-
-                        if (!"".equals(messageId)) {
-                            mSendHandler.post(new Runnable() {
-
-                                @Override
-                                public void run() {
-                                    mClient.send(String.format("6:::%s", messageId));
-                                }
-                            });
-                        }
-                        mHandler.onMessage(dataString);
-                        break;
-                    }
-                    case 4: {
-                        // json message
-                        final String messageId = parts[1];
-                        final String dataString = parts[3];
-
-                        JSONObject jsonMessage = null;
-
-                        try {
-                            jsonMessage = new JSONObject(dataString);
-                        } catch (JSONException e) {
-                            jsonMessage = new JSONObject();
-                        }
-                        if (!"".equals(messageId)) {
-                            mSendHandler.post(new Runnable() {
-
-                                @Override
-                                public void run() {
-                                    mClient.send(String.format("6:::%s", messageId));
-                                }
-                            });
-                        }
-                        mHandler.onJSON(jsonMessage);
-                        break;
-                    }
-                    case 5: {
-                        final String messageId = parts[1];
-                        final String dataString = parts[3];
-                        JSONObject data = new JSONObject(dataString);
-                        String event = data.getString("name");
-                        JSONArray args;
-                        try {
-                            args = data.getJSONArray("args");
-                        } catch (JSONException e) {
-                            args = new JSONArray();
-                        }
-                        if (!"".equals(messageId)) {
-                            mSendHandler.post(new Runnable() {
-                                @Override
-                                public void run() {
-                                    mClient.send(String.format("6:::%s", messageId));
-                                }
-                            });
-                        }
-                        mHandler.on(event, args);
-                        break;
-                    }
-                    case 6:
-                        // ACK
-                        if (parts[3] != null && parts[3].contains("+")) {
-                            String[] ackParts = parts[3].split("\\+");
-                            int ackId = Integer.valueOf(ackParts[0]);
-
-                            String ackArgs = ackParts[1];
-
-                            int startIndex = ackArgs.indexOf('[') + 1;
-
-                            ackArgs = ackArgs.substring(startIndex, ackArgs.length() - 1);
-
-                            Acknowledge acknowledge = mAcknowledges.get(ackId);
-
-                            if (acknowledge != null) {
-
-                                String[] params = ackArgs.split(",");
-                                for (int i = 0; i < params.length; i++) {
-                                    params[i] = params[i].replace("\"", "");
-                                }
-                                acknowledge.acknowledge(params);
-                            }
-
-                            mAcknowledges.remove(ackId);
-                        }
-                        break;
-                    case 7:
-                        // error
-                        throw new Exception(message);
-                    case 8:
-                        // noop
-                        break;
-                    default:
-                        throw new Exception("unknown code");
-                    }
-                } catch (Exception ex) {
-                    onError(ex);
-                }
-            }
-
-            @Override
-            public void onError(Exception error) {
-
-                // Removed call to cleanup because now we can
-                // reconnect automatically so we need the mSendHandler
-                // instance
-                mHandler.onError(error);
-
-                if (mReconnectOnError) {
-
-                    if (mClient != null)
-                        mClient.disconnect();
-                    mClient = null;
-                    mMessageIdCounter.set(0);
-                    mAcknowledges.clear();
-
-                    mSendHandler.postDelayed(new Runnable() {
-
-                        @Override
-                        public void run() {
-
-                            // Doing this check here because the client
-                            // can be forcibly connected before the
-                            // Runnable runs
-                            if (!mClient.isConnected()) {
-                                connect();
-                            }
-
-                        }
-                    }, getDelayInMillis());
-                }
-
-                else {
-                    cleanup();
-                }
-            }
-
-            @Override
-            public void onDisconnect(int code, String reason) {
-                cleanup();
-                // attempt reconnect with same session?
-                mHandler.onDisconnect(code, reason);
-            }
-
-            @Override
-            public void onConnect() {
-                mConnectPower = 1;
-                mSendHandler.postDelayed(new Runnable() {
-                    @Override
-                    public void run() {
-                        mSendHandler.postDelayed(this, mHeartbeat);
-                        mClient.send("2:::");
-                    }
-                }, mHeartbeat);
-            }
-        }, null);
-        mClient.connect();
-    }
-
-    private long getDelayInMillis() {
-        return (long) (Math.pow(CONNECT_MULTIPLIER, (mConnectPower == MAX_CONNECT_POWER) ? MAX_CONNECT_POWER
-                : mConnectPower++) * 1000);
-    }
-
-    public void disconnect() throws IOException {
-        cleanup();
-    }
-
-    private void cleanup() {
-        if (mClient != null)
-            mClient.disconnect();
-        mClient = null;
-        mMessageIdCounter.set(0);
-        mAcknowledges.clear();
-        mSendLooper.quit();
-        mSendLooper = null;
-        mSendHandler = null;
-    }
-
-    public void connect() {
-        if (mClient != null)
-            return;
-        new Thread() {
-            public void run() {
-                HttpPost post = new HttpPost(mURL);
-                try {
-                    String line = downloadUriAsString(post);
-                    String[] parts = line.split(":");
-                    mSession = parts[0];
-                    String heartbeat = parts[1];
-                    if (!"".equals(heartbeat))
-                        mHeartbeat = Integer.parseInt(heartbeat) / 2 * 1000;
-                    String transportsLine = parts[3];
-                    String[] transports = transportsLine.split(",");
-                    HashSet<String> set = new HashSet<String>(Arrays.asList(transports));
-                    if (!set.contains("websocket"))
-                        throw new Exception("websocket not supported");
-
-                    Looper.prepare();
-                    mSendLooper = Looper.myLooper();
-                    mSendHandler = new android.os.Handler();
-
-                    connectSession();
-
-                    Looper.loop();
-                } catch (Exception e) {
-                    mHandler.onError(e);
-                }
-            };
-        }.start();
-    }
-
-    /**
-     * Connect to an endpoint
-     */
-    public void connectToEndpoint(final String endpoint) {
-
-        if (mClient.isConnected() && !TextUtils.isEmpty(endpoint)) {
-            mEndpoint = endpoint;
-            mSendHandler.post(new Runnable() {
-
-                @Override
-                public void run() {
-                    mClient.send("1::" + endpoint);
-                }
-            });
-        }
-    }
-
-    /**
-     * Disconnect from an endpoint or socket
-     * 
-     * @param endpoint
-     *            {@code null} to disconnect the entire socket, otherwise the
-     *            endpoint to disconnect from
-     */
-    public void sendDisconnect(final String endpoint) {
-
-        if (TextUtils.isEmpty(endpoint)) {
-
-            mSendHandler.post(new Runnable() {
-
-                @Override
-                public void run() {
-                    mClient.send("0");
-                }
-            });
-        }
-
-        else {
-            mSendHandler.post(new Runnable() {
-
-                @Override
-                public void run() {
-                    mClient.send("0::" + endpoint);
-                }
-            });
-        }
-    }
-
-    /**
-     * Get the current connected endpoint
-     * 
-     * @return The current connected endpoint, "socket.io" if connected to the
-     *         default endpoint
-     */
-    public String getConnectedEndpoint() {
-        return mEndpoint;
-    }
-}
diff --git a/src/com/koushikdutta/async/http/socketio/Acknowledge.java b/src/com/koushikdutta/async/http/socketio/Acknowledge.java
new file mode 100644
index 0000000..8bf1c64
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/Acknowledge.java
@@ -0,0 +1,7 @@
+package com.koushikdutta.async.http.socketio;
+
+import org.json.JSONArray;
+
+public interface Acknowledge {
+    void acknowledge(JSONArray arguments);
+}
diff --git a/src/com/koushikdutta/async/http/socketio/ConnectCallback.java b/src/com/koushikdutta/async/http/socketio/ConnectCallback.java
new file mode 100644
index 0000000..7ff69d7
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/ConnectCallback.java
@@ -0,0 +1,5 @@
+package com.koushikdutta.async.http.socketio;
+
+public interface ConnectCallback {
+    public void onConnectCompleted(Exception ex, SocketIOClient client);
+}
\ No newline at end of file
diff --git a/src/com/koushikdutta/async/http/socketio/DisconnectCallback.java b/src/com/koushikdutta/async/http/socketio/DisconnectCallback.java
new file mode 100644
index 0000000..97ec4b6
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/DisconnectCallback.java
@@ -0,0 +1,8 @@
+package com.koushikdutta.async.http.socketio;
+
+/**
+ * Created by koush on 7/2/13.
+ */
+public interface DisconnectCallback {
+    void onDisconnect(Exception e);
+}
diff --git a/src/com/koushikdutta/async/http/socketio/ErrorCallback.java b/src/com/koushikdutta/async/http/socketio/ErrorCallback.java
new file mode 100644
index 0000000..6411667
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/ErrorCallback.java
@@ -0,0 +1,8 @@
+package com.koushikdutta.async.http.socketio;
+
+/**
+ * Created by koush on 7/2/13.
+ */
+public interface ErrorCallback {
+    void onError(String error);
+}
diff --git a/src/com/koushikdutta/async/http/socketio/EventCallback.java b/src/com/koushikdutta/async/http/socketio/EventCallback.java
new file mode 100644
index 0000000..191a663
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/EventCallback.java
@@ -0,0 +1,7 @@
+package com.koushikdutta.async.http.socketio;
+
+import org.json.JSONArray;
+
+public interface EventCallback {
+    public void onEvent(JSONArray argument, Acknowledge acknowledge);
+}
\ No newline at end of file
diff --git a/src/com/koushikdutta/async/http/socketio/EventEmitter.java b/src/com/koushikdutta/async/http/socketio/EventEmitter.java
new file mode 100644
index 0000000..1c1af7c
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/EventEmitter.java
@@ -0,0 +1,54 @@
+package com.koushikdutta.async.http.socketio;
+
+import com.koushikdutta.async.util.HashList;
+
+import org.json.JSONArray;
+
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Created by koush on 7/1/13.
+ */
+public class EventEmitter {
+    interface OnceCallback extends EventCallback {
+    }
+
+    HashList<EventCallback> callbacks = new HashList<EventCallback>();
+    void onEvent(String event, JSONArray arguments, Acknowledge acknowledge) {
+        List<EventCallback> list = callbacks.get(event);
+        if (list == null)
+            return;
+        Iterator<EventCallback> iter = list.iterator();
+        while (iter.hasNext()) {
+            EventCallback cb = iter.next();
+            cb.onEvent(arguments, acknowledge);
+            if (cb instanceof OnceCallback)
+                iter.remove();
+        }
+    }
+
+    public void addListener(String event, EventCallback callback) {
+        on(event, callback);
+    }
+
+    public void once(final String event, final EventCallback callback) {
+        on(event, new OnceCallback() {
+            @Override
+            public void onEvent(JSONArray arguments, Acknowledge acknowledge) {
+                callback.onEvent(arguments, acknowledge);
+            }
+        });
+    }
+
+    public void on(String event, EventCallback callback) {
+        callbacks.add(event, callback);
+    }
+
+    public void removeListener(String event, EventCallback callback) {
+        List<EventCallback> list = callbacks.get(event);
+        if (list == null)
+            return;
+        list.remove(callback);
+    }
+}
diff --git a/src/com/koushikdutta/async/http/socketio/JSONCallback.java b/src/com/koushikdutta/async/http/socketio/JSONCallback.java
new file mode 100644
index 0000000..52d8aff
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/JSONCallback.java
@@ -0,0 +1,8 @@
+package com.koushikdutta.async.http.socketio;
+
+import org.json.JSONObject;
+
+public interface JSONCallback {
+    public void onJSON(JSONObject json, Acknowledge acknowledge);
+}
+    
diff --git a/src/com/koushikdutta/async/http/socketio/ReconnectCallback.java b/src/com/koushikdutta/async/http/socketio/ReconnectCallback.java
new file mode 100644
index 0000000..bd427f4
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/ReconnectCallback.java
@@ -0,0 +1,5 @@
+package com.koushikdutta.async.http.socketio;
+
+public interface ReconnectCallback {
+    public void onReconnect();
+}
\ No newline at end of file
diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
new file mode 100644
index 0000000..a4ad818
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
@@ -0,0 +1,179 @@
+package com.koushikdutta.async.http.socketio;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import android.os.Handler;
+import android.text.TextUtils;
+
+import com.koushikdutta.http.AsyncHttpClient;
+import com.koushikdutta.http.AsyncHttpClient.SocketIORequest;
+import com.koushikdutta.http.WebSocket;
+
+public class SocketIOClient extends EventEmitter {
+    boolean connected;
+    boolean disconnected;
+
+    private void emitRaw(int type, String message, Acknowledge acknowledge) {
+        connection.emitRaw(type, this, message, acknowledge);
+    }
+
+    public void emit(String name, JSONArray args) {
+        emit(name, args, null);
+    }
+
+    public void emit(final String message) {
+        emit(message, (Acknowledge) null);
+    }
+
+    public void emit(final JSONObject jsonMessage) {
+        emit(jsonMessage, null);
+    }
+
+    public void emit(String name, JSONArray args, Acknowledge acknowledge) {
+        final JSONObject event = new JSONObject();
+        try {
+            event.put("name", name);
+            event.put("args", args);
+            emitRaw(5, event.toString(), acknowledge);
+        } catch (Exception e) {
+        }
+    }
+
+    public void emit(final String message, Acknowledge acknowledge) {
+        emitRaw(3, message, acknowledge);
+    }
+
+    public void emit(final JSONObject jsonMessage, Acknowledge acknowledge) {
+        emitRaw(4, jsonMessage.toString(), acknowledge);
+    }
+
+    public static void connect(String uri, final ConnectCallback callback, final Handler handler) {
+        connect(new SocketIORequest(uri), callback, handler);
+    }
+
+    ConnectCallback connectCallback;
+
+    public static void connect(final SocketIORequest request, final ConnectCallback callback, final Handler handler) {
+
+        final SocketIOConnection connection = new SocketIOConnection(handler, new AsyncHttpClient(), request);
+
+        final ConnectCallback wrappedCallback = new ConnectCallback() {
+            @Override
+            public void onConnectCompleted(Exception ex, SocketIOClient client) {
+                if (ex != null || TextUtils.isEmpty(request.getEndpoint())) {
+                    if (callback != null)
+                        callback.onConnectCompleted(ex, client);
+                    
+                    return;
+                }
+
+                // remove the root client since that's not actually being used.
+                connection.clients.remove(client);
+
+                // connect to the endpoint we want
+                client.of(request.getEndpoint(), new ConnectCallback() {
+                    @Override
+                    public void onConnectCompleted(Exception ex, SocketIOClient client) {
+                        if (callback != null)
+                            callback.onConnectCompleted(ex, client);
+                    }
+                });
+            }
+        };
+
+        connection.clients.add(new SocketIOClient(connection, "", wrappedCallback));
+        connection.reconnect();
+
+    }
+
+    ErrorCallback errorCallback;
+
+    public ErrorCallback getErrorCallback() {
+        return errorCallback;
+    }
+
+    public void setErrorCallback(ErrorCallback callback) {
+        errorCallback = callback;
+    }
+
+    DisconnectCallback disconnectCallback;
+
+    public DisconnectCallback getDisconnectCallback() {
+        return disconnectCallback;
+    }
+
+    public void setDisconnectCallback(DisconnectCallback callback) {
+        disconnectCallback = callback;
+    }
+
+    ReconnectCallback reconnectCallback;
+
+    public ReconnectCallback getReconnectCallback() {
+        return reconnectCallback;
+    }
+
+    public void setReconnectCallback(ReconnectCallback callback) {
+        reconnectCallback = callback;
+    }
+
+    JSONCallback jsonCallback;
+
+    public JSONCallback getJSONCallback() {
+        return jsonCallback;
+    }
+
+    public void setJSONCallback(JSONCallback callback) {
+        jsonCallback = callback;
+    }
+
+    StringCallback stringCallback;
+
+    public StringCallback getStringCallback() {
+        return stringCallback;
+    }
+
+    public void setStringCallback(StringCallback callback) {
+        stringCallback = callback;
+    }
+    
+    EventCallback eventCallback;
+    
+    public EventCallback getEventCallback() {
+        return eventCallback;
+    }
+    
+    public void setEventCallback(EventCallback callback) {
+        eventCallback = callback;
+    }
+
+    SocketIOConnection connection;
+    String endpoint;
+
+    private SocketIOClient(SocketIOConnection connection, String endpoint,
+            ConnectCallback callback) {
+        this.endpoint = endpoint;
+        this.connection = connection;
+        this.connectCallback = callback;
+    }
+
+    public boolean isConnected() {
+        return connected && !disconnected && connection.isConnected();
+    }
+
+    public void disconnect() {
+        connection.disconnect(this);
+        DisconnectCallback disconnectCallback = this.disconnectCallback;
+        if (disconnectCallback != null) {
+            disconnectCallback.onDisconnect(null);
+        }
+    }
+
+    public void of(String endpoint, ConnectCallback connectCallback) {
+        connection.connect(new SocketIOClient(connection, endpoint, connectCallback));
+    }
+
+    public WebSocket getWebSocket() {
+        return connection.webSocket;
+    }
+}
diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
new file mode 100644
index 0000000..53900ad
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
@@ -0,0 +1,396 @@
+package com.koushikdutta.async.http.socketio;
+
+import android.os.Handler;
+import android.text.TextUtils;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import com.koushikdutta.http.AsyncHttpClient;
+import com.koushikdutta.http.AsyncHttpClient.SocketIORequest;
+import com.koushikdutta.http.WebSocket;
+import com.koushikdutta.http.WebSocket.ClosedCallback;
+import com.koushikdutta.http.WebSocket.DataCallback;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Hashtable;
+
+/**
+ * Created by koush on 7/1/13.
+ */
+class SocketIOConnection {
+
+    private Handler mHandler;
+    AsyncHttpClient httpClient;
+    int heartbeat;
+    ArrayList<SocketIOClient> clients = new ArrayList<SocketIOClient>();
+    WebSocket webSocket;
+    SocketIORequest request;
+
+    public SocketIOConnection(Handler handler, AsyncHttpClient httpClient,
+            SocketIORequest request) {
+        mHandler = handler;
+        this.httpClient = httpClient;
+        this.request = request;
+    }
+
+    public boolean isConnected() {
+        return webSocket != null && webSocket.isOpen();
+    }
+
+    Hashtable<String, Acknowledge> acknowledges = new Hashtable<String, Acknowledge>();
+    int ackCount;
+
+    public void emitRaw(int type, SocketIOClient client, String message, Acknowledge acknowledge) {
+        String ack = "";
+        if (acknowledge != null) {
+            String id = "" + ackCount++;
+            ack = id + "+";
+            acknowledges.put(id, acknowledge);
+        }
+        webSocket.send(String.format("%d:%s:%s:%s", type, ack, client.endpoint, message));
+    }
+
+    public void connect(SocketIOClient client) {
+        clients.add(client);
+        webSocket.send(String.format("1::%s", client.endpoint));
+    }
+
+    public void disconnect(SocketIOClient client) {
+        clients.remove(client);
+
+        // see if we can leave this endpoint completely
+        boolean needsEndpointDisconnect = true;
+        for (SocketIOClient other : clients) {
+            // if this is the default endpoint (which disconnects everything),
+            // or another client is using this endpoint,
+            // we can't disconnect
+            if (TextUtils.equals(other.endpoint, client.endpoint)
+                    || TextUtils.isEmpty(client.endpoint)) {
+                needsEndpointDisconnect = false;
+                break;
+            }
+        }
+
+        if (needsEndpointDisconnect)
+            webSocket.send(String.format("0::%s", client.endpoint));
+
+        // and see if we can disconnect the socket completely
+        if (clients.size() > 0)
+            return;
+
+        webSocket.setStringCallback(null);
+        webSocket.setClosedCallback(null);
+        webSocket.disconnect();
+        webSocket = null;
+    }
+
+    void reconnect() {
+        if (isConnected()) {
+            return;
+        }
+
+        // initiate a session
+        httpClient.executeString(request, new AsyncHttpClient.StringCallback() {
+            @Override
+            public void onCompleted(final Exception e, String result) {
+                if (e != null) {
+                    reportDisconnect(e);
+                    return;
+                }
+
+                try {
+                    String[] parts = result.split(":");
+                    String session = parts[0];
+                    if (!"".equals(parts[1]))
+                        heartbeat = Integer.parseInt(parts[1]) / 2 * 1000;
+                    else
+                        heartbeat = 0;
+
+                    String transportsLine = parts[3];
+                    String[] transports = transportsLine.split(",");
+                    HashSet<String> set = new HashSet<String>(Arrays.asList(transports));
+                    if (!set.contains("websocket"))
+                        throw new Exception("websocket not supported");
+
+                    final String sessionUrl = request.getUri().toString()
+                            + "websocket/" + session + "/";
+
+                    httpClient.websocket(sessionUrl, null, new AsyncHttpClient.WebSocketConnectCallback() {
+                        @Override
+                        public void onCompleted(Exception ex, WebSocket webSocket) {
+                            if (ex != null) {
+                                reportDisconnect(ex);
+                                return;
+                            }
+
+                            reconnectDelay = 1000L;
+                            SocketIOConnection.this.webSocket = webSocket;
+                            attach();
+                        }
+                    });
+
+                } catch (Exception ex) {
+                    reportDisconnect(ex);
+                }
+            }
+        });
+
+    }
+
+    void setupHeartbeat() {
+        final WebSocket ws = webSocket;
+        Runnable heartbeatRunner = new Runnable() {
+            @Override
+            public void run() {
+                if (heartbeat <= 0 || ws != webSocket || ws == null
+                        || !ws.isOpen())
+                    return;
+                webSocket.send("2:::");
+
+                mHandler.postDelayed(this, heartbeat);
+            }
+        };
+        heartbeatRunner.run();
+    }
+
+    private interface SelectCallback {
+        void onSelect(SocketIOClient client);
+    }
+
+    private void select(String endpoint, SelectCallback callback) {
+        for (SocketIOClient client : clients) {
+            if (endpoint == null || TextUtils.equals(client.endpoint, endpoint)) {
+                callback.onSelect(client);
+            }
+        }
+    }
+
+    private void delayReconnect() {
+        if (webSocket != null || clients.size() == 0)
+            return;
+
+        // see if any client has disconnected,
+        // and that we need a reconnect
+        boolean disconnected = false;
+        for (SocketIOClient client : clients) {
+            if (client.disconnected) {
+                disconnected = true;
+                break;
+            }
+        }
+
+        if (!disconnected)
+            return;
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                reconnect();
+            }
+        }, reconnectDelay);
+        reconnectDelay *= 2;
+    }
+
+    long reconnectDelay = 1000L;
+
+    private void reportDisconnect(final Exception ex) {
+        select(null, new SelectCallback() {
+            @Override
+            public void onSelect(SocketIOClient client) {
+                if (client.connected) {
+                    client.disconnected = true;
+                    DisconnectCallback closed = client.getDisconnectCallback();
+                    if (closed != null)
+                        closed.onDisconnect(ex);
+                } else {
+                    // client has never connected, this is a initial connect
+                    // failure
+                    ConnectCallback callback = client.connectCallback;
+                    if (callback != null)
+                        callback.onConnectCompleted(ex, client);
+                }
+            }
+        });
+
+        delayReconnect();
+    }
+
+    private void reportConnect(String endpoint) {
+        select(endpoint, new SelectCallback() {
+            @Override
+            public void onSelect(SocketIOClient client) {
+                if (client.isConnected())
+                    return;
+                if (!client.connected) {
+                    // normal connect
+                    client.connected = true;
+                    ConnectCallback callback = client.connectCallback;
+                    if (callback != null)
+                        callback.onConnectCompleted(null, client);
+                } else if (client.disconnected) {
+                    // reconnect
+                    client.disconnected = false;
+                    ReconnectCallback callback = client.reconnectCallback;
+                    if (callback != null)
+                        callback.onReconnect();
+                } else {
+                    // double connect?
+                    // assert false;
+                }
+            }
+        });
+    }
+
+    private void reportJson(String endpoint, final JSONObject jsonMessage, final Acknowledge acknowledge) {
+        select(endpoint, new SelectCallback() {
+            @Override
+            public void onSelect(SocketIOClient client) {
+                JSONCallback callback = client.jsonCallback;
+                if (callback != null)
+                    callback.onJSON(jsonMessage, acknowledge);
+            }
+        });
+    }
+
+    private void reportString(String endpoint, final String string, final Acknowledge acknowledge) {
+        select(endpoint, new SelectCallback() {
+            @Override
+            public void onSelect(SocketIOClient client) {
+                StringCallback callback = client.stringCallback;
+                if (callback != null)
+                    callback.onString(string, acknowledge);
+            }
+        });
+    }
+
+    private void reportEvent(String endpoint, final String event, final JSONArray arguments, final Acknowledge acknowledge) {
+        select(endpoint, new SelectCallback() {
+            @Override
+            public void onSelect(SocketIOClient client) {
+                client.onEvent(event, arguments, acknowledge);
+            }
+        });
+    }
+
+    private void reportError(String endpoint, final String error) {
+        select(endpoint, new SelectCallback() {
+            @Override
+            public void onSelect(SocketIOClient client) {
+                ErrorCallback callback = client.errorCallback;
+                if (callback != null)
+                    callback.onError(error);
+            }
+        });
+    }
+
+    private Acknowledge acknowledge(final String messageId) {
+        if (TextUtils.isEmpty(messageId))
+            return null;
+
+        return new Acknowledge() {
+            @Override
+            public void acknowledge(JSONArray arguments) {
+                String data = "";
+                if (arguments != null)
+                    data += "+" + arguments.toString();
+                webSocket.send(String.format("6:::%s%s", messageId, data));
+            }
+        };
+    }
+
+    private void attach() {
+        setupHeartbeat();
+
+        webSocket.setDataCallback(new DataCallback() {
+
+            @Override
+            public void onDataAvailable(byte[] data) {
+                // Do nothing
+            }
+        });
+        webSocket.setClosedCallback(new ClosedCallback() {
+
+            @Override
+            public void onCompleted(final Exception ex) {
+                webSocket = null;
+                reportDisconnect(ex);
+            }
+        });
+
+        webSocket.setStringCallback(new WebSocket.StringCallback() {
+            @Override
+            public void onStringAvailable(String message) {
+                try {
+                    // Log.d(TAG, "Message: " + message);
+                    String[] parts = message.split(":", 4);
+                    int code = Integer.parseInt(parts[0]);
+                    switch (code) {
+                    case 0:
+                        // disconnect
+                        webSocket.disconnect();
+                        reportDisconnect(null);
+                        break;
+                    case 1:
+                        // connect
+                        reportConnect(parts[2]);
+                        break;
+                    case 2:
+                        // heartbeat
+                        webSocket.send("2::");
+                        break;
+                    case 3: {
+                        // message
+                        reportString(parts[2], parts[3], acknowledge(parts[1]));
+                        break;
+                    }
+                    case 4: {
+                        // json message
+                        final String dataString = parts[3];
+                        final JSONObject jsonMessage = new JSONObject(dataString);
+                        reportJson(parts[2], jsonMessage, acknowledge(parts[1]));
+                        break;
+                    }
+                    case 5: {
+                        final String dataString = parts[3];
+                        final JSONObject data = new JSONObject(dataString);
+                        final String event = data.getString("name");
+                        final JSONArray args = data.optJSONArray("args");
+                        reportEvent(parts[2], event, args, acknowledge(parts[1]));
+                        break;
+                    }
+                    case 6:
+                        // ACK
+                        final String[] ackParts = parts[3].split("\\+", 2);
+                        Acknowledge ack = acknowledges.remove(ackParts[0]);
+                        if (ack == null)
+                            return;
+                        JSONArray arguments = null;
+                        if (ackParts.length == 2)
+                            arguments = new JSONArray(ackParts[1]);
+                        ack.acknowledge(arguments);
+                        break;
+                    case 7:
+                        // error
+                        reportError(parts[2], parts[3]);
+                        break;
+                    case 8:
+                        // noop
+                        break;
+                    default:
+                        throw new Exception("unknown code");
+                    }
+                } catch (Exception ex) {
+                    webSocket.setClosedCallback(null);
+                    webSocket.disconnect();
+                    webSocket = null;
+                    reportDisconnect(ex);
+                }
+            }
+        });
+        
+        webSocket.startParsing();
+    }
+}
diff --git a/src/com/koushikdutta/async/http/socketio/StringCallback.java b/src/com/koushikdutta/async/http/socketio/StringCallback.java
new file mode 100644
index 0000000..7062b0f
--- /dev/null
+++ b/src/com/koushikdutta/async/http/socketio/StringCallback.java
@@ -0,0 +1,5 @@
+package com.koushikdutta.async.http.socketio;
+
+public interface StringCallback {
+    public void onString(String string, Acknowledge acknowledge);
+}
\ No newline at end of file
diff --git a/src/com/koushikdutta/async/util/HashList.java b/src/com/koushikdutta/async/util/HashList.java
new file mode 100644
index 0000000..2b2a0c6
--- /dev/null
+++ b/src/com/koushikdutta/async/util/HashList.java
@@ -0,0 +1,26 @@
+package com.koushikdutta.async.util;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+
+/**
+ * Created by koush on 5/27/13.
+ */
+public class HashList<T> extends Hashtable<String, ArrayList<T>> {
+    public HashList() {
+    }
+
+    public boolean contains(String key) {
+        ArrayList<T> check = get(key);
+        return check != null && check.size() > 0;
+    }
+
+    public void add(String key, T value) {
+        ArrayList<T> ret = get(key);
+        if (ret == null) {
+            ret = new ArrayList<T>();
+            put(key, ret);
+        }
+        ret.add(value);
+    }
+}
diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java
new file mode 100644
index 0000000..434ff51
--- /dev/null
+++ b/src/com/koushikdutta/http/AsyncHttpClient.java
@@ -0,0 +1,126 @@
+package com.koushikdutta.http;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+
+import android.net.Uri;
+import android.net.http.AndroidHttpClient;
+import android.os.AsyncTask;
+
+public class AsyncHttpClient {
+
+    public AsyncHttpClient() {
+
+    }
+
+    public static class SocketIORequest {
+
+        private String mUri;
+        private String mEndpoint;
+
+        public SocketIORequest(String uri) {
+            this(uri, null);
+        }
+
+        public SocketIORequest(String uri, String endpoint) {
+
+            mUri = Uri.parse(uri).buildUpon().encodedPath("/socket.io/1/").build().toString();
+            mEndpoint = endpoint;
+        }
+
+        public String getUri() {
+
+            return mUri;
+        }
+
+        public String getEndpoint() {
+
+            return mEndpoint;
+        }
+    }
+
+    public static interface StringCallback {
+        public void onCompleted(final Exception e, String result);
+    }
+
+    public static interface WebSocketConnectCallback {
+        public void onCompleted(Exception ex, WebSocket webSocket);
+    }
+
+    public void executeString(final SocketIORequest socketIORequest, final StringCallback stringCallback) {
+
+        new AsyncTask<Void, Void, Void>() {
+
+            @Override
+            protected Void doInBackground(Void... params) {
+
+                AndroidHttpClient httpClient = AndroidHttpClient.newInstance("android-websockets-2.0");
+                HttpPost post = new HttpPost(socketIORequest.getUri());
+
+                try {
+                    HttpResponse res = httpClient.execute(post);
+                    String responseString = readToEnd(res.getEntity().getContent());
+
+                    if (stringCallback != null) {
+                        stringCallback.onCompleted(null, responseString);
+                    }
+
+                } catch (IOException e) {
+
+                    if (stringCallback != null) {
+                        stringCallback.onCompleted(e, null);
+                    }
+                } finally {
+                    httpClient.close();
+                    httpClient = null;
+                }
+                return null;
+            }
+        }.execute();
+    }
+
+    private byte[] readToEndAsArray(InputStream input) throws IOException {
+        DataInputStream dis = new DataInputStream(input);
+        byte[] stuff = new byte[1024];
+        ByteArrayOutputStream buff = new ByteArrayOutputStream();
+        int read = 0;
+        while ((read = dis.read(stuff)) != -1) {
+            buff.write(stuff, 0, read);
+        }
+
+        return buff.toByteArray();
+    }
+
+    private String readToEnd(InputStream input) throws IOException {
+        return new String(readToEndAsArray(input));
+    }
+
+    public void websocket(final String uri, final String protocol, final WebSocketConnectCallback callback) {
+
+        WebSocket.create(URI.create(uri), new WebSocket.Listener() {
+
+            @Override
+            public void onConnect(WebSocket websocket) {
+                if (callback != null) {
+                    callback.onCompleted(null, websocket);
+                }
+
+            }
+
+            @Override
+            public void onError(Exception ex) {
+                if (callback != null) {
+                    callback.onCompleted(ex, null);
+                }
+
+            }
+        });
+    }
+
+}
diff --git a/src/com/codebutler/android_websockets/HybiParser.java b/src/com/koushikdutta/http/HybiParser.java
similarity index 93%
rename from src/com/codebutler/android_websockets/HybiParser.java
rename to src/com/koushikdutta/http/HybiParser.java
index 1f83f2a..6f2f609 100644
--- a/src/com/codebutler/android_websockets/HybiParser.java
+++ b/src/com/koushikdutta/http/HybiParser.java
@@ -28,18 +28,22 @@
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package com.codebutler.android_websockets;
+package com.koushikdutta.http;
 
-import android.util.Log;
-
-import java.io.*;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
 import java.util.Arrays;
 import java.util.List;
 
+import android.util.Log;
+
 public class HybiParser {
     private static final String TAG = "HybiParser";
 
-    private WebSocketClient mClient;
+    private WebSocket mWebSocket;
 
     private boolean mMasking = true;
 
@@ -91,8 +95,8 @@ public class HybiParser {
         OP_CONTINUATION, OP_TEXT, OP_BINARY
     );
 
-    public HybiParser(WebSocketClient client) {
-        mClient = client;
+    public HybiParser(WebSocket client) {
+        mWebSocket = client;
     }
 
     private static byte[] mask(byte[] payload, byte[] mask, int offset) {
@@ -128,7 +132,7 @@ public void start(HappyDataInputStream stream) throws IOException {
                     break;
             }
         }
-        mClient.getListener().onDisconnect(0, "EOF");
+        mWebSocket.getClosedCallback().onCompleted(new IOException("EOF"));
     }
 
     private void parseOpcode(byte data) throws ProtocolError {
@@ -241,12 +245,12 @@ private byte[] frame(Object data, int opcode, int errorCode) {
     }
 
     public void ping(String message) {
-        mClient.send(frame(message, OP_PING, -1));
+        mWebSocket.send(frame(message, OP_PING, -1));
     }
 
     public void close(int code, String reason) {
         if (mClosed) return;
-        mClient.send(frame(reason, OP_CLOSE, code));
+        mWebSocket.send(frame(reason, OP_CLOSE, code));
         mClosed = true;
     }
 
@@ -262,9 +266,9 @@ private void emitFrame() throws IOException {
             if (mFinal) {
                 byte[] message = mBuffer.toByteArray();
                 if (mMode == MODE_TEXT) {
-                    mClient.getListener().onMessage(encode(message));
+                    mWebSocket.getStringCallback().onStringAvailable(encode(message));
                 } else {
-                    mClient.getListener().onMessage(message);
+                    mWebSocket.getDataCallback().onDataAvailable(message);
                 }
                 reset();
             }
@@ -272,7 +276,7 @@ private void emitFrame() throws IOException {
         } else if (opcode == OP_TEXT) {
             if (mFinal) {
                 String messageText = encode(payload);
-                mClient.getListener().onMessage(messageText);
+                mWebSocket.getStringCallback().onStringAvailable(messageText);
             } else {
                 mMode = MODE_TEXT;
                 mBuffer.write(payload);
@@ -280,7 +284,7 @@ private void emitFrame() throws IOException {
 
         } else if (opcode == OP_BINARY) {
             if (mFinal) {
-                mClient.getListener().onMessage(payload);
+                mWebSocket.getDataCallback().onDataAvailable(payload);
             } else {
                 mMode = MODE_BINARY;
                 mBuffer.write(payload);
@@ -290,12 +294,12 @@ private void emitFrame() throws IOException {
             int    code   = (payload.length >= 2) ? 256 * payload[0] + payload[1] : 0;
             String reason = (payload.length >  2) ? encode(slice(payload, 2))     : null;
             Log.d(TAG, "Got close op! " + code + " " + reason);
-            mClient.getListener().onDisconnect(code, reason);
+            mWebSocket.getClosedCallback().onCompleted(new IOException("Got close op! " + code + " " + reason));
 
         } else if (opcode == OP_PING) {
             if (payload.length > 125) { throw new ProtocolError("Ping payload too large"); }
             Log.d(TAG, "Sending pong!!");
-            mClient.sendFrame(frame(payload, OP_PONG, -1));
+            mWebSocket.sendFrame(frame(payload, OP_PONG, -1));
 
         } else if (opcode == OP_PONG) {
             String message = encode(payload);
diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/koushikdutta/http/WebSocket.java
similarity index 70%
rename from src/com/codebutler/android_websockets/WebSocketClient.java
rename to src/com/koushikdutta/http/WebSocket.java
index a75ec64..f6fd3f1 100644
--- a/src/com/codebutler/android_websockets/WebSocketClient.java
+++ b/src/com/koushikdutta/http/WebSocket.java
@@ -1,20 +1,5 @@
-package com.codebutler.android_websockets;
+package com.koushikdutta.http;
 
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.text.TextUtils;
-import android.util.Base64;
-import android.util.Log;
-import org.apache.http.*;
-import org.apache.http.client.HttpResponseException;
-import org.apache.http.message.BasicLineParser;
-import org.apache.http.message.BasicNameValuePair;
-
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -25,18 +10,47 @@
 import java.security.NoSuchAlgorithmException;
 import java.util.List;
 
-public class WebSocketClient {
-    private static final String TAG = "WebSocketClient";
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+
+import org.apache.http.Header;
+import org.apache.http.HttpException;
+import org.apache.http.HttpStatus;
+import org.apache.http.NameValuePair;
+import org.apache.http.StatusLine;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.message.BasicLineParser;
+import org.apache.http.message.BasicNameValuePair;
+
+import com.koushikdutta.http.HybiParser.HappyDataInputStream;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+public class WebSocket {
+    private static final String TAG = "WebSocket";
 
-    private URI                      mURI;
-    private Listener                 mListener;
-    private Socket                   mSocket;
-    private Thread                   mThread;
-    private HandlerThread            mHandlerThread;
-    private Handler                  mHandler;
+    private URI mURI;
+    private Listener mListener;
+    private Socket mSocket;
+    private Thread mThread;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
     private List<BasicNameValuePair> mExtraHeaders;
-    private HybiParser               mParser;
-    private boolean                  mConnected;
+    private HybiParser mParser;
+    private boolean mConnected;
+
+    private DataCallback mDataCallback;
+    private StringCallback mStringCallback;
+    private ClosedCallback mClosedCallback;
+
+    private HappyDataInputStream stream;
 
     private final Object mSendLock = new Object();
 
@@ -46,12 +60,13 @@ public static void setTrustManagers(TrustManager[] tm) {
         sTrustManagers = tm;
     }
 
-    public WebSocketClient(URI uri, Listener listener, List<BasicNameValuePair> extraHeaders) {
-        mURI          = uri;
-        mListener     = listener;
+    public WebSocket(URI uri, Listener listener,
+            List<BasicNameValuePair> extraHeaders) {
+        mURI = uri;
+        mListener = listener;
         mExtraHeaders = extraHeaders;
-        mConnected    = false;
-        mParser       = new HybiParser(this);
+        mConnected = false;
+        mParser = new HybiParser(this);
 
         mHandlerThread = new HandlerThread("websocket-thread");
         mHandlerThread.start();
@@ -68,20 +83,26 @@ public void connect() {
         }
 
         mThread = new Thread(new Runnable() {
+
             @Override
             public void run() {
                 try {
-                    int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80);
+                    int port = (mURI.getPort() != -1) ? mURI.getPort()
+                            : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443
+                                    : 80);
 
-                    String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath();
+                    String path = TextUtils.isEmpty(mURI.getPath()) ? "/"
+                            : mURI.getPath();
                     if (!TextUtils.isEmpty(mURI.getQuery())) {
                         path += "?" + mURI.getQuery();
                     }
 
-                    String originScheme = mURI.getScheme().equals("wss") ? "https" : "http";
+                    String originScheme = mURI.getScheme().equals("wss") ? "https"
+                            : "http";
                     URI origin = new URI(originScheme, "//" + mURI.getHost(), null);
 
-                    SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault();
+                    SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory()
+                            : SocketFactory.getDefault();
                     mSocket = factory.createSocket(mURI.getHost(), port);
 
                     PrintWriter out = new PrintWriter(mSocket.getOutputStream());
@@ -100,7 +121,7 @@ public void run() {
                     out.print("\r\n");
                     out.flush();
 
-                    HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream());
+                    stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream());
 
                     // Read HTTP response status line.
                     StatusLine statusLine = parseStatusLine(readLine(stream));
@@ -119,22 +140,19 @@ public void run() {
                         }
                     }
 
-                    mListener.onConnect();
+                    mListener.onConnect(WebSocket.this);
 
                     mConnected = true;
 
-                    // Now decode websocket frames.
-                    mParser.start(stream);
-
                 } catch (EOFException ex) {
                     Log.d(TAG, "WebSocket EOF!", ex);
-                    mListener.onDisconnect(0, "EOF");
+                    mListener.onError(ex);
                     mConnected = false;
 
                 } catch (SSLException ex) {
                     // Connection reset by peer
                     Log.d(TAG, "Websocket SSL error!", ex);
-                    mListener.onDisconnect(0, "SSL");
+                    mListener.onError(ex);
                     mConnected = false;
 
                 } catch (Exception ex) {
@@ -145,6 +163,16 @@ public void run() {
         mThread.start();
     }
 
+    public void startParsing() {
+        
+        // Now decode websocket frames.
+        try {
+            mParser.start(stream);
+        } catch (IOException e) {
+            mClosedCallback.onCompleted(e);
+        }
+    }
+
     public void disconnect() {
         if (mSocket != null) {
             mHandler.post(new Runnable() {
@@ -173,7 +201,7 @@ public void send(byte[] data) {
         sendFrame(mParser.frame(data));
     }
 
-    public boolean isConnected() {
+    public boolean isOpen() {
         return mConnected;
     }
 
@@ -233,12 +261,47 @@ public void run() {
         });
     }
 
-    public interface Listener {
-        public void onConnect();
-        public void onMessage(String message);
-        public void onMessage(byte[] data);
-        public void onDisconnect(int code, String reason);
-        public void onError(Exception error);
+    public static interface Listener {
+        public void onConnect(WebSocket webSocket);
+
+        public void onError(Exception ex);
+
+    }
+
+    public static interface DataCallback {
+        public void onDataAvailable(byte[] data);
+    }
+
+    public static interface StringCallback {
+        public void onStringAvailable(String message);
+    }
+
+    public static interface ClosedCallback {
+        public void onCompleted(Exception ex);
+    }
+
+    public DataCallback getDataCallback() {
+        return mDataCallback;
+    }
+
+    public void setDataCallback(DataCallback dataCallback) {
+        this.mDataCallback = dataCallback;
+    }
+
+    public StringCallback getStringCallback() {
+        return mStringCallback;
+    }
+
+    public void setStringCallback(StringCallback stringCallback) {
+        this.mStringCallback = stringCallback;
+    }
+
+    public ClosedCallback getClosedCallback() {
+        return mClosedCallback;
+    }
+
+    public void setClosedCallback(ClosedCallback closedCallback) {
+        this.mClosedCallback = closedCallback;
     }
 
     private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {
@@ -246,4 +309,9 @@ private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException,
         context.init(null, sTrustManagers, null);
         return context.getSocketFactory();
     }
+
+    public static void create(URI uri, Listener listener) {
+        WebSocket webSocket = new WebSocket(uri, listener, null);
+        webSocket.connect();
+    }
 }

From 92911b8f8865d4fd567a220dfb18904fb1ecc8a7 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Wed, 10 Jul 2013 01:25:03 +0530
Subject: [PATCH 45/56] Removed eventcallback addition to SocketIOClient as we
 need to use addListener()

---
 .../async/http/socketio/SocketIOClient.java            | 10 ----------
 1 file changed, 10 deletions(-)

diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
index a4ad818..46a0617 100644
--- a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
@@ -137,16 +137,6 @@ public void setStringCallback(StringCallback callback) {
         stringCallback = callback;
     }
     
-    EventCallback eventCallback;
-    
-    public EventCallback getEventCallback() {
-        return eventCallback;
-    }
-    
-    public void setEventCallback(EventCallback callback) {
-        eventCallback = callback;
-    }
-
     SocketIOConnection connection;
     String endpoint;
 

From e70653596603355bfec099f5a75ddf50169a2d85 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Thu, 11 Jul 2013 22:53:06 +0530
Subject: [PATCH 46/56] Added wrappers around Callbacks to mimic earlier
 Handler interface

---
 .../async/http/socketio/EventCallback.java    |   2 +-
 .../async/http/socketio/EventEmitter.java     |   7 +-
 .../async/http/socketio/SocketIOClient.java   | 155 ++++++++++++++----
 .../http/socketio/SocketIOConnection.java     |  58 +++----
 src/com/koushikdutta/async/util/HashList.java |   3 +
 .../koushikdutta/http/AsyncHttpClient.java    |  10 +-
 src/com/koushikdutta/http/HybiParser.java     |   7 +-
 .../{WebSocket.java => WebSocketClient.java}  |  12 +-
 8 files changed, 181 insertions(+), 73 deletions(-)
 rename src/com/koushikdutta/http/{WebSocket.java => WebSocketClient.java} (96%)

diff --git a/src/com/koushikdutta/async/http/socketio/EventCallback.java b/src/com/koushikdutta/async/http/socketio/EventCallback.java
index 191a663..d416b2d 100644
--- a/src/com/koushikdutta/async/http/socketio/EventCallback.java
+++ b/src/com/koushikdutta/async/http/socketio/EventCallback.java
@@ -3,5 +3,5 @@
 import org.json.JSONArray;
 
 public interface EventCallback {
-    public void onEvent(JSONArray argument, Acknowledge acknowledge);
+    public void onEvent(String event, JSONArray argument, Acknowledge acknowledge);
 }
\ No newline at end of file
diff --git a/src/com/koushikdutta/async/http/socketio/EventEmitter.java b/src/com/koushikdutta/async/http/socketio/EventEmitter.java
index 1c1af7c..f6dbb7a 100644
--- a/src/com/koushikdutta/async/http/socketio/EventEmitter.java
+++ b/src/com/koushikdutta/async/http/socketio/EventEmitter.java
@@ -15,6 +15,7 @@ interface OnceCallback extends EventCallback {
     }
 
     HashList<EventCallback> callbacks = new HashList<EventCallback>();
+    
     void onEvent(String event, JSONArray arguments, Acknowledge acknowledge) {
         List<EventCallback> list = callbacks.get(event);
         if (list == null)
@@ -22,7 +23,7 @@ void onEvent(String event, JSONArray arguments, Acknowledge acknowledge) {
         Iterator<EventCallback> iter = list.iterator();
         while (iter.hasNext()) {
             EventCallback cb = iter.next();
-            cb.onEvent(arguments, acknowledge);
+            cb.onEvent(event, arguments, acknowledge);
             if (cb instanceof OnceCallback)
                 iter.remove();
         }
@@ -35,8 +36,8 @@ public void addListener(String event, EventCallback callback) {
     public void once(final String event, final EventCallback callback) {
         on(event, new OnceCallback() {
             @Override
-            public void onEvent(JSONArray arguments, Acknowledge acknowledge) {
-                callback.onEvent(arguments, acknowledge);
+            public void onEvent(String event, JSONArray arguments, Acknowledge acknowledge) {
+                callback.onEvent(event, arguments, acknowledge);
             }
         });
     }
diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
index 46a0617..d678a61 100644
--- a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
@@ -1,5 +1,7 @@
 package com.koushikdutta.async.http.socketio;
 
+import java.util.List;
+
 import org.json.JSONArray;
 import org.json.JSONObject;
 
@@ -8,12 +10,14 @@
 
 import com.koushikdutta.http.AsyncHttpClient;
 import com.koushikdutta.http.AsyncHttpClient.SocketIORequest;
-import com.koushikdutta.http.WebSocket;
+import com.koushikdutta.http.WebSocketClient;
 
 public class SocketIOClient extends EventEmitter {
     boolean connected;
     boolean disconnected;
 
+    private SocketIOCallbacks mSocketIOCallbacks;
+
     private void emitRaw(int type, String message, Acknowledge acknowledge) {
         connection.emitRaw(type, this, message, acknowledge);
     }
@@ -62,9 +66,11 @@ public static void connect(final SocketIORequest request, final ConnectCallback
             @Override
             public void onConnectCompleted(Exception ex, SocketIOClient client) {
                 if (ex != null || TextUtils.isEmpty(request.getEndpoint())) {
-                    if (callback != null)
+                    if (callback != null) {
+                        client.setupCallbacks();
                         callback.onConnectCompleted(ex, client);
-                    
+                    }
+
                     return;
                 }
 
@@ -75,8 +81,10 @@ public void onConnectCompleted(Exception ex, SocketIOClient client) {
                 client.of(request.getEndpoint(), new ConnectCallback() {
                     @Override
                     public void onConnectCompleted(Exception ex, SocketIOClient client) {
-                        if (callback != null)
+                        if (callback != null) {
+                            client.setupCallbacks();
                             callback.onConnectCompleted(ex, client);
+                        }
                     }
                 });
             }
@@ -86,57 +94,131 @@ public void onConnectCompleted(Exception ex, SocketIOClient client) {
         connection.reconnect();
 
     }
+    
+    private void setupCallbacks() {
+        setStringCallback(new StringCallback() {
 
-    ErrorCallback errorCallback;
+            @Override
+            public void onString(String string, Acknowledge acknowledge) {
+
+                if (mSocketIOCallbacks != null) {
+                    mSocketIOCallbacks.onMessage(string);
+                }
+            }
+        });
+
+        setJSONCallback(new JSONCallback() {
+
+            @Override
+            public void onJSON(JSONObject json, Acknowledge acknowledge) {
+
+                if (mSocketIOCallbacks != null) {
+                    mSocketIOCallbacks.onJSON(json);
+                }
+
+            }
+        });
+
+        setReconnectCallback(new ReconnectCallback() {
+
+            @Override
+            public void onReconnect() {
+
+                if (mSocketIOCallbacks != null) {
+                    mSocketIOCallbacks.onReconnect();
+                }
+
+            }
+        });
+
+        setDisconnectCallback(new DisconnectCallback() {
+
+            @Override
+            public void onDisconnect(Exception e) {
+
+                if (mSocketIOCallbacks != null) {
+                    mSocketIOCallbacks.onDisconnect(0, e.getMessage());
+                }
+
+            }
+        });
+
+        setErrorCallback(new ErrorCallback() {
+
+            @Override
+            public void onError(String error) {
+                if (mSocketIOCallbacks != null) {
+                    mSocketIOCallbacks.onError(new Exception(error));
+                }
 
-    public ErrorCallback getErrorCallback() {
-        return errorCallback;
+            }
+        });
     }
 
-    public void setErrorCallback(ErrorCallback callback) {
+    ErrorCallback errorCallback;
+
+    private void setErrorCallback(ErrorCallback callback) {
         errorCallback = callback;
     }
 
     DisconnectCallback disconnectCallback;
 
-    public DisconnectCallback getDisconnectCallback() {
-        return disconnectCallback;
+    private void setDisconnectCallback(DisconnectCallback callback) {
+        disconnectCallback = callback;
     }
 
-    public void setDisconnectCallback(DisconnectCallback callback) {
-        disconnectCallback = callback;
+    public DisconnectCallback getDisconnectCallback() {
+        return disconnectCallback;
     }
 
     ReconnectCallback reconnectCallback;
 
-    public ReconnectCallback getReconnectCallback() {
-        return reconnectCallback;
-    }
-
-    public void setReconnectCallback(ReconnectCallback callback) {
+    private void setReconnectCallback(ReconnectCallback callback) {
         reconnectCallback = callback;
     }
 
     JSONCallback jsonCallback;
 
-    public JSONCallback getJSONCallback() {
-        return jsonCallback;
-    }
-
-    public void setJSONCallback(JSONCallback callback) {
+    private void setJSONCallback(JSONCallback callback) {
         jsonCallback = callback;
     }
 
     StringCallback stringCallback;
 
-    public StringCallback getStringCallback() {
-        return stringCallback;
+    private void setStringCallback(StringCallback callback) {
+        stringCallback = callback;
     }
 
-    public void setStringCallback(StringCallback callback) {
-        stringCallback = callback;
+    public void setSocketIOCallbacks(SocketIOCallbacks callbacks) {
+        mSocketIOCallbacks = callbacks;
+    }
+    
+    public SocketIOCallbacks getSocketIOCallbacks() {
+        return mSocketIOCallbacks;
     }
     
+    public void listenForEvents(List<String> events) {
+        
+        if(events == null) {
+            return;
+        }
+        
+        EventCallback callback = new EventCallback() {
+            
+            @Override
+            public void onEvent(String event, JSONArray argument, Acknowledge acknowledge) {
+                
+                if(mSocketIOCallbacks != null) {
+                    mSocketIOCallbacks.on(event, argument);
+                }
+            }
+        };
+        
+        for(String event : events) {
+            on(event, callback);
+        }
+    }
+
     SocketIOConnection connection;
     String endpoint;
 
@@ -158,12 +240,27 @@ public void disconnect() {
             disconnectCallback.onDisconnect(null);
         }
     }
-
+    
     public void of(String endpoint, ConnectCallback connectCallback) {
         connection.connect(new SocketIOClient(connection, endpoint, connectCallback));
     }
 
-    public WebSocket getWebSocket() {
-        return connection.webSocket;
+    public WebSocketClient getWebSocket() {
+        return connection.webSocketClient;
+    }
+
+    public static interface SocketIOCallbacks {
+
+        public void on(String event, JSONArray arguments);
+
+        public void onDisconnect(int code, String reason);
+
+        public void onReconnect();
+
+        public void onJSON(JSONObject json);
+
+        public void onMessage(String message);
+
+        public void onError(Exception error);
     }
 }
diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
index 53900ad..93aae55 100644
--- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
@@ -8,9 +8,9 @@
 
 import com.koushikdutta.http.AsyncHttpClient;
 import com.koushikdutta.http.AsyncHttpClient.SocketIORequest;
-import com.koushikdutta.http.WebSocket;
-import com.koushikdutta.http.WebSocket.ClosedCallback;
-import com.koushikdutta.http.WebSocket.DataCallback;
+import com.koushikdutta.http.WebSocketClient;
+import com.koushikdutta.http.WebSocketClient.ClosedCallback;
+import com.koushikdutta.http.WebSocketClient.DataCallback;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -26,7 +26,7 @@ class SocketIOConnection {
     AsyncHttpClient httpClient;
     int heartbeat;
     ArrayList<SocketIOClient> clients = new ArrayList<SocketIOClient>();
-    WebSocket webSocket;
+    WebSocketClient webSocketClient;
     SocketIORequest request;
 
     public SocketIOConnection(Handler handler, AsyncHttpClient httpClient,
@@ -37,7 +37,7 @@ public SocketIOConnection(Handler handler, AsyncHttpClient httpClient,
     }
 
     public boolean isConnected() {
-        return webSocket != null && webSocket.isOpen();
+        return webSocketClient != null && webSocketClient.isOpen();
     }
 
     Hashtable<String, Acknowledge> acknowledges = new Hashtable<String, Acknowledge>();
@@ -50,12 +50,12 @@ public void emitRaw(int type, SocketIOClient client, String message, Acknowledge
             ack = id + "+";
             acknowledges.put(id, acknowledge);
         }
-        webSocket.send(String.format("%d:%s:%s:%s", type, ack, client.endpoint, message));
+        webSocketClient.send(String.format("%d:%s:%s:%s", type, ack, client.endpoint, message));
     }
 
     public void connect(SocketIOClient client) {
         clients.add(client);
-        webSocket.send(String.format("1::%s", client.endpoint));
+        webSocketClient.send(String.format("1::%s", client.endpoint));
     }
 
     public void disconnect(SocketIOClient client) {
@@ -75,16 +75,16 @@ public void disconnect(SocketIOClient client) {
         }
 
         if (needsEndpointDisconnect)
-            webSocket.send(String.format("0::%s", client.endpoint));
+            webSocketClient.send(String.format("0::%s", client.endpoint));
 
         // and see if we can disconnect the socket completely
         if (clients.size() > 0)
             return;
 
-        webSocket.setStringCallback(null);
-        webSocket.setClosedCallback(null);
-        webSocket.disconnect();
-        webSocket = null;
+        webSocketClient.setStringCallback(null);
+        webSocketClient.setClosedCallback(null);
+        webSocketClient.disconnect();
+        webSocketClient = null;
     }
 
     void reconnect() {
@@ -120,14 +120,14 @@ public void onCompleted(final Exception e, String result) {
 
                     httpClient.websocket(sessionUrl, null, new AsyncHttpClient.WebSocketConnectCallback() {
                         @Override
-                        public void onCompleted(Exception ex, WebSocket webSocket) {
+                        public void onCompleted(Exception ex, WebSocketClient webSocket) {
                             if (ex != null) {
                                 reportDisconnect(ex);
                                 return;
                             }
 
                             reconnectDelay = 1000L;
-                            SocketIOConnection.this.webSocket = webSocket;
+                            SocketIOConnection.this.webSocketClient = webSocket;
                             attach();
                         }
                     });
@@ -141,14 +141,14 @@ public void onCompleted(Exception ex, WebSocket webSocket) {
     }
 
     void setupHeartbeat() {
-        final WebSocket ws = webSocket;
+        final WebSocketClient ws = webSocketClient;
         Runnable heartbeatRunner = new Runnable() {
             @Override
             public void run() {
-                if (heartbeat <= 0 || ws != webSocket || ws == null
+                if (heartbeat <= 0 || ws != webSocketClient || ws == null
                         || !ws.isOpen())
                     return;
-                webSocket.send("2:::");
+                webSocketClient.send("2:::");
 
                 mHandler.postDelayed(this, heartbeat);
             }
@@ -169,7 +169,7 @@ private void select(String endpoint, SelectCallback callback) {
     }
 
     private void delayReconnect() {
-        if (webSocket != null || clients.size() == 0)
+        if (webSocketClient != null || clients.size() == 0)
             return;
 
         // see if any client has disconnected,
@@ -296,7 +296,7 @@ public void acknowledge(JSONArray arguments) {
                 String data = "";
                 if (arguments != null)
                     data += "+" + arguments.toString();
-                webSocket.send(String.format("6:::%s%s", messageId, data));
+                webSocketClient.send(String.format("6:::%s%s", messageId, data));
             }
         };
     }
@@ -304,23 +304,23 @@ public void acknowledge(JSONArray arguments) {
     private void attach() {
         setupHeartbeat();
 
-        webSocket.setDataCallback(new DataCallback() {
+        webSocketClient.setDataCallback(new DataCallback() {
 
             @Override
             public void onDataAvailable(byte[] data) {
                 // Do nothing
             }
         });
-        webSocket.setClosedCallback(new ClosedCallback() {
+        webSocketClient.setClosedCallback(new ClosedCallback() {
 
             @Override
             public void onCompleted(final Exception ex) {
-                webSocket = null;
+                webSocketClient = null;
                 reportDisconnect(ex);
             }
         });
 
-        webSocket.setStringCallback(new WebSocket.StringCallback() {
+        webSocketClient.setStringCallback(new WebSocketClient.StringCallback() {
             @Override
             public void onStringAvailable(String message) {
                 try {
@@ -330,7 +330,7 @@ public void onStringAvailable(String message) {
                     switch (code) {
                     case 0:
                         // disconnect
-                        webSocket.disconnect();
+                        webSocketClient.disconnect();
                         reportDisconnect(null);
                         break;
                     case 1:
@@ -339,7 +339,7 @@ public void onStringAvailable(String message) {
                         break;
                     case 2:
                         // heartbeat
-                        webSocket.send("2::");
+                        webSocketClient.send("2::");
                         break;
                     case 3: {
                         // message
@@ -383,14 +383,14 @@ public void onStringAvailable(String message) {
                         throw new Exception("unknown code");
                     }
                 } catch (Exception ex) {
-                    webSocket.setClosedCallback(null);
-                    webSocket.disconnect();
-                    webSocket = null;
+                    webSocketClient.setClosedCallback(null);
+                    webSocketClient.disconnect();
+                    webSocketClient = null;
                     reportDisconnect(ex);
                 }
             }
         });
         
-        webSocket.startParsing();
+        webSocketClient.startParsing();
     }
 }
diff --git a/src/com/koushikdutta/async/util/HashList.java b/src/com/koushikdutta/async/util/HashList.java
index 2b2a0c6..0036661 100644
--- a/src/com/koushikdutta/async/util/HashList.java
+++ b/src/com/koushikdutta/async/util/HashList.java
@@ -7,6 +7,9 @@
  * Created by koush on 5/27/13.
  */
 public class HashList<T> extends Hashtable<String, ArrayList<T>> {
+   
+    private static final long serialVersionUID = 1L;
+
     public HashList() {
     }
 
diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java
index 434ff51..a09c340 100644
--- a/src/com/koushikdutta/http/AsyncHttpClient.java
+++ b/src/com/koushikdutta/http/AsyncHttpClient.java
@@ -13,6 +13,10 @@
 import android.net.http.AndroidHttpClient;
 import android.os.AsyncTask;
 
+/**
+ * 
+ * Created by Vinay S Shenoy on 07/09/2013
+ */
 public class AsyncHttpClient {
 
     public AsyncHttpClient() {
@@ -50,7 +54,7 @@ public static interface StringCallback {
     }
 
     public static interface WebSocketConnectCallback {
-        public void onCompleted(Exception ex, WebSocket webSocket);
+        public void onCompleted(Exception ex, WebSocketClient webSocket);
     }
 
     public void executeString(final SocketIORequest socketIORequest, final StringCallback stringCallback) {
@@ -103,10 +107,10 @@ private String readToEnd(InputStream input) throws IOException {
 
     public void websocket(final String uri, final String protocol, final WebSocketConnectCallback callback) {
 
-        WebSocket.create(URI.create(uri), new WebSocket.Listener() {
+        WebSocketClient.create(URI.create(uri), new WebSocketClient.Listener() {
 
             @Override
-            public void onConnect(WebSocket websocket) {
+            public void onConnect(WebSocketClient websocket) {
                 if (callback != null) {
                     callback.onCompleted(null, websocket);
                 }
diff --git a/src/com/koushikdutta/http/HybiParser.java b/src/com/koushikdutta/http/HybiParser.java
index 6f2f609..56cfd1b 100644
--- a/src/com/koushikdutta/http/HybiParser.java
+++ b/src/com/koushikdutta/http/HybiParser.java
@@ -43,7 +43,7 @@
 public class HybiParser {
     private static final String TAG = "HybiParser";
 
-    private WebSocket mWebSocket;
+    private WebSocketClient mWebSocket;
 
     private boolean mMasking = true;
 
@@ -95,7 +95,7 @@ public class HybiParser {
         OP_CONTINUATION, OP_TEXT, OP_BINARY
     );
 
-    public HybiParser(WebSocket client) {
+    public HybiParser(WebSocketClient client) {
         mWebSocket = client;
     }
 
@@ -375,6 +375,9 @@ private byte[] slice(byte[] array, int start) {
     }
 
     public static class ProtocolError extends IOException {
+        
+        private static final long serialVersionUID = 1L;
+
         public ProtocolError(String detailMessage) {
             super(detailMessage);
         }
diff --git a/src/com/koushikdutta/http/WebSocket.java b/src/com/koushikdutta/http/WebSocketClient.java
similarity index 96%
rename from src/com/koushikdutta/http/WebSocket.java
rename to src/com/koushikdutta/http/WebSocketClient.java
index f6fd3f1..dcd6ad5 100644
--- a/src/com/koushikdutta/http/WebSocket.java
+++ b/src/com/koushikdutta/http/WebSocketClient.java
@@ -33,8 +33,8 @@
 import android.util.Base64;
 import android.util.Log;
 
-public class WebSocket {
-    private static final String TAG = "WebSocket";
+public class WebSocketClient {
+    private static final String TAG = "WebSocketClient";
 
     private URI mURI;
     private Listener mListener;
@@ -60,7 +60,7 @@ public static void setTrustManagers(TrustManager[] tm) {
         sTrustManagers = tm;
     }
 
-    public WebSocket(URI uri, Listener listener,
+    public WebSocketClient(URI uri, Listener listener,
             List<BasicNameValuePair> extraHeaders) {
         mURI = uri;
         mListener = listener;
@@ -140,7 +140,7 @@ public void run() {
                         }
                     }
 
-                    mListener.onConnect(WebSocket.this);
+                    mListener.onConnect(WebSocketClient.this);
 
                     mConnected = true;
 
@@ -262,7 +262,7 @@ public void run() {
     }
 
     public static interface Listener {
-        public void onConnect(WebSocket webSocket);
+        public void onConnect(WebSocketClient webSocket);
 
         public void onError(Exception ex);
 
@@ -311,7 +311,7 @@ private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException,
     }
 
     public static void create(URI uri, Listener listener) {
-        WebSocket webSocket = new WebSocket(uri, listener, null);
+        WebSocketClient webSocket = new WebSocketClient(uri, listener, null);
         webSocket.connect();
     }
 }

From 58c1f5ec31ca6fae773389a6f2e0994ace9d043a Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Thu, 11 Jul 2013 23:34:30 +0530
Subject: [PATCH 47/56] Removed Handler interface, Invoking callbacks on the
 passed Handler

---
 .../async/http/socketio/SocketIOClient.java   | 150 ++++--------------
 .../http/socketio/SocketIOConnection.java     |  92 ++++++++---
 2 files changed, 107 insertions(+), 135 deletions(-)

diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
index d678a61..58f2197 100644
--- a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
@@ -1,7 +1,5 @@
 package com.koushikdutta.async.http.socketio;
 
-import java.util.List;
-
 import org.json.JSONArray;
 import org.json.JSONObject;
 
@@ -13,10 +11,10 @@
 import com.koushikdutta.http.WebSocketClient;
 
 public class SocketIOClient extends EventEmitter {
+
     boolean connected;
     boolean disconnected;
-
-    private SocketIOCallbacks mSocketIOCallbacks;
+    Handler handler;
 
     private void emitRaw(int type, String message, Acknowledge acknowledge) {
         connection.emitRaw(type, this, message, acknowledge);
@@ -66,8 +64,9 @@ public static void connect(final SocketIORequest request, final ConnectCallback
             @Override
             public void onConnectCompleted(Exception ex, SocketIOClient client) {
                 if (ex != null || TextUtils.isEmpty(request.getEndpoint())) {
+
+                    client.handler = handler;
                     if (callback != null) {
-                        client.setupCallbacks();
                         callback.onConnectCompleted(ex, client);
                     }
 
@@ -82,7 +81,6 @@ public void onConnectCompleted(Exception ex, SocketIOClient client) {
                     @Override
                     public void onConnectCompleted(Exception ex, SocketIOClient client) {
                         if (callback != null) {
-                            client.setupCallbacks();
                             callback.onConnectCompleted(ex, client);
                         }
                     }
@@ -94,76 +92,20 @@ public void onConnectCompleted(Exception ex, SocketIOClient client) {
         connection.reconnect();
 
     }
-    
-    private void setupCallbacks() {
-        setStringCallback(new StringCallback() {
-
-            @Override
-            public void onString(String string, Acknowledge acknowledge) {
-
-                if (mSocketIOCallbacks != null) {
-                    mSocketIOCallbacks.onMessage(string);
-                }
-            }
-        });
-
-        setJSONCallback(new JSONCallback() {
-
-            @Override
-            public void onJSON(JSONObject json, Acknowledge acknowledge) {
-
-                if (mSocketIOCallbacks != null) {
-                    mSocketIOCallbacks.onJSON(json);
-                }
-
-            }
-        });
-
-        setReconnectCallback(new ReconnectCallback() {
-
-            @Override
-            public void onReconnect() {
-
-                if (mSocketIOCallbacks != null) {
-                    mSocketIOCallbacks.onReconnect();
-                }
-
-            }
-        });
-
-        setDisconnectCallback(new DisconnectCallback() {
-
-            @Override
-            public void onDisconnect(Exception e) {
-
-                if (mSocketIOCallbacks != null) {
-                    mSocketIOCallbacks.onDisconnect(0, e.getMessage());
-                }
-
-            }
-        });
-
-        setErrorCallback(new ErrorCallback() {
-
-            @Override
-            public void onError(String error) {
-                if (mSocketIOCallbacks != null) {
-                    mSocketIOCallbacks.onError(new Exception(error));
-                }
-
-            }
-        });
-    }
 
     ErrorCallback errorCallback;
 
-    private void setErrorCallback(ErrorCallback callback) {
+    public void setErrorCallback(ErrorCallback callback) {
         errorCallback = callback;
     }
 
+    public ErrorCallback getErrorCallback() {
+        return errorCallback;
+    }
+
     DisconnectCallback disconnectCallback;
 
-    private void setDisconnectCallback(DisconnectCallback callback) {
+    public void setDisconnectCallback(DisconnectCallback callback) {
         disconnectCallback = callback;
     }
 
@@ -173,50 +115,32 @@ public DisconnectCallback getDisconnectCallback() {
 
     ReconnectCallback reconnectCallback;
 
-    private void setReconnectCallback(ReconnectCallback callback) {
+    public void setReconnectCallback(ReconnectCallback callback) {
         reconnectCallback = callback;
     }
 
+    public ReconnectCallback getReconnectCallback() {
+        return reconnectCallback;
+    }
+
     JSONCallback jsonCallback;
 
-    private void setJSONCallback(JSONCallback callback) {
+    public void setJSONCallback(JSONCallback callback) {
         jsonCallback = callback;
     }
 
+    public JSONCallback getJSONCallback() {
+        return jsonCallback;
+    }
+
     StringCallback stringCallback;
 
-    private void setStringCallback(StringCallback callback) {
+    public void setStringCallback(StringCallback callback) {
         stringCallback = callback;
     }
 
-    public void setSocketIOCallbacks(SocketIOCallbacks callbacks) {
-        mSocketIOCallbacks = callbacks;
-    }
-    
-    public SocketIOCallbacks getSocketIOCallbacks() {
-        return mSocketIOCallbacks;
-    }
-    
-    public void listenForEvents(List<String> events) {
-        
-        if(events == null) {
-            return;
-        }
-        
-        EventCallback callback = new EventCallback() {
-            
-            @Override
-            public void onEvent(String event, JSONArray argument, Acknowledge acknowledge) {
-                
-                if(mSocketIOCallbacks != null) {
-                    mSocketIOCallbacks.on(event, argument);
-                }
-            }
-        };
-        
-        for(String event : events) {
-            on(event, callback);
-        }
+    public StringCallback getStringCallback() {
+        return stringCallback;
     }
 
     SocketIOConnection connection;
@@ -235,12 +159,20 @@ public boolean isConnected() {
 
     public void disconnect() {
         connection.disconnect(this);
-        DisconnectCallback disconnectCallback = this.disconnectCallback;
+        final DisconnectCallback disconnectCallback = this.disconnectCallback;
         if (disconnectCallback != null) {
-            disconnectCallback.onDisconnect(null);
+            handler.post(new Runnable() {
+
+                @Override
+                public void run() {
+                    disconnectCallback.onDisconnect(null);
+
+                }
+            });
+
         }
     }
-    
+
     public void of(String endpoint, ConnectCallback connectCallback) {
         connection.connect(new SocketIOClient(connection, endpoint, connectCallback));
     }
@@ -249,18 +181,4 @@ public WebSocketClient getWebSocket() {
         return connection.webSocketClient;
     }
 
-    public static interface SocketIOCallbacks {
-
-        public void on(String event, JSONArray arguments);
-
-        public void onDisconnect(int code, String reason);
-
-        public void onReconnect();
-
-        public void onJSON(JSONObject json);
-
-        public void onMessage(String message);
-
-        public void onError(Exception error);
-    }
 }
diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
index 93aae55..ea71f5d 100644
--- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
@@ -199,18 +199,36 @@ public void run() {
     private void reportDisconnect(final Exception ex) {
         select(null, new SelectCallback() {
             @Override
-            public void onSelect(SocketIOClient client) {
+            public void onSelect(final SocketIOClient client) {
                 if (client.connected) {
                     client.disconnected = true;
-                    DisconnectCallback closed = client.getDisconnectCallback();
-                    if (closed != null)
-                        closed.onDisconnect(ex);
+                    final DisconnectCallback closed = client.getDisconnectCallback();
+                    if (closed != null) {
+                        mHandler.post(new Runnable() {
+
+                            @Override
+                            public void run() {
+                                closed.onDisconnect(ex);
+
+                            }
+                        });
+
+                    }
                 } else {
                     // client has never connected, this is a initial connect
                     // failure
-                    ConnectCallback callback = client.connectCallback;
-                    if (callback != null)
-                        callback.onConnectCompleted(ex, client);
+                    final ConnectCallback callback = client.connectCallback;
+                    if (callback != null) {
+                        mHandler.post(new Runnable() {
+
+                            @Override
+                            public void run() {
+                                callback.onConnectCompleted(ex, client);
+
+                            }
+                        });
+
+                    }
                 }
             }
         });
@@ -248,9 +266,18 @@ private void reportJson(String endpoint, final JSONObject jsonMessage, final Ack
         select(endpoint, new SelectCallback() {
             @Override
             public void onSelect(SocketIOClient client) {
-                JSONCallback callback = client.jsonCallback;
-                if (callback != null)
-                    callback.onJSON(jsonMessage, acknowledge);
+                final JSONCallback callback = client.jsonCallback;
+                if (callback != null) {
+                    mHandler.post(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            callback.onJSON(jsonMessage, acknowledge);
+
+                        }
+                    });
+                }
+
             }
         });
     }
@@ -259,9 +286,18 @@ private void reportString(String endpoint, final String string, final Acknowledg
         select(endpoint, new SelectCallback() {
             @Override
             public void onSelect(SocketIOClient client) {
-                StringCallback callback = client.stringCallback;
-                if (callback != null)
-                    callback.onString(string, acknowledge);
+                final StringCallback callback = client.stringCallback;
+                if (callback != null) {
+                    mHandler.post(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            callback.onString(string, acknowledge);
+
+                        }
+                    });
+
+                }
             }
         });
     }
@@ -269,8 +305,16 @@ public void onSelect(SocketIOClient client) {
     private void reportEvent(String endpoint, final String event, final JSONArray arguments, final Acknowledge acknowledge) {
         select(endpoint, new SelectCallback() {
             @Override
-            public void onSelect(SocketIOClient client) {
-                client.onEvent(event, arguments, acknowledge);
+            public void onSelect(final SocketIOClient client) {
+                mHandler.post(new Runnable() {
+
+                    @Override
+                    public void run() {
+                        client.onEvent(event, arguments, acknowledge);
+
+                    }
+                });
+
             }
         });
     }
@@ -279,9 +323,19 @@ private void reportError(String endpoint, final String error) {
         select(endpoint, new SelectCallback() {
             @Override
             public void onSelect(SocketIOClient client) {
-                ErrorCallback callback = client.errorCallback;
-                if (callback != null)
-                    callback.onError(error);
+                final ErrorCallback callback = client.errorCallback;
+                if (callback != null) {
+
+                    mHandler.post(new Runnable() {
+
+                        @Override
+                        public void run() {
+                            callback.onError(error);
+
+                        }
+                    });
+
+                }
             }
         });
     }
@@ -390,7 +444,7 @@ public void onStringAvailable(String message) {
                 }
             }
         });
-        
+
         webSocketClient.startParsing();
     }
 }

From e61b05e986e1b681bfe2e50e792b72f37f729164 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Fri, 12 Jul 2013 00:43:03 +0530
Subject: [PATCH 48/56] Repackaged HybiParser and WebSocketClient

---
 .../android_websockets}/HybiParser.java       |  2 +-
 .../android_websockets}/WebSocketClient.java  | 19 +++++++------------
 .../async/http/socketio/SocketIOClient.java   |  2 +-
 .../http/socketio/SocketIOConnection.java     |  6 +++---
 .../koushikdutta/http/AsyncHttpClient.java    |  2 ++
 5 files changed, 14 insertions(+), 17 deletions(-)
 rename src/com/{koushikdutta/http => codebutler/android_websockets}/HybiParser.java (99%)
 rename src/com/{koushikdutta/http => codebutler/android_websockets}/WebSocketClient.java (95%)

diff --git a/src/com/koushikdutta/http/HybiParser.java b/src/com/codebutler/android_websockets/HybiParser.java
similarity index 99%
rename from src/com/koushikdutta/http/HybiParser.java
rename to src/com/codebutler/android_websockets/HybiParser.java
index 56cfd1b..5aa1ed5 100644
--- a/src/com/koushikdutta/http/HybiParser.java
+++ b/src/com/codebutler/android_websockets/HybiParser.java
@@ -28,7 +28,7 @@
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
-package com.koushikdutta.http;
+package com.codebutler.android_websockets;
 
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
diff --git a/src/com/koushikdutta/http/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java
similarity index 95%
rename from src/com/koushikdutta/http/WebSocketClient.java
rename to src/com/codebutler/android_websockets/WebSocketClient.java
index dcd6ad5..a42f2e9 100644
--- a/src/com/koushikdutta/http/WebSocketClient.java
+++ b/src/com/codebutler/android_websockets/WebSocketClient.java
@@ -1,4 +1,4 @@
-package com.koushikdutta.http;
+package com.codebutler.android_websockets;
 
 import java.io.EOFException;
 import java.io.IOException;
@@ -25,7 +25,7 @@
 import org.apache.http.message.BasicLineParser;
 import org.apache.http.message.BasicNameValuePair;
 
-import com.koushikdutta.http.HybiParser.HappyDataInputStream;
+import com.codebutler.android_websockets.HybiParser.HappyDataInputStream;
 
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -87,22 +87,17 @@ public void connect() {
             @Override
             public void run() {
                 try {
-                    int port = (mURI.getPort() != -1) ? mURI.getPort()
-                            : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443
-                                    : 80);
+                    int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80);
 
-                    String path = TextUtils.isEmpty(mURI.getPath()) ? "/"
-                            : mURI.getPath();
+                    String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath();
                     if (!TextUtils.isEmpty(mURI.getQuery())) {
                         path += "?" + mURI.getQuery();
                     }
 
-                    String originScheme = mURI.getScheme().equals("wss") ? "https"
-                            : "http";
+                    String originScheme = mURI.getScheme().equals("wss") ? "https" : "http";
                     URI origin = new URI(originScheme, "//" + mURI.getHost(), null);
 
-                    SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory()
-                            : SocketFactory.getDefault();
+                    SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault();
                     mSocket = factory.createSocket(mURI.getHost(), port);
 
                     PrintWriter out = new PrintWriter(mSocket.getOutputStream());
@@ -164,7 +159,7 @@ public void run() {
     }
 
     public void startParsing() {
-        
+
         // Now decode websocket frames.
         try {
             mParser.start(stream);
diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
index 58f2197..af704fe 100644
--- a/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOClient.java
@@ -6,9 +6,9 @@
 import android.os.Handler;
 import android.text.TextUtils;
 
+import com.codebutler.android_websockets.WebSocketClient;
 import com.koushikdutta.http.AsyncHttpClient;
 import com.koushikdutta.http.AsyncHttpClient.SocketIORequest;
-import com.koushikdutta.http.WebSocketClient;
 
 public class SocketIOClient extends EventEmitter {
 
diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
index ea71f5d..b328717 100644
--- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
@@ -6,11 +6,11 @@
 import org.json.JSONArray;
 import org.json.JSONObject;
 
+import com.codebutler.android_websockets.WebSocketClient;
+import com.codebutler.android_websockets.WebSocketClient.ClosedCallback;
+import com.codebutler.android_websockets.WebSocketClient.DataCallback;
 import com.koushikdutta.http.AsyncHttpClient;
 import com.koushikdutta.http.AsyncHttpClient.SocketIORequest;
-import com.koushikdutta.http.WebSocketClient;
-import com.koushikdutta.http.WebSocketClient.ClosedCallback;
-import com.koushikdutta.http.WebSocketClient.DataCallback;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java
index a09c340..4d9f02e 100644
--- a/src/com/koushikdutta/http/AsyncHttpClient.java
+++ b/src/com/koushikdutta/http/AsyncHttpClient.java
@@ -9,6 +9,8 @@
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpPost;
 
+import com.codebutler.android_websockets.WebSocketClient;
+
 import android.net.Uri;
 import android.net.http.AndroidHttpClient;
 import android.os.AsyncTask;

From 7215aea58eaad6c47a90e2654b18ed37dba653b6 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinay@notarealid.com>
Date: Fri, 12 Jul 2013 22:22:41 +0530
Subject: [PATCH 49/56] Switched back to original WebsocketClient and
 HybiParser

---
 .../android_websockets/HybiParser.java        |  37 ++-
 .../android_websockets/WebSocketClient.java   | 149 ++++--------
 .../http/socketio/SocketIOConnection.java     | 219 +++++++++---------
 .../koushikdutta/http/AsyncHttpClient.java    |  27 +--
 4 files changed, 166 insertions(+), 266 deletions(-)

diff --git a/src/com/codebutler/android_websockets/HybiParser.java b/src/com/codebutler/android_websockets/HybiParser.java
index 5aa1ed5..e13eb8d 100644
--- a/src/com/codebutler/android_websockets/HybiParser.java
+++ b/src/com/codebutler/android_websockets/HybiParser.java
@@ -30,20 +30,16 @@
 
 package com.codebutler.android_websockets;
 
-import java.io.ByteArrayOutputStream;
-import java.io.DataInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
+import android.util.Log;
+
+import java.io.*;
 import java.util.Arrays;
 import java.util.List;
 
-import android.util.Log;
-
 public class HybiParser {
     private static final String TAG = "HybiParser";
 
-    private WebSocketClient mWebSocket;
+    private WebSocketClient mClient;
 
     private boolean mMasking = true;
 
@@ -96,7 +92,7 @@ public class HybiParser {
     );
 
     public HybiParser(WebSocketClient client) {
-        mWebSocket = client;
+        mClient = client;
     }
 
     private static byte[] mask(byte[] payload, byte[] mask, int offset) {
@@ -132,7 +128,7 @@ public void start(HappyDataInputStream stream) throws IOException {
                     break;
             }
         }
-        mWebSocket.getClosedCallback().onCompleted(new IOException("EOF"));
+        mClient.getListener().onDisconnect(0, "EOF");
     }
 
     private void parseOpcode(byte data) throws ProtocolError {
@@ -245,12 +241,12 @@ private byte[] frame(Object data, int opcode, int errorCode) {
     }
 
     public void ping(String message) {
-        mWebSocket.send(frame(message, OP_PING, -1));
+        mClient.send(frame(message, OP_PING, -1));
     }
 
     public void close(int code, String reason) {
         if (mClosed) return;
-        mWebSocket.send(frame(reason, OP_CLOSE, code));
+        mClient.send(frame(reason, OP_CLOSE, code));
         mClosed = true;
     }
 
@@ -266,9 +262,9 @@ private void emitFrame() throws IOException {
             if (mFinal) {
                 byte[] message = mBuffer.toByteArray();
                 if (mMode == MODE_TEXT) {
-                    mWebSocket.getStringCallback().onStringAvailable(encode(message));
+                    mClient.getListener().onMessage(encode(message));
                 } else {
-                    mWebSocket.getDataCallback().onDataAvailable(message);
+                    mClient.getListener().onMessage(message);
                 }
                 reset();
             }
@@ -276,7 +272,7 @@ private void emitFrame() throws IOException {
         } else if (opcode == OP_TEXT) {
             if (mFinal) {
                 String messageText = encode(payload);
-                mWebSocket.getStringCallback().onStringAvailable(messageText);
+                mClient.getListener().onMessage(messageText);
             } else {
                 mMode = MODE_TEXT;
                 mBuffer.write(payload);
@@ -284,7 +280,7 @@ private void emitFrame() throws IOException {
 
         } else if (opcode == OP_BINARY) {
             if (mFinal) {
-                mWebSocket.getDataCallback().onDataAvailable(payload);
+                mClient.getListener().onMessage(payload);
             } else {
                 mMode = MODE_BINARY;
                 mBuffer.write(payload);
@@ -294,12 +290,12 @@ private void emitFrame() throws IOException {
             int    code   = (payload.length >= 2) ? 256 * payload[0] + payload[1] : 0;
             String reason = (payload.length >  2) ? encode(slice(payload, 2))     : null;
             Log.d(TAG, "Got close op! " + code + " " + reason);
-            mWebSocket.getClosedCallback().onCompleted(new IOException("Got close op! " + code + " " + reason));
+            mClient.getListener().onDisconnect(code, reason);
 
         } else if (opcode == OP_PING) {
             if (payload.length > 125) { throw new ProtocolError("Ping payload too large"); }
             Log.d(TAG, "Sending pong!!");
-            mWebSocket.sendFrame(frame(payload, OP_PONG, -1));
+            mClient.sendFrame(frame(payload, OP_PONG, -1));
 
         } else if (opcode == OP_PONG) {
             String message = encode(payload);
@@ -375,9 +371,6 @@ private byte[] slice(byte[] array, int start) {
     }
 
     public static class ProtocolError extends IOException {
-        
-        private static final long serialVersionUID = 1L;
-
         public ProtocolError(String detailMessage) {
             super(detailMessage);
         }
@@ -420,4 +413,4 @@ public byte[] readBytes(int length) throws IOException {
             return buffer;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java
index a42f2e9..0475d5e 100644
--- a/src/com/codebutler/android_websockets/WebSocketClient.java
+++ b/src/com/codebutler/android_websockets/WebSocketClient.java
@@ -1,5 +1,20 @@
 package com.codebutler.android_websockets;
 
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+import org.apache.http.*;
+import org.apache.http.client.HttpResponseException;
+import org.apache.http.message.BasicLineParser;
+import org.apache.http.message.BasicNameValuePair;
+
+import javax.net.SocketFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
 import java.io.EOFException;
 import java.io.IOException;
 import java.io.OutputStream;
@@ -10,47 +25,18 @@
 import java.security.NoSuchAlgorithmException;
 import java.util.List;
 
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-
-import org.apache.http.Header;
-import org.apache.http.HttpException;
-import org.apache.http.HttpStatus;
-import org.apache.http.NameValuePair;
-import org.apache.http.StatusLine;
-import org.apache.http.client.HttpResponseException;
-import org.apache.http.message.BasicLineParser;
-import org.apache.http.message.BasicNameValuePair;
-
-import com.codebutler.android_websockets.HybiParser.HappyDataInputStream;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.text.TextUtils;
-import android.util.Base64;
-import android.util.Log;
-
 public class WebSocketClient {
     private static final String TAG = "WebSocketClient";
 
-    private URI mURI;
-    private Listener mListener;
-    private Socket mSocket;
-    private Thread mThread;
-    private HandlerThread mHandlerThread;
-    private Handler mHandler;
+    private URI                      mURI;
+    private Listener                 mListener;
+    private Socket                   mSocket;
+    private Thread                   mThread;
+    private HandlerThread            mHandlerThread;
+    private Handler                  mHandler;
     private List<BasicNameValuePair> mExtraHeaders;
-    private HybiParser mParser;
-    private boolean mConnected;
-
-    private DataCallback mDataCallback;
-    private StringCallback mStringCallback;
-    private ClosedCallback mClosedCallback;
-
-    private HappyDataInputStream stream;
+    private HybiParser               mParser;
+    private boolean                  mConnected;
 
     private final Object mSendLock = new Object();
 
@@ -60,13 +46,12 @@ public static void setTrustManagers(TrustManager[] tm) {
         sTrustManagers = tm;
     }
 
-    public WebSocketClient(URI uri, Listener listener,
-            List<BasicNameValuePair> extraHeaders) {
-        mURI = uri;
-        mListener = listener;
+    public WebSocketClient(URI uri, Listener listener, List<BasicNameValuePair> extraHeaders) {
+        mURI          = uri;
+        mListener     = listener;
         mExtraHeaders = extraHeaders;
-        mConnected = false;
-        mParser = new HybiParser(this);
+        mConnected    = false;
+        mParser       = new HybiParser(this);
 
         mHandlerThread = new HandlerThread("websocket-thread");
         mHandlerThread.start();
@@ -83,7 +68,6 @@ public void connect() {
         }
 
         mThread = new Thread(new Runnable() {
-
             @Override
             public void run() {
                 try {
@@ -116,7 +100,7 @@ public void run() {
                     out.print("\r\n");
                     out.flush();
 
-                    stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream());
+                    HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream());
 
                     // Read HTTP response status line.
                     StatusLine statusLine = parseStatusLine(readLine(stream));
@@ -135,19 +119,22 @@ public void run() {
                         }
                     }
 
-                    mListener.onConnect(WebSocketClient.this);
+                    mListener.onConnect();
 
                     mConnected = true;
 
+                    // Now decode websocket frames.
+                    mParser.start(stream);
+
                 } catch (EOFException ex) {
                     Log.d(TAG, "WebSocket EOF!", ex);
-                    mListener.onError(ex);
+                    mListener.onDisconnect(0, "EOF");
                     mConnected = false;
 
                 } catch (SSLException ex) {
                     // Connection reset by peer
                     Log.d(TAG, "Websocket SSL error!", ex);
-                    mListener.onError(ex);
+                    mListener.onDisconnect(0, "SSL");
                     mConnected = false;
 
                 } catch (Exception ex) {
@@ -158,16 +145,6 @@ public void run() {
         mThread.start();
     }
 
-    public void startParsing() {
-
-        // Now decode websocket frames.
-        try {
-            mParser.start(stream);
-        } catch (IOException e) {
-            mClosedCallback.onCompleted(e);
-        }
-    }
-
     public void disconnect() {
         if (mSocket != null) {
             mHandler.post(new Runnable() {
@@ -196,7 +173,7 @@ public void send(byte[] data) {
         sendFrame(mParser.frame(data));
     }
 
-    public boolean isOpen() {
+    public boolean isConnected() {
         return mConnected;
     }
 
@@ -256,47 +233,12 @@ public void run() {
         });
     }
 
-    public static interface Listener {
-        public void onConnect(WebSocketClient webSocket);
-
-        public void onError(Exception ex);
-
-    }
-
-    public static interface DataCallback {
-        public void onDataAvailable(byte[] data);
-    }
-
-    public static interface StringCallback {
-        public void onStringAvailable(String message);
-    }
-
-    public static interface ClosedCallback {
-        public void onCompleted(Exception ex);
-    }
-
-    public DataCallback getDataCallback() {
-        return mDataCallback;
-    }
-
-    public void setDataCallback(DataCallback dataCallback) {
-        this.mDataCallback = dataCallback;
-    }
-
-    public StringCallback getStringCallback() {
-        return mStringCallback;
-    }
-
-    public void setStringCallback(StringCallback stringCallback) {
-        this.mStringCallback = stringCallback;
-    }
-
-    public ClosedCallback getClosedCallback() {
-        return mClosedCallback;
-    }
-
-    public void setClosedCallback(ClosedCallback closedCallback) {
-        this.mClosedCallback = closedCallback;
+    public interface Listener {
+        public void onConnect();
+        public void onMessage(String message);
+        public void onMessage(byte[] data);
+        public void onDisconnect(int code, String reason);
+        public void onError(Exception error);
     }
 
     private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException {
@@ -304,9 +246,4 @@ private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException,
         context.init(null, sTrustManagers, null);
         return context.getSocketFactory();
     }
-
-    public static void create(URI uri, Listener listener) {
-        WebSocketClient webSocket = new WebSocketClient(uri, listener, null);
-        webSocket.connect();
-    }
-}
+}
\ No newline at end of file
diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
index b328717..669a288 100644
--- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
@@ -1,22 +1,23 @@
 package com.koushikdutta.async.http.socketio;
 
-import android.os.Handler;
-import android.text.TextUtils;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Hashtable;
 
 import org.json.JSONArray;
 import org.json.JSONObject;
 
+import android.os.Handler;
+import android.text.TextUtils;
+
 import com.codebutler.android_websockets.WebSocketClient;
-import com.codebutler.android_websockets.WebSocketClient.ClosedCallback;
-import com.codebutler.android_websockets.WebSocketClient.DataCallback;
+import com.codebutler.android_websockets.WebSocketClient.Listener;
 import com.koushikdutta.http.AsyncHttpClient;
 import com.koushikdutta.http.AsyncHttpClient.SocketIORequest;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Hashtable;
-
 /**
  * Created by koush on 7/1/13.
  */
@@ -37,7 +38,7 @@ public SocketIOConnection(Handler handler, AsyncHttpClient httpClient,
     }
 
     public boolean isConnected() {
-        return webSocketClient != null && webSocketClient.isOpen();
+        return webSocketClient != null && webSocketClient.isConnected();
     }
 
     Hashtable<String, Acknowledge> acknowledges = new Hashtable<String, Acknowledge>();
@@ -81,8 +82,6 @@ public void disconnect(SocketIOClient client) {
         if (clients.size() > 0)
             return;
 
-        webSocketClient.setStringCallback(null);
-        webSocketClient.setClosedCallback(null);
         webSocketClient.disconnect();
         webSocketClient = null;
     }
@@ -117,20 +116,104 @@ public void onCompleted(final Exception e, String result) {
 
                     final String sessionUrl = request.getUri().toString()
                             + "websocket/" + session + "/";
-
-                    httpClient.websocket(sessionUrl, null, new AsyncHttpClient.WebSocketConnectCallback() {
+                    
+                    SocketIOConnection.this.webSocketClient =  new WebSocketClient(URI.create(sessionUrl), new Listener() {
+                        
                         @Override
-                        public void onCompleted(Exception ex, WebSocketClient webSocket) {
-                            if (ex != null) {
+                        public void onMessage(byte[] data) {
+                            //Do nothing
+                            
+                        }
+                        
+                        @Override
+                        public void onMessage(String message) {
+                            try {
+                                // Log.d(TAG, "Message: " + message);
+                                String[] parts = message.split(":", 4);
+                                int code = Integer.parseInt(parts[0]);
+                                switch (code) {
+                                case 0:
+                                    // disconnect
+                                    webSocketClient.disconnect();
+                                    reportDisconnect(null);
+                                    break;
+                                case 1:
+                                    // connect
+                                    reportConnect(parts[2]);
+                                    break;
+                                case 2:
+                                    // heartbeat
+                                    webSocketClient.send("2::");
+                                    break;
+                                case 3: {
+                                    // message
+                                    reportString(parts[2], parts[3], acknowledge(parts[1]));
+                                    break;
+                                }
+                                case 4: {
+                                    // json message
+                                    final String dataString = parts[3];
+                                    final JSONObject jsonMessage = new JSONObject(dataString);
+                                    reportJson(parts[2], jsonMessage, acknowledge(parts[1]));
+                                    break;
+                                }
+                                case 5: {
+                                    final String dataString = parts[3];
+                                    final JSONObject data = new JSONObject(dataString);
+                                    final String event = data.getString("name");
+                                    final JSONArray args = data.optJSONArray("args");
+                                    reportEvent(parts[2], event, args, acknowledge(parts[1]));
+                                    break;
+                                }
+                                case 6:
+                                    // ACK
+                                    final String[] ackParts = parts[3].split("\\+", 2);
+                                    Acknowledge ack = acknowledges.remove(ackParts[0]);
+                                    if (ack == null)
+                                        return;
+                                    JSONArray arguments = null;
+                                    if (ackParts.length == 2)
+                                        arguments = new JSONArray(ackParts[1]);
+                                    ack.acknowledge(arguments);
+                                    break;
+                                case 7:
+                                    // error
+                                    reportError(parts[2], parts[3]);
+                                    break;
+                                case 8:
+                                    // noop
+                                    break;
+                                default:
+                                    throw new Exception("unknown code");
+                                }
+                            } catch (Exception ex) {
+                                webSocketClient.disconnect();
+                                webSocketClient = null;
                                 reportDisconnect(ex);
-                                return;
                             }
-
+                        
+                            
+                        }
+                        
+                        @Override
+                        public void onError(Exception error) {
+                            reportDisconnect(error);
+                        }
+                        
+                        @Override
+                        public void onDisconnect(int code, String reason) {
+                            
+                            reportDisconnect(new IOException(String.format("Disconnected code %d for reason %s", code, reason)));
+                        }
+                        
+                        @Override
+                        public void onConnect() {
                             reconnectDelay = 1000L;
-                            SocketIOConnection.this.webSocketClient = webSocket;
-                            attach();
+                            setupHeartbeat();
+                            
                         }
-                    });
+                    }, null);
+                    SocketIOConnection.this.webSocketClient.connect();
 
                 } catch (Exception ex) {
                     reportDisconnect(ex);
@@ -146,7 +229,7 @@ void setupHeartbeat() {
             @Override
             public void run() {
                 if (heartbeat <= 0 || ws != webSocketClient || ws == null
-                        || !ws.isOpen())
+                        || !ws.isConnected())
                     return;
                 webSocketClient.send("2:::");
 
@@ -355,96 +438,6 @@ public void acknowledge(JSONArray arguments) {
         };
     }
 
-    private void attach() {
-        setupHeartbeat();
-
-        webSocketClient.setDataCallback(new DataCallback() {
-
-            @Override
-            public void onDataAvailable(byte[] data) {
-                // Do nothing
-            }
-        });
-        webSocketClient.setClosedCallback(new ClosedCallback() {
-
-            @Override
-            public void onCompleted(final Exception ex) {
-                webSocketClient = null;
-                reportDisconnect(ex);
-            }
-        });
+    
 
-        webSocketClient.setStringCallback(new WebSocketClient.StringCallback() {
-            @Override
-            public void onStringAvailable(String message) {
-                try {
-                    // Log.d(TAG, "Message: " + message);
-                    String[] parts = message.split(":", 4);
-                    int code = Integer.parseInt(parts[0]);
-                    switch (code) {
-                    case 0:
-                        // disconnect
-                        webSocketClient.disconnect();
-                        reportDisconnect(null);
-                        break;
-                    case 1:
-                        // connect
-                        reportConnect(parts[2]);
-                        break;
-                    case 2:
-                        // heartbeat
-                        webSocketClient.send("2::");
-                        break;
-                    case 3: {
-                        // message
-                        reportString(parts[2], parts[3], acknowledge(parts[1]));
-                        break;
-                    }
-                    case 4: {
-                        // json message
-                        final String dataString = parts[3];
-                        final JSONObject jsonMessage = new JSONObject(dataString);
-                        reportJson(parts[2], jsonMessage, acknowledge(parts[1]));
-                        break;
-                    }
-                    case 5: {
-                        final String dataString = parts[3];
-                        final JSONObject data = new JSONObject(dataString);
-                        final String event = data.getString("name");
-                        final JSONArray args = data.optJSONArray("args");
-                        reportEvent(parts[2], event, args, acknowledge(parts[1]));
-                        break;
-                    }
-                    case 6:
-                        // ACK
-                        final String[] ackParts = parts[3].split("\\+", 2);
-                        Acknowledge ack = acknowledges.remove(ackParts[0]);
-                        if (ack == null)
-                            return;
-                        JSONArray arguments = null;
-                        if (ackParts.length == 2)
-                            arguments = new JSONArray(ackParts[1]);
-                        ack.acknowledge(arguments);
-                        break;
-                    case 7:
-                        // error
-                        reportError(parts[2], parts[3]);
-                        break;
-                    case 8:
-                        // noop
-                        break;
-                    default:
-                        throw new Exception("unknown code");
-                    }
-                } catch (Exception ex) {
-                    webSocketClient.setClosedCallback(null);
-                    webSocketClient.disconnect();
-                    webSocketClient = null;
-                    reportDisconnect(ex);
-                }
-            }
-        });
-
-        webSocketClient.startParsing();
-    }
 }
diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java
index 4d9f02e..b8a15d0 100644
--- a/src/com/koushikdutta/http/AsyncHttpClient.java
+++ b/src/com/koushikdutta/http/AsyncHttpClient.java
@@ -4,17 +4,16 @@
 import java.io.DataInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.URI;
 
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpPost;
 
-import com.codebutler.android_websockets.WebSocketClient;
-
 import android.net.Uri;
 import android.net.http.AndroidHttpClient;
 import android.os.AsyncTask;
 
+import com.codebutler.android_websockets.WebSocketClient;
+
 /**
  * 
  * Created by Vinay S Shenoy on 07/09/2013
@@ -107,26 +106,4 @@ private String readToEnd(InputStream input) throws IOException {
         return new String(readToEndAsArray(input));
     }
 
-    public void websocket(final String uri, final String protocol, final WebSocketConnectCallback callback) {
-
-        WebSocketClient.create(URI.create(uri), new WebSocketClient.Listener() {
-
-            @Override
-            public void onConnect(WebSocketClient websocket) {
-                if (callback != null) {
-                    callback.onCompleted(null, websocket);
-                }
-
-            }
-
-            @Override
-            public void onError(Exception ex) {
-                if (callback != null) {
-                    callback.onCompleted(ex, null);
-                }
-
-            }
-        });
-    }
-
 }

From 29e00424461b5238a306c2d56e399b72451a3004 Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinaysshenoy@gmail.com>
Date: Fri, 12 Jul 2013 22:47:51 +0530
Subject: [PATCH 50/56] Update README.md

---
 README.md | 129 +++++++++++++++++++++++++++++-------------------------
 1 file changed, 70 insertions(+), 59 deletions(-)

diff --git a/README.md b/README.md
index 63b1a0d..9005dcd 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,5 @@
 # WebSocket and Socket.IO client for Android
 
-A very simple bare-minimum WebSocket and Socket.IO client for Android.
-
 ## Credits
 
 The hybi parser is based on code from the [faye project](https://github.com/faye/faye-websocket-node). Faye is Copyright (c) 2009-2012 James Coglan. Many thanks for the great open-source library!
@@ -12,6 +10,8 @@ The WebSocket client was written by [Eric Butler](https://twitter.com/codebutler
 
 The Socket.IO client was written by [Koushik Dutta](https://twitter.com/koush).
 
+The Socket.IO client component was ported from Koushik Dutta's AndroidAsync(https://github.com/koush/AndroidAsync) by [Vinay S Shenoy](https://twitter.com/vinaysshenoy)
+
 ## WebSocket Usage
 
 ```java
@@ -58,75 +58,86 @@ client.disconnect();
 ## Socket.IO Usage
 
 ```java
-SocketIOClient client = new SocketIOClient(URI.create("wss://example.com"), new SocketIOClient.Handler() {
-    @Override
-    public void onConnect() {
-        Log.d(TAG, "Connected!");
-    }
+SocketIOClient.connect("http://localhost:80", new ConnectCallback() {
 
     @Override
-    public void on(String event, JSONArray arguments) {
-        Log.d(TAG, String.format("Got event %s: %s", event, arguments.toString()));
+    public void onConnectCompleted(Exception ex, SocketIOClient client) {
+        
+        if (ex != null) {
+            return;
+        }
+
+        //Save the returned SocketIOClient instance into a variable so you can disconnect it later
+        client.setDisconnectCallback(MainActivity.this);
+        client.setErrorCallback(MainActivity.this);
+        client.setJSONCallback(MainActivity.this);
+        client.setStringCallback(MainActivity.this);
+        client.addListener("news", MainActivity.this);
+
+        client.of("/chat", new ConnectCallback() {
+        
+            @Override
+            public void onConnectCompleted(Exception ex, SocketIOClient client) {
+                
+                if (ex != null) {
+                    ex.printStackTrace();
+                    return;
+                }
+
+                //This client instance will be using the same websocket as the original client, 
+                //but will point to the indicated endpoint
+                client.setDisconnectCallback(MainActivity.this);
+                client.setErrorCallback(MainActivity.this);
+                client.setJSONCallback(MainActivity.this);
+                client.setStringCallback(MainActivity.this);
+                client.addListener("a message", MainActivity.this);
+
+            }
+        });
+
     }
-    
-    @Override
-    public void onJSON(JSONObject json) {
-    	try {
-    		Log.d(TAG, String.format("Got JSON Object: %s", json.toString()));
-    	} catch(JSONException e) {
-    	}
+}, new Handler());
+
+        
+@Override
+public void onEvent(String event, JSONArray argument, Acknowledge acknowledge) {
+    try {
+        Log.d("MainActivity", "Event:" + event + "Arguments:"
+                + argument.toString(2));
+    } catch (JSONException e) {
+        e.printStackTrace();
     }
 
-    @Override
-    public void onMessage(String message) {
-    	Log.d(TAG, String.format("Got message: %s", message));
-    }
+}
 
-    @Override
-    public void onDisconnect(int code, String reason) {
-        Log.d(TAG, String.format("Disconnected! Code: %d Reason: %s", code, reason));
-    }
+@Override
+public void onString(String string, Acknowledge acknowledge) {
+    Log.d("MainActivity", string);
 
-    @Override
-    public void onError(Exception error) {
-        Log.e(TAG, "Error!", error);
-    }
-    
-    @Override
-    public void onConnectToEndpoint(String endpoint) {
-    	Log.d(TAG, "Connected to:" + endpoint);
+}
 
+@Override
+public void onJSON(JSONObject json, Acknowledge acknowledge) {
+    try {
+        Log.d("MainActivity", "json:" + json.toString(2));
+    } catch (JSONException e) {
+        e.printStackTrace();
     }
-});
 
-client.connect();
+}
 
-// Later… 
-client.emit("Message"); //Message
-JSONArray arguments = new JSONArray();
-arguments.put("first argument");
-JSONObject second = new JSONObject();
-second.put("dictionary", true);
-client.emit(second); //JSON Message
-arguments.put(second);
-client.emit("hello", arguments); //Event
-client.disconnect();
+@Override
+public void onError(String error) {
+    Log.d("MainActivity", error);
+
+}
+
+@Override
+public void onDisconnect(Exception e) {
+    Log.d(mComponentTag, "Disconnected:" + e.getMessage());
+
+}
 
-/*How to use Acknowledges
-*Call any of the emit() methods 
-*with the Acknowledge parameter.
-*
-*Each message must have a new
-*Acknowledge object as they
-*are matched against the Message Id.
-*/
-client.emit("Message", new Acknowledge {
-
-	@Override
-	public void acknowledge(String[] args) {
-		//Perform your acknowledge handling here
-	}
-});
 ```
 
 

From 383d075fb6fe992bfde97287c885ffc98c78dfaf Mon Sep 17 00:00:00 2001
From: Vinay S Shenoy <vinaysshenoy@gmail.com>
Date: Fri, 12 Jul 2013 22:49:17 +0530
Subject: [PATCH 51/56] Update README.md

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index 9005dcd..a50657a 100644
--- a/README.md
+++ b/README.md
@@ -72,6 +72,8 @@ SocketIOClient.connect("http://localhost:80", new ConnectCallback() {
         client.setErrorCallback(MainActivity.this);
         client.setJSONCallback(MainActivity.this);
         client.setStringCallback(MainActivity.this);
+        
+        //You need to explicitly specify which events you are interested in receiving
         client.addListener("news", MainActivity.this);
 
         client.of("/chat", new ConnectCallback() {

From 9e5b14b09ac7781f5515b69d3c3352c172de26c5 Mon Sep 17 00:00:00 2001
From: Payden Sutherland <payden@paydensutherland.com>
Date: Mon, 14 Oct 2013 00:36:45 -0400
Subject: [PATCH 52/56] Verify server's Sec-WebSocket-Accept

---
 .../android_websockets/WebSocketClient.java   | 24 +++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/src/com/codebutler/android_websockets/WebSocketClient.java b/src/com/codebutler/android_websockets/WebSocketClient.java
index 0475d5e..84b46b2 100644
--- a/src/com/codebutler/android_websockets/WebSocketClient.java
+++ b/src/com/codebutler/android_websockets/WebSocketClient.java
@@ -22,6 +22,7 @@
 import java.net.Socket;
 import java.net.URI;
 import java.security.KeyManagementException;
+import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.util.List;
 
@@ -85,12 +86,13 @@ public void run() {
                     mSocket = factory.createSocket(mURI.getHost(), port);
 
                     PrintWriter out = new PrintWriter(mSocket.getOutputStream());
+                    String secretKey = createSecret();
                     out.print("GET " + path + " HTTP/1.1\r\n");
                     out.print("Upgrade: websocket\r\n");
                     out.print("Connection: Upgrade\r\n");
                     out.print("Host: " + mURI.getHost() + "\r\n");
                     out.print("Origin: " + origin.toString() + "\r\n");
-                    out.print("Sec-WebSocket-Key: " + createSecret() + "\r\n");
+                    out.print("Sec-WebSocket-Key: " + secretKey + "\r\n");
                     out.print("Sec-WebSocket-Version: 13\r\n");
                     if (mExtraHeaders != null) {
                         for (NameValuePair pair : mExtraHeaders) {
@@ -115,7 +117,12 @@ public void run() {
                     while (!TextUtils.isEmpty(line = readLine(stream))) {
                         Header header = parseHeader(line);
                         if (header.getName().equals("Sec-WebSocket-Accept")) {
-                            // FIXME: Verify the response...
+                            String expected = expectedKey(secretKey);
+                            if (expected == null) {
+                                throw new Exception("SHA-1 algorithm not found");
+                            } else if (!expected.equals(header.getValue())) {
+                                throw new Exception("Invalid Sec-WebSocket-Accept, expected: " + expected + ", got: " + header.getValue());
+                            }
                         }
                     }
 
@@ -208,6 +215,19 @@ private String readLine(HybiParser.HappyDataInputStream reader) throws IOExcepti
         return string.toString();
     }
 
+    private String expectedKey(String secret) {
+        //concatenate, SHA1-hash, base64-encode
+        try {
+            final String GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+            final String secretGUID = secret + GUID;
+            MessageDigest md = MessageDigest.getInstance("SHA-1");
+            byte[] digest = md.digest(secretGUID.getBytes());
+            return Base64.encodeToString(digest, Base64.DEFAULT).trim();
+        } catch (NoSuchAlgorithmException e) {
+            return null;
+        }
+    }
+
     private String createSecret() {
         byte[] nonce = new byte[16];
         for (int i = 0; i < 16; i++) {

From 3f5788ae77b2fa7940ccd204b3b17702f8b1fef1 Mon Sep 17 00:00:00 2001
From: Rahul Parsani <rahul.parsani@bubblemotion.com>
Date: Thu, 30 Jan 2014 22:47:37 +0800
Subject: [PATCH 53/56] Better handle queries when building the session url

---
 .../async/http/socketio/SocketIOConnection.java             | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
index 669a288..f8efcdd 100644
--- a/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
+++ b/src/com/koushikdutta/async/http/socketio/SocketIOConnection.java
@@ -10,6 +10,7 @@
 import org.json.JSONArray;
 import org.json.JSONObject;
 
+import android.net.Uri;
 import android.os.Handler;
 import android.text.TextUtils;
 
@@ -114,8 +115,9 @@ public void onCompleted(final Exception e, String result) {
                     if (!set.contains("websocket"))
                         throw new Exception("websocket not supported");
 
-                    final String sessionUrl = request.getUri().toString()
-                            + "websocket/" + session + "/";
+                    final String sessionUrl = Uri.parse(request.getUri()).buildUpon()
+                    		.appendPath("websocket").appendPath(session)
+                    		.build().toString();
                     
                     SocketIOConnection.this.webSocketClient =  new WebSocketClient(URI.create(sessionUrl), new Listener() {
                         

From 75e052b360834d8e24c36403a642e401573720a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sergio=20Campama=CC=81?= <sergiocampama@gmail.com>
Date: Fri, 11 Apr 2014 16:40:38 -0300
Subject: [PATCH 54/56] Added support for headers in SocketIORequest

Added a new constructor that accepts a `List<BasicNameValuePair>` containing
extra headers to add to the `HttpPost` request.
---
 .../koushikdutta/http/AsyncHttpClient.java    | 28 +++++++++++++++++--
 1 file changed, 26 insertions(+), 2 deletions(-)

diff --git a/src/com/koushikdutta/http/AsyncHttpClient.java b/src/com/koushikdutta/http/AsyncHttpClient.java
index b8a15d0..c1855a5 100644
--- a/src/com/koushikdutta/http/AsyncHttpClient.java
+++ b/src/com/koushikdutta/http/AsyncHttpClient.java
@@ -4,9 +4,15 @@
 import java.io.DataInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 
+import org.apache.http.HttpRequest;
 import org.apache.http.HttpResponse;
 import org.apache.http.client.methods.HttpPost;
+import org.apache.http.message.BasicNameValuePair;
 
 import android.net.Uri;
 import android.net.http.AndroidHttpClient;
@@ -28,26 +34,33 @@ public static class SocketIORequest {
 
         private String mUri;
         private String mEndpoint;
+        private List<BasicNameValuePair> mHeaders;
 
         public SocketIORequest(String uri) {
             this(uri, null);
         }
 
         public SocketIORequest(String uri, String endpoint) {
+            this(uri, endpoint, null);
+        }
 
+        public SocketIORequest(String uri, String endpoint, List<BasicNameValuePair> headers) {
             mUri = Uri.parse(uri).buildUpon().encodedPath("/socket.io/1/").build().toString();
             mEndpoint = endpoint;
+            mHeaders = headers;
         }
 
         public String getUri() {
-
             return mUri;
         }
 
         public String getEndpoint() {
-
             return mEndpoint;
         }
+
+        public List<BasicNameValuePair> getHeaders() {
+            return mHeaders; 
+        }
     }
 
     public static interface StringCallback {
@@ -67,6 +80,7 @@ protected Void doInBackground(Void... params) {
 
                 AndroidHttpClient httpClient = AndroidHttpClient.newInstance("android-websockets-2.0");
                 HttpPost post = new HttpPost(socketIORequest.getUri());
+                addHeadersToRequest(post, socketIORequest.getHeaders());
 
                 try {
                     HttpResponse res = httpClient.execute(post);
@@ -87,6 +101,16 @@ protected Void doInBackground(Void... params) {
                 }
                 return null;
             }
+
+            private void addHeadersToRequest(HttpRequest request, List<BasicNameValuePair> headers) {
+                if (headers != null) {
+                    Iterator<BasicNameValuePair> it = headers.iterator();
+                    while (it.hasNext()) {
+                        BasicNameValuePair header = it.next();
+                        request.addHeader(header.getName(), header.getValue());
+                    }
+                }
+            }
         }.execute();
     }
 

From 0f1a2fae11a02ce710d02345f08346cae07663fa Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koush@koushikdutta.com>
Date: Fri, 11 Apr 2014 13:28:03 -0700
Subject: [PATCH 55/56] Update README.md

---
 README.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/README.md b/README.md
index a50657a..38d042b 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+# THIS LIBRARY IS DEPRECATED IN FAVOR OF:
+
+https://github.com/koush/AndroidAsync
+
 # WebSocket and Socket.IO client for Android
 
 ## Credits

From d18376158a7839d701692b8a54abc2bf5d407968 Mon Sep 17 00:00:00 2001
From: Koushik Dutta <koush@koushikdutta.com>
Date: Fri, 11 Apr 2014 13:28:23 -0700
Subject: [PATCH 56/56] Update README.md

---
 README.md | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 38d042b..26aa1f7 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,13 @@
 # THIS LIBRARY IS DEPRECATED IN FAVOR OF:
 
-https://github.com/koush/AndroidAsync
+[AndroidAsync](https://github.com/koush/AndroidAsync)
+
+
+
+
+
+
+
 
 # WebSocket and Socket.IO client for Android