mobile/android/base/EventDispatcher.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko;
michael@0 6
michael@0 7 import org.mozilla.gecko.GeckoAppShell;
michael@0 8 import org.mozilla.gecko.GeckoEvent;
michael@0 9 import org.mozilla.gecko.util.EventCallback;
michael@0 10 import org.mozilla.gecko.util.GeckoEventListener;
michael@0 11 import org.mozilla.gecko.util.NativeEventListener;
michael@0 12 import org.mozilla.gecko.util.NativeJSContainer;
michael@0 13
michael@0 14 import org.json.JSONException;
michael@0 15 import org.json.JSONObject;
michael@0 16
michael@0 17 import android.util.Log;
michael@0 18
michael@0 19 import java.util.HashMap;
michael@0 20 import java.util.List;
michael@0 21 import java.util.Map;
michael@0 22 import java.util.concurrent.CopyOnWriteArrayList;
michael@0 23
michael@0 24 public final class EventDispatcher {
michael@0 25 private static final String LOGTAG = "GeckoEventDispatcher";
michael@0 26 private static final String GUID = "__guid__";
michael@0 27 private static final String STATUS_CANCEL = "cancel";
michael@0 28 private static final String STATUS_ERROR = "error";
michael@0 29 private static final String STATUS_SUCCESS = "success";
michael@0 30
michael@0 31 /**
michael@0 32 * The capacity of a HashMap is rounded up to the next power-of-2. Every time the size
michael@0 33 * of the map goes beyond 75% of the capacity, the map is rehashed. Therefore, to
michael@0 34 * empirically determine the initial capacity that avoids rehashing, we need to
michael@0 35 * determine the initial size, divide it by 75%, and round up to the next power-of-2.
michael@0 36 */
michael@0 37 private static final int GECKO_NATIVE_EVENTS_COUNT = 0; // Default for HashMap
michael@0 38 private static final int GECKO_JSON_EVENTS_COUNT = 256; // Empirically measured
michael@0 39
michael@0 40 private final Map<String, List<NativeEventListener>> mGeckoThreadNativeListeners =
michael@0 41 new HashMap<String, List<NativeEventListener>>(GECKO_NATIVE_EVENTS_COUNT);
michael@0 42 private final Map<String, List<GeckoEventListener>> mGeckoThreadJSONListeners =
michael@0 43 new HashMap<String, List<GeckoEventListener>>(GECKO_JSON_EVENTS_COUNT);
michael@0 44
michael@0 45 private <T> void registerListener(final Class<? extends List<T>> listType,
michael@0 46 final Map<String, List<T>> listenersMap,
michael@0 47 final T listener,
michael@0 48 final String[] events) {
michael@0 49 try {
michael@0 50 synchronized (listenersMap) {
michael@0 51 for (final String event : events) {
michael@0 52 List<T> listeners = listenersMap.get(event);
michael@0 53 if (listeners == null) {
michael@0 54 listeners = listType.newInstance();
michael@0 55 listenersMap.put(event, listeners);
michael@0 56 }
michael@0 57 if (!AppConstants.RELEASE_BUILD && listeners.contains(listener)) {
michael@0 58 throw new IllegalStateException("Already registered " + event);
michael@0 59 }
michael@0 60 listeners.add(listener);
michael@0 61 }
michael@0 62 }
michael@0 63 } catch (final IllegalAccessException e) {
michael@0 64 throw new IllegalArgumentException("Invalid new list type", e);
michael@0 65 } catch (final InstantiationException e) {
michael@0 66 throw new IllegalArgumentException("Invalid new list type", e);
michael@0 67 }
michael@0 68 }
michael@0 69
michael@0 70 private <T> void checkNotRegistered(final Map<String, List<T>> listenersMap,
michael@0 71 final String[] events) {
michael@0 72 synchronized (listenersMap) {
michael@0 73 for (final String event: events) {
michael@0 74 if (listenersMap.get(event) != null) {
michael@0 75 throw new IllegalStateException(
michael@0 76 "Already registered " + event + " under a different type");
michael@0 77 }
michael@0 78 }
michael@0 79 }
michael@0 80 }
michael@0 81
michael@0 82 private <T> void unregisterListener(final Map<String, List<T>> listenersMap,
michael@0 83 final T listener,
michael@0 84 final String[] events) {
michael@0 85 synchronized (listenersMap) {
michael@0 86 for (final String event : events) {
michael@0 87 List<T> listeners = listenersMap.get(event);
michael@0 88 if ((listeners == null ||
michael@0 89 !listeners.remove(listener)) && !AppConstants.RELEASE_BUILD) {
michael@0 90 throw new IllegalArgumentException(event + " was not registered");
michael@0 91 }
michael@0 92 }
michael@0 93 }
michael@0 94 }
michael@0 95
michael@0 96 @SuppressWarnings("unchecked")
michael@0 97 public void registerGeckoThreadListener(final NativeEventListener listener,
michael@0 98 final String... events) {
michael@0 99 checkNotRegistered(mGeckoThreadJSONListeners, events);
michael@0 100
michael@0 101 // For listeners running on the Gecko thread, we want to notify the listeners
michael@0 102 // outside of our synchronized block, because the listeners may take an
michael@0 103 // indeterminate amount of time to run. Therefore, to ensure concurrency when
michael@0 104 // iterating the list outside of the synchronized block, we use a
michael@0 105 // CopyOnWriteArrayList.
michael@0 106 registerListener((Class)CopyOnWriteArrayList.class,
michael@0 107 mGeckoThreadNativeListeners, listener, events);
michael@0 108 }
michael@0 109
michael@0 110 @Deprecated // Use NativeEventListener instead
michael@0 111 @SuppressWarnings("unchecked")
michael@0 112 private void registerGeckoThreadListener(final GeckoEventListener listener,
michael@0 113 final String... events) {
michael@0 114 checkNotRegistered(mGeckoThreadNativeListeners, events);
michael@0 115
michael@0 116 registerListener((Class)CopyOnWriteArrayList.class,
michael@0 117 mGeckoThreadJSONListeners, listener, events);
michael@0 118 }
michael@0 119
michael@0 120 public void unregisterGeckoThreadListener(final NativeEventListener listener,
michael@0 121 final String... events) {
michael@0 122 unregisterListener(mGeckoThreadNativeListeners, listener, events);
michael@0 123 }
michael@0 124
michael@0 125 @Deprecated // Use NativeEventListener instead
michael@0 126 private void unregisterGeckoThreadListener(final GeckoEventListener listener,
michael@0 127 final String... events) {
michael@0 128 unregisterListener(mGeckoThreadJSONListeners, listener, events);
michael@0 129 }
michael@0 130
michael@0 131 @Deprecated // Use one of the variants above.
michael@0 132 public void registerEventListener(final String event, final GeckoEventListener listener) {
michael@0 133 registerGeckoThreadListener(listener, event);
michael@0 134 }
michael@0 135
michael@0 136 @Deprecated // Use one of the variants above
michael@0 137 public void unregisterEventListener(final String event, final GeckoEventListener listener) {
michael@0 138 unregisterGeckoThreadListener(listener, event);
michael@0 139 }
michael@0 140
michael@0 141 public void dispatchEvent(final NativeJSContainer message) {
michael@0 142 EventCallback callback = null;
michael@0 143 try {
michael@0 144 // First try native listeners.
michael@0 145 final String type = message.getString("type");
michael@0 146
michael@0 147 final List<NativeEventListener> listeners;
michael@0 148 synchronized (mGeckoThreadNativeListeners) {
michael@0 149 listeners = mGeckoThreadNativeListeners.get(type);
michael@0 150 }
michael@0 151
michael@0 152 final String guid = message.optString(GUID, null);
michael@0 153 if (guid != null) {
michael@0 154 callback = new GeckoEventCallback(guid, type);
michael@0 155 }
michael@0 156
michael@0 157 if (listeners != null) {
michael@0 158 if (listeners.size() == 0) {
michael@0 159 Log.w(LOGTAG, "No listeners for " + type);
michael@0 160 }
michael@0 161 for (final NativeEventListener listener : listeners) {
michael@0 162 listener.handleMessage(type, message, callback);
michael@0 163 }
michael@0 164 // If we found native listeners, we assume we don't have any JSON listeners
michael@0 165 // and return early. This assumption is checked when registering listeners.
michael@0 166 return;
michael@0 167 }
michael@0 168 } catch (final IllegalArgumentException e) {
michael@0 169 // Message doesn't have a "type" property, fallback to JSON
michael@0 170 }
michael@0 171 try {
michael@0 172 // If we didn't find native listeners, try JSON listeners.
michael@0 173 dispatchEvent(new JSONObject(message.toString()), callback);
michael@0 174 } catch (final JSONException e) {
michael@0 175 Log.e(LOGTAG, "Cannot parse JSON");
michael@0 176 } catch (final UnsupportedOperationException e) {
michael@0 177 Log.e(LOGTAG, "Cannot convert message to JSON");
michael@0 178 }
michael@0 179 }
michael@0 180
michael@0 181 public void dispatchEvent(final JSONObject message, final EventCallback callback) {
michael@0 182 // {
michael@0 183 // "type": "value",
michael@0 184 // "event_specific": "value",
michael@0 185 // ...
michael@0 186 try {
michael@0 187 final String type = message.getString("type");
michael@0 188
michael@0 189 List<GeckoEventListener> listeners;
michael@0 190 synchronized (mGeckoThreadJSONListeners) {
michael@0 191 listeners = mGeckoThreadJSONListeners.get(type);
michael@0 192 }
michael@0 193 if (listeners == null || listeners.size() == 0) {
michael@0 194 Log.w(LOGTAG, "No listeners for " + type);
michael@0 195
michael@0 196 // If there are no listeners, cancel the callback to prevent Gecko-side observers
michael@0 197 // from being leaked.
michael@0 198 if (callback != null) {
michael@0 199 callback.sendCancel();
michael@0 200 }
michael@0 201 return;
michael@0 202 }
michael@0 203 for (final GeckoEventListener listener : listeners) {
michael@0 204 listener.handleMessage(type, message);
michael@0 205 }
michael@0 206 } catch (final JSONException e) {
michael@0 207 Log.e(LOGTAG, "handleGeckoMessage throws " + e, e);
michael@0 208 }
michael@0 209 }
michael@0 210
michael@0 211 @Deprecated
michael@0 212 public static void sendResponse(JSONObject message, Object response) {
michael@0 213 sendResponseHelper(STATUS_SUCCESS, message, response);
michael@0 214 }
michael@0 215
michael@0 216 @Deprecated
michael@0 217 public static void sendError(JSONObject message, Object response) {
michael@0 218 sendResponseHelper(STATUS_ERROR, message, response);
michael@0 219 }
michael@0 220
michael@0 221 @Deprecated
michael@0 222 private static void sendResponseHelper(String status, JSONObject message, Object response) {
michael@0 223 try {
michael@0 224 final JSONObject wrapper = new JSONObject();
michael@0 225 wrapper.put(GUID, message.getString(GUID));
michael@0 226 wrapper.put("status", status);
michael@0 227 wrapper.put("response", response);
michael@0 228 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(
michael@0 229 message.getString("type") + ":Response", wrapper.toString()));
michael@0 230 } catch (final JSONException e) {
michael@0 231 Log.e(LOGTAG, "Unable to send response", e);
michael@0 232 }
michael@0 233 }
michael@0 234
michael@0 235 private static class GeckoEventCallback implements EventCallback {
michael@0 236 private final String guid;
michael@0 237 private final String type;
michael@0 238 private boolean sent;
michael@0 239
michael@0 240 public GeckoEventCallback(final String guid, final String type) {
michael@0 241 this.guid = guid;
michael@0 242 this.type = type;
michael@0 243 }
michael@0 244
michael@0 245 public void sendSuccess(final Object response) {
michael@0 246 sendResponse(STATUS_SUCCESS, response);
michael@0 247 }
michael@0 248
michael@0 249 public void sendError(final Object response) {
michael@0 250 sendResponse(STATUS_ERROR, response);
michael@0 251 }
michael@0 252
michael@0 253 public void sendCancel() {
michael@0 254 sendResponse(STATUS_CANCEL, null);
michael@0 255 }
michael@0 256
michael@0 257 private void sendResponse(final String status, final Object response) {
michael@0 258 if (sent) {
michael@0 259 throw new IllegalStateException("Callback has already been executed for type=" +
michael@0 260 type + ", guid=" + guid);
michael@0 261 }
michael@0 262
michael@0 263 sent = true;
michael@0 264
michael@0 265 try {
michael@0 266 final JSONObject wrapper = new JSONObject();
michael@0 267 wrapper.put(GUID, guid);
michael@0 268 wrapper.put("status", status);
michael@0 269 wrapper.put("response", response);
michael@0 270 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(type + ":Response",
michael@0 271 wrapper.toString()));
michael@0 272 } catch (final JSONException e) {
michael@0 273 Log.e(LOGTAG, "Unable to send response for: " + type, e);
michael@0 274 }
michael@0 275 }
michael@0 276 }
michael@0 277 }

mercurial