| Index: mobile/android/thirdparty/org/adblockplus/browser/AddOnBridge.java |
| =================================================================== |
| --- a/mobile/android/thirdparty/org/adblockplus/browser/AddOnBridge.java |
| +++ b/mobile/android/thirdparty/org/adblockplus/browser/AddOnBridge.java |
| @@ -13,36 +13,32 @@ |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| package org.adblockplus.browser; |
| import java.util.ArrayList; |
| -import java.util.HashMap; |
| import java.util.List; |
| -import java.util.Map; |
| import android.annotation.SuppressLint; |
| import android.content.Context; |
| import android.content.SharedPreferences; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.util.Log; |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| -import org.json.JSONObject; |
| import org.mozilla.gecko.EventDispatcher; |
| import org.mozilla.gecko.GeckoAppShell; |
| +import org.mozilla.gecko.util.BundleEventListener; |
| import org.mozilla.gecko.util.EventCallback; |
| -import org.mozilla.gecko.util.GeckoRequest; |
| -import org.mozilla.gecko.util.NativeEventListener; |
| -import org.mozilla.gecko.util.NativeJSObject; |
| +import org.mozilla.gecko.util.GeckoBundle; |
| @SuppressLint("DefaultLocale") |
| public class AddOnBridge |
| { |
| private static final String TAG = "AdblockBrowser.AddOnBridge"; |
| private static final String REQUEST_NAME = "AdblockPlus:Api"; |
| // Handler+HandlerThread for posting delayed re-tries without interfering with |
| // other threads (e.g. the UI or Gecko thread) |
| @@ -53,17 +49,17 @@ public class AddOnBridge |
| private static final Handler GLOBAL_HANDLER; |
| // Sometimes, the app is killed before the extension is able to save all changes regarding |
| // AddOnBridge requests. Given that, we need to store the pending requests on SharedPrefs, |
| // so we can resend them to the extension once the app restarts |
| // See https://issues.adblockplus.org/ticket/2853 |
| private static final AddOnEventListener ADD_ON_EVENT_LISTENER = new AddOnEventListener(); |
| private static final String ON_FILTERS_LOAD_EVENT = "Abb:OnFiltersLoad"; |
| private static final String ON_FILTERS_SAVE_EVENT = "Abb:OnFiltersSave"; |
| - private static final List<AddOnRequest> PENDING_REQUESTS = new ArrayList<>(); |
| + private static final List<GeckoBundle> PENDING_REQUESTS = new ArrayList<>(); |
| private static final String PENDING_REQUESTS_PREFS_KEY = "PENDING_REQUESTS_PREFS_KEY"; |
| private static SharedPreferences sharedPrefs; |
| private static boolean filtersLoaded; |
| static |
| { |
| PRIVATE_HANDLER_THREAD = new HandlerThread("abp-private-handler"); |
| @@ -75,72 +71,186 @@ public class AddOnBridge |
| GLOBAL_HANDLER_THREAD.setDaemon(true); |
| GLOBAL_HANDLER_THREAD.start(); |
| GLOBAL_HANDLER = new Handler(GLOBAL_HANDLER_THREAD.getLooper()); |
| } |
| public static void init(Context context) |
| { |
| sharedPrefs = context.getSharedPreferences(AddOnBridge.class.getName(), Context.MODE_PRIVATE); |
| - EventDispatcher.getInstance().registerGeckoThreadListener(ADD_ON_EVENT_LISTENER, ON_FILTERS_LOAD_EVENT, ON_FILTERS_SAVE_EVENT); |
| + EventDispatcher.getInstance().registerGeckoThreadListener( |
| + ADD_ON_EVENT_LISTENER, ON_FILTERS_LOAD_EVENT, ON_FILTERS_SAVE_EVENT); |
| loadPendingRequests(); |
| } |
| public static void postToHandler(Runnable runnable) |
| { |
| GLOBAL_HANDLER.post(runnable); |
| } |
| public static void postToHandlerDelayed(Runnable runnable, long delayMillis) |
| { |
| GLOBAL_HANDLER.postDelayed(runnable, delayMillis); |
| } |
| + public static void queryValue(String name, AdblockPlusApiCallback callback) |
| + { |
| + Log.d(TAG, "queryValue for " + name); |
| + callFunction("get" + makeFirstCharacterUppercase(name), null, callback); |
| + } |
| + |
| + public static void setBoolean(String name, boolean enable, AdblockPlusApiCallback callback) |
| + { |
| + Log.d(TAG, "setBoolean " + enable + " for " + name); |
| + final GeckoBundle data = new GeckoBundle(); |
| + data.putBoolean("enable", enable); |
| + callFunction("set" + makeFirstCharacterUppercase(name), data, true, callback); |
| + } |
| + |
| + public static void querySubscriptionListStatus(String url, AdblockPlusApiCallback callback) |
| + { |
| + Log.d(TAG, "querySubscriptionListStatus for " + url); |
| + final GeckoBundle data = new GeckoBundle(); |
| + data.putString("url", url); |
| + callFunction("isSubscriptionListed", data, callback); |
| + } |
| + |
| + public static void addSubscription(String url, String title, AdblockPlusApiCallback callback) |
| + { |
| + Log.d(TAG, "addSubscription for " + url + " (" + title + ")"); |
| + final GeckoBundle data = new GeckoBundle(); |
| + data.putString("url", url); |
| + if (title != null) |
| + { |
| + data.putString("title", title); |
| + } |
| + callFunction("addSubscription", data, true, callback); |
| + } |
| + |
| + public static void queryActiveSubscriptions(AdblockPlusApiCallback callback) |
| + { |
| + Log.d(TAG, "queryActiveSubscriptions"); |
| + callFunction("getActiveSubscriptions", null, callback); |
| + } |
| + |
| + public static void removeSubscription(String url, AdblockPlusApiCallback callback) |
| + { |
| + Log.d(TAG, "removeSubscription for " + url); |
| + final GeckoBundle data = new GeckoBundle(); |
| + data.putString("url", url); |
| + callFunction("removeSubscription", data, true, callback); |
| + } |
| + |
| + public static void queryIsLocal(String url, AdblockPlusApiCallback callback) |
| + { |
| + Log.d(TAG, "queryIsLocal for " + url); |
| + final GeckoBundle data = new GeckoBundle(); |
| + data.putString("url", url); |
| + callFunction("isLocal", data, callback); |
| + } |
| + |
| + public static void queryIsPageWhitelisted(String url, AdblockPlusApiCallback callback) |
| + { |
| + Log.d(TAG, "queryIsPageWhitelisted for " + url); |
| + final GeckoBundle data = new GeckoBundle(); |
| + data.putString("url", url); |
| + callFunction("isPageWhitelisted", data, callback); |
| + } |
| + |
| + public static void whitelistSite(String url, boolean whitelisted, AdblockPlusApiCallback callback) |
| + { |
| + Log.d(TAG, "whitelistSite for " + url); |
| + final GeckoBundle data = new GeckoBundle(); |
| + data.putString("url", url); |
| + data.putBoolean("whitelisted", whitelisted); |
| + callFunction("whitelistSite", data, true, callback); |
| + } |
| + |
| + private static String makeFirstCharacterUppercase(String str) |
| + { |
| + if (Character.isUpperCase(str.charAt(0))) |
| + { |
| + return str; |
| + } |
| + return Character.toString(Character.toUpperCase(str.charAt(0))) + str.substring(1); |
| + } |
| + |
| + private static void storeStringPref(String key, String value) |
| + { |
| + final SharedPreferences.Editor editor = sharedPrefs.edit(); |
| + editor.putString(key, value); |
| + editor.commit(); |
| + } |
| + |
| + private static List<GeckoBundle> jsonStringToRequestList(String jsonString) |
| + { |
| + final List<GeckoBundle> requestList = new ArrayList<>(); |
| + if (jsonString == null) |
| + { |
| + return requestList; |
| + } |
| + try |
| + { |
| + final JSONArray jsonArray = new JSONArray(jsonString); |
| + for (int i = 0; i < jsonArray.length(); i++) |
| + { |
| + final GeckoBundle request = GeckoBundle.fromJSONObject(jsonArray.getJSONObject(i)); |
| + requestList.add(request); |
| + } |
| + } |
| + catch (JSONException e) |
| + { |
| + Log.e(TAG, "Failed to parse json to request list with error: " + e.getMessage(), e); |
| + } |
| + return requestList; |
| + } |
| + |
| private static void loadPendingRequests() |
| { |
| PRIVATE_HANDLER.post(new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| final String jsonString = sharedPrefs.getString(PENDING_REQUESTS_PREFS_KEY, null); |
| PENDING_REQUESTS.addAll(0, jsonStringToRequestList(jsonString)); |
| } |
| }); |
| } |
| - private static void sendOrEnqueueRequest(final AddOnRequest request) |
| + private static void sendOrEnqueueRequest(final GeckoBundle request, |
| + final AdblockPlusApiCallback callback) |
| { |
| PRIVATE_HANDLER.post(new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| if (!filtersLoaded) |
| { |
| PENDING_REQUESTS.add(request); |
| } |
| else |
| { |
| - GeckoAppShell.sendRequestToGecko(request); |
| + dispatchRequestToGecko(request, callback); |
| } |
| } |
| }); |
| } |
| private static void sendPendingRequests() |
| { |
| PRIVATE_HANDLER.post(new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| - for (final AddOnRequest request : PENDING_REQUESTS) |
| + for (final GeckoBundle request : PENDING_REQUESTS) |
| { |
| - GeckoAppShell.sendRequestToGecko(request); |
| + dispatchRequestToGecko(request); |
| } |
| PENDING_REQUESTS.clear(); |
| } |
| }); |
| } |
| private static void clearPendingRequests() |
| { |
| @@ -149,315 +259,100 @@ public class AddOnBridge |
| @Override |
| public void run() |
| { |
| storeStringPref(PENDING_REQUESTS_PREFS_KEY, null); |
| } |
| }); |
| } |
| - private static void storePendingRequest(final AddOnRequest request) |
| + private static void storePendingRequest(final GeckoBundle request) |
| { |
| PRIVATE_HANDLER.post(new Runnable() |
| { |
| @Override |
| public void run() |
| { |
| - final String jsonString = sharedPrefs.getString(PENDING_REQUESTS_PREFS_KEY, null); |
| + final String jsonStr = sharedPrefs.getString(PENDING_REQUESTS_PREFS_KEY, null); |
| try |
| { |
| - final JSONArray jsonArray = jsonString != null ? new JSONArray(jsonString) : new JSONArray(); |
| - jsonArray.put(request.value); |
| + final JSONArray jsonArray = jsonStr != null ? new JSONArray(jsonStr) : new JSONArray(); |
| + jsonArray.put(request.toJSONObject()); |
| storeStringPref(PENDING_REQUESTS_PREFS_KEY, jsonArray.toString()); |
| } |
| catch (JSONException e) |
| { |
| Log.e(TAG, "Failed to store pending request with error: " + e.getMessage(), e); |
| } |
| } |
| }); |
| } |
| - private static List<AddOnRequest> jsonStringToRequestList(final String jsonString) |
| - { |
| - final List<AddOnRequest> requestList = new ArrayList<>(); |
| - if (jsonString == null) |
| - { |
| - return requestList; |
| - } |
| - try |
| - { |
| - final JSONArray jsonArray = new JSONArray(jsonString); |
| - for (int i = 0; i < jsonArray.length(); i++) |
| - { |
| - final AddOnRequest request = new AddOnRequest(jsonArray.getJSONObject(i), null); |
| - requestList.add(request); |
| - } |
| - } |
| - catch (JSONException e) |
| - { |
| - Log.e(TAG, "Failed to parse json to request list with error: " + e.getMessage(), e); |
| - } |
| - return requestList; |
| - } |
| - |
| - private static void storeStringPref(String key, String value) |
| - { |
| - final SharedPreferences.Editor editor = sharedPrefs.edit(); |
| - editor.putString(key, value); |
| - editor.commit(); |
| - } |
| - |
| - public static boolean getBooleanFromJsObject(final NativeJSObject obj, final String name, |
| - final boolean defaultValue) |
| - { |
| - try |
| - { |
| - return obj.getBoolean(name); |
| - } |
| - catch (final Exception e) |
| - { |
| - return defaultValue; |
| - } |
| - } |
| - |
| - public static String getStringFromJsObject(final NativeJSObject obj, final String name, |
| - final String defaultValue) |
| - { |
| - try |
| - { |
| - return obj.getString(name); |
| - } |
| - catch (final Exception e) |
| - { |
| - return defaultValue; |
| - } |
| - } |
| - |
| - private static JSONObject createRequestData(final String action) |
| - { |
| - final JSONObject obj = new JSONObject(); |
| - try |
| - { |
| - obj.put("action", action); |
| - } |
| - catch (JSONException e) |
| - { |
| - // we're only adding sane objects |
| - Log.e(TAG, "Creating request data failed with: " + e.getMessage(), e); |
| - } |
| - return obj; |
| - } |
| - |
| - private static JSONObject createRequestData(final String action, final boolean enable) |
| - { |
| - final JSONObject obj = createRequestData(action); |
| - try |
| - { |
| - obj.put("enable", enable); |
| - } |
| - catch (JSONException e) |
| - { |
| - // we're only adding sane objects |
| - Log.e(TAG, "Creating request data failed with: " + e.getMessage(), e); |
| - } |
| - return obj; |
| - } |
| - |
| - public static String makeFirstCharacterUppercase(String str) |
| - { |
| - if (Character.isUpperCase(str.charAt(0))) |
| - { |
| - return str; |
| - } |
| - return Character.toString(Character.toUpperCase(str.charAt(0))) + str.substring(1); |
| - } |
| - |
| - public static void queryValue(final AdblockPlusApiCallback callback, final String name) |
| - { |
| - Log.d(TAG, "queryValue for " + name); |
| - final AddOnRequest request = |
| - new AddOnRequest(createRequestData("get" + makeFirstCharacterUppercase(name)), callback); |
| - sendOrEnqueueRequest(request); |
| - } |
| - |
| - public static void setBoolean(final AdblockPlusApiCallback callback, final String name, |
| - final boolean enable) |
| - { |
| - Log.d(TAG, "setBoolean " + enable + " for " + name); |
| - final AddOnRequest request = |
| - new AddOnRequest(createRequestData("set" + makeFirstCharacterUppercase(name), enable), callback); |
| - sendOrEnqueueRequest(request); |
| - storePendingRequest(request); |
| - } |
| - |
| - private static void callFunction(final AdblockPlusApiCallback callback, final String name, |
| - final Map<String, Object> parameters) |
| + private static void callFunction(String name, GeckoBundle data, AdblockPlusApiCallback callback) |
| { |
| // By default, requests are not stored on the pending request prefs. This should apply for |
| // requests that doesn't result in save operations performed by the extension |
| - callFunction(callback, name, parameters, false); |
| + callFunction(name, data, false, callback); |
| } |
| - private static void callFunction(final AdblockPlusApiCallback callback, final String name, |
| - final Map<String, Object> parameters, boolean resendIfAborted) |
| + private static void callFunction(String name, GeckoBundle data, boolean resendIfAborted, |
| + AdblockPlusApiCallback callback) |
| { |
| - final JSONObject requestData = createRequestData(name); |
| - try |
| + if (data == null) |
| { |
| - for (Map.Entry<String, Object> entry : parameters.entrySet()) |
| - { |
| - requestData.put(entry.getKey(), entry.getValue()); |
| - } |
| + data = new GeckoBundle(); |
| } |
| - catch (JSONException e) |
| - { |
| - // we're only adding sane objects |
| - Log.e(TAG, "Creating request data failed with: " + e.getMessage(), e); |
| - } |
| - final AddOnRequest request = new AddOnRequest(requestData, callback); |
| - sendOrEnqueueRequest(request); |
| + data.putString("action", name); |
| + sendOrEnqueueRequest(data, callback); |
| if (resendIfAborted) |
| { |
| - storePendingRequest(request); |
| + storePendingRequest(data); |
| } |
| } |
| - public static void querySubscriptionListStatus(final AdblockPlusApiCallback callback, |
| - final String url) |
| - { |
| - Log.d(TAG, "querySubscriptionListStatus for " + url); |
| - final Map<String, Object> parameters = new HashMap<String, Object>(); |
| - parameters.put("url", url); |
| - callFunction(callback, "isSubscriptionListed", parameters); |
| - } |
| - |
| - public static void addSubscription(final AdblockPlusApiCallback callback, |
| - final String url, final String title) |
| - { |
| - Log.d(TAG, "addSubscription for " + url + " (" + title + ")"); |
| - final Map<String, Object> parameters = new HashMap<String, Object>(); |
| - parameters.put("url", url); |
| - if (title != null) |
| - { |
| - parameters.put("title", title); |
| - } |
| - callFunction(callback, "addSubscription", parameters, true); |
| - } |
| - |
| - public static void queryActiveSubscriptions(final AdblockPlusApiCallback callback) |
| - { |
| - Log.d(TAG, "queryActiveSubscriptions"); |
| - final Map<String, Object> parameters = new HashMap<String, Object>(); |
| - callFunction(callback, "getActiveSubscriptions", parameters); |
| - } |
| - |
| - public static void removeSubscription(final AdblockPlusApiCallback callback, |
| - final String url) |
| + private static void dispatchRequestToGecko(GeckoBundle request) |
| { |
| - Log.d(TAG, "removeSubscription for " + url); |
| - final Map<String, Object> parameters = new HashMap<String, Object>(); |
| - parameters.put("url", url); |
| - callFunction(callback, "removeSubscription", parameters, true); |
| - } |
| - |
| - public static void queryIsLocal(final AdblockPlusApiCallback callback, |
| - final String url) |
| - { |
| - Log.d(TAG, "queryIsLocal for " + url); |
| - final Map<String, Object> parameters = new HashMap<String, Object>(); |
| - parameters.put("url", url); |
| - callFunction(callback, "isLocal", parameters); |
| - } |
| - |
| - public static void queryIsPageWhitelisted(final AdblockPlusApiCallback callback, |
| - final String url) |
| - { |
| - Log.d(TAG, "queryIsPageWhitelisted for " + url); |
| - final Map<String, Object> parameters = new HashMap<String, Object>(); |
| - parameters.put("url", url); |
| - callFunction(callback, "isPageWhitelisted", parameters); |
| - } |
| - |
| - public static void whitelistSite(final AdblockPlusApiCallback callback, final String url, |
| - final boolean whitelisted) |
| - { |
| - Log.d(TAG, "whitelistSite for " + url); |
| - final Map<String, Object> parameters = new HashMap<String, Object>(); |
| - parameters.put("url", url); |
| - parameters.put("whitelisted", whitelisted); |
| - callFunction(callback, "whitelistSite", parameters, true); |
| + dispatchRequestToGecko(request, null); |
| } |
| - private static class AddOnRequest extends GeckoRequest |
| + private static void dispatchRequestToGecko(GeckoBundle request, |
| + final AdblockPlusApiCallback callback) |
| { |
| - private final JSONObject value; |
| - private final AdblockPlusApiCallback apiCallback; |
| - |
| - AddOnRequest(final JSONObject value, final AdblockPlusApiCallback callback) |
| - { |
| - super(AddOnBridge.REQUEST_NAME, value); |
| - this.value = value; |
| - this.apiCallback = callback; |
| - } |
| - |
| - private void invokeSuccessCallback(final NativeJSObject jsObject) |
| - { |
| - try |
| - { |
| - if (this.apiCallback != null) |
| - { |
| - this.apiCallback.onApiRequestSucceeded(jsObject); |
| - } |
| - } |
| - catch (final Exception e) |
| - { |
| - Log.e(TAG, "onApiRequestSucceeded threw exception: " + e.getMessage(), e); |
| - } |
| - } |
| - |
| - private void invokeFailureCallback(final String msg) |
| + Log.d(TAG, "dispatchRequestToGecko: " + request); |
| + EventDispatcher.getInstance().dispatch(REQUEST_NAME, request, new EventCallback() |
| { |
| - if (this.apiCallback != null) |
| + @Override |
| + public void sendSuccess(final Object response) |
| { |
| - this.apiCallback.onApiRequestFailed(msg); |
| + Log.d(TAG, "dispatchRequestToGecko sendSuccess " + response); |
| + if (callback == null) |
| + { |
| + return; |
| + } |
| + callback.onApiRequestSucceeded((GeckoBundle) response); |
| } |
| - } |
| - |
| - private void invokeFailureCallback(final NativeJSObject jsObject) |
| - { |
| - invokeFailureCallback(getStringFromJsObject(jsObject, "error", "unknown error")); |
| - } |
| - @Override |
| - public void onError(final NativeJSObject error) |
| - { |
| - this.invokeFailureCallback( |
| - "GeckoRequest error: " + error.optString("message", "<no message>") + "\n" + |
| - error.optString("stack", "<no stack>")); |
| - } |
| - |
| - @Override |
| - public void onResponse(final NativeJSObject jsObject) |
| - { |
| - if (getBooleanFromJsObject(jsObject, "success", false)) |
| + @Override |
| + public void sendError(final Object response) |
| { |
| - this.invokeSuccessCallback(jsObject); |
| + Log.d(TAG, "dispatchRequestToGecko sendError " + response); |
| + if (callback == null) |
| + { |
| + return; |
| + } |
| + final String error = response != null ? response.toString() : "unknown error"; |
| + callback.onApiRequestFailed(error); |
| } |
| - else |
| - { |
| - this.invokeFailureCallback(jsObject); |
| - } |
| - } |
| + }); |
| } |
| - private static class AddOnEventListener implements NativeEventListener |
| + private static class AddOnEventListener implements BundleEventListener |
| { |
| @Override |
| - public void handleMessage(String event, NativeJSObject message, EventCallback callback) |
| + public void handleMessage(String event, GeckoBundle message, EventCallback callback) |
| { |
| if (ON_FILTERS_LOAD_EVENT.equals(event)) |
| { |
| // The filters have been loaded by the extension. Given that, we can send all pending requests |
| filtersLoaded = true; |
| sendPendingRequests(); |
| } |
| else if (ON_FILTERS_SAVE_EVENT.equals(event)) |