<commit_before>/*
 * Copyright 2014 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.battlelancer.seriesguide.extensions;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import com.battlelancer.seriesguide.api.Action;
import com.battlelancer.seriesguide.api.Episode;
import com.battlelancer.seriesguide.api.SeriesGuideExtension;
import com.battlelancer.seriesguide.api.internal.IncomingConstants;
import de.greenrobot.event.EventBus;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import timber.log.Timber;

public class ExtensionManager {

    private static final String PREF_FILE_SUBSCRIPTIONS = "seriesguide_extensions";
    private static final String PREF_SUBSCRIPTIONS = "subscriptions";

    private static final int HARD_CACHE_CAPACITY = 5;

    // Cashes received actions for the last few displayed episodes.
    private final static android.support.v4.util.LruCache<Integer, Map<ComponentName, Action>>
            sEpisodeActionsCache = new android.support.v4.util.LruCache<>(HARD_CACHE_CAPACITY);

    private static ExtensionManager _instance;

    public static synchronized ExtensionManager getInstance(Context context) {
        if (_instance == null) {
            _instance = new ExtensionManager(context);
        }
        return _instance;
    }

    public static class EpisodeActionReceivedEvent {
        public int episodeTvdbId;
        public EpisodeActionReceivedEvent(int episodeTvdbId) {
            this.episodeTvdbId = episodeTvdbId;
        }
    }

    private Context mContext;
    private SharedPreferences mSharedPrefs;
    private ComponentName mSubscriberComponentName;

    private Map<ComponentName, String> mSubscriptions;
    private Map<String, ComponentName> mTokens; // mirrored map for faster token searching

    private ExtensionManager(Context context) {
        Timber.d("Initializing extension manager");
        mContext = context.getApplicationContext();
        mSharedPrefs = mContext.getSharedPreferences(PREF_FILE_SUBSCRIPTIONS, 0);
        mSubscriptions = new HashMap<>();
        mTokens = new HashMap<>();
        mSubscriberComponentName = new ComponentName(mContext, ExtensionSubscriberService.class);
        loadSubscriptions();
    }

    public List<Extension> queryAllAvailableExtensions() {
        Intent queryIntent = new Intent(SeriesGuideExtension.ACTION_SERIESGUIDE_EXTENSION);
        PackageManager pm = mContext.getPackageManager();
        List<ResolveInfo> resolveInfos = pm.queryIntentServices(queryIntent,
                PackageManager.GET_META_DATA);

        List<Extension> extensions = new ArrayList<>();
        for (ResolveInfo info : resolveInfos) {
            Extension extension = new Extension();
            // get label, icon and component name
            extension.label = info.loadLabel(pm).toString();
            extension.icon = info.loadIcon(pm);
            extension.componentName = new ComponentName(info.serviceInfo.packageName,
                    info.serviceInfo.name);
            // get description
            Context packageContext;
            try {
                packageContext = mContext.createPackageContext(
                        extension.componentName.getPackageName(), 0);
                Resources packageRes = packageContext.getResources();
                extension.description = packageRes.getString(info.serviceInfo.descriptionRes);
            } catch (SecurityException | PackageManager.NameNotFoundException e) {
                Timber.e(e, "Reading package resources for extension " + extension.componentName
                        + " failed");
            }
            // get (optional) settings activity
            Bundle metaData = info.serviceInfo.metaData;
            if (metaData != null) {
                String settingsActivity = metaData.getString("settingsActivity");
                if (!TextUtils.isEmpty(settingsActivity)) {
                    extension.settingsActivity = ComponentName.unflattenFromString(
                            info.serviceInfo.packageName + "/" + settingsActivity);
                }
            }

            Timber.d("queryAllAvailableExtensions: found extension " + extension.label + " "
                    + extension.componentName);
            extensions.add(extension);
        }

        return extensions;
    }

    public void enableExtension(ComponentName extension) {
        if (extension == null) {
            Timber.e("enableExtension: empty extension");
        }

        synchronized (this) {
            if (mSubscriptions.containsKey(extension)) {
                // already subscribed
                Timber.d("enableExtension: already subscribed to " + extension);
                return;
            }

            // subscribe
            String token = UUID.randomUUID().toString();
            while (mTokens.containsKey(token)) {
                // create another UUID on collision
                /**
                 * As the number of enabled extensions is rather low compared to the UUID number
                 * space we shouldn't have to worry about this ever looping.
                 */
                token = UUID.randomUUID().toString();
            }
            Timber.d("enableExtension: subscribing to " + extension);
            mSubscriptions.put(extension, token);
            mTokens.put(token, extension);
            mContext.startService(new Intent(IncomingConstants.ACTION_SUBSCRIBE)
                    .setComponent(extension)
                    .putExtra(IncomingConstants.EXTRA_SUBSCRIBER_COMPONENT,
                            mSubscriberComponentName)<commit_msg>Add isExtensionEnabled helper to ExtensionManager.<commit_after>/*
 * Copyright 2014 <NAME>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.battlelancer.seriesguide.extensions;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.TextUtils;
import com.battlelancer.seriesguide.api.Action;
import com.battlelancer.seriesguide.api.Episode;
import com.battlelancer.seriesguide.api.SeriesGuideExtension;
import com.battlelancer.seriesguide.api.internal.IncomingConstants;
import de.greenrobot.event.EventBus;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import timber.log.Timber;

public class ExtensionManager {

    private static final String PREF_FILE_SUBSCRIPTIONS = "seriesguide_extensions";
    private static final String PREF_SUBSCRIPTIONS = "subscriptions";

    private static final int HARD_CACHE_CAPACITY = 5;

    // Cashes received actions for the last few displayed episodes.
    private final static android.support.v4.util.LruCache<Integer, Map<ComponentName, Action>>
            sEpisodeActionsCache = new android.support.v4.util.LruCache<>(HARD_CACHE_CAPACITY);

    private static ExtensionManager _instance;

    public static synchronized ExtensionManager getInstance(Context context) {
        if (_instance == null) {
            _instance = new ExtensionManager(context);
        }
        return _instance;
    }

    public static class EpisodeActionReceivedEvent {
        public int episodeTvdbId;

        public EpisodeActionReceivedEvent(int episodeTvdbId) {
            this.episodeTvdbId = episodeTvdbId;
        }
    }

    private Context mContext;
    private SharedPreferences mSharedPrefs;
    private ComponentName mSubscriberComponentName;

    private Map<ComponentName, String> mSubscriptions;
    private Map<String, ComponentName> mTokens; // mirrored map for faster token searching

    private ExtensionManager(Context context) {
        Timber.d("Initializing extension manager");
        mContext = context.getApplicationContext();
        mSharedPrefs = mContext.getSharedPreferences(PREF_FILE_SUBSCRIPTIONS, 0);
        mSubscriptions = new HashMap<>();
        mTokens = new HashMap<>();
        mSubscriberComponentName = new ComponentName(mContext, ExtensionSubscriberService.class);
        loadSubscriptions();
    }

    public List<Extension> queryAllAvailableExtensions() {
        Intent queryIntent = new Intent(SeriesGuideExtension.ACTION_SERIESGUIDE_EXTENSION);
        PackageManager pm = mContext.getPackageManager();
        List<ResolveInfo> resolveInfos = pm.queryIntentServices(queryIntent,
                PackageManager.GET_META_DATA);

        List<Extension> extensions = new ArrayList<>();
        for (ResolveInfo info : resolveInfos) {
            Extension extension = new Extension();
            // get label, icon and component name
            extension.label = info.loadLabel(pm).toString();
            extension.icon = info.loadIcon(pm);
            extension.componentName = new ComponentName(info.serviceInfo.packageName,
                    info.serviceInfo.name);
            // get description
            Context packageContext;
            try {
                packageContext = mContext.createPackageContext(
                        extension.componentName.getPackageName(), 0);
                Resources packageRes = packageContext.getResources();
                extension.description = packageRes.getString(info.serviceInfo.descriptionRes);
            } catch (SecurityException | PackageManager.NameNotFoundException e) {
                Timber.e(e, "Reading package resources for extension " + extension.componentName
                        + " failed");
            }
            // get (optional) settings activity
            Bundle metaData = info.serviceInfo.metaData;
            if (metaData != null) {
                String settingsActivity = metaData.getString("settingsActivity");
                if (!TextUtils.isEmpty(settingsActivity)) {
                    extension.settingsActivity = ComponentName.unflattenFromString(
                            info.serviceInfo.packageName + "/" + settingsActivity);
                }
            }

            Timber.d("queryAllAvailableExtensions: found extension " + extension.label + " "
                    + extension.componentName);
            extensions.add(extension);
        }

        return extensions;
    }

    /**
     * Returns whether the given extension is currently enabled and will be nudged for actions when
     * {@link #requestActions(com.battlelancer.seriesguide.api.Episode)} is called.
     */
    public synchronized boolean isExtensionEnabled(ComponentName extension) {
        return mSubscriptions.get(extension) != null;
    }

    public void enableExtension(ComponentName extension) {
        if (extension == null) {
            Timber.e("enableExtension: empty extension");
        }

        synchronized (this) {
            if (mSubscriptions.containsKey(extension)) {
                // already subscribed
                Timber.d("enableExtension: already subscribed to " + extension);
                return;
            }

            // subscribe
            String token = UUID.randomUUID().toString();
            while (mTokens.containsKey(token)) {
                // create another UUID on collision
                /**
                 * As the number of enabled extensions is rather low compared to the UUID number
                 * space we shouldn't have to worry about this ever looping.
                 */
                token = UUID.randomUUID().toString();
            }
            Timber.d("enableExtension: subscribing to " + extension);
            mSubscriptions.put(extension, token);
            mTokens.put(token, extension);
            mContext.startService(new Intent(IncomingConstants.ACTION_SUBSCRIBE)
                    .setComponent(extension)
                    .putExtra(IncomingConstants.EXTRA_SUBSCRIBER_COMPONENT,
                            mSubscriberComponentName)
{'fraction_non_alphanumeric': 0.08050692256977511, 'fraction_numerical': 0.0019046223719874002, 'mean_word_length': 2.651243648034234, 'pattern_counts': {'":': 0, '<': 27, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 9, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7063697', 'n_tokens_mistral': 3383, 'n_tokens_neox': 3275, 'n_words': 856}