mobile/android/base/JavaAddonManager.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
michael@0 2 * This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 package org.mozilla.gecko;
michael@0 7
michael@0 8 import org.mozilla.gecko.util.GeckoEventListener;
michael@0 9
michael@0 10 import org.json.JSONException;
michael@0 11 import org.json.JSONObject;
michael@0 12
michael@0 13 import android.content.Context;
michael@0 14 import android.os.Bundle;
michael@0 15 import android.os.Handler;
michael@0 16 import android.os.Message;
michael@0 17 import android.util.Log;
michael@0 18
michael@0 19 import dalvik.system.DexClassLoader;
michael@0 20
michael@0 21 import java.io.File;
michael@0 22 import java.lang.reflect.Constructor;
michael@0 23 import java.util.HashMap;
michael@0 24 import java.util.Iterator;
michael@0 25 import java.util.Map;
michael@0 26
michael@0 27 /**
michael@0 28 * The manager for addon-provided Java code.
michael@0 29 *
michael@0 30 * Java code in addons can be loaded using the Dex:Load message, and unloaded
michael@0 31 * via the Dex:Unload message. Addon classes loaded are checked for a constructor
michael@0 32 * that takes a Map&lt;String, Handler.Callback&gt;. If such a constructor
michael@0 33 * exists, it is called and the objects populated into the map by the constructor
michael@0 34 * are registered as event listeners. If no such constructor exists, the default
michael@0 35 * constructor is invoked instead.
michael@0 36 *
michael@0 37 * Note: The Map and Handler.Callback classes were used in this API definition
michael@0 38 * rather than defining a custom class. This was done explicitly so that the
michael@0 39 * addon code can be compiled against the android.jar provided in the Android
michael@0 40 * SDK, rather than having to be compiled against Fennec source code.
michael@0 41 *
michael@0 42 * The Handler.Callback instances provided (as described above) are inovked with
michael@0 43 * Message objects when the corresponding events are dispatched. The Bundle
michael@0 44 * object attached to the Message will contain the "primitive" values from the
michael@0 45 * JSON of the event. ("primitive" includes bool/int/long/double/String). If
michael@0 46 * the addon callback wishes to synchronously return a value back to the event
michael@0 47 * dispatcher, they can do so by inserting the response string into the bundle
michael@0 48 * under the key "response".
michael@0 49 */
michael@0 50 class JavaAddonManager implements GeckoEventListener {
michael@0 51 private static final String LOGTAG = "GeckoJavaAddonManager";
michael@0 52
michael@0 53 private static JavaAddonManager sInstance;
michael@0 54
michael@0 55 private final EventDispatcher mDispatcher;
michael@0 56 private final Map<String, Map<String, GeckoEventListener>> mAddonCallbacks;
michael@0 57
michael@0 58 private Context mApplicationContext;
michael@0 59
michael@0 60 public static JavaAddonManager getInstance() {
michael@0 61 if (sInstance == null) {
michael@0 62 sInstance = new JavaAddonManager();
michael@0 63 }
michael@0 64 return sInstance;
michael@0 65 }
michael@0 66
michael@0 67 private JavaAddonManager() {
michael@0 68 mDispatcher = GeckoAppShell.getEventDispatcher();
michael@0 69 mAddonCallbacks = new HashMap<String, Map<String, GeckoEventListener>>();
michael@0 70 }
michael@0 71
michael@0 72 void init(Context applicationContext) {
michael@0 73 if (mApplicationContext != null) {
michael@0 74 // we've already done this registration. don't do it again
michael@0 75 return;
michael@0 76 }
michael@0 77 mApplicationContext = applicationContext;
michael@0 78 mDispatcher.registerEventListener("Dex:Load", this);
michael@0 79 mDispatcher.registerEventListener("Dex:Unload", this);
michael@0 80 }
michael@0 81
michael@0 82 @Override
michael@0 83 public void handleMessage(String event, JSONObject message) {
michael@0 84 try {
michael@0 85 if (event.equals("Dex:Load")) {
michael@0 86 String zipFile = message.getString("zipfile");
michael@0 87 String implClass = message.getString("impl");
michael@0 88 Log.d(LOGTAG, "Attempting to load classes.dex file from " + zipFile + " and instantiate " + implClass);
michael@0 89 try {
michael@0 90 File tmpDir = mApplicationContext.getDir("dex", 0);
michael@0 91 DexClassLoader loader = new DexClassLoader(zipFile, tmpDir.getAbsolutePath(), null, mApplicationContext.getClassLoader());
michael@0 92 Class<?> c = loader.loadClass(implClass);
michael@0 93 try {
michael@0 94 Constructor<?> constructor = c.getDeclaredConstructor(Map.class);
michael@0 95 Map<String, Handler.Callback> callbacks = new HashMap<String, Handler.Callback>();
michael@0 96 constructor.newInstance(callbacks);
michael@0 97 registerCallbacks(zipFile, callbacks);
michael@0 98 } catch (NoSuchMethodException nsme) {
michael@0 99 Log.d(LOGTAG, "Did not find constructor with parameters Map<String, Handler.Callback>. Falling back to default constructor...");
michael@0 100 // fallback for instances with no constructor that takes a Map<String, Handler.Callback>
michael@0 101 c.newInstance();
michael@0 102 }
michael@0 103 } catch (Exception e) {
michael@0 104 Log.e(LOGTAG, "Unable to load dex successfully", e);
michael@0 105 }
michael@0 106 } else if (event.equals("Dex:Unload")) {
michael@0 107 String zipFile = message.getString("zipfile");
michael@0 108 unregisterCallbacks(zipFile);
michael@0 109 }
michael@0 110 } catch (JSONException e) {
michael@0 111 Log.e(LOGTAG, "Exception handling message [" + event + "]:", e);
michael@0 112 }
michael@0 113 }
michael@0 114
michael@0 115 private void registerCallbacks(String zipFile, Map<String, Handler.Callback> callbacks) {
michael@0 116 Map<String, GeckoEventListener> addonCallbacks = mAddonCallbacks.get(zipFile);
michael@0 117 if (addonCallbacks != null) {
michael@0 118 Log.w(LOGTAG, "Found pre-existing callbacks for zipfile [" + zipFile + "]; aborting re-registration!");
michael@0 119 return;
michael@0 120 }
michael@0 121 addonCallbacks = new HashMap<String, GeckoEventListener>();
michael@0 122 for (String event : callbacks.keySet()) {
michael@0 123 CallbackWrapper wrapper = new CallbackWrapper(callbacks.get(event));
michael@0 124 mDispatcher.registerEventListener(event, wrapper);
michael@0 125 addonCallbacks.put(event, wrapper);
michael@0 126 }
michael@0 127 mAddonCallbacks.put(zipFile, addonCallbacks);
michael@0 128 }
michael@0 129
michael@0 130 private void unregisterCallbacks(String zipFile) {
michael@0 131 Map<String, GeckoEventListener> callbacks = mAddonCallbacks.remove(zipFile);
michael@0 132 if (callbacks == null) {
michael@0 133 Log.w(LOGTAG, "Attempting to unregister callbacks from zipfile [" + zipFile + "] which has no callbacks registered.");
michael@0 134 return;
michael@0 135 }
michael@0 136 for (String event : callbacks.keySet()) {
michael@0 137 mDispatcher.unregisterEventListener(event, callbacks.get(event));
michael@0 138 }
michael@0 139 }
michael@0 140
michael@0 141 private static class CallbackWrapper implements GeckoEventListener {
michael@0 142 private final Handler.Callback mDelegate;
michael@0 143 private Bundle mBundle;
michael@0 144
michael@0 145 CallbackWrapper(Handler.Callback delegate) {
michael@0 146 mDelegate = delegate;
michael@0 147 }
michael@0 148
michael@0 149 private Bundle jsonToBundle(JSONObject json) {
michael@0 150 // XXX right now we only support primitive types;
michael@0 151 // we don't recurse down into JSONArray or JSONObject instances
michael@0 152 Bundle b = new Bundle();
michael@0 153 for (Iterator<?> keys = json.keys(); keys.hasNext(); ) {
michael@0 154 try {
michael@0 155 String key = (String)keys.next();
michael@0 156 Object value = json.get(key);
michael@0 157 if (value instanceof Integer) {
michael@0 158 b.putInt(key, (Integer)value);
michael@0 159 } else if (value instanceof String) {
michael@0 160 b.putString(key, (String)value);
michael@0 161 } else if (value instanceof Boolean) {
michael@0 162 b.putBoolean(key, (Boolean)value);
michael@0 163 } else if (value instanceof Long) {
michael@0 164 b.putLong(key, (Long)value);
michael@0 165 } else if (value instanceof Double) {
michael@0 166 b.putDouble(key, (Double)value);
michael@0 167 }
michael@0 168 } catch (JSONException e) {
michael@0 169 Log.d(LOGTAG, "Error during JSON->bundle conversion", e);
michael@0 170 }
michael@0 171 }
michael@0 172 return b;
michael@0 173 }
michael@0 174
michael@0 175 @Override
michael@0 176 public void handleMessage(String event, JSONObject json) {
michael@0 177 try {
michael@0 178 if (mBundle != null) {
michael@0 179 Log.w(LOGTAG, "Event [" + event + "] handler is re-entrant; response messages may be lost");
michael@0 180 }
michael@0 181 mBundle = jsonToBundle(json);
michael@0 182 Message msg = new Message();
michael@0 183 msg.setData(mBundle);
michael@0 184 mDelegate.handleMessage(msg);
michael@0 185
michael@0 186 JSONObject obj = new JSONObject();
michael@0 187 obj.put("response", mBundle.getString("response"));
michael@0 188 EventDispatcher.sendResponse(json, obj);
michael@0 189 mBundle = null;
michael@0 190 } catch (Exception e) {
michael@0 191 Log.e(LOGTAG, "Caught exception thrown from wrapped addon message handler", e);
michael@0 192 }
michael@0 193 }
michael@0 194 }
michael@0 195 }

mercurial