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