diff options
author | Jonathan Velasquez <jonathanve@google.com> | 2015-11-03 15:59:49 -0800 |
---|---|---|
committer | Ang Li <angli@google.com> | 2016-01-28 14:01:31 -0800 |
commit | 0a93e31e8ce69dcbe2afcbbbd0c90d81e338fc18 (patch) | |
tree | 20a2ff1f273e5ce96fca0057c9b28bbadf3b114b | |
parent | b17b984be222640a9fc282c118de3617278a4a8a (diff) | |
download | connectivity-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
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()); + } +} |