summaryrefslogtreecommitdiff
path: root/plugins/cordova-plugin-websocket/src/android/com
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/cordova-plugin-websocket/src/android/com')
-rw-r--r--plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ConnectionTask.java150
-rw-r--r--plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DestroyTask.java66
-rw-r--r--plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DisconnectionTask.java71
-rw-r--r--plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ResetTask.java55
-rw-r--r--plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/SendingTask.java69
-rw-r--r--plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskBean.java33
-rw-r--r--plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskRunner.java75
-rw-r--r--plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocket.java90
-rw-r--r--plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocketGenerator.java128
9 files changed, 737 insertions, 0 deletions
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ConnectionTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ConnectionTask.java
new file mode 100644
index 00000000..f2a3b138
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ConnectionTask.java
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.knowledgecode.cordova.websocket;
+
+import java.net.URI;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.PluginResult;
+import org.apache.cordova.PluginResult.Status;
+import org.eclipse.jetty.websocket.PerMessageDeflateExtension;
+import org.eclipse.jetty.websocket.WebSocket.Connection;
+import org.eclipse.jetty.websocket.WebSocketClient;
+import org.eclipse.jetty.websocket.WebSocketClientFactory;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import com.knowledgecode.cordova.websocket.TaskRunner.Task;
+import com.knowledgecode.cordova.websocket.WebSocketGenerator.OnCloseListener;
+import com.knowledgecode.cordova.websocket.WebSocketGenerator.OnOpenListener;
+
+import android.util.SparseArray;
+import android.webkit.CookieManager;
+
+/**
+ * Connect to server.
+ */
+class ConnectionTask implements Task {
+
+ private static final long MAX_CONNECT_TIME = 75000;
+ private static final int MAX_TEXT_MESSAGE_SIZE = -1;
+ private static final int MAX_BINARY_MESSAGE_SIZE = -1;
+
+ private final WebSocketClientFactory _factory;
+ private final SparseArray<Connection> _map;
+
+ /**
+ * Constructor
+ *
+ * @param factory
+ * @param map
+ */
+ public ConnectionTask(WebSocketClientFactory factory, SparseArray<Connection> map) {
+ _factory = factory;
+ _map = map;
+
+ if (!_factory.isRunning()) {
+ try {
+ _factory.start();
+ } catch (Exception e) {
+ }
+ }
+ }
+
+ /**
+ * Set cookies, if any.
+ *
+ * @param cookies
+ * @param url
+ */
+ private static void setCookie(Map<String, String> cookies, String url) {
+ String cookie = CookieManager.getInstance().getCookie(url);
+
+ if (cookie != null) {
+ for (String c : cookie.split(";")) {
+ String[] pair = c.split("=");
+
+ if (pair.length == 2) {
+ cookies.put(pair[0], pair[1]);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void execute(String rawArgs, CallbackContext ctx) {
+ try {
+ WebSocketClient client = _factory.newWebSocketClient();
+
+ JSONArray args = new JSONArray(rawArgs);
+ int id = Integer.parseInt(args.getString(0), 16);
+ URI uri = new URI(args.getString(1));
+ String protocol = args.getString(2);
+ JSONObject options = args.getJSONObject(5);
+ String origin = options.optString("origin", args.getString(3));
+ String agent = options.optString("agent", args.getString(4));
+ boolean deflate = options.optBoolean("perMessageDeflate", true);
+ long maxConnectTime = options.optLong("maxConnectTime", MAX_CONNECT_TIME);
+
+ client.setMaxTextMessageSize(options.optInt("maxTextMessageSize", MAX_TEXT_MESSAGE_SIZE));
+ client.setMaxBinaryMessageSize(options.optInt("maxBinaryMessageSize", MAX_BINARY_MESSAGE_SIZE));
+ if (protocol.length() > 0) {
+ client.setProtocol(protocol);
+ }
+ if (origin.length() > 0) {
+ client.setOrigin(origin);
+ }
+ if (agent.length() > 0) {
+ client.setAgent(agent);
+ }
+ if (deflate) {
+ client.getExtensions().add(new PerMessageDeflateExtension());
+ }
+
+ setCookie(client.getCookies(), uri.getHost());
+
+ WebSocketGenerator gen = new WebSocketGenerator(id, ctx);
+
+ gen.setOnOpenListener(new OnOpenListener() {
+ @Override
+ public void onOpen(int id, Connection conn) {
+ _map.put(id, conn);
+ }
+ });
+ gen.setOnCloseListener(new OnCloseListener() {
+ @Override
+ public void onClose(int id) {
+ if (_map.indexOfKey(id) >= 0) {
+ _map.remove(id);
+ }
+ }
+ });
+ client.open(uri, gen, maxConnectTime, TimeUnit.MILLISECONDS);
+ } catch (Exception e) {
+ if (!ctx.isFinished()) {
+ PluginResult result = new PluginResult(Status.ERROR);
+ result.setKeepCallback(true);
+ ctx.sendPluginResult(result);
+ }
+ }
+ }
+}
+
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DestroyTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DestroyTask.java
new file mode 100644
index 00000000..dcf13445
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DestroyTask.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.knowledgecode.cordova.websocket;
+
+import org.apache.cordova.CallbackContext;
+import org.eclipse.jetty.websocket.WebSocket.Connection;
+import org.eclipse.jetty.websocket.WebSocketClientFactory;
+
+import com.knowledgecode.cordova.websocket.TaskRunner.Task;
+
+import android.util.SparseArray;
+
+/**
+ * Stop WebSocket client.
+ */
+class DestroyTask implements Task {
+
+ private final WebSocketClientFactory _factory;
+ private final SparseArray<Connection> _map;
+
+ /**
+ * Constructor
+ *
+ * @param factory
+ * @param map
+ */
+ public DestroyTask(WebSocketClientFactory factory, SparseArray<Connection> map) {
+ _factory = factory;
+ _map = map;
+ }
+
+ @Override
+ public void execute(String rawArgs, CallbackContext ctx) {
+ for (int i = 0; i < _map.size(); i++) {
+ int key = _map.keyAt(i);
+
+ if (_map.get(key).isOpen()) {
+ _map.get(key).close();
+ }
+ }
+ _map.clear();
+
+ if (_factory.isRunning()) {
+ try {
+ _factory.stop();
+ } catch (Exception e) {
+ }
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DisconnectionTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DisconnectionTask.java
new file mode 100644
index 00000000..18502488
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/DisconnectionTask.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.knowledgecode.cordova.websocket;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.PluginResult;
+import org.apache.cordova.PluginResult.Status;
+import org.eclipse.jetty.websocket.WebSocket.Connection;
+import org.json.JSONArray;
+
+import com.knowledgecode.cordova.websocket.TaskRunner.Task;
+
+import android.util.SparseArray;
+
+/**
+ * Close a connection.
+ */
+class DisconnectionTask implements Task {
+
+ private final SparseArray<Connection> _map;
+
+ /**
+ * Constructor
+ *
+ * @param map
+ */
+ public DisconnectionTask(SparseArray<Connection> map) {
+ _map = map;
+ }
+
+ @Override
+ public void execute(String rawArgs, CallbackContext ctx) {
+ try {
+ JSONArray args = new JSONArray(rawArgs);
+ int id = Integer.parseInt(args.getString(0), 16);
+ int code = args.getInt(1);
+ String reason = args.getString(2);
+ Connection conn = _map.get(id);
+
+ if (conn != null) {
+ if (code > 0) {
+ conn.close(code, reason);
+ } else {
+ conn.close();
+ }
+ }
+ } catch (Exception e) {
+ if (!ctx.isFinished()) {
+ PluginResult result = new PluginResult(Status.ERROR);
+ result.setKeepCallback(true);
+ ctx.sendPluginResult(result);
+ }
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ResetTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ResetTask.java
new file mode 100644
index 00000000..16d4267b
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/ResetTask.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.knowledgecode.cordova.websocket;
+
+import org.apache.cordova.CallbackContext;
+import org.eclipse.jetty.websocket.WebSocket.Connection;
+
+import com.knowledgecode.cordova.websocket.TaskRunner.Task;
+
+import android.util.SparseArray;
+
+/**
+ * Close all connections.
+ */
+class ResetTask implements Task {
+
+ private final SparseArray<Connection> _map;
+
+ /**
+ * Constructor
+ *
+ * @param map
+ */
+ public ResetTask(SparseArray<Connection> map) {
+ _map = map;
+ }
+
+ @Override
+ public void execute(String rawArgs, CallbackContext ctx) {
+ for (int i = 0; i < _map.size(); i++) {
+ int key = _map.keyAt(i);
+
+ if (_map.get(key).isOpen()) {
+ _map.get(key).close();
+ }
+ }
+ _map.clear();
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/SendingTask.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/SendingTask.java
new file mode 100644
index 00000000..ae73da2b
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/SendingTask.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.knowledgecode.cordova.websocket;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.PluginResult;
+import org.apache.cordova.PluginResult.Status;
+import org.eclipse.jetty.websocket.WebSocket.Connection;
+
+import com.knowledgecode.cordova.websocket.TaskRunner.Task;
+
+import android.util.Base64;
+import android.util.SparseArray;
+
+/**
+ * Send text/binary data.
+ */
+class SendingTask implements Task {
+
+ private final SparseArray<Connection> _map;
+
+ /**
+ * Constructor
+ *
+ * @param map
+ */
+ public SendingTask(SparseArray<Connection> map) {
+ _map = map;
+ }
+
+ @Override
+ public void execute(String rawArgs, CallbackContext ctx) {
+ try {
+ Connection conn = _map.get(Integer.parseInt(rawArgs.substring(2, 10), 16));
+
+ if (conn != null) {
+ if (rawArgs.charAt(10) == '1') {
+ byte[] binary = Base64.decode(rawArgs.substring(rawArgs.indexOf(',') + 1, rawArgs.length() - 2),
+ Base64.NO_WRAP);
+ conn.sendMessage(binary, 0, binary.length);
+ } else {
+ conn.sendMessage(rawArgs.substring(11, rawArgs.length() - 2));
+ }
+ }
+ } catch (Exception e) {
+ if (!ctx.isFinished()) {
+ PluginResult result = new PluginResult(Status.ERROR);
+ result.setKeepCallback(true);
+ ctx.sendPluginResult(result);
+ }
+ }
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskBean.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskBean.java
new file mode 100644
index 00000000..444f3824
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskBean.java
@@ -0,0 +1,33 @@
+package com.knowledgecode.cordova.websocket;
+
+import org.apache.cordova.CallbackContext;
+
+class TaskBean {
+ private final String _action;
+ private final String _rawArgs;
+ private final CallbackContext _ctx;
+
+ public TaskBean(final String action) {
+ _action = action;
+ _rawArgs = "[]";
+ _ctx = null;
+ }
+
+ public TaskBean(final String action, final String rawArgs, final CallbackContext ctx) {
+ _action = action;
+ _rawArgs = rawArgs;
+ _ctx = ctx;
+ }
+
+ public String getAction() {
+ return _action;
+ }
+
+ public String getRawArgs() {
+ return _rawArgs;
+ }
+
+ public CallbackContext getCtx() {
+ return _ctx;
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskRunner.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskRunner.java
new file mode 100644
index 00000000..9fe2ad59
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/TaskRunner.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.knowledgecode.cordova.websocket;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import org.apache.cordova.CallbackContext;
+
+class TaskRunner implements Runnable {
+
+ interface Task {
+ public void execute(String rawArgs, CallbackContext ctx);
+ }
+
+ private BlockingQueue<TaskBean> _queue;
+ private Map<String, Task> _map;
+
+ public TaskRunner() {
+ _queue = new LinkedBlockingQueue<TaskBean>();
+ _map = new HashMap<String, Task>();
+ }
+
+ public void setTask(String action, Task task) {
+ _map.put(action, task);
+ }
+
+ public boolean addTaskQueue(TaskBean bean) {
+ try {
+ _queue.put(bean);
+ } catch (InterruptedException e) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ TaskBean task;
+
+ try {
+ task = _queue.take();
+ } catch (InterruptedException e) {
+ break;
+ }
+ String action = task.getAction();
+
+ _map.get(action).execute(task.getRawArgs(), task.getCtx());
+ if (WebSocket.DESTROY_TASK.equals(action)) {
+ break;
+ }
+ }
+ _queue.clear();
+ _map.clear();
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocket.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocket.java
new file mode 100644
index 00000000..479de7ee
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocket.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.knowledgecode.cordova.websocket;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.cordova.CordovaWebView;
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.CordovaInterface;
+import org.apache.cordova.CordovaPlugin;
+import org.eclipse.jetty.websocket.WebSocket.Connection;
+import org.eclipse.jetty.websocket.WebSocketClientFactory;
+
+import android.util.SparseArray;
+
+/**
+ * Cordova WebSocket Plugin for Android
+ * This plugin is using Jetty under the terms of the Apache License v2.0.
+ *
+ * @author KNOWLEDGECODE <knowledgecode@gmail.com>
+ * @version 0.11.0
+ */
+public class WebSocket extends CordovaPlugin {
+
+ static final String CREATE_TASK = "create";
+ static final String SEND_TASK = "send";
+ static final String CLOSE_TASK = "close";
+ static final String RESET_TASK = "reset";
+ static final String DESTROY_TASK = "destroy";
+
+ private WebSocketClientFactory _factory;
+ private SparseArray<Connection> _conn;
+ private ExecutorService _executor;
+ private TaskRunner _runner;
+
+ @Override
+ public void initialize(CordovaInterface cordova, final CordovaWebView webView) {
+ super.initialize(cordova, webView);
+ _factory = new WebSocketClientFactory();
+ _conn = new SparseArray<Connection>();
+ _executor = Executors.newSingleThreadExecutor();
+ _runner = new TaskRunner();
+ _runner.setTask(CREATE_TASK, new ConnectionTask(_factory, _conn));
+ _runner.setTask(SEND_TASK, new SendingTask(_conn));
+ _runner.setTask(CLOSE_TASK, new DisconnectionTask(_conn));
+ _runner.setTask(RESET_TASK, new ResetTask(_conn));
+ _runner.setTask(DESTROY_TASK, new DestroyTask(_factory, _conn));
+ _executor.execute(_runner);
+ }
+
+ @Override
+ public boolean execute(String action, String rawArgs, CallbackContext ctx) {
+ return _runner.addTaskQueue(new TaskBean(action, rawArgs, ctx));
+ };
+
+ @Override
+ public void onReset() {
+ _runner.addTaskQueue(new TaskBean(RESET_TASK));
+ super.onReset();
+ }
+
+ @Override
+ public void onDestroy() {
+ _runner.addTaskQueue(new TaskBean(DESTROY_TASK));
+ _executor.shutdown();
+ try {
+ _executor.awaitTermination(Long.MAX_VALUE, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ }
+ super.onDestroy();
+ }
+}
diff --git a/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocketGenerator.java b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocketGenerator.java
new file mode 100644
index 00000000..3132240a
--- /dev/null
+++ b/plugins/cordova-plugin-websocket/src/android/com/knowledgecode/cordova/websocket/WebSocketGenerator.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package com.knowledgecode.cordova.websocket;
+
+import org.apache.cordova.CallbackContext;
+import org.apache.cordova.PluginResult;
+import org.apache.cordova.PluginResult.Status;
+
+import android.util.Base64;
+
+class WebSocketGenerator implements
+ org.eclipse.jetty.websocket.WebSocket.OnTextMessage,
+ org.eclipse.jetty.websocket.WebSocket.OnBinaryMessage {
+
+ interface OnOpenListener {
+ public void onOpen(int id, Connection conn);
+ }
+
+ interface OnCloseListener {
+ public void onClose(int id);
+ }
+
+ private final int _id;
+ private final CallbackContext _ctx;
+ private OnOpenListener _openListener;
+ private OnCloseListener _closeListener;
+
+ /**
+ * Constructor
+ *
+ * @param id
+ * @param ctx
+ */
+ public WebSocketGenerator(int id, CallbackContext ctx) {
+ _id = id;
+ _ctx = ctx;
+ _openListener = new OnOpenListener() {
+ @Override
+ public void onOpen(int id, Connection conn) {
+ // NOP
+ }
+ };
+ _closeListener = new OnCloseListener() {
+ @Override
+ public void onClose(int id) {
+ // NOP
+ }
+ };
+ }
+
+ /**
+ * Set OnOpen listener.
+ *
+ * @param l
+ */
+ public void setOnOpenListener(OnOpenListener l) {
+ _openListener = l;
+ }
+
+ /**
+ * Set OnClose listener.
+ *
+ * @param l
+ */
+ public void setOnCloseListener(OnCloseListener l) {
+ _closeListener = l;
+ }
+
+ @Override
+ public void onOpen(Connection conn) {
+ _openListener.onOpen(_id, conn);
+
+ String protocol = conn.getProtocol();
+ String extensions = conn.getExtensions();
+ protocol = protocol == null ? "" : protocol;
+ extensions = extensions == null ? "" : extensions;
+ sendCallback(String.format("O[\"%s\",\"%s\"]", protocol, extensions), true);
+ }
+
+ @Override
+ public void onMessage(String data) {
+ sendCallback("T" + data, true);
+ }
+
+ @Override
+ public void onMessage(byte[] data, int offset, int length) {
+ sendCallback("B" + Base64.encodeToString(data, offset, length, Base64.NO_WRAP), true);
+ }
+
+ @Override
+ public void onClose(int code, String reason) {
+ _closeListener.onClose(_id);
+
+ String wasClean = code == 1000 ? "1" : "0";
+ reason = reason == null ? "" : reason;
+ sendCallback(String.format("C%s%4d%s", wasClean, code, reason), false);
+ }
+
+ /**
+ * Send plugin result.
+ *
+ * @param callbackString
+ * @param keepCallback
+ */
+ private void sendCallback(String callbackString, boolean keepCallback) {
+ if (!_ctx.isFinished()) {
+ PluginResult result = new PluginResult(Status.OK, callbackString);
+ result.setKeepCallback(keepCallback);
+ _ctx.sendPluginResult(result);
+ }
+ }
+}