summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonathan Velasquez <jonathanve@google.com>2015-11-03 15:59:49 -0800
committerAng Li <angli@google.com>2016-01-28 14:01:31 -0800
commit0a93e31e8ce69dcbe2afcbbbd0c90d81e338fc18 (patch)
tree20a2ff1f273e5ce96fca0057c9b28bbadf3b114b
parentb17b984be222640a9fc282c118de3617278a4a8a (diff)
downloadconnectivity-0a93e31e8ce69dcbe2afcbbbd0c90d81e338fc18.tar.gz
Revert "Revert "Creating facade service for SL4A.""
Re-submitting without refactor of FacadeManagerFactory since it causes issues with the existing event queue. This reverts commit 4a2d59d962ea23a7031bb671fcddc1fd4e40fef0. Cherry-pick of CL missed by project moving https://googleplex-android-review.git.corp.google.com/#/c/807033/ Change-Id: Ic95fb84d7140a9d1930154720747c7a33bc814d3
-rw-r--r--sl4a/Common/src/com/googlecode/android_scripting/rpc/MethodDescriptor.java68
-rw-r--r--sl4a/ScriptingLayerForAndroid/AndroidManifest.xml5
-rw-r--r--sl4a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/FacadeService.java55
-rw-r--r--sl4a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/MessageHandler.java116
4 files changed, 244 insertions, 0 deletions
diff --git a/sl4a/Common/src/com/googlecode/android_scripting/rpc/MethodDescriptor.java b/sl4a/Common/src/com/googlecode/android_scripting/rpc/MethodDescriptor.java
index 2f8b1ee..5c31c34 100644
--- a/sl4a/Common/src/com/googlecode/android_scripting/rpc/MethodDescriptor.java
+++ b/sl4a/Common/src/com/googlecode/android_scripting/rpc/MethodDescriptor.java
@@ -18,6 +18,8 @@ package com.googlecode.android_scripting.rpc;
import android.content.Intent;
import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcelable;
import com.googlecode.android_scripting.facade.AndroidFacade;
import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
@@ -101,6 +103,37 @@ public final class MethodDescriptor {
}
}
+ return invoke(manager, args);
+ }
+
+ /**
+ * Invokes the call that belongs to this object with the given parameters. Wraps the response
+ * (possibly an exception) in a JSONObject.
+ *
+ * @param parameters {@code Bundle} containing the parameters
+ * @return result
+ * @throws Throwable
+ */
+ public Object invoke(RpcReceiverManager manager, final Bundle parameters) throws Throwable {
+ final Annotation annotations[][] = getParameterAnnotations();
+ final Class<?>[] parameterTypes = getMethod().getParameterTypes();
+ final Object[] args = new Object[parameterTypes.length];
+
+ for (int i = 0; i < parameterTypes.length; i++) {
+ Class<?> parameterType = parameterTypes[i];
+ String parameterName = getName(annotations[i]);
+ if (i < parameterTypes.length) {
+ args[i] = convertParameter(parameters, parameterType, parameterName);
+ } else if (MethodDescriptor.hasDefaultValue(annotations[i])) {
+ args[i] = MethodDescriptor.getDefaultValue(parameterType, annotations[i]);
+ } else {
+ throw new RpcError("Argument " + (i + 1) + " is not present");
+ }
+ }
+ return invoke(manager, args);
+ }
+
+ private Object invoke(RpcReceiverManager manager, Object[] args) throws Throwable{
Object result = null;
try {
result = manager.invoke(mClass, mMethod, args);
@@ -168,6 +201,41 @@ public final class MethodDescriptor {
}
}
+ private Object convertParameter(Bundle bundle, Class<?> type, String name) {
+ Object param = null;
+ if (type.isAssignableFrom(Boolean.class)) {
+ param = bundle.getBoolean(name, false);
+ }
+ if (type.isAssignableFrom(Boolean[].class)) {
+ param = bundle.getBooleanArray(name);
+ }
+ if (type.isAssignableFrom(String.class)) {
+ param = bundle.getString(name);
+ }
+ if (type.isAssignableFrom(String[].class)) {
+ param = bundle.getStringArray(name);
+ }
+ if (type.isAssignableFrom(Integer.class)) {
+ param = bundle.getInt(name, 0);
+ }
+ if (type.isAssignableFrom(Integer[].class)) {
+ param = bundle.getIntArray(name);
+ }
+ if (type.isAssignableFrom(Bundle.class)) {
+ param = bundle.getBundle(name);
+ }
+ if (type.isAssignableFrom(Parcelable.class)) {
+ param = bundle.getParcelable(name);
+ }
+ if (type.isAssignableFrom(Parcelable[].class)) {
+ param = bundle.getParcelableArray(name);
+ }
+ if (type.isAssignableFrom(Intent.class)) {
+ param = bundle.getParcelable(name);
+ }
+ return param;
+ }
+
public static Object buildIntent(JSONObject jsonObject) throws JSONException {
Intent intent = new Intent();
if (jsonObject.has("action")) {
diff --git a/sl4a/ScriptingLayerForAndroid/AndroidManifest.xml b/sl4a/ScriptingLayerForAndroid/AndroidManifest.xml
index 6eddb4b..cc9f2b8 100644
--- a/sl4a/ScriptingLayerForAndroid/AndroidManifest.xml
+++ b/sl4a/ScriptingLayerForAndroid/AndroidManifest.xml
@@ -186,6 +186,11 @@
<action android:name="android.telecom.InCallService"/>
</intent-filter>
</service>
+ <service android:name=".service.FacadeService" android:enabled="true" android:exported="true" >
+ <intent-filter>
+ <action android:name="com.googlecode.android_scripting.service.FacadeService.ACTION_BIND" />
+ </intent-filter>
+ </service>
<activity android:name=".activity.InterpreterManager" android:launchMode="singleTask" android:configChanges="keyboardHidden|orientation" />
<activity android:name=".activity.LogcatViewer" android:launchMode="singleTask" android:configChanges="keyboardHidden|orientation" />
<activity android:name=".activity.ScriptsLiveFolder" android:label="Scripts" android:icon="@drawable/live_folder" android:configChanges="keyboardHidden|orientation">
diff --git a/sl4a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/FacadeService.java b/sl4a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/FacadeService.java
new file mode 100644
index 0000000..7ca4e18
--- /dev/null
+++ b/sl4a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/FacadeService.java
@@ -0,0 +1,55 @@
+package com.googlecode.android_scripting.service;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Messenger;
+
+import com.googlecode.android_scripting.facade.FacadeConfiguration;
+import com.googlecode.android_scripting.facade.FacadeManagerFactory;
+import com.googlecode.android_scripting.jsonrpc.RpcReceiverManagerFactory;
+
+/**
+ * FacadeService exposes SL4A's Facade methods through a {@link Service} so
+ * they can be invoked from third-party apps.
+ * <p>
+ * Example binding code:<br>
+ * {@code
+ * Intent intent = new Intent();
+ * intent.setPackage("com.googlecode.android_scripting");
+ * intent.setAction("com.googlecode.android_scripting.service.FacadeService.ACTION_BIND");
+ * sl4aService = bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ * }
+ * Example using the service:<br>
+ * {@code
+ * Bundle sl4aBundle = new Bundle;
+ * bundle.putString{"sl4aMethod", "makeToast"};
+ * bundle.putString{"message", "Hello World!"};
+ * Message msg = Message.obtain(null, SL4A_ACTION);
+ * msg.setData(sl4aBundle);
+ * msg.replyTo = myReplyHandler; // Set a Messenger if you need the response
+ * mSl4aService.send(msg);
+ * }
+ * <p>
+ * For more info on binding a {@link Service} using a {@link Messenger} please
+ * refer to Android's public developer documentation.
+ */
+public class FacadeService extends Service {
+
+ private RpcReceiverManagerFactory rpcReceiverManagerFactory;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ if (rpcReceiverManagerFactory == null) {
+ rpcReceiverManagerFactory =
+ new FacadeManagerFactory(FacadeConfiguration.getSdkLevel(), this, null,
+ FacadeConfiguration.getFacadeClasses());
+ }
+ HandlerThread handlerThread = new HandlerThread("MessageHandlerThread");
+ handlerThread.start();
+ Messenger aMessenger = new Messenger(new MessageHandler(handlerThread,
+ rpcReceiverManagerFactory));
+ return aMessenger.getBinder();
+ }
+}
diff --git a/sl4a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/MessageHandler.java b/sl4a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/MessageHandler.java
new file mode 100644
index 0000000..8c6fb3f
--- /dev/null
+++ b/sl4a/ScriptingLayerForAndroid/src/com/googlecode/android_scripting/service/MessageHandler.java
@@ -0,0 +1,116 @@
+package com.googlecode.android_scripting.service;
+
+import android.annotation.TargetApi;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Message;
+import android.os.RemoteException;
+
+import com.googlecode.android_scripting.Log;
+import com.googlecode.android_scripting.jsonrpc.JsonRpcResult;
+import com.googlecode.android_scripting.jsonrpc.RpcReceiverManager;
+import com.googlecode.android_scripting.jsonrpc.RpcReceiverManagerFactory;
+import com.googlecode.android_scripting.rpc.MethodDescriptor;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Class responsible for Handling messages that came through the FacadeService
+ * interface.
+ * <br>
+ * Please refer to {@link FacadeService} for details on how to use.
+ */
+@TargetApi(3)
+public class MessageHandler extends Handler {
+
+ private static final int SL4A_ACTION = 0;
+ private static final int DEFAULT_SENDING_ID = 0;
+
+ // Android sets this to -1 when the message is not sent by a Messenger.
+ // see http://developer.android.com/reference/android/os/Message.html#sendingUid
+ private static final int DEFAULT_UNSET_SENDING_ID = 1;
+
+ // Keys for the Bundles.
+ private static final String SL4A_METHOD = "sl4aMethod";
+ private static final String SL4A_RESULT = "sl4aResult";
+
+ private final RpcReceiverManagerFactory mRpcReceiverManagerFactory;
+
+ public MessageHandler(HandlerThread handlerThread,
+ RpcReceiverManagerFactory rpcReceiverManagerFactory) {
+ super(handlerThread.getLooper());
+ this.mRpcReceiverManagerFactory = rpcReceiverManagerFactory;
+ }
+
+ /**
+ * Handles messages for the service. It does this via the same mechanism used
+ * for RPCs through RpcManagers.
+ *
+ * @param message The message that contains the method and parameters to
+ * 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;
+ if (message.what == SL4A_ACTION) {
+ RpcReceiverManager receiverManager;
+ if (mRpcReceiverManagerFactory.getRpcReceiverManagers().containsKey(senderId)) {
+ receiverManager = mRpcReceiverManagerFactory.getRpcReceiverManagers().get(senderId);
+ } else {
+ receiverManager = mRpcReceiverManagerFactory.create(senderId);
+ }
+ Bundle sl4aRequest = message.getData();
+ String method = sl4aRequest.getString(SL4A_METHOD);
+ if (method == null || "".equals(method)) {
+ Log.e("No SL4A method specified on the Bundle. Specify one with "
+ + SL4A_METHOD);
+ return;
+ }
+ MethodDescriptor rpc = receiverManager.getMethodDescriptor(method);
+ if (rpc == null) {
+ Log.e("Unknown RPC: \"" + method + "\"");
+ return;
+ }
+ try {
+ Log.d("Invoking method " + rpc.getName());
+ Object result = rpc.invoke(receiverManager, sl4aRequest);
+ // Only return a result if we were passed a Messenger. Otherwise assume
+ // client did not care for the response.
+ if (message.replyTo != null) {
+ Message reply = Message.obtain();
+ Bundle sl4aResponse = new Bundle();
+ putResult(senderId, result, sl4aResponse);
+ reply.setData(sl4aResponse);
+ message.replyTo.send(reply);
+ }
+ } catch (RemoteException e) {
+ Log.e("Could not send reply back to client", e);
+ } catch (Throwable t) {
+ Log.e("Exception while executing sl4a method", t);
+ }
+ }
+ }
+
+ private void putResult(int id, Object result, Bundle reply) {
+ JSONObject json;
+ try {
+ if (result instanceof Throwable) {
+ json = JsonRpcResult.error(id, (Throwable) result);
+ } else {
+ json = JsonRpcResult.result(id, result);
+ }
+ } catch (JSONException e) {
+ // There was an error converting the result to JSON. This shouldn't
+ // happen normally.
+ Log.e("Caught exception when filling JSON result.", e);
+ reply.putString(SL4A_RESULT, e.toString());
+ return;
+ }
+ Log.d("Returning result: " + json.toString());
+ reply.putString(SL4A_RESULT, json.toString());
+ }
+}