diff options
Diffstat (limited to 'third_party/sl4a/src/main/java/com')
8 files changed, 288 insertions, 186 deletions
diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/ReflectionSnippetManagerFactory.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/ReflectionSnippetManagerFactory.java deleted file mode 100644 index 5e4259d..0000000 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/ReflectionSnippetManagerFactory.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2016 Google Inc. - * - * 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.google.android.mobly.snippet.manager; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.Bundle; -import com.google.android.mobly.snippet.Snippet; -import com.google.android.mobly.snippet.event.EventSnippet; -import com.google.android.mobly.snippet.util.Log; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class ReflectionSnippetManagerFactory implements SnippetManagerFactory { - private static final String METADATA_TAG_NAME = "mobly-snippets"; - - private final Context mContext; - private final Set<Class<? extends Snippet>> mClasses; - private final Map<Integer, SnippetManager> mSnippetManagers; - - public ReflectionSnippetManagerFactory(Context context) { - mContext = context; - mClasses = loadSnippets(); - mSnippetManagers = new HashMap<>(); - } - - @Override - public SnippetManager create(Integer UID) { - SnippetManager manager = new SnippetManager(mClasses); - mSnippetManagers.put(UID, manager); - return manager; - } - - @Override - public Map<Integer, SnippetManager> getSnippetManagers() { - return Collections.unmodifiableMap(mSnippetManagers); - } - - private Set<Class<? extends Snippet>> loadSnippets() { - ApplicationInfo appInfo; - try { - appInfo = - mContext.getPackageManager() - .getApplicationInfo( - mContext.getPackageName(), PackageManager.GET_META_DATA); - } catch (PackageManager.NameNotFoundException e) { - throw new IllegalStateException( - "Failed to find ApplicationInfo with package name: " - + mContext.getPackageName()); - } - Bundle metadata = appInfo.metaData; - String snippets = metadata.getString(METADATA_TAG_NAME); - if (snippets == null) { - throw new IllegalStateException( - "AndroidManifest.xml does not contain a <metadata> tag with " - + "name=\"" - + METADATA_TAG_NAME - + "\""); - } - String[] snippetClassNames = snippets.split("\\s*,\\s*"); - Set<Class<? extends Snippet>> receiverSet = new HashSet<>(); - /** Add the event snippet class which is provided within the Snippet Lib. */ - receiverSet.add(EventSnippet.class); - for (String snippetClassName : snippetClassNames) { - try { - Log.i("Trying to load Snippet class: " + snippetClassName); - Class<?> snippetClass = Class.forName(snippetClassName); - receiverSet.add((Class<? extends Snippet>) snippetClass); - } catch (ClassNotFoundException e) { - Log.e("Failed to find class " + snippetClassName); - throw new RuntimeException(e); - } - } - if (receiverSet.isEmpty()) { - throw new IllegalStateException("Found no subclasses of Snippet."); - } - return receiverSet; - } -} diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManager.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManager.java index ff0aec3..66e33b3 100644 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManager.java +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManager.java @@ -16,11 +16,17 @@ package com.google.android.mobly.snippet.manager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; import android.os.Build; +import android.os.Bundle; import com.google.android.mobly.snippet.Snippet; +import com.google.android.mobly.snippet.event.EventSnippet; import com.google.android.mobly.snippet.rpc.MethodDescriptor; import com.google.android.mobly.snippet.rpc.RpcMinSdk; import com.google.android.mobly.snippet.rpc.RunOnUiThread; +import com.google.android.mobly.snippet.schedulerpc.ScheduleRpcSnippet; import com.google.android.mobly.snippet.util.Log; import com.google.android.mobly.snippet.util.MainThread; import com.google.android.mobly.snippet.util.SnippetLibException; @@ -30,18 +36,24 @@ import java.lang.reflect.Method; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.Callable; public class SnippetManager { + private static final String METADATA_TAG_NAME = "mobly-snippets"; private final Map<Class<? extends Snippet>, Snippet> mSnippets; /** A map of strings to known RPCs. */ private final Map<String, MethodDescriptor> mKnownRpcs; - public SnippetManager(Collection<Class<? extends Snippet>> classList) { + private static SnippetManager sInstance = null; + private boolean mShutdown = false; + + private SnippetManager(Collection<Class<? extends Snippet>> classList) { // Synchronized for multiple connections on the same session. Can't use ConcurrentHashMap // because we have to put in a value of 'null' before the class is constructed, but // ConcurrentHashMap does not allow null values. @@ -65,6 +77,25 @@ public class SnippetManager { mKnownRpcs = Collections.unmodifiableMap(knownRpcs); } + public static synchronized SnippetManager initSnippetManager(Context context) { + if (sInstance != null) { + throw new IllegalStateException("SnippetManager should not be re-initialized"); + } + Collection<Class<? extends Snippet>> classList = findSnippetClassesFromMetadata(context); + sInstance = new SnippetManager(classList); + return sInstance; + } + + public static SnippetManager getInstance() { + if (sInstance == null) { + throw new IllegalStateException("getInstance() called before init()"); + } + if (sInstance.isShutdown()) { + throw new IllegalStateException("shutdown() called before getInstance()"); + } + return sInstance; + } + public MethodDescriptor getMethodDescriptor(String methodName) { return mKnownRpcs.get(methodName); } @@ -114,6 +145,56 @@ public class SnippetManager { entry.getValue().shutdown(); } } + mSnippets.clear(); + mKnownRpcs.clear(); + mShutdown = true; + } + + public boolean isShutdown() { + return mShutdown; + } + + private static Set<Class<? extends Snippet>> findSnippetClassesFromMetadata(Context context) { + ApplicationInfo appInfo; + try { + appInfo = + context.getPackageManager() + .getApplicationInfo( + context.getPackageName(), PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException( + "Failed to find ApplicationInfo with package name: " + + context.getPackageName()); + } + Bundle metadata = appInfo.metaData; + String snippets = metadata.getString(METADATA_TAG_NAME); + if (snippets == null) { + throw new IllegalStateException( + "AndroidManifest.xml does not contain a <metadata> tag with " + + "name=\"" + + METADATA_TAG_NAME + + "\""); + } + String[] snippetClassNames = snippets.split("\\s*,\\s*"); + Set<Class<? extends Snippet>> receiverSet = new HashSet<>(); + /** Add the event snippet class which is provided within the Snippet Lib. */ + receiverSet.add(EventSnippet.class); + /** Add the schedule RPC snippet class which is provided within the Snippet Lib. */ + receiverSet.add(ScheduleRpcSnippet.class); + for (String snippetClassName : snippetClassNames) { + try { + Log.i("Trying to load Snippet class: " + snippetClassName); + Class<?> snippetClass = Class.forName(snippetClassName); + receiverSet.add((Class<? extends Snippet>) snippetClass); + } catch (ClassNotFoundException e) { + Log.e("Failed to find class " + snippetClassName); + throw new RuntimeException(e); + } + } + if (receiverSet.isEmpty()) { + throw new IllegalStateException("Found no subclasses of Snippet."); + } + return receiverSet; } private Snippet get(Class<? extends Snippet> clazz) throws Exception { diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManagerFactory.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManagerFactory.java deleted file mode 100644 index 50a3552..0000000 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/manager/SnippetManagerFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2016 Google Inc. - * - * 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.google.android.mobly.snippet.manager; - -import java.util.Map; - -public interface SnippetManagerFactory { - SnippetManager create(Integer UID); - - Map<Integer, SnippetManager> getSnippetManagers(); -} diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/AndroidProxy.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/AndroidProxy.java index cb2e772..9428c82 100644 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/AndroidProxy.java +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/AndroidProxy.java @@ -17,18 +17,14 @@ package com.google.android.mobly.snippet.rpc; import android.content.Context; -import com.google.android.mobly.snippet.manager.ReflectionSnippetManagerFactory; -import com.google.android.mobly.snippet.manager.SnippetManagerFactory; import java.io.IOException; public class AndroidProxy { private final JsonRpcServer mJsonRpcServer; - private final SnippetManagerFactory mSnippetManagerFactory; public AndroidProxy(Context context) { - mSnippetManagerFactory = new ReflectionSnippetManagerFactory(context); - mJsonRpcServer = new JsonRpcServer(mSnippetManagerFactory); + mJsonRpcServer = new JsonRpcServer(context); } public void startLocal(int port) throws IOException { diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonRpcResult.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonRpcResult.java index de8537c..90cf8f9 100644 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonRpcResult.java +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonRpcResult.java @@ -60,11 +60,7 @@ public class JsonRpcResult { } public static JSONObject error(int id, Throwable t) throws JSONException { - StringWriter stackTraceWriter = new StringWriter(); - stackTraceWriter.write("\n-------------- Java Stacktrace ---------------\n"); - t.printStackTrace(new PrintWriter(stackTraceWriter)); - stackTraceWriter.write("----------------------------------------------"); - String stackTrace = stackTraceWriter.toString(); + String stackTrace = getStackTrace(t); JSONObject json = new JSONObject(); json.put("id", id); json.put("result", JSONObject.NULL); @@ -72,4 +68,12 @@ public class JsonRpcResult { json.put("error", stackTrace); return json; } + + public static String getStackTrace(Throwable throwable) { + StringWriter stackTraceWriter = new StringWriter(); + stackTraceWriter.write("\n-------------- Java Stacktrace ---------------\n"); + throwable.printStackTrace(new PrintWriter(stackTraceWriter)); + stackTraceWriter.write("----------------------------------------------"); + return stackTraceWriter.toString(); + } } diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonRpcServer.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonRpcServer.java index 219c471..1dde423 100644 --- a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonRpcServer.java +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/rpc/JsonRpcServer.java @@ -16,9 +16,10 @@ package com.google.android.mobly.snippet.rpc; +import android.content.Context; import com.google.android.mobly.snippet.manager.SnippetManager; -import com.google.android.mobly.snippet.manager.SnippetManagerFactory; import com.google.android.mobly.snippet.util.Log; +import com.google.android.mobly.snippet.util.RpcUtil; import java.io.BufferedReader; import java.io.PrintWriter; import java.net.Socket; @@ -35,33 +36,19 @@ public class JsonRpcServer extends SimpleServer { private static final String CMD_CLOSE_SESSION = "closeSl4aSession"; private static final String CMD_HELP = "help"; - private final SnippetManagerFactory mSnippetManagerFactory; + private final SnippetManager mSnippetManager; + private final RpcUtil mRpcUtil; - /** - * Construct a {@link JsonRpcServer} connected to the provided {@link SnippetManager}. - * - * @param managerFactory the {@link SnippetManager} to register with the server - */ - public JsonRpcServer(SnippetManagerFactory managerFactory) { - mSnippetManagerFactory = managerFactory; + /** Construct a {@link JsonRpcServer} connected to the provided {@link SnippetManager}. */ + public JsonRpcServer(Context context) { + mSnippetManager = SnippetManager.initSnippetManager(context); + mRpcUtil = new RpcUtil(); } @Override protected void handleRPCConnection( Socket sock, Integer UID, BufferedReader reader, PrintWriter writer) throws Exception { - SnippetManager receiverManager = null; - Map<Integer, SnippetManager> mgrs = mSnippetManagerFactory.getSnippetManagers(); - synchronized (mgrs) { - Log.d("UID " + UID); - Log.d("manager map keys: " + mSnippetManagerFactory.getSnippetManagers().keySet()); - if (mgrs.containsKey(UID)) { - Log.d("Look up existing session"); - receiverManager = mgrs.get(UID); - } else { - Log.d("Create a new session"); - receiverManager = mSnippetManagerFactory.create(UID); - } - } + Log.d("UID " + UID); String data; while ((data = reader.readLine()) != null) { Log.v("Session " + UID + " Received: " + data); @@ -72,16 +59,13 @@ public class JsonRpcServer extends SimpleServer { // Handle builtin commands if (method.equals(CMD_HELP)) { - help(writer, id, receiverManager, UID); + help(writer, id, mSnippetManager, UID); continue; } else if (method.equals(CMD_CLOSE_SESSION)) { Log.d("Got shutdown signal"); synchronized (writer) { // Shut down all RPC receivers. - for (SnippetManager manager : - mSnippetManagerFactory.getSnippetManagers().values()) { - manager.shutdown(); - } + mSnippetManager.shutdown(); // Shut down this client connection. As soon as this happens, the client will // kill us by triggering the 'stop' action from another instrumentation, so no @@ -93,35 +77,11 @@ public class JsonRpcServer extends SimpleServer { // Shut down this server. shutdown(); - mgrs.clear(); } return; } - - MethodDescriptor rpc = receiverManager.getMethodDescriptor(method); - if (rpc == null) { - send(writer, JsonRpcResult.error(id, new RpcError("Unknown RPC: " + method)), UID); - continue; - } - try { - /** If calling an {@link AsyncRpc}, put the message ID as the first param. */ - if (rpc.isAsync()) { - String callbackId = String.format("%d-%d", UID, id); - JSONArray newParams = new JSONArray(); - newParams.put(callbackId); - for (int i = 0; i < params.length(); i++) { - newParams.put(params.get(i)); - } - Object returnValue = rpc.invoke(receiverManager, newParams); - send(writer, JsonRpcResult.callback(id, returnValue, callbackId), UID); - } else { - Object returnValue = rpc.invoke(receiverManager, params); - send(writer, JsonRpcResult.result(id, returnValue), UID); - } - } catch (Throwable t) { - Log.e("Invocation error.", t); - send(writer, JsonRpcResult.error(id, t), UID); - } + JSONObject returnValue = mRpcUtil.invokeRpc(method, params, id, UID); + send(writer, returnValue, UID); } } diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/schedulerpc/ScheduleRpcSnippet.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/schedulerpc/ScheduleRpcSnippet.java new file mode 100644 index 0000000..88dea24 --- /dev/null +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/schedulerpc/ScheduleRpcSnippet.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2017 Google Inc. + * + * 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.google.android.mobly.snippet.schedulerpc; + +import com.google.android.mobly.snippet.Snippet; +import com.google.android.mobly.snippet.manager.SnippetManager; +import com.google.android.mobly.snippet.rpc.AsyncRpc; +import com.google.android.mobly.snippet.util.RpcUtil; +import org.json.JSONArray; + +/** Snippet that provides {@link AsyncRpc} to schedule other RPCs. */ +public class ScheduleRpcSnippet implements Snippet { + + private final SnippetManager mReceiverManager; + private final RpcUtil mRpcUtil; + + public ScheduleRpcSnippet() { + mReceiverManager = SnippetManager.getInstance(); + mRpcUtil = new RpcUtil(); + } + + @AsyncRpc(description = "Delay the given RPC by provided milli-seconds.") + public void scheduleRpc( + String callbackId, String methodName, long delayTimerMs, JSONArray params) + throws Throwable { + mRpcUtil.scheduleRpc(callbackId, methodName, delayTimerMs, params); + } + + @Override + public void shutdown() {} +} diff --git a/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/util/RpcUtil.java b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/util/RpcUtil.java new file mode 100644 index 0000000..7eba07f --- /dev/null +++ b/third_party/sl4a/src/main/java/com/google/android/mobly/snippet/util/RpcUtil.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2016 Google Inc. + * + * 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.google.android.mobly.snippet.util; + +import com.google.android.mobly.snippet.event.EventCache; +import com.google.android.mobly.snippet.event.SnippetEvent; +import com.google.android.mobly.snippet.manager.SnippetManager; +import com.google.android.mobly.snippet.rpc.JsonRpcResult; +import com.google.android.mobly.snippet.rpc.MethodDescriptor; +import com.google.android.mobly.snippet.rpc.RpcError; +import java.util.Timer; +import java.util.TimerTask; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * Class that implements APIs to schedule other RPCs. + * + * <p>If a device is required to be disconnected (e.g., USB power off), no RPCs can be made while + * device is offline. + * + * <p>However, We still need snippet continue to run and execute previously scheduled RPCs + * + * <p>The return value of the scheduled RPC is cached in {@link EventCache} and can be retrieved + * later after device is back online. + */ +public class RpcUtil { + // RPC ID is used for reporting responses back to the client. However, the results of + // scheduled RPCs are reported back to the client via events instead of through synchronous + // responses, so the RPC ID is unused. We pass an arbitrary value of 0. + private static final int DEFAULT_ID = 0; + private final SnippetManager mReceiverManager; + private final EventCache mEventCache = EventCache.getInstance(); + + public RpcUtil() { + mReceiverManager = SnippetManager.getInstance(); + } + + /** + * Schedule given RPC with some delay. + * + * @param callbackId The callback ID used to cache RPC results. + * @param methodName The RPC name to be scheduled. + * @param delayMs The delay in ms + * @param params Array of the parameters to the RPC + */ + public void scheduleRpc( + final String callbackId, + final String methodName, + final long delayMs, + final JSONArray params) + throws Throwable { + Timer timer = new Timer(); + TimerTask task = + new TimerTask() { + @Override + public void run() { + SnippetEvent event = new SnippetEvent(callbackId, methodName); + try { + JSONObject obj = invokeRpc(methodName, params, DEFAULT_ID, callbackId); + // Cache RPC method return value. + for (int i = 0; i < obj.names().length(); i++) { + String key = obj.names().getString(i); + event.getData().putString(key, obj.get(key).toString()); + } + } catch (JSONException e) { + String stackTrace = JsonRpcResult.getStackTrace(e); + event.getData().putString("error", stackTrace); + } finally { + mEventCache.postEvent(event); + } + } + }; + timer.schedule(task, delayMs); + } + + /** + * Invoke the RPC. + * + * @param methodName The RPC name to be invoked. + * @param params Array of the parameters to the RPC + * @param id The ID that identifies an RPC + * @param UID Globally unique session ID. + */ + public JSONObject invokeRpc(String methodName, JSONArray params, int id, Integer UID) + throws JSONException { + return invokeRpc(methodName, params, id, String.format("%d-%d", UID, id)); + } + + /** + * Invoke the RPC. + * + * @param methodName The RPC name to be invoked. + * @param params Array of the parameters to the RPC + * @param id The ID that identifies an RPC + * @param callbackId The callback ID used to cache RPC results. + */ + public JSONObject invokeRpc(String methodName, JSONArray params, int id, String callbackId) + throws JSONException { + MethodDescriptor rpc = mReceiverManager.getMethodDescriptor(methodName); + if (rpc == null) { + return JsonRpcResult.error(id, new RpcError("Unknown RPC: " + methodName)); + } + try { + JSONArray newParams = new JSONArray(); + /** If calling an {@link AsyncRpc}, put the message ID as the first param. */ + if (rpc.isAsync()) { + newParams.put(callbackId); + for (int i = 0; i < params.length(); i++) { + newParams.put(params.get(i)); + } + Object returnValue = rpc.invoke(mReceiverManager, newParams); + return JsonRpcResult.callback(id, returnValue, callbackId); + } else { + Object returnValue = rpc.invoke(mReceiverManager, params); + return JsonRpcResult.result(id, returnValue); + } + } catch (Throwable t) { + Log.e("Invocation error.", t); + return JsonRpcResult.error(id, t); + } + } +} |