Snap for 5044688 from 78c810085e2405e9fb3ec7960a5c8444e8d88801 to pi-qpr2-release

Change-Id: I19792936df773c192be86107b2f8ecbe72fd4ab3
diff --git a/Common/src/com/googlecode/android_scripting/Sl4aErrors.java b/Common/src/com/googlecode/android_scripting/Sl4aErrors.java
new file mode 100644
index 0000000..7effabe
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/Sl4aErrors.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.googlecode.android_scripting;
+
+import com.googlecode.android_scripting.jsonrpc.JsonRpcResult;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * A enum of Sl4aException objects. This enum holds all of the innate systematic
+ * errors that can be sent to the client. Errors that come from Facades should
+ * not use these Sl4aErrors, unless the Facades themselves are designed to
+ * modify the state of SL4A's connection
+ */
+public enum Sl4aErrors {
+    // Process-Level Errors 0-99
+
+    // JSON RPC Errors 100 - 199
+    JSON_RPC_UNKNOWN_EXCEPTION(100,
+            "Something went horribly wrong when parsing or returning your request."),
+    JSON_RPC_REQUEST_NOT_JSON(101, "The request sent was not a valid JSONObject."),
+    JSON_RPC_MISSING_ID(102, "The \"id\" field is missing."),
+    JSON_RPC_MISSING_METHOD(103, "The \"method\" field is missing."),
+    JSON_RPC_METHOD_NOT_STRING(104, "The \"method\" field must be a string."),
+    JSON_RPC_PARAMS_NOT_AN_ARRAY(105, "The \"params\" field must be an array."),
+    JSON_RPC_UNKNOWN_RPC_METHOD(106, "No known RPC for the given \"method\"."),
+    JSON_RPC_RESULT_NOT_JSON_VALID(107,
+            "The JsonBuilder was unable to convert the result to valid JSON."),
+    JSON_RPC_FAILED_TO_BUILD_RESULT(108, "The JsonBuilder failed to build the result."),
+    JSON_RPC_FACADE_EXCEPTION_OCCURRED(109, "An exception occurred while handling this RPC. "
+            + "Check the \"data\" field for the error received."),
+    JSON_RPC_INVALID_PARAMETERS(110, "The \"params\" given are not valid for this \"method\"."),
+
+    // Session Errors 200 - 299
+    SESSION_UNKNOWN_EXCEPTION(200, "Something went horribly wrong when handling your session."),
+    SESSION_INVALID(201, "This session no longer exists or is invalid."),
+    ;
+
+    private final Sl4aException mSl4aException;
+
+    Sl4aErrors(int errorCode, String errorMessage) {
+        mSl4aException = new Sl4aException(errorCode, errorMessage);
+    }
+
+    /**
+     * Returns the underlying {@see Sl4aException}.
+     */
+    public Sl4aException getError() {
+        return mSl4aException;
+    }
+
+    /**
+     * Converts this Sl4aError to a JSON-RPC 2.0 compliant JSONObject.
+     * @param id the id given by the request
+     * @return a JSON-RPC 2.0 error response as a JSONObject
+     * @throws JSONException
+     */
+    public JSONObject toJson(Object id) throws JSONException {
+        return JsonRpcResult.error(id, mSl4aException);
+    }
+
+    /**
+     * Converts this Sl4aError to a JSON-RPC 2.0 compliant JSONObject.
+     * @param id      the id given by the request
+     * @param details additional details to pass along with the error
+     * @return a JSON-RPC 2.0 error response as a JSONObject
+     * @throws JSONException
+     */
+    public JSONObject toJson(Object id, Object details) throws JSONException {
+        return JsonRpcResult.error(id, mSl4aException, details);
+    }
+}
diff --git a/Common/src/com/googlecode/android_scripting/Sl4aException.java b/Common/src/com/googlecode/android_scripting/Sl4aException.java
new file mode 100644
index 0000000..7f5ae0c
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/Sl4aException.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.googlecode.android_scripting;
+
+/**
+ * The base class for all errors to be sent over RPC.
+ *
+ * For user defined errors, convention dictates that error code values
+ * should start with 10000.
+ */
+public class Sl4aException extends Exception {
+
+    private int mErrorCode;
+    private String mErrorMessage;
+
+    Sl4aException(int errorCode, String errorMessage) {
+        mErrorCode = errorCode;
+        mErrorMessage = errorMessage;
+    }
+
+    /**
+     * @return the error code associated with this exception
+     */
+    public int getErrorCode() {
+        return mErrorCode;
+    }
+
+    /**
+     * @return the error message associated with this exception
+     */
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+}
diff --git a/Common/src/com/googlecode/android_scripting/facade/EventFacade.java b/Common/src/com/googlecode/android_scripting/facade/EventFacade.java
index 42c3f83..0e666f5 100644
--- a/Common/src/com/googlecode/android_scripting/facade/EventFacade.java
+++ b/Common/src/com/googlecode/android_scripting/facade/EventFacade.java
@@ -371,26 +371,45 @@
         return eventWaitFor(eventName, removeEvent, timeout);
     }
 
+    /**
+     * Closes this SL4A session, and sends a terminating signal to the event observers.
+     */
     @Rpc(description = "sl4a session is shutting down, send terminate event to client.")
     public void closeSl4aSession() {
         eventClearBuffer();
         postEvent("EventDispatcherShutdown", null);
     }
 
+    /**
+     * Shuts down the RPC server.
+     */
     @Override
     public void shutdown() {
         mGlobalEventObservers.clear();
         mEventQueue.clear();
     }
 
+    /**
+     * Adds a named observer to the event listening queue.
+     * @param eventName the name of the event to listen to
+     * @param observer  the observer object
+     */
     public void addNamedEventObserver(String eventName, EventObserver observer) {
         mNamedEventObservers.put(eventName, observer);
     }
 
+    /**
+     * Adds a global event listener ot the listening queue.
+     * @param observer the observer object
+     */
     public void addGlobalEventObserver(EventObserver observer) {
         mGlobalEventObservers.add(observer);
     }
 
+    /**
+     * Removes an observer from the event listening queue.
+     * @param observer the observer to remove
+     */
     public void removeEventObserver(EventObserver observer) {
         mNamedEventObservers.removeAll(observer);
         mGlobalEventObservers.remove(observer);
diff --git a/Common/src/com/googlecode/android_scripting/facade/FacadeManagerFactory.java b/Common/src/com/googlecode/android_scripting/facade/FacadeManagerFactory.java
index 8d287c9..02fbb32 100644
--- a/Common/src/com/googlecode/android_scripting/facade/FacadeManagerFactory.java
+++ b/Common/src/com/googlecode/android_scripting/facade/FacadeManagerFactory.java
@@ -23,36 +23,53 @@
 import com.googlecode.android_scripting.jsonrpc.RpcReceiverManager;
 import com.googlecode.android_scripting.jsonrpc.RpcReceiverManagerFactory;
 
-import java.util.HashMap;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Map;
 
 public class FacadeManagerFactory implements RpcReceiverManagerFactory {
 
-  private final int mSdkLevel;
-  private final Service mService;
-  private final Intent mIntent;
-  private final Collection<Class<? extends RpcReceiver>> mClassList;
-  private final Map<Integer, RpcReceiverManager> mFacadeManagers;
+    private final int mSdkLevel;
+    private final Service mService;
+    private final Intent mIntent;
+    private final Collection<Class<? extends RpcReceiver>> mClassList;
+    private final Map<String, RpcReceiverManager> mFacadeManagers;
 
-  public FacadeManagerFactory(int sdkLevel, Service service, Intent intent,
-      Collection<Class<? extends RpcReceiver>> classList) {
-    mSdkLevel = sdkLevel;
-    mService = service;
-    mIntent = intent;
-    mClassList = classList;
-    mFacadeManagers = new HashMap<Integer, RpcReceiverManager>();
-  }
+    public FacadeManagerFactory(int sdkLevel, Service service, Intent intent,
+                                Collection<Class<? extends RpcReceiver>> classList) {
+        mSdkLevel = sdkLevel;
+        mService = service;
+        mIntent = intent;
+        mClassList = classList;
+        mFacadeManagers = new HashMap<>();
+    }
 
-  @Override
-  public FacadeManager create(Integer UID) {
-    FacadeManager facadeManager = new FacadeManager(mSdkLevel, mService, mIntent, mClassList);
-    mFacadeManagers.put(UID, facadeManager);
-    return facadeManager;
-  }
+    @Override
+    public FacadeManager create(String sessionId) throws IllegalArgumentException {
+        FacadeManager facadeManager = new FacadeManager(mSdkLevel, mService, mIntent, mClassList);
+        // TODO(markdr): This lock isn't entirely safe, since another object injects this reference
+        // into the RpcReceiverManagerFactory, and can therefore make mutations elsewhere.
+        // Refactor this to prevent that from potentially occurring.
+        synchronized (mFacadeManagers) {
+            if (mFacadeManagers.containsKey(sessionId)) {
+                throw new IllegalArgumentException("SessionID " + sessionId + " already exists.");
+            }
+            mFacadeManagers.put(sessionId, facadeManager);
+        }
+        return facadeManager;
+    }
 
-  @Override
-  public Map<Integer, RpcReceiverManager> getRpcReceiverManagers() {
-    return mFacadeManagers;
-  }
+    @Override
+    public boolean destroy(String sessionId) {
+        RpcReceiverManager removed = mFacadeManagers.remove(sessionId);
+        if (removed != null) {
+            removed.shutdown();
+        }
+        return removed != null;
+    }
+
+    @Override
+    public Map<String, RpcReceiverManager> getRpcReceiverManagers() {
+        return mFacadeManagers;
+    }
 }
diff --git a/Common/src/com/googlecode/android_scripting/facade/webcam/MjpegServer.java b/Common/src/com/googlecode/android_scripting/facade/webcam/MjpegServer.java
index 3ee0b33..34d8d7b 100644
--- a/Common/src/com/googlecode/android_scripting/facade/webcam/MjpegServer.java
+++ b/Common/src/com/googlecode/android_scripting/facade/webcam/MjpegServer.java
@@ -16,54 +16,49 @@
 
 package com.googlecode.android_scripting.facade.webcam;
 
-import java.io.BufferedReader;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.net.Socket;
 import com.googlecode.android_scripting.Log;
 import com.googlecode.android_scripting.SimpleServer;
 
+import java.io.OutputStream;
+import java.net.Socket;
+
 class MjpegServer extends SimpleServer {
 
-  private final JpegProvider mProvider;
+    private final JpegProvider mProvider;
 
-  public MjpegServer(JpegProvider provider) {
-    mProvider = provider;
-  }
-
-  @Override
-  protected void handleConnection(Socket socket) throws Exception {
-    Log.d("handle Mjpeg connection");
-    byte[] data = mProvider.getJpeg();
-    if (data == null) {
-      return;
+    MjpegServer(JpegProvider provider) {
+        mProvider = provider;
     }
-    OutputStream outputStream = socket.getOutputStream();
-    outputStream.write((
-        "HTTP/1.0 200 OK\r\n" +
-        "Server: SL4A\r\n" +
-        "Connection: close\r\n" +
-        "Max-Age: 0\r\n" +
-        "Expires: 0\r\n" +
-        "Cache-Control: no-cache, private\r\n" +
-        "Pragma: no-cache\r\n" +
-        "Content-Type: multipart/x-mixed-replace; boundary=--BoundaryString\r\n\r\n").getBytes());
-    while (true) {
-      data = mProvider.getJpeg();
-      if (data == null) {
-        return;
-      }
-      outputStream.write("--BoundaryString\r\n".getBytes());
-      outputStream.write("Content-type: image/jpg\r\n".getBytes());
-      outputStream.write(("Content-Length: " + data.length + "\r\n\r\n").getBytes());
-      outputStream.write(data);
-      outputStream.write("\r\n\r\n".getBytes());
-      outputStream.flush();
-    }
-  }
 
-  @Override
-  protected void handleRPCConnection(Socket sock, Integer UID, BufferedReader reader, PrintWriter writer)
-      throws Exception {
-  }
+    @Override
+    protected void handleConnection(Socket socket) throws Exception {
+        Log.d("handle Mjpeg connection");
+        byte[] data = mProvider.getJpeg();
+        if (data == null) {
+            return;
+        }
+        OutputStream outputStream = socket.getOutputStream();
+        outputStream.write((
+                "HTTP/1.0 200 OK\r\n"
+                        + "Server: SL4A\r\n"
+                        + "Connection: close\r\n"
+                        + "Max-Age: 0\r\n"
+                        + "Expires: 0\r\n"
+                        + "Cache-Control: no-cache, private\r\n"
+                        + "Pragma: no-cache\r\n"
+                        + "Content-Type: multipart/x-mixed-replace; "
+                        + "boundary=--BoundaryString\r\n\r\n").getBytes());
+        while (true) {
+            data = mProvider.getJpeg();
+            if (data == null) {
+                return;
+            }
+            outputStream.write("--BoundaryString\r\n".getBytes());
+            outputStream.write("Content-type: image/jpg\r\n".getBytes());
+            outputStream.write(("Content-Length: " + data.length + "\r\n\r\n").getBytes());
+            outputStream.write(data);
+            outputStream.write("\r\n\r\n".getBytes());
+            outputStream.flush();
+        }
+    }
 }
diff --git a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcHandler.java b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcHandler.java
new file mode 100644
index 0000000..8f8d887
--- /dev/null
+++ b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcHandler.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.googlecode.android_scripting.jsonrpc;
+
+import com.googlecode.android_scripting.Log;
+import com.googlecode.android_scripting.Sl4aErrors;
+import com.googlecode.android_scripting.rpc.MethodDescriptor;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * A class that parses given JSON RPC Messages, and handles their execution.
+ */
+public class JsonRpcHandler {
+    private int mNextSessionId = 1;
+    private String mSessionId = "";
+    private final RpcReceiverManagerFactory mManagerFactory;
+
+    public JsonRpcHandler(RpcReceiverManagerFactory managerFactory) {
+        mManagerFactory = managerFactory;
+    }
+
+    /**
+     * Returns the response object for the given request.
+     * <p>
+     * The response will be returned as a JSONObject, unless something fatal has occurred. In that
+     * case, the resulting object will be a JSON formatted string.
+     *
+     * @param request the received request as a string
+     * @return the response to the request
+     */
+    public Object getResponse(String request) {
+        try {
+            return getResponse(new JSONObject(request));
+        } catch (JSONException ignored) {
+            // May have been a bad request.
+        }
+        try {
+            return Sl4aErrors.JSON_RPC_REQUEST_NOT_JSON.toJson(JSONObject.NULL);
+        } catch (JSONException e) {
+            // This error will never occur.
+            Log.e("Received JSONException on invalid request.", e);
+            return JsonRpcResult.wtf();
+        }
+    }
+
+    /**
+     * Returns the response for the given request.
+     * <p>
+     * This message will be returned as a JSONObject, unless something fatal has occurred. In that
+     * case, the resulting object will be a JSON formatted string.
+     *
+     * @param request the JSON-RPC request message as a JSONObject
+     * @return the response to the request
+     */
+    public Object getResponse(JSONObject request) {
+        if (isSessionManagementRpc(request)) {
+            return handleSessionRpc(request);
+        }
+
+        Object validationErrors = getValidationErrors(request);
+        if (validationErrors != null) {
+            return validationErrors;
+        }
+
+        try {
+            Object id = request.get("id");
+            String method = request.getString("method");
+
+            JSONArray params = new JSONArray();
+            if (request.has("params")) {
+                params = request.getJSONArray("params");
+            }
+
+            RpcReceiverManager receiverManager =
+                    mManagerFactory.getRpcReceiverManagers().get(mSessionId);
+            if (receiverManager == null) {
+                Log.e("Unable to find sessionId \"" + mSessionId + "\".");
+                for (String key : mManagerFactory.getRpcReceiverManagers().keySet()) {
+                    Log.d("Available key: " + key);
+                }
+                return Sl4aErrors.SESSION_INVALID.toJson(id, mSessionId);
+            }
+            MethodDescriptor rpc = receiverManager.getMethodDescriptor(method);
+            if (rpc == null) {
+                return Sl4aErrors.JSON_RPC_UNKNOWN_RPC_METHOD.toJson(id, method);
+            }
+            Object rpcResult;
+            try {
+                rpcResult = rpc.invoke(receiverManager, params);
+            } catch (Throwable t) {
+                Log.e("RPC call threw an error.", t);
+                return Sl4aErrors.JSON_RPC_FACADE_EXCEPTION_OCCURRED.toJson(id, t);
+            }
+            try {
+                return JsonRpcResult.result(id, rpcResult);
+            } catch (JSONException e) {
+                // This error is rare, but may occur when the JsonBuilder fails to properly
+                // convert the resulting class from the RPC into valid JSON. This may happen
+                // when a resulting number is returned as NaN or infinite, but is not converted
+                // to a String first.
+                Log.e("Unable to build object of class \"" + rpcResult.getClass() + "\".", e);
+                return Sl4aErrors.JSON_RPC_RESULT_NOT_JSON_VALID.toJson(id, e);
+            } catch (Throwable t) {
+                // This error will occur whenever the underlying Android APIs built against are
+                // unavailable for the version of Android SL4A is installed onto.
+                Log.e("Unable to build object of class \"" + rpcResult.getClass() + "\".", t);
+                return Sl4aErrors.JSON_RPC_FAILED_TO_BUILD_RESULT.toJson(id, t);
+            }
+        } catch (JSONException exception) {
+            // This error should never happen. NULL and all values found within Sl4aErrors are
+            // guaranteed not to raise an error when being converted to JSON.
+            // Also, all JSONObject.get() calls are only done after they have been validated to
+            // exist after a corresponding JSONObject.has() call has been made.
+            // Returning a raw string here to prevent the need to catch JsonRpcResult.error().
+            Log.e(exception);
+            return JsonRpcResult.wtf();
+        }
+    }
+
+    /**
+     * Returns a validation error if the request is invalid. Null otherwise.
+     * @param message the request message
+     * @return a JSONObject error response, or null if the message is valid
+     */
+    private Object getValidationErrors(JSONObject message) {
+        Object id = JSONObject.NULL;
+        try {
+            if (!message.has("id")) {
+                return Sl4aErrors.JSON_RPC_MISSING_ID.toJson(id);
+            } else {
+                id = message.get("id");
+            }
+            if (!message.has("method")) {
+                return Sl4aErrors.JSON_RPC_MISSING_METHOD.toJson(id);
+            } else if (!(message.get("method") instanceof String)) {
+                return Sl4aErrors.JSON_RPC_METHOD_NOT_STRING.toJson(id);
+            }
+            if (message.has("params") && !(message.get("params") instanceof JSONArray)) {
+                return Sl4aErrors.JSON_RPC_PARAMS_NOT_AN_ARRAY.toJson(id);
+            }
+        } catch (JSONException exception) {
+            // This error should never happen. NULL and all values found within Sl4aException are
+            // guaranteed not to raise an error when being converted to JSON.
+            // Also, all JSONObject.get() calls are only done after they have been validated to
+            // exist after a corresponding JSONObject.has() call has been made.
+            // Returning a raw string here to prevent the need to wrap JsonRpcResult.error().
+            Log.e(exception);
+            return JsonRpcResult.wtf();
+        }
+        return null;
+    }
+
+    //TODO(markdr): Refactor this function into a class that handles session management.
+    private boolean isSessionManagementRpc(JSONObject request) {
+        return request.has("cmd") && request.has("uid");
+    }
+
+    //TODO(markdr): Refactor this function into a class that handles session management.
+    private Object handleSessionRpc(JSONObject request) {
+        try {
+            Object id = request.get("uid");
+            String cmd = request.getString("cmd");
+            switch (cmd) {
+                case "initiate": {
+                    String newId;
+                    synchronized (mManagerFactory) {
+                        newId = "" + mNextSessionId;
+                        while (mManagerFactory.getRpcReceiverManagers().containsKey(newId)) {
+                            newId = "" + mNextSessionId++;
+                        }
+                        mSessionId = newId;
+                        mManagerFactory.create(newId);
+                    }
+                    return "{\"uid\": \"" + newId + "\", \"status\":true}";
+                }
+                case "continue": {
+                    for (String key : mManagerFactory.getRpcReceiverManagers().keySet()) {
+                        Log.d("Available key: " + key);
+                    }
+                    String stringId = id.toString();
+                    boolean canContinue =
+                            mManagerFactory.getRpcReceiverManagers().containsKey(stringId);
+                    if (canContinue) {
+                        mSessionId = stringId;
+                        return "{\"uid\":\"" + id + "\",\"status\":true}";
+                    }
+                    return "{\"uid\":\"" + id + "\",\"status\":false,"
+                            + "\"error\":\"Session does not exist.\"}";
+                }
+                default: {
+                    // This case should never be reached if isSessionManagementRpc is called first.
+                    Log.e("Hit default case for handling session rpcs.");
+                    return JsonRpcResult.wtf();
+                }
+            }
+        } catch (JSONException jsonException) {
+            // This error will never occur, because the above IDs and objects will always be JSON
+            // serializable.
+            Log.e("Exception during handleRpcCall: " + jsonException);
+            return JsonRpcResult.wtf();
+        }
+    }
+}
diff --git a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcResult.java b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcResult.java
index 0eda23a..7e12ffb 100644
--- a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcResult.java
+++ b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcResult.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2018 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,6 +16,9 @@
 
 package com.googlecode.android_scripting.jsonrpc;
 
+import com.googlecode.android_scripting.Sl4aErrors;
+import com.googlecode.android_scripting.Sl4aException;
+
 import org.json.JSONException;
 import org.json.JSONObject;
 
@@ -23,35 +26,111 @@
  * Represents a JSON RPC result.
  *
  * @see http://json-rpc.org/wiki/specification
- *
  */
 public class JsonRpcResult {
 
-  private JsonRpcResult() {
-    // Utility class.
-  }
+    private JsonRpcResult() {
+        // Utility class.
+    }
 
-  public static JSONObject empty(int id) throws JSONException {
-    JSONObject json = new JSONObject();
-    json.put("id", id);
-    json.put("result", JSONObject.NULL);
-    json.put("error", JSONObject.NULL);
-    return json;
-  }
+    /**
+     * Returns a JSON-RPC 1.0 formatted request response.
+     *
+     * @param id   the id given by the request
+     * @param data the data to pass into the result field
+     * @return a JSON-RPC 1.0 formatted request response
+     * @throws JSONException if JsonBuilder.build() cannot correctly convert the object into JSON
+     */
+    public static JSONObject result(Object id, Object data) throws JSONException {
+        JSONObject response = new JSONObject();
+        response.put("id", id);
+        response.put("result", JsonBuilder.build(data));
+        response.put("error", JSONObject.NULL);
+        return response;
+    }
 
-  public static JSONObject result(int id, Object data) throws JSONException {
-    JSONObject json = new JSONObject();
-    json.put("id", id);
-    json.put("result", JsonBuilder.build(data));
-    json.put("error", JSONObject.NULL);
-    return json;
-  }
+    /**
+     * Returns a JSON-RPC 2.0 formatted error response.
+     *
+     * @param id      the id given by the request
+     * @param message the error message to send
+     * @return a JSON-RPC 2.0 formatted request response
+     * @throws JSONException if the world is ending
+     */
+    public static JSONObject error(Object id, Object message) throws JSONException {
+        JSONObject response = new JSONObject();
+        if (id == null) {
+            id = JSONObject.NULL;
+        }
+        response.put("id", id);
+        response.put("result", JSONObject.NULL);
+        response.put("error", message.toString());
+        return response;
+    }
 
-  public static JSONObject error(int id, Throwable t) throws JSONException {
-    JSONObject json = new JSONObject();
-    json.put("id", id);
-    json.put("result", JSONObject.NULL);
-    json.put("error", t.toString());
-    return json;
-  }
+    /**
+     * Returns a JSON-RPC 2.0 formatted error response.
+     *
+     * @param id            the id given by the request
+     * @param sl4aException the Sl4aException to send over JSON-RPC
+     * @return a JSON-RPC 2.0 formatted error response
+     * @throws JSONException if the world is ending
+     */
+    public static JSONObject error(Object id, Sl4aException sl4aException) throws JSONException {
+        JSONObject response = new JSONObject();
+        if (id == null) {
+            id = JSONObject.NULL;
+        }
+        response.put("id", id);
+        response.put("result", JSONObject.NULL);
+        JSONObject error = new JSONObject();
+        error.put("code", sl4aException.getErrorCode());
+        error.put("message", sl4aException.getErrorMessage());
+        response.put("error", error);
+        return response;
+    }
+
+    /**
+     * Returns a JSON-RPC 2.0 formatted error response.
+     *
+     * @param id        the id given by the request
+     * @param sl4aError the Sl4aErrors object to send over JSON-RPC
+     * @param details   additional data associated with the error
+     * @return a JSON-RPC 1.0 formatted error response
+     * @throws JSONException if details cannot be converted to JSON
+     */
+    public static JSONObject error(Object id, Sl4aErrors sl4aError, Object details)
+            throws JSONException {
+        return error(id, sl4aError.getError(), details);
+    }
+
+
+    /**
+     * Returns a JSON-RPC 2.0 formatted error response.
+     *
+     * @param id            the id given by the request
+     * @param sl4aException the Sl4aException to send over JSON-RPC
+     * @param details       additional data associated with the error
+     * @return a JSON-RPC 2.0 formatted error response
+     * @throws JSONException if the world is ending
+     */
+    public static JSONObject error(Object id, Sl4aException sl4aException, Object details)
+            throws JSONException {
+        JSONObject returnValue = error(id, sl4aException);
+        returnValue.getJSONObject("error").put("data", details);
+        return returnValue;
+    }
+
+    /**
+     * A function that returns a valid JSON-RPC error as a string when all else has failed.
+     *
+     * @return a String representation of {@see Sl4aErrors.JSON_RPC_UNKNOWN_EXCEPTION} in JSON
+     */
+    public static String wtf() {
+        Sl4aException exception = Sl4aErrors.JSON_RPC_UNKNOWN_EXCEPTION.getError();
+        return "{\"id\":null,"
+                + "\"error\":{"
+                + "\"code\":" + exception.getErrorCode() + ","
+                + "\"message\":\"" + exception.getErrorMessage() + "\"}}";
+    }
 }
diff --git a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcServer.java b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcServer.java
index d3c3704..17ab6bf 100644
--- a/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcServer.java
+++ b/Common/src/com/googlecode/android_scripting/jsonrpc/JsonRpcServer.java
@@ -16,18 +16,13 @@
 
 package com.googlecode.android_scripting.jsonrpc;
 
-import java.io.BufferedReader;
-import java.io.PrintWriter;
-import java.net.Socket;
-import java.util.Map;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
 import com.googlecode.android_scripting.Log;
 import com.googlecode.android_scripting.SimpleServer;
-import com.googlecode.android_scripting.rpc.MethodDescriptor;
-import com.googlecode.android_scripting.rpc.RpcError;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.net.Socket;
 
 /**
  * A JSON RPC server that forwards RPC calls to a specified receiver object.
@@ -59,53 +54,22 @@
         }
     }
 
-    @Override
-    protected void handleRPCConnection(Socket sock, Integer UID, BufferedReader reader,
-            PrintWriter writer) throws Exception {
-        RpcReceiverManager receiverManager = null;
-        Map<Integer, RpcReceiverManager> mgrs = mRpcReceiverManagerFactory.getRpcReceiverManagers();
-        synchronized (mgrs) {
-            Log.d("UID " + UID);
-            Log.d("manager map keys: "
-                    + mRpcReceiverManagerFactory.getRpcReceiverManagers().keySet());
-            if (mgrs.containsKey(UID)) {
-                Log.d("Look up existing session");
-                receiverManager = mgrs.get(UID);
-            } else {
-                Log.d("Create a new session");
-                receiverManager = mRpcReceiverManagerFactory.create(UID);
-            }
-        }
-        // boolean passedAuthentication = false;
-        String data;
-        while ((data = reader.readLine()) != null) {
-            Log.v("Session " + UID + " Received: " + data);
-            JSONObject request = new JSONObject(data);
-            int id = request.getInt("id");
-            String method = request.getString("method");
-            JSONArray params = request.getJSONArray("params");
-
-            MethodDescriptor rpc = receiverManager.getMethodDescriptor(method);
-            if (rpc == null) {
-                send(writer, JsonRpcResult.error(id, new RpcError("Unknown RPC: " + method)), UID);
-                continue;
-            }
-            try {
-                send(writer, JsonRpcResult.result(id, rpc.invoke(receiverManager, params)), UID);
-            } catch (Throwable t) {
-                Log.e("Invocation error.", t);
-                send(writer, JsonRpcResult.error(id, t), UID);
-            }
-        }
-    }
-
-    private void send(PrintWriter writer, JSONObject result, int UID) {
+    private void send(PrintWriter writer, Object result) {
         writer.write(result + "\n");
         writer.flush();
-        Log.v("Session " + UID + " Sent: " + result);
+        Log.v("Sent Response: " + result);
     }
 
     @Override
     protected void handleConnection(Socket socket) throws Exception {
+        BufferedReader reader =
+                new BufferedReader(new InputStreamReader(socket.getInputStream()), 8192);
+        PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
+        String data;
+        JsonRpcHandler jsonRpcHandler = new JsonRpcHandler(mRpcReceiverManagerFactory);
+        while ((data = reader.readLine()) != null) {
+            Log.v("Received Request: " + data);
+            send(writer, jsonRpcHandler.getResponse(data));
+        }
     }
 }
diff --git a/Common/src/com/googlecode/android_scripting/jsonrpc/RpcReceiverManagerFactory.java b/Common/src/com/googlecode/android_scripting/jsonrpc/RpcReceiverManagerFactory.java
index 46526e9..d59503f 100644
--- a/Common/src/com/googlecode/android_scripting/jsonrpc/RpcReceiverManagerFactory.java
+++ b/Common/src/com/googlecode/android_scripting/jsonrpc/RpcReceiverManagerFactory.java
@@ -19,7 +19,26 @@
 import java.util.Map;
 
 public interface RpcReceiverManagerFactory {
-  public RpcReceiverManager create(Integer UID);
 
-  public Map<Integer, RpcReceiverManager> getRpcReceiverManagers();
+    /**
+     * Creates an {@code RpcReceiverManager} with the key {@param id}.
+     *
+     * @param sessionId the session id for the new RpcReceiverManager
+     * @return the newly created RpcReceiverManager
+     */
+    RpcReceiverManager create(String sessionId);
+
+    /**
+     * Destroys the RpcReceiverManager that corresponds to the given id.
+     *
+     * @param sessionId the session id
+     * @return true if the RpcReceiverManager existed, false otherwise
+     */
+    boolean destroy(String sessionId);
+
+    /**
+     * Returns the map of Session Id -> RpcReceiverManagers.
+     * @return
+     */
+    Map<String, RpcReceiverManager> getRpcReceiverManagers();
 }
diff --git a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/MessageHandler.java b/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/MessageHandler.java
index f8f7b9f..8c9ff47 100644
--- a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/MessageHandler.java
+++ b/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/MessageHandler.java
@@ -65,19 +65,21 @@
      * for RPCs through RpcManagers.
      *
      * @param message The message that contains the method and parameters to
-     *     execute.
+     *                execute.
      */
     @Override
     public void handleMessage(Message message) {
         Log.d("Handling Remote request");
         int senderId = message.sendingUid == DEFAULT_UNSET_SENDING_ID ?
                 DEFAULT_SENDING_ID : message.sendingUid;
+        String sessionId = "" + senderId;
         if (message.what == SL4A_ACTION) {
             RpcReceiverManager receiverManager;
-            if (mRpcReceiverManagerFactory.getRpcReceiverManagers().containsKey(senderId)) {
-                receiverManager = mRpcReceiverManagerFactory.getRpcReceiverManagers().get(senderId);
+            if (mRpcReceiverManagerFactory.getRpcReceiverManagers().containsKey(sessionId)) {
+                receiverManager =
+                        mRpcReceiverManagerFactory.getRpcReceiverManagers().get(sessionId);
             } else {
-                receiverManager = mRpcReceiverManagerFactory.create(senderId);
+                receiverManager = mRpcReceiverManagerFactory.create(sessionId);
             }
             Bundle sl4aRequest = message.getData();
             String method = sl4aRequest.getString(SL4A_METHOD);
diff --git a/Utils/src/com/googlecode/android_scripting/SimpleServer.java b/Utils/src/com/googlecode/android_scripting/SimpleServer.java
index a995ed7..b970d8c 100644
--- a/Utils/src/com/googlecode/android_scripting/SimpleServer.java
+++ b/Utils/src/com/googlecode/android_scripting/SimpleServer.java
@@ -18,13 +18,7 @@
 
 import com.google.common.collect.Lists;
 
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.io.BufferedReader;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.PrintWriter;
 import java.net.BindException;
 import java.net.Inet4Address;
 import java.net.InetAddress;
@@ -37,331 +31,284 @@
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
-import java.util.concurrent.ConcurrentHashMap;
-//import com.googlecode.android_scripting.jsonrpc.RpcReceiverManager;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * A simple server.
  */
 public abstract class SimpleServer {
-  private static int threadIndex = 0;
-  private final ConcurrentHashMap<Integer, ConnectionThread> mConnectionThreads =
-      new ConcurrentHashMap<Integer, ConnectionThread>();
-  private final List<SimpleServerObserver> mObservers = Lists.newArrayList();
-  private volatile boolean mStopServer = false;
-  private ServerSocket mServer;
-  private Thread mServerThread;
+    private final CopyOnWriteArrayList<ConnectionThread> mConnectionThreads =
+            new CopyOnWriteArrayList<>();
+    private final List<SimpleServerObserver> mObservers = Lists.newArrayList();
+    private volatile boolean mStopServer = false;
+    private ServerSocket mServer;
+    private Thread mServerThread;
 
-  public interface SimpleServerObserver {
-    public void onConnect();
-    public void onDisconnect();
-  }
-
-  protected abstract void handleConnection(Socket socket) throws Exception;
-  protected abstract void handleRPCConnection(Socket socket,
-                                              Integer UID,
-                                              BufferedReader reader,
-                                              PrintWriter writer) throws Exception;
-
-  /** Adds an observer. */
-  public void addObserver(SimpleServerObserver observer) {
-    mObservers.add(observer);
-  }
-
-  /** Removes an observer. */
-  public void removeObserver(SimpleServerObserver observer) {
-    mObservers.remove(observer);
-  }
-
-  private void notifyOnConnect() {
-    for (SimpleServerObserver observer : mObservers) {
-      observer.onConnect();
-    }
-  }
-
-  private void notifyOnDisconnect() {
-    for (SimpleServerObserver observer : mObservers) {
-      observer.onDisconnect();
-    }
-  }
-
-  private final class ConnectionThread extends Thread {
-    private final Socket mmSocket;
-    private final BufferedReader reader;
-    private final PrintWriter writer;
-    private final Integer UID;
-    private final boolean isRpc;
-
-    private ConnectionThread(Socket socket, boolean rpc, Integer uid, BufferedReader reader, PrintWriter writer) {
-      setName("SimpleServer ConnectionThread " + getId());
-      mmSocket = socket;
-      this.UID = uid;
-      this.reader = reader;
-      this.writer = writer;
-      this.isRpc = rpc;
+    /**
+     * An interface for accessing SimpleServer events.
+     */
+    public interface SimpleServerObserver {
+        /** The function to be called when a ConnectionThread is established.*/
+        void onConnect();
+        /** The function to be called when a ConnectionThread disconnects.*/
+        void onDisconnect();
     }
 
-    @Override
-    public void run() {
-      Log.v("Server thread " + getId() + " started.");
-      try {
-        if(isRpc) {
-          Log.d("Handling RPC connection in "+getId());
-          handleRPCConnection(mmSocket, UID, reader, writer);
-        }else{
-          Log.d("Handling Non-RPC connection in "+getId());
-          handleConnection(mmSocket);
+    /** An abstract method for handling non-RPC connections. */
+    protected abstract void handleConnection(Socket socket) throws Exception;
+
+    /**
+     * Adds an observer.
+     */
+    public void addObserver(SimpleServerObserver observer) {
+        mObservers.add(observer);
+    }
+
+    /**
+     * Removes an observer.
+     */
+    public void removeObserver(SimpleServerObserver observer) {
+        mObservers.remove(observer);
+    }
+
+    /** Notifies all subscribers when a new ConnectionThread has been created.
+     *
+     * This applies to both newly instantiated sessions and continued sessions.
+     */
+    private void notifyOnConnect() {
+        for (SimpleServerObserver observer : mObservers) {
+            observer.onConnect();
         }
-      } catch (Exception e) {
-        if (!mStopServer) {
-          Log.e("Server error.", e);
-        }
-      } finally {
-        close();
-        mConnectionThreads.remove(this.UID);
-        notifyOnDisconnect();
-        Log.v("Server thread " + getId() + " stopped.");
-      }
     }
 
-    private void close() {
-      if (mmSocket != null) {
+    /** Notifies all subscribers when a ConnectionThread has been terminated. */
+    private void notifyOnDisconnect() {
+        for (SimpleServerObserver observer : mObservers) {
+            observer.onDisconnect();
+        }
+    }
+
+    /** An implementation of a thread that holds data about its server connection status. */
+    private final class ConnectionThread extends Thread {
+        /** The socket used for communication. */
+        private final Socket mmSocket;
+
+        private ConnectionThread(Socket socket) {
+            setName("SimpleServer ConnectionThread " + getId());
+            mmSocket = socket;
+        }
+
+        @Override
+        public void run() {
+            Log.v("Server thread " + getId() + " started.");
+            try {
+                handleConnection(mmSocket);
+            } catch (Exception e) {
+                if (!mStopServer) {
+                    Log.e("Server error.", e);
+                }
+            } finally {
+                close();
+                mConnectionThreads.remove(this);
+                notifyOnDisconnect();
+                Log.v("Server thread " + getId() + " stopped.");
+            }
+        }
+
+        private void close() {
+            if (mmSocket != null) {
+                try {
+                    mmSocket.close();
+                } catch (IOException e) {
+                    Log.e(e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the number of active connections to this server.
+     */
+    public int getNumberOfConnections() {
+        return mConnectionThreads.size();
+    }
+
+    /**
+     * Returns the private InetAddress
+     * @return the private InetAddress
+     * @throws UnknownHostException If unable to resolve localhost during fallback.
+     * @throws SocketException if an IOError occurs while querying the network interfaces.
+     */
+    public static InetAddress getPrivateInetAddress() throws UnknownHostException, SocketException {
+        InetAddress candidate = null;
+        Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
+        for (NetworkInterface netint : Collections.list(nets)) {
+            if (!netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active
+                continue;
+            }
+            Enumeration<InetAddress> addresses = netint.getInetAddresses();
+            for (InetAddress address : Collections.list(addresses)) {
+                if (address instanceof Inet4Address) {
+                    Log.d("local address " + address);
+                    return address; // Prefer ipv4
+                }
+                candidate = address; // Probably an ipv6
+            }
+        }
+        if (candidate != null) {
+            return candidate; // return ipv6 address if no suitable ipv6
+        }
+        return InetAddress.getLocalHost(); // No damn matches. Give up, return local host.
+    }
+
+    /**
+     * Returns the public InetAddress
+     * @return the private InetAddress
+     * @throws UnknownHostException If unable to resolve localhost during fallback.
+     * @throws SocketException if an IOError occurs while querying the network interfaces.
+     */
+    public static InetAddress getPublicInetAddress() throws UnknownHostException, SocketException {
+        InetAddress candidate = null;
+        Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
+        for (NetworkInterface netint : Collections.list(nets)) {
+            // TODO(markdr): The only diff between this and above fn is the ! on the line below.
+            //               Merge these two functions.
+            if (netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active
+                continue;
+            }
+            Enumeration<InetAddress> addresses = netint.getInetAddresses();
+            for (InetAddress address : Collections.list(addresses)) {
+                if (address instanceof Inet4Address) {
+                    return address; // Prefer ipv4
+                }
+                candidate = address; // Probably an ipv6
+            }
+        }
+        if (candidate != null) {
+            return candidate; // return ipv6 address if no suitable ipv6
+        }
+        return InetAddress.getLocalHost(); // No damn matches. Give up, return local host.
+    }
+
+    /**
+     * Starts the RPC server bound to the localhost address.
+     *
+     * @param port the port to bind to or 0 to pick any unused port
+     * @return the port that the server is bound to
+     * @throws IOException
+     */
+    public InetSocketAddress startLocal(int port) {
+        InetAddress address;
         try {
-          mmSocket.close();
-        } catch (IOException e) {
-          Log.e(e.getMessage(), e);
-        }
-      }
-    }
-  }
-
-  /** Returns the number of active connections to this server. */
-  public int getNumberOfConnections() {
-    return mConnectionThreads.size();
-  }
-
-  public static InetAddress getPrivateInetAddress() throws UnknownHostException, SocketException {
-
-    InetAddress candidate = null;
-    Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
-    for (NetworkInterface netint : Collections.list(nets)) {
-      if (!netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active
-        continue;
-      }
-      Enumeration<InetAddress> addresses = netint.getInetAddresses();
-      for (InetAddress address : Collections.list(addresses)) {
-        if (address instanceof Inet4Address) {
-          Log.d("local address " + address);
-          return address; // Prefer ipv4
-        }
-        candidate = address; // Probably an ipv6
-      }
-    }
-    if (candidate != null) {
-      return candidate; // return ipv6 address if no suitable ipv6
-    }
-    return InetAddress.getLocalHost(); // No damn matches. Give up, return local host.
-  }
-
-  public static InetAddress getPublicInetAddress() throws UnknownHostException, SocketException {
-
-    InetAddress candidate = null;
-    Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
-    for (NetworkInterface netint : Collections.list(nets)) {
-      if (netint.isLoopback() || !netint.isUp()) { // Ignore if localhost or not active
-        continue;
-      }
-      Enumeration<InetAddress> addresses = netint.getInetAddresses();
-      for (InetAddress address : Collections.list(addresses)) {
-        if (address instanceof Inet4Address) {
-          return address; // Prefer ipv4
-        }
-        candidate = address; // Probably an ipv6
-      }
-    }
-    if (candidate != null) {
-      return candidate; // return ipv6 address if no suitable ipv6
-    }
-    return InetAddress.getLocalHost(); // No damn matches. Give up, return local host.
-  }
-
-  /**
-   * Starts the RPC server bound to the localhost address.
-   *
-   * @param port
-   *          the port to bind to or 0 to pick any unused port
-   *
-   * @return the port that the server is bound to
-   * @throws IOException
-   */
-  public InetSocketAddress startLocal(int port) {
-    InetAddress address;
-    try {
-      // address = InetAddress.getLocalHost();
-      address = getPrivateInetAddress();
-      mServer = new ServerSocket(port, 5, address);
-    } catch (BindException e) {
-      Log.e("Port " + port + " already in use.");
-      try {
-        address = getPrivateInetAddress();
-        mServer = new ServerSocket(0, 5, address);
-      } catch (IOException e1) {
-        e1.printStackTrace();
-        return null;
-      }
-    } catch (Exception e) {
-      Log.e("Failed to start server.", e);
-      return null;
-    }
-    int boundPort = start();
-    return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), boundPort);
-  }
-
-  /**
-   * data Starts the RPC server bound to the public facing address.
-   *
-   * @param port
-   *          the port to bind to or 0 to pick any unused port
-   *
-   * @return the port that the server is bound to
-   */
-  public InetSocketAddress startPublic(int port) {
-    InetAddress address;
-    try {
-      // address = getPublicInetAddress();
-      address = null;
-      mServer = new ServerSocket(port, 5 /* backlog */, address);
-    } catch (Exception e) {
-      Log.e("Failed to start server.", e);
-      return null;
-    }
-    int boundPort = start();
-    return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), boundPort);
-  }
-
-  /**
-   * data Starts the RPC server bound to all interfaces
-   *
-   * @param port
-   *          the port to bind to or 0 to pick any unused port
-   *
-   * @return the port that the server is bound to
-   */
-  public InetSocketAddress startAllInterfaces(int port) {
-    try {
-      mServer = new ServerSocket(port, 5 /* backlog */);
-    } catch (Exception e) {
-      Log.e("Failed to start server.", e);
-      return null;
-    }
-    int boundPort = start();
-    return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(), boundPort);
-  }
-
-  private int start() {
-    mServerThread = new Thread() {
-      @Override
-      public void run() {
-        while (!mStopServer) {
-          try {
-            Socket sock = mServer.accept();
-            if (!mStopServer) {
-              startConnectionThread(sock);
-            } else {
-              sock.close();
+            // address = InetAddress.getLocalHost();
+            address = getPrivateInetAddress();
+            mServer = new ServerSocket(port, 5, address);
+        } catch (BindException e) {
+            Log.e("Port " + port + " already in use.");
+            try {
+                address = getPrivateInetAddress();
+                mServer = new ServerSocket(0, 5, address);
+            } catch (IOException e1) {
+                e1.printStackTrace();
+                return null;
             }
-          } catch (IOException e) {
-            if (!mStopServer) {
-              Log.e("Failed to accept connection.", e);
-            }
-          } catch (JSONException e) {
-            if (!mStopServer) {
-              Log.e("Failed to parse request.", e);
-            }
-          }
+        } catch (Exception e) {
+            Log.e("Failed to start server.", e);
+            return null;
         }
-      }
-    };
-    mServerThread.start();
-    Log.v("Bound to " + mServer.getInetAddress());
-    return mServer.getLocalPort();
-  }
+        int boundPort = start();
+        return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(),
+                boundPort);
+    }
 
-  private void startConnectionThread(final Socket sock) throws IOException, JSONException {
-    BufferedReader reader =
-        new BufferedReader(new InputStreamReader(sock.getInputStream()), 8192);
-    PrintWriter writer = new PrintWriter(sock.getOutputStream(), true);
-    String data;
-    if((data = reader.readLine()) != null) {
-      Log.v("Received: " + data);
-      JSONObject request = new JSONObject(data);
-      if(request.has("cmd") && request.has("uid")) {
-        String cmd = request.getString("cmd");
-        int uid = request.getInt("uid");
-        JSONObject result = new JSONObject();
-        if(cmd.equals("initiate")) {
-          Log.d("Initiate a new session");
-          threadIndex += 1;
-          int mUID = threadIndex;
-          ConnectionThread networkThread = new ConnectionThread(sock,true,mUID,reader,writer);
-          mConnectionThreads.put(mUID, networkThread);
-          networkThread.start();
-          notifyOnConnect();
-          result.put("uid", mUID);
-          result.put("status",true);
-          result.put("error", null);
-        }else if(cmd.equals("continue")) {
-          Log.d("Continue an existing session");
-          Log.d("keys: "+mConnectionThreads.keySet().toString());
-          if(!mConnectionThreads.containsKey(uid)) {
-            result.put("uid", uid);
-            result.put("status",false);
-            result.put("error", "Session does not exist.");
-          }else{
-            ConnectionThread networkThread = new ConnectionThread(sock,true,uid,reader,writer);
-            mConnectionThreads.put(uid, networkThread);
-            networkThread.start();
-            notifyOnConnect();
-            result.put("uid", uid);
-            result.put("status",true);
-            result.put("error", null);
-          }
-        }else {
-          result.put("uid", uid);
-          result.put("status",false);
-          result.put("error", "Unrecognized command.");
+    /**
+     * Starts the RPC server bound to the public facing address.
+     *
+     * @param port the port to bind to or 0 to pick any unused port
+     * @return the port that the server is bound to
+     */
+    public InetSocketAddress startPublic(int port) {
+        InetAddress address;
+        try {
+            // address = getPublicInetAddress();
+            address = null;
+            mServer = new ServerSocket(port, 5 /* backlog */, address);
+        } catch (Exception e) {
+            Log.e("Failed to start server.", e);
+            return null;
         }
-        writer.write(result + "\n");
-        writer.flush();
-        Log.v("Sent: " + result);
-      }else{
-        ConnectionThread networkThread = new ConnectionThread(sock,false,0,reader,writer);
-        mConnectionThreads.put(0, networkThread);
+        int boundPort = start();
+        return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(),
+                boundPort);
+    }
+
+    /**
+     * Starts the RPC server bound to all interfaces.
+     *
+     * @param port the port to bind to or 0 to pick any unused port
+     * @return the port that the server is bound to
+     */
+    public InetSocketAddress startAllInterfaces(int port) {
+        try {
+            mServer = new ServerSocket(port, 5 /* backlog */);
+        } catch (Exception e) {
+            Log.e("Failed to start server.", e);
+            return null;
+        }
+        int boundPort = start();
+        return InetSocketAddress.createUnresolved(mServer.getInetAddress().getHostAddress(),
+                boundPort);
+    }
+
+    private int start() {
+        mServerThread = new Thread(() -> {
+            while (!mStopServer) {
+                try {
+                    Socket sock = mServer.accept();
+                    if (!mStopServer) {
+                        startConnectionThread(sock);
+                    } else {
+                        sock.close();
+                    }
+                } catch (IOException e) {
+                    if (!mStopServer) {
+                        Log.e("Failed to accept connection.", e);
+                    }
+                }
+            }
+        });
+        mServerThread.start();
+        Log.v("Bound to " + mServer.getInetAddress());
+        return mServer.getLocalPort();
+    }
+
+    protected void startConnectionThread(final Socket sock) {
+        ConnectionThread networkThread = new ConnectionThread(sock);
+        mConnectionThreads.add(networkThread);
         networkThread.start();
         notifyOnConnect();
-      }
     }
-  }
 
-  public void shutdown() {
-    // Stop listening on the server socket to ensure that
-    // beyond this point there are no incoming requests.
-    mStopServer = true;
-    try {
-      mServer.close();
-    } catch (IOException e) {
-      Log.e("Failed to close server socket.", e);
+    /** Closes the server, preventing new connections from being added. */
+    public void shutdown() {
+        // Stop listening on the server socket to ensure that
+        // beyond this point there are no incoming requests.
+        mStopServer = true;
+        try {
+            mServer.close();
+        } catch (IOException e) {
+            Log.e("Failed to close server socket.", e);
+        }
+        // Since the server is not running, the mNetworkThreads set can only
+        // shrink from this point onward. We can just stop all of the running helper
+        // threads. In the worst case, one of the running threads will already have
+        // shut down. Since this is a CopyOnWriteList, we don't have to worry about
+        // concurrency issues while iterating over the set of threads.
+        for (ConnectionThread connectionThread : mConnectionThreads) {
+            connectionThread.close();
+        }
+        for (SimpleServerObserver observer : mObservers) {
+            removeObserver(observer);
+        }
     }
-    // Since the server is not running, the mNetworkThreads set can only
-    // shrink from this point onward. We can just stop all of the running helper
-    // threads. In the worst case, one of the running threads will already have
-    // shut down. Since this is a CopyOnWriteList, we don't have to worry about
-    // concurrency issues while iterating over the set of threads.
-    for (ConnectionThread connectionThread : mConnectionThreads.values()) {
-      connectionThread.close();
-    }
-    for (SimpleServerObserver observer : mObservers) {
-      removeObserver(observer);
-    }
-  }
 }