package net.trentgardner.cordova.androidwear; import java.util.ArrayList; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import org.apache.cordova.CordovaArgs; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaWebView; import org.apache.cordova.PluginResult; import org.json.JSONException; import org.json.JSONObject; import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import net.trentgardner.cordova.androidwear.WearMessageApi; import net.trentgardner.cordova.androidwear.WearMessageListener; import net.trentgardner.cordova.androidwear.WearProviderService; public class AndroidWearPlugin extends CordovaPlugin { private final String TAG = AndroidWearPlugin.class.getSimpleName(); private final String ACTION_ONCONNECT = "onConnect"; private final String ACTION_ONDATARECEIVED = "onDataReceived"; private final String ACTION_ONERROR = "onError"; private final String ACTION_SENDDATA = "sendData"; private WearMessageApi api = null; private Intent serviceIntent = null; private Hashtable<String, WearConnection> connections = new Hashtable<String, WearConnection>(); private ServiceConnection serviceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { api = WearMessageApi.Stub.asInterface(service); try { api.addListener(messageListener); Log.i(TAG, "Listener registered with service"); } catch (RemoteException e) { Log.e(TAG, "Failed to add listener", e); } } public void onServiceDisconnected(ComponentName className) { api = null; Log.i(TAG, "Service connection closed!"); } }; private WearMessageListener.Stub messageListener = new WearMessageListener.Stub() { @Override public void onConnect(String connectionId) throws RemoteException { Log.d(TAG, "messageListener.onConnect"); createNewWearConnection(connectionId); } @Override public void onDataReceived(String nodeId, String data) throws RemoteException { Log.d(TAG, String.format("messageListener.onDataReceived - nodeId: %s", nodeId)); WearConnection connection = connections.get(nodeId); if (connection == null) connection = createNewWearConnection(nodeId); connection.onDataReceived(data); } @Override public void onError(String connectionId, String data) throws RemoteException { Log.d(TAG, "messageListener.onError"); WearConnection connection = connections.get(connectionId); if (connection != null) { connection.onError(data); connections.remove(connectionId); } } }; private WearConnection createNewWearConnection(String connectionId) { WearConnection connection = new WearConnection(connectionId); connections.put(connectionId, connection); notifyCallbacksOfConnection(connectionId); return connection; } private class WearConnection { private String mHandle; private List<CallbackContext> dataCallbacks = new ArrayList<CallbackContext>(); private List<CallbackContext> errorCallbacks = new ArrayList<CallbackContext>(); public WearConnection(String handle) { mHandle = handle; } public void addDataListener(CallbackContext callback) { dataCallbacks.add(callback); } public void addErrorListener(CallbackContext callback) { errorCallbacks.add(callback); } public void onDataReceived(String data) { notifyCallbacks(dataCallbacks, createJSONObject(mHandle, data)); } public void onError(String data) { notifyCallbacks(errorCallbacks, createJSONObject(mHandle, data)); } private void notifyCallbacks(final List<CallbackContext> callbacks, final JSONObject data) { cordova.getActivity().runOnUiThread(new Runnable() { public void run() { Log.d(TAG, String.format("Notifying %d callbacks", callbacks.size())); for (CallbackContext context : callbacks) { keepCallback(context, data); } } }); } } private List<CallbackContext> connectCallbacks = new ArrayList<CallbackContext>(); @Override public void initialize(CordovaInterface cordova, CordovaWebView webView) { super.initialize(cordova, webView); Log.d(TAG, "initialize"); Activity context = cordova.getActivity(); serviceIntent = new Intent(context, WearProviderService.class); Log.d(TAG, "Attempting to start service"); context.startService(serviceIntent); Log.d(TAG, "Attempting to bind to service"); context.bindService(serviceIntent, serviceConnection, Context.BIND_AUTO_CREATE); } @Override public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) throws JSONException { if (ACTION_ONCONNECT.equals(action)) onConnect(args, callbackContext); else if (ACTION_ONDATARECEIVED.equals(action)) onDataReceived(args, callbackContext); else if (ACTION_ONERROR.equals(action)) onError(args, callbackContext); else if (ACTION_SENDDATA.equals(action)) sendData(args, callbackContext); else return false; return true; } @Override public void onDestroy() { Log.d(TAG, "onDestroy"); try { Activity context = cordova.getActivity(); if (api != null) api.removeListener(messageListener); context.unbindService(serviceConnection); context.stopService(serviceIntent); } catch (Throwable t) { // catch any issues, typical for destroy routines // even if we failed to destroy something, we need to continue // destroying Log.w(TAG, "Failed to unbind from the service", t); } super.onDestroy(); } private void sendData(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { Log.d(TAG, "sendData"); String connectionId = args.getString(0); String data = args.getString(1); try { if (api != null) { api.sendData(connectionId, data); callbackContext.success(); } else { callbackContext.error("Service not present"); } } catch (RemoteException e) { callbackContext.error(e.getMessage()); } } private void onConnect(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { Log.d(TAG, "onConnect"); connectCallbacks.add(callbackContext); // Alert the client of any existing connections Enumeration<String> keys = connections.keys(); while(keys.hasMoreElements()) { connect(callbackContext, keys.nextElement()); } } private void onDataReceived(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { Log.d(TAG, "onDataReceived"); String connectionId = args.getString(0); WearConnection connection = connections.get(connectionId); if (connection != null) { connection.addDataListener(callbackContext); } else { callbackContext.error("Invalid connection handle"); } } private void onError(final CordovaArgs args, final CallbackContext callbackContext) throws JSONException { Log.d(TAG, "onError"); String connectionId = args.getString(0); WearConnection connection = connections.get(connectionId); if (connection != null) { connection.addErrorListener(callbackContext); } else { callbackContext.error("Invalid connection handle"); } } private void connect(CallbackContext callbackContext, String connectionId) { JSONObject o = createJSONObject(connectionId, null); keepCallback(callbackContext, o); } private void notifyCallbacksOfConnection(final String connectionId) { this.cordova.getActivity().runOnUiThread(new Runnable() { public void run() { for (CallbackContext context : connectCallbacks) { connect(context, connectionId); } } }); } private void keepCallback(final CallbackContext callbackContext, JSONObject message) { PluginResult r = new PluginResult(PluginResult.Status.OK, message); r.setKeepCallback(true); callbackContext.sendPluginResult(r); } private JSONObject createJSONObject(String handle, String data) { JSONObject o = new JSONObject(); try { o.put("handle", handle); if (data != null) o.put("data", data); } catch (JSONException e) { e.printStackTrace(); } return o; } }