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.

     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/. */
     6 package org.mozilla.gecko;
     8 import org.mozilla.gecko.util.GeckoEventListener;
    10 import org.json.JSONException;
    11 import org.json.JSONObject;
    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;
    19 import dalvik.system.DexClassLoader;
    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;
    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&lt;String, Handler.Callback&gt;. 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";
    53     private static JavaAddonManager sInstance;
    55     private final EventDispatcher mDispatcher;
    56     private final Map<String, Map<String, GeckoEventListener>> mAddonCallbacks;
    58     private Context mApplicationContext;
    60     public static JavaAddonManager getInstance() {
    61         if (sInstance == null) {
    62             sInstance = new JavaAddonManager();
    63         }
    64         return sInstance;
    65     }
    67     private JavaAddonManager() {
    68         mDispatcher = GeckoAppShell.getEventDispatcher();
    69         mAddonCallbacks = new HashMap<String, Map<String, GeckoEventListener>>();
    70     }
    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     }
    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     }
   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     }
   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     }
   141     private static class CallbackWrapper implements GeckoEventListener {
   142         private final Handler.Callback mDelegate;
   143         private Bundle mBundle;
   145         CallbackWrapper(Handler.Callback delegate) {
   146             mDelegate = delegate;
   147         }
   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         }
   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);
   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 }

mercurial