| Index: mobile/android/thirdparty/org/adblockplus/browser/SubscriptionContainer.java | 
| diff --git a/mobile/android/thirdparty/org/adblockplus/browser/SubscriptionContainer.java b/mobile/android/thirdparty/org/adblockplus/browser/SubscriptionContainer.java | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..1a9a740eb9541e2b06064f9258979b02aec2725a | 
| --- /dev/null | 
| +++ b/mobile/android/thirdparty/org/adblockplus/browser/SubscriptionContainer.java | 
| @@ -0,0 +1,280 @@ | 
| +/* | 
| + * This file is part of Adblock Plus <https://adblockplus.org/>, | 
| + * Copyright (C) 2006-2015 Eyeo GmbH | 
| + * | 
| + * Adblock Plus is free software: you can redistribute it and/or modify | 
| + * it under the terms of the GNU General Public License version 3 as | 
| + * published by the Free Software Foundation. | 
| + * | 
| + * Adblock Plus is distributed in the hope that it will be useful, | 
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| + * GNU General Public License for more details. | 
| + * | 
| + * 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.io.IOException; | 
| +import java.io.StringReader; | 
| +import java.util.ArrayList; | 
| +import java.util.HashMap; | 
| +import java.util.List; | 
| +import java.util.concurrent.ConcurrentHashMap; | 
| +import java.util.concurrent.Semaphore; | 
| + | 
| +import org.mozilla.gecko.util.NativeJSObject; | 
| +import org.xmlpull.v1.XmlPullParser; | 
| +import org.xmlpull.v1.XmlPullParserException; | 
| + | 
| +import android.util.Log; | 
| +import android.util.Xml; | 
| + | 
| +final class SubscriptionContainer implements AdblockPlusApiCallback | 
| +{ | 
| +  private static final String TAG = SubscriptionContainer.class.getName(); | 
| +  private final ConcurrentHashMap<String, Boolean> enableState = new ConcurrentHashMap<String, Boolean>(); | 
| +  private final Semaphore entriesReady = new Semaphore(0); | 
| +  private final List<SubscriptionContainer.Subscription> entries = new ArrayList<SubscriptionContainer.Subscription>(); | 
| +  private final HashMap<String, SubscriptionContainer.Subscription> urlMap = new HashMap<String, SubscriptionContainer.Subscription>(); | 
| + | 
| +  private SubscriptionContainer() | 
| +  { | 
| +    // prevent external instantiation | 
| +  } | 
| + | 
| +  public final static SubscriptionContainer create() | 
| +  { | 
| +    return create(true); | 
| +  } | 
| + | 
| +  public final static SubscriptionContainer create(final boolean refresh) | 
| +  { | 
| +    final SubscriptionContainer sc = new SubscriptionContainer(); | 
| +    AddOnBridge.queryValue(sc, "subscriptionsXml"); | 
| +    sc.entriesReady.acquireUninterruptibly(); | 
| + | 
| +    for (final SubscriptionContainer.Subscription e : sc.entries) | 
| +    { | 
| +      sc.urlMap.put(e.url, e); | 
| +      sc.enableState.put(e.url, Boolean.FALSE); | 
| +    } | 
| + | 
| +    if (refresh) | 
| +    { | 
| +      sc.refresh(); | 
| +    } | 
| + | 
| +    return sc; | 
| +  } | 
| + | 
| +  public void refresh() | 
| +  { | 
| +    if (!this.entries.isEmpty()) | 
| +    { | 
| +      for (int i = 0; i < this.entries.size(); i++) | 
| +      { | 
| +        SubscriptionChangeAction.post(this.entries.get(i), | 
| +            this, | 
| +            SubscriptionChangeAction.Mode.QUERY_SUBSCRIPTION_ENABLED); | 
| +      } | 
| + | 
| +      this.entriesReady.acquireUninterruptibly(this.entries.size()); | 
| +    } | 
| +  } | 
| + | 
| +  public List<SubscriptionContainer.Subscription> getSubscriptions(boolean enabled) | 
| +  { | 
| +    final List<SubscriptionContainer.Subscription> ret = new ArrayList<SubscriptionContainer.Subscription>(); | 
| +    for (final SubscriptionContainer.Subscription e : this.entries) | 
| +    { | 
| +      if (this.isSubscriptionListed(e.url) == enabled) | 
| +      { | 
| +        ret.add(e); | 
| +      } | 
| +    } | 
| +    return ret; | 
| +  } | 
| + | 
| +  public boolean isSubscriptionListed(final String url) | 
| +  { | 
| +    return this.enableState.containsKey(url) && this.enableState.get(url).booleanValue(); | 
| +  } | 
| + | 
| +  public void changeSubscriptionState(final String url, final boolean enable) | 
| +  { | 
| +    final SubscriptionContainer.Subscription e = this.urlMap.get(url); | 
| +    if (e != null) | 
| +    { | 
| +      if (enable) | 
| +      { | 
| +        SubscriptionChangeAction.post(e, SubscriptionPreferenceCategory.subscriptionContainer, | 
| +            SubscriptionChangeAction.Mode.ENABLE_SUBSCRIPTION); | 
| +      } | 
| +      else | 
| +      { | 
| +        SubscriptionChangeAction.post(e, SubscriptionPreferenceCategory.subscriptionContainer, | 
| +            SubscriptionChangeAction.Mode.DISABLE_SUBSCRIPTION); | 
| +      } | 
| +    } | 
| +  } | 
| + | 
| +  @Override | 
| +  public void onApiRequestSucceeded(NativeJSObject jsObject) | 
| +  { | 
| +    final XmlPullParser parser = Xml.newPullParser(); | 
| +    try | 
| +    { | 
| +      parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); | 
| +      parser.setInput(new StringReader(AddOnBridge.getStringFromJsObject(jsObject, "value", ""))); | 
| +      parser.nextTag(); | 
| +      parser.require(XmlPullParser.START_TAG, null, "subscriptions"); | 
| +      while (parser.next() != XmlPullParser.END_TAG) | 
| +      { | 
| +        if (parser.getEventType() != XmlPullParser.START_TAG) | 
| +        { | 
| +          continue; | 
| +        } | 
| +        if ("subscription".equals(parser.getName())) | 
| +        { | 
| +          final String title = parser.getAttributeValue(null, "title"); | 
| +          final String specialization = parser.getAttributeValue(null, "specialization"); | 
| +          final String url = parser.getAttributeValue(null, "url"); | 
| +          this.entries.add(new Subscription(title, specialization, url)); | 
| +        } | 
| +        parser.next(); | 
| +      } | 
| +    } | 
| +    catch (XmlPullParserException e) | 
| +    { | 
| +      Log.e(TAG, "Failed to parse subscriptions.xml: " + e.getMessage(), e); | 
| +    } | 
| +    catch (IOException e) | 
| +    { | 
| +      Log.e(TAG, "Failed to parse subscriptions.xml: " + e.getMessage(), e); | 
| +    } | 
| +    finally | 
| +    { | 
| +      this.entriesReady.release(); | 
| +    } | 
| +  } | 
| + | 
| +  @Override | 
| +  public void onApiRequestFailed(String errorMessage) | 
| +  { | 
| +    Log.e(TAG, "Error: " + errorMessage); | 
| +    this.entriesReady.release(); | 
| +  } | 
| + | 
| +  private static class SubscriptionChangeAction implements AdblockPlusApiCallback | 
| +  { | 
| +    private static final String TAG = SubscriptionContainer.SubscriptionChangeAction.class.getName(); | 
| + | 
| +    private final SubscriptionContainer.Subscription subscription; | 
| +    private final SubscriptionContainer parent; | 
| +    private final SubscriptionChangeAction.Mode mode; | 
| + | 
| +    public enum Mode | 
| +    { | 
| +      QUERY_SUBSCRIPTION_ENABLED, | 
| +      ENABLE_SUBSCRIPTION, | 
| +      DISABLE_SUBSCRIPTION, | 
| +    } | 
| + | 
| +    public SubscriptionChangeAction(final SubscriptionContainer.Subscription subscription, | 
| +        final SubscriptionContainer parent, | 
| +        final SubscriptionChangeAction.Mode mode) | 
| +    { | 
| +      this.subscription = subscription; | 
| +      this.parent = parent; | 
| +      this.mode = mode; | 
| +    } | 
| + | 
| +    public static SubscriptionContainer.SubscriptionChangeAction post(final SubscriptionContainer.Subscription subscription, | 
| +        final SubscriptionContainer parent, | 
| +        final SubscriptionChangeAction.Mode mode) | 
| +    { | 
| +      return new SubscriptionChangeAction(subscription, parent, mode).post(); | 
| +    } | 
| + | 
| +    public SubscriptionContainer.SubscriptionChangeAction post() | 
| +    { | 
| +      switch (this.mode) | 
| +      { | 
| +        case QUERY_SUBSCRIPTION_ENABLED: | 
| +          AddOnBridge.querySubscriptionListStatus(this, this.subscription.url); | 
| +          break; | 
| +        case ENABLE_SUBSCRIPTION: | 
| +          AddOnBridge.addSubscription(this, this.subscription.url, this.subscription.title); | 
| +          break; | 
| +        case DISABLE_SUBSCRIPTION: | 
| +          AddOnBridge.removeSubscription(this, this.subscription.url); | 
| +          break; | 
| +        default: | 
| +          break; | 
| +      } | 
| +      return this; | 
| +    } | 
| + | 
| +    @Override | 
| +    public void onApiRequestSucceeded(NativeJSObject jsObject) | 
| +    { | 
| +      switch (this.mode) | 
| +      { | 
| +        case QUERY_SUBSCRIPTION_ENABLED: | 
| +          try | 
| +          { | 
| +            this.parent.enableState.put(this.subscription.url, | 
| +                Boolean.valueOf(AddOnBridge.getBooleanFromJsObject(jsObject, "value", false))); | 
| +          } | 
| +          finally | 
| +          { | 
| +            this.parent.entriesReady.release(); | 
| +          } | 
| +          break; | 
| +        default: | 
| +          break; | 
| +      } | 
| +    } | 
| + | 
| +    @Override | 
| +    public void onApiRequestFailed(String errorMessage) | 
| +    { | 
| +      switch (this.mode) | 
| +      { | 
| +        case QUERY_SUBSCRIPTION_ENABLED: | 
| +          this.parent.enableState.put(this.subscription.url, Boolean.FALSE); | 
| +          this.parent.entriesReady.release(); | 
| +          break; | 
| +        default: | 
| +          break; | 
| +      } | 
| + | 
| +      Log.e(TAG, "Error for '" + this.subscription.url | 
| +          + "', mode: " + this.mode + ": " | 
| +          + errorMessage); | 
| +    } | 
| +  } | 
| + | 
| +  public static class Subscription | 
| +  { | 
| +    public final String title; | 
| +    public final String specialization; | 
| +    public final String url; | 
| + | 
| +    public Subscription(final String title, final String specialization, final String url) | 
| +    { | 
| +      this.title = title; | 
| +      this.specialization = specialization; | 
| +      this.url = url; | 
| +    } | 
| + | 
| +    @Override | 
| +    public String toString() | 
| +    { | 
| +      return this.specialization + " (" + this.title + ") @ " + this.url; | 
| +    } | 
| +  } | 
| +} | 
|  |