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);
- }
- }
}