com.linkbubble.Settings.java Source code

Java tutorial

Introduction

Here is the source code for com.linkbubble.Settings.java

Source

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package com.linkbubble;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;

import com.linkbubble.ui.TabView;
import com.linkbubble.util.Analytics;
import com.linkbubble.util.CrashTracking;
import com.linkbubble.util.Util;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.Vector;

public class Settings {

    public static final String PREFERENCE_ENABLED = "preference_enabled";
    public static final String PREFERENCE_CHECK_FOR_YOUTUBE_EMBEDS = "preference_scan_for_youtube_embeds";
    public static final String PREFERENCE_IGNORE_LINKS_FROM = "preference_ignore_links_from";

    public static final String PREFERENCE_AUTO_CONTENT_DISPLAY_LINK_LOADED = "preference_auto_content_display_link_loaded";
    public static final boolean PREFERENCE_AUTO_CONTENT_DISPLAY_LINK_LOADED_DEFAULT = false;
    public static final String PREFERENCE_SHOW_UNDO_CLOSE_TAB = "preference_show_undo_close_tab_prompt";

    public static final String PREFERENCE_LEFT_CONSUME_BUBBLE = "preference_left_consume_bubble";
    public static final String PREFERENCE_LEFT_CONSUME_BUBBLE_PACKAGE_NAME = "preference_left_consume_bubble_package_name";
    public static final String PREFERENCE_LEFT_CONSUME_BUBBLE_ACTIVITY_CLASS_NAME = "preference_left_consume_bubble_activity_class_name";
    public static final String PREFERENCE_LEFT_CONSUME_BUBBLE_LABEL = "preference_left_consume_bubble_label";
    public static final String PREFERENCE_LEFT_CONSUME_BUBBLE_TYPE = "preference_left_consume_bubble_type";

    public static final String PREFERENCE_RIGHT_CONSUME_BUBBLE = "preference_right_consume_bubble";
    public static final String PREFERENCE_RIGHT_CONSUME_BUBBLE_PACKAGE_NAME = "preference_right_consume_bubble_package_name";
    public static final String PREFERENCE_RIGHT_CONSUME_BUBBLE_ACTIVITY_CLASS_NAME = "preference_right_consume_bubble_activity_class_name";
    public static final String PREFERENCE_RIGHT_CONSUME_BUBBLE_LABEL = "preference_right_consume_bubble_label";
    public static final String PREFERENCE_RIGHT_CONSUME_BUBBLE_TYPE = "preference_right_consume_bubble_type";

    //public static final String PREFERENCE_LINK_DOUBLE_TAP = "preference_double_tap";
    //public static final String PREFERENCE_LINK_DOUBLE_TAP_PACKAGE_NAME = "preference_double_tap_package_name";
    //public static final String PREFERENCE_LINK_DOUBLE_TAP_ACTIVITY_CLASS_NAME = "preference_double_tap_activity_class_name";
    //public static final String PREFERENCE_LINK_DOUBLE_TAP_LABEL = "preference_double_tap_bubble_label";
    //public static final String PREFERENCE_LINK_DOUBLE_TAP_TYPE = "preference_double_tap_bubble_type";

    public static final String PREFERENCE_DEFAULT_BROWSER = "preference_default_browser";
    public static final String PREFERENCE_DEFAULT_BROWSER_PACKAGE_NAME = "preference_default_browser_package_name";
    public static final String PREFERENCE_DEFAULT_BROWSER_LABEL = "preference_default_browser_bubble_label";

    public static final String KEY_ARTICLE_MODE_PREFERENCE = "preference_article_mode";
    public static final String KEY_ARTICLE_MODE_ON_WEAR_PREFERENCE = "preference_reading_mode_on_wear";

    public static final String PREFERENCE_THEME_DARK = "preference_theme_dark";
    public static final String PREFERENCE_COLORED_PROGRESS_INDICATOR = "preference_colored_progress_indicator";

    public static final String PREFERENCE_CURRENT_TABS = "preference_current_bubbles";
    public static final String PREFERENCE_DEFAULT_APPS = "preference_default_apps";
    public static final String PREFERENCE_FALLBACK_REDIRECT_HOSTS = "preference_redirect_hosts";
    public static final String PREFERENCE_THEME_TOOLBAR = "preference_theme_toolbar";

    public static final String PREFERENCE_AUTO_ARTICLE_MODE = "preference_auto_article_mode";
    public static final String PREFERENCE_INCOGNITO_MODE = "preference_incognito";

    public static final String PREFERENCE_HIDE_BUBBLES = "preference_hide_bubbles";

    public static final String PREFERENCE_WEBVIEW_BATTERY_SAVING_MODE = "preference_webview_battery_save_v2";
    public static final String PREFERENCE_TRACKINGPROTECTION_MODE = "preference_trackingprotection";
    public static final String PREFERENCE_ADBLOCK_MODE = "preference_adblock";
    public static final String PREFERENCE_BLOCK_3P_COOKIES = "preference_block_3p";
    public static final String PREFERENCE_HTTPS_EVERYWHERE_MODE = "preference_httpseverywhere";

    public static final String PREFERENCE_WEBVIEW_TEXT_ZOOM = "preference_webview_text_zoom2";
    public static final int PREFERENCE_WEBVIEW_TEXT_ZOOM_MIN = 50;
    public static final int PREFERENCE_WEBVIEW_TEXT_ZOOM_DEFAULT = 100;
    public static final int PREFERENCE_WEBVIEW_TEXT_ZOOM_MAX = 250;

    public static final String PREFERENCE_USER_AGENT = "preference_user_agent";

    private static final String SAY_THANKS_CLICKED = "say_thanks_clicked";

    private static final String DEFAULT_APPS_MAP_KEY_HOST = "host";
    private static final String DEFAULT_APPS_MAP_KEY_COMPONENT = "component";

    private static final String BUBBLE_RESTING_X = "bubble_resting_x";
    private static final String BUBBLE_RESTING_Y = "bubble_resting_y";

    private static final String WELCOME_MESSAGE_DISPLAYED = "welcome_message_displayed";
    private static final String TERMS_ACCEPTED = "terms_accepted";
    private static final String LAST_FLUSH_WEBVIEW_CACHE_TIME = "last_flush_cache_time";
    private static final String PREFERENCE_DID_RESET_FALLBACK_BROWSER = "did_reset_fallback_browser";
    private static final String PREFERENCE_SHOW_NEW_BRAVE_BROWSER = "show_new_brave_browser";

    public enum WebViewBatterySaveMode {
        Aggressive, Default, Off,
    }

    enum ColorTheme {
        Light, Dark, Palette,
    }

    /*
    *
    */
    public static void initModule(Context context) {
        mInstance = new Settings(context);
    }

    /*
     *
     */
    public static void deinitModule() {
        mInstance = null;
    }

    /*
     *
     */
    public static Settings get() {
        return mInstance;
    }

    private static Settings mInstance = null;

    public static class OnConsumeBubblesChangedEvent {
    }

    private SharedPreferences mSharedPreferences;
    private Context mContext;
    private TreeMap<String, String> mDefaultAppsMap = new TreeMap<String, String>();
    private List<Intent> mBrowsers;
    private List<String> mBrowserPackageNames;
    private ResolveInfo mYouTubeViewResolveInfo;
    public ResolveInfo mLinkBubbleEntryActivityResolveInfo;
    private boolean mCheckedForYouTubeResolveInfo = false;
    private List<String> mIgnoreLinksFromPackageNames;
    private WebViewBatterySaveMode mWebViewBatterySaveMode;
    // The point to save
    private Point mBubbleRestingPoint = new Point();
    // The point used as the return value. Required so we don't overwrite the desired point in landscape mode
    private Point mTempBubbleRestingPoint = new Point();

    private HashSet<String> mFallbackRedirectHosts = new HashSet<>();

    static public int COLOR_TEXT_DARK;
    static public int COLOR_TEXT_LIGHT;
    static public int COLOR_LINK_DARK;
    static public int COLOR_LINK_LIGHT;
    static public int COLOR_CONTENT_VIEW_DARK;
    static public int COLOR_CONTENT_VIEW_LIGHT;
    static public int COLOR_PROGRESS_DARK;
    static public int COLOR_PROGRESS_LIGHT;
    static public int COLOR_WHITE;
    static public int COLOR_BLACK;

    Settings(Context context) {
        mContext = context;
        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
        //mDownloadHandlerComponentName = new ComponentName(mContext, DownloadHandlerActivity.class);

        COLOR_WHITE = mContext.getResources().getColor(android.R.color.white);
        COLOR_BLACK = mContext.getResources().getColor(android.R.color.black);
        COLOR_TEXT_DARK = mContext.getResources().getColor(R.color.color_text_dark);
        COLOR_TEXT_LIGHT = mContext.getResources().getColor(R.color.color_text_light);
        COLOR_LINK_DARK = mContext.getResources().getColor(R.color.color_link_dark);
        COLOR_LINK_LIGHT = mContext.getResources().getColor(R.color.color_link_light);
        COLOR_CONTENT_VIEW_DARK = mContext.getResources().getColor(R.color.color_content_view_dark);
        COLOR_CONTENT_VIEW_LIGHT = mContext.getResources().getColor(R.color.color_content_view_light);
        COLOR_PROGRESS_DARK = mContext.getResources().getColor(R.color.color_progress_default_dark);
        COLOR_PROGRESS_LIGHT = mContext.getResources().getColor(R.color.color_progress_default_light);

        checkForVersionUpgrade();

        setDefaultRightConsumeBubble();
        setDefaultLeftConsumeBubble();

        if (mSharedPreferences.getBoolean("first_run", true)) {
            SharedPreferences.Editor editor = mSharedPreferences.edit();
            editor.putBoolean("first_run", false);
            editor.putLong(LAST_FLUSH_WEBVIEW_CACHE_TIME, System.currentTimeMillis());
            editor.commit();

            PackageManager packageManager = mContext.getPackageManager();

            configureDefaultApp(packageManager, "https://www.youtube.com/watch?v=_Aj-PRdU7xA",
                    "com.google.android.youtube");
            configureDefaultApp(packageManager, "https://plus.google.com/+LinkBubble/posts/RdMoBbbjPUi",
                    "com.google.android.apps.plus");
            configureDefaultApp(packageManager,
                    "https://play.google.com/store/apps/details?id=com.linkbubble.playstore&hl=en",
                    "com.android.vending");
            configureDefaultApp(packageManager,
                    "https://maps.google.com/maps/ms?msid=212078515518849153944.000434d59f7fc56a57668",
                    "com.google.android.apps.maps");
            saveDefaultApps();
        } else {
            // This option is being added in 1.3. For people upgrading from older versions, set the value to force a clear very soon
            if (mSharedPreferences.getLong(LAST_FLUSH_WEBVIEW_CACHE_TIME, -1) == -1) {
                SharedPreferences.Editor editor = mSharedPreferences.edit();
                editor.putLong(LAST_FLUSH_WEBVIEW_CACHE_TIME,
                        System.currentTimeMillis() - Constant.EMPTY_WEBVIEW_CACHE_INTERVAL);
                editor.apply();
            }
            // This option is being added in 1.9.58 to reset fallback browser, to set it to tabbed Brave.
            if (!mSharedPreferences.getBoolean(PREFERENCE_DID_RESET_FALLBACK_BROWSER, false)) {
                SharedPreferences.Editor editor = mSharedPreferences.edit();
                editor.putBoolean(PREFERENCE_DID_RESET_FALLBACK_BROWSER, true);
                editor.remove(PREFERENCE_DEFAULT_BROWSER_PACKAGE_NAME);
                editor.remove(PREFERENCE_DEFAULT_BROWSER_LABEL);
                editor.apply();
            }
        }

        configureDefaultApps(mSharedPreferences.getString(PREFERENCE_DEFAULT_APPS, null));
        mBubbleRestingPoint.set(-1, -1);

        loadLinkLoadStats();
        loadRecentAppRedirects();
        loadIgnoreLinksFromPackageNames();

        setWebViewBatterySaveMode(
                mSharedPreferences.getString(PREFERENCE_WEBVIEW_BATTERY_SAVING_MODE, "aggressive"));

        HashSet<String> defaultRedirects = new HashSet<>();
        defaultRedirects.add("accounts.google.com");
        configureFallbackRedirectHosts(
                mSharedPreferences.getStringSet(PREFERENCE_FALLBACK_REDIRECT_HOSTS, defaultRedirects));
    }

    public boolean showNewBraveBrowserNotification() {
        // This option is being added in 1.9.58 to show notification about new tabbed Brave browser.
        if (mSharedPreferences.getBoolean(PREFERENCE_SHOW_NEW_BRAVE_BROWSER, true)) {
            SharedPreferences.Editor editor = mSharedPreferences.edit();
            editor.putBoolean(PREFERENCE_SHOW_NEW_BRAVE_BROWSER, false);
            editor.apply();

            return true;
        }

        return false;
    }

    private void checkForVersionUpgrade() {
        final String key = "lastUpgradeVersion";
        int lastUpgradeVersion = mSharedPreferences.getInt(key, -1);
        int upgradeVersionToSet = -1;
        if (lastUpgradeVersion < 1) {
            // Remove all defaults to TapPath
            String defaultAppsAsString = mSharedPreferences.getString(PREFERENCE_DEFAULT_APPS, null);

            try {
                if (defaultAppsAsString != null) {
                    JSONArray defaultApps = new JSONArray(defaultAppsAsString);
                    mDefaultAppsMap.clear();

                    boolean tapPathFound = false;

                    for (int i = 0; i < defaultApps.length(); i++) {
                        try {
                            JSONObject object = defaultApps.getJSONObject(i);
                            String host = object.getString(DEFAULT_APPS_MAP_KEY_HOST);
                            String flattenedName = object.getString(DEFAULT_APPS_MAP_KEY_COMPONENT);
                            if (flattenedName.contains(BuildConfig.TAP_PATH_PACKAGE_NAME) == false) {
                                mDefaultAppsMap.put(host, flattenedName);
                            } else {
                                tapPathFound = true;
                                //Log.d("blerg", "ignore " + host + ", " + flattenedName);
                            }

                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }

                    if (tapPathFound) {
                        saveDefaultApps();
                    }
                }
            } catch (JSONException e) {
                mDefaultAppsMap.clear();
            }

            upgradeVersionToSet = 1;
        }

        if (upgradeVersionToSet > -1) {
            SharedPreferences.Editor editor = mSharedPreferences.edit();
            editor.putInt(key, upgradeVersionToSet);
            editor.apply();
        }
    }

    private void configureDefaultApp(PackageManager packageManager, String urlAsString, String desiredPackageName) {
        try {
            URL url = new URL(urlAsString);
            List<ResolveInfo> tempResolveInfos = new ArrayList<>();
            if (!urlAsString.equals(mContext.getString(R.string.empty_bubble_page))) {
                tempResolveInfos = getAppsThatHandleUrl(urlAsString, packageManager);
            }
            final List<ResolveInfo> resolveInfos = tempResolveInfos;

            if (null == resolveInfos) {
                return;
            }
            for (ResolveInfo resolveInfo : resolveInfos) {
                if (resolveInfo.activityInfo != null) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    if (packageName.equals(BuildConfig.APPLICATION_ID) == false
                            && packageName.contains(desiredPackageName)) {
                        setDefaultApp(url.toString(), resolveInfo, false);
                        return;
                    }
                }
            }
        } catch (MalformedURLException ex) {

        }
    }

    public void saveData() {
        saveLinkLoadStats();
        saveRecentAppRedirects();
    }

    public void onOrientationChange() {
        int bubbleRestingX = mBubbleRestingPoint.x;
        if (bubbleRestingX == -1) {
            bubbleRestingX = mSharedPreferences.getInt(BUBBLE_RESTING_X, -1);
            if (bubbleRestingX == -1) {
                bubbleRestingX = Config.mBubbleSnapLeftX;
            }
        }
        if (bubbleRestingX < Config.mScreenCenterX) {
            bubbleRestingX = Config.mBubbleSnapLeftX;
        } else {
            bubbleRestingX = Config.mBubbleSnapRightX;
        }

        int bubbleRestingY = mBubbleRestingPoint.y;
        if (bubbleRestingY == -1) {
            bubbleRestingY = mSharedPreferences.getInt(BUBBLE_RESTING_Y, -1);
            if (bubbleRestingY == -1) {
                bubbleRestingY = (int) (Config.mScreenHeight * 0.35f);
            }
        }

        mBubbleRestingPoint.set(bubbleRestingX, bubbleRestingY);
    }

    public void updateBrowsers() {
        if (mBrowsers == null) {
            mBrowsers = new Vector<Intent>();
            mBrowserPackageNames = new ArrayList<String>();
        } else {
            mBrowsers.clear();
            mBrowserPackageNames.clear();
        }
        PackageManager packageManager = mContext.getPackageManager();
        Intent queryIntent = new Intent();
        queryIntent.setAction(Intent.ACTION_VIEW);
        queryIntent.setData(Uri.parse("http://www.fdasfjsadfdsfas.com")); // Something stupid that no non-browser app will handle
        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(queryIntent,
                PackageManager.GET_RESOLVED_FILTER);
        String fallbackDefaultBrowserPackageName = null;
        String fallbackDefaultBrowserActivityClassName = null;
        for (ResolveInfo resolveInfo : resolveInfos) {
            IntentFilter filter = resolveInfo.filter;
            if (filter != null && filter.hasAction(Intent.ACTION_VIEW)
                    && filter.hasCategory(Intent.CATEGORY_BROWSABLE)) {
                // Ignore LinkBubble from this list
                if (resolveInfo.activityInfo.packageName.equals(BuildConfig.APPLICATION_ID)) {
                    mLinkBubbleEntryActivityResolveInfo = resolveInfo;
                } else if (Util.isValidBrowserPackageName(resolveInfo.activityInfo.packageName)) {
                    Intent intent = new Intent(Intent.ACTION_VIEW);
                    intent.setClassName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
                    mBrowsers.add(intent);
                    mBrowserPackageNames.add(resolveInfo.activityInfo.packageName);
                    if (resolveInfo.activityInfo.packageName
                            .equals(mContext.getResources().getString(R.string.tab_based_browser_id_name))) {
                        fallbackDefaultBrowserPackageName = resolveInfo.activityInfo.packageName;
                        fallbackDefaultBrowserActivityClassName = resolveInfo.activityInfo.name;
                    }
                }
            }
        }

        String defaultBrowserPackage = mSharedPreferences.getString(PREFERENCE_DEFAULT_BROWSER_PACKAGE_NAME, null);
        //String rightConsumeBubblePackageName = mSharedPreferences.getString(PREFERENCE_RIGHT_CONSUME_BUBBLE_PACKAGE_NAME, null);
        String leftConsumeBubblePackageName = mSharedPreferences
                .getString(PREFERENCE_LEFT_CONSUME_BUBBLE_PACKAGE_NAME, null);

        if (fallbackDefaultBrowserPackageName != null) {
            try {
                ApplicationInfo applicationInfo = packageManager
                        .getApplicationInfo(fallbackDefaultBrowserPackageName, 0);
                String defaultBrowserLabel = packageManager.getApplicationLabel(applicationInfo).toString();

                if (defaultBrowserPackage == null || !doesPackageExist(packageManager, defaultBrowserPackage)) {
                    SharedPreferences.Editor editor = mSharedPreferences.edit();
                    editor.putString(PREFERENCE_DEFAULT_BROWSER_LABEL, defaultBrowserLabel);
                    editor.putString(PREFERENCE_DEFAULT_BROWSER_PACKAGE_NAME, fallbackDefaultBrowserPackageName);
                    editor.commit();
                }
                if (leftConsumeBubblePackageName != null
                        && !doesPackageExist(packageManager, leftConsumeBubblePackageName)) {
                    setConsumeBubble(Constant.BubbleAction.ConsumeLeft, Constant.ActionType.View,
                            defaultBrowserLabel, fallbackDefaultBrowserPackageName,
                            fallbackDefaultBrowserActivityClassName);
                }

            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    private boolean doesPackageExist(PackageManager pm, String targetPackage) {
        try {
            PackageInfo info = pm.getPackageInfo(targetPackage, PackageManager.GET_META_DATA);
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
        return true;
    }

    private ResolveInfo findResolveInfoForPackageName(List<ResolveInfo> resolveInfos, String packageName) {
        for (ResolveInfo resolveInfo : resolveInfos) {
            if (resolveInfo.activityInfo.packageName.equals(packageName)) {
                return resolveInfo;
            }
        }

        return null;
    }

    public ResolveInfo getYouTubeViewResolveInfo() {
        if (mCheckedForYouTubeResolveInfo == false) {
            PackageManager packageManager = mContext.getPackageManager();
            Intent queryIntent = new Intent();
            queryIntent.setAction(Intent.ACTION_VIEW);
            queryIntent.setData(Uri.parse("http://www.youtube.com/watch?v=jNQXAC9IVRw"));
            List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(queryIntent,
                    PackageManager.GET_RESOLVED_FILTER);
            for (ResolveInfo resolveInfo : resolveInfos) {
                if (resolveInfo.activityInfo != null
                        && resolveInfo.activityInfo.packageName.contains("com.google.android.youtube")) {
                    mYouTubeViewResolveInfo = resolveInfo;
                    break;
                }
            }
            mCheckedForYouTubeResolveInfo = true;
        }

        return mYouTubeViewResolveInfo;
    }

    private void setDefaultRightConsumeBubble() {
        PackageManager packageManager = mContext.getPackageManager();
        String rightConsumeBubblePackageName = mSharedPreferences
                .getString(PREFERENCE_RIGHT_CONSUME_BUBBLE_PACKAGE_NAME, null);
        if (rightConsumeBubblePackageName == null || (rightConsumeBubblePackageName != null
                && !rightConsumeBubblePackageName.equals(BuildConfig.APPLICATION_ID)
                && !doesPackageExist(packageManager, rightConsumeBubblePackageName))) {
            setConsumeBubble(Constant.BubbleAction.ConsumeRight, Constant.ActionType.Share,
                    mContext.getResources().getString(R.string.share_picker_label), BuildConfig.APPLICATION_ID,
                    Constant.SHARE_PICKER_NAME);
        }
    }

    private void setDefaultLeftConsumeBubble() {
        String leftConsumeBubblePackageName = mSharedPreferences
                .getString(PREFERENCE_LEFT_CONSUME_BUBBLE_PACKAGE_NAME, null);
        if (leftConsumeBubblePackageName == null) {
            Intent intent = new Intent(Intent.ACTION_SEND);
            intent.setType("text/plain");
            PackageManager packageManager = mContext.getPackageManager();
            List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(intent, 0);

            if (setDefaultLeftConsumeBubble(
                    findResolveInfoForPackageName(resolveInfos, Constant.POCKET_PACKAGE_NAME),
                    packageManager) == false) {
                if (setDefaultLeftConsumeBubble(
                        findResolveInfoForPackageName(resolveInfos, "com.instapaper.android"),
                        packageManager) == false) {
                    if (setDefaultLeftConsumeBubble(
                            findResolveInfoForPackageName(resolveInfos, "com.facebook.katana"),
                            packageManager) == false) {
                        if (setDefaultLeftConsumeBubble(
                                findResolveInfoForPackageName(resolveInfos, "com.twitter.android"),
                                packageManager) == false) {
                            if (setDefaultLeftConsumeBubble(
                                    findResolveInfoForPackageName(resolveInfos, "com.google.android.apps.plus"),
                                    packageManager) == false) {
                                if (setDefaultLeftConsumeBubble(
                                        findResolveInfoForPackageName(resolveInfos, "com.google.android.gm"),
                                        packageManager) == false) {
                                    // Can't imagine *none* of the above apps will not be installed too often, but if so, fall back to the first item in the list...
                                    setDefaultLeftConsumeBubble(resolveInfos.get(0), packageManager);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private boolean setDefaultLeftConsumeBubble(ResolveInfo resolveInfo, PackageManager packageManager) {
        if (resolveInfo != null) {
            setConsumeBubble(Constant.BubbleAction.ConsumeLeft, Constant.ActionType.Share,
                    resolveInfo.loadLabel(packageManager).toString(), resolveInfo.activityInfo.packageName,
                    resolveInfo.activityInfo.name);
            return true;
        }
        return false;
    }

    public List<Intent> getBrowsers() {
        if (mBrowsers == null) {
            updateBrowsers();
        }
        return mBrowsers;
    }

    public void initiateBrowsersUpdate() {
        mBrowsers = null;
    }

    public List<String> getBrowserPackageNames() {
        if (mBrowserPackageNames == null) {
            updateBrowsers();
        }

        return mBrowserPackageNames;
    }

    public void setDefaultBrowser(String label, String packageName) {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(PREFERENCE_DEFAULT_BROWSER_LABEL, label);
        editor.putString(PREFERENCE_DEFAULT_BROWSER_PACKAGE_NAME, packageName);
        editor.commit();
    }

    public String getDefaultBrowserLabel() {
        return mSharedPreferences.getString(PREFERENCE_DEFAULT_BROWSER_LABEL, null);
    }

    String getDefaultBrowserPackageName() {
        return mSharedPreferences.getString(PREFERENCE_DEFAULT_BROWSER_PACKAGE_NAME, null);
    }

    ComponentName getDefaultBrowserComponentName(Context context) {
        String defaultBrowserPackageName = getDefaultBrowserPackageName();
        if (defaultBrowserPackageName != null) {
            Intent browserIntent = context.getPackageManager().getLaunchIntentForPackage(defaultBrowserPackageName);
            if (browserIntent != null) {
                return browserIntent.getComponent();
            }
        }
        return null;
    }

    public Drawable getDefaultBrowserIcon(Context context) {
        ComponentName componentName = getDefaultBrowserComponentName(context);
        if (componentName != null) {
            try {
                return context.getPackageManager().getActivityIcon(componentName);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e("Exception", e.getMessage(), e);
            }
        }

        return null;
    }

    public void setConsumeBubble(Constant.BubbleAction action, Constant.ActionType type, String label,
            String packageName, String activityClassName) {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        switch (action) {
        case ConsumeLeft:
            editor.putString(PREFERENCE_LEFT_CONSUME_BUBBLE_LABEL, label);
            editor.putString(PREFERENCE_LEFT_CONSUME_BUBBLE_PACKAGE_NAME, packageName);
            editor.putString(PREFERENCE_LEFT_CONSUME_BUBBLE_ACTIVITY_CLASS_NAME, activityClassName);
            editor.putString(PREFERENCE_LEFT_CONSUME_BUBBLE_TYPE, type.name());
            break;

        case ConsumeRight:
            editor.putString(PREFERENCE_RIGHT_CONSUME_BUBBLE_LABEL, label);
            editor.putString(PREFERENCE_RIGHT_CONSUME_BUBBLE_PACKAGE_NAME, packageName);
            editor.putString(PREFERENCE_RIGHT_CONSUME_BUBBLE_ACTIVITY_CLASS_NAME, activityClassName);
            editor.putString(PREFERENCE_RIGHT_CONSUME_BUBBLE_TYPE, type.name());
            break;

        //case LinkDoubleTap:
        //    editor.putString(PREFERENCE_LINK_DOUBLE_TAP_LABEL, label);
        //    editor.putString(PREFERENCE_LINK_DOUBLE_TAP_PACKAGE_NAME, packageName);
        //    editor.putString(PREFERENCE_LINK_DOUBLE_TAP_ACTIVITY_CLASS_NAME, activityClassName);
        //    editor.putString(PREFERENCE_LINK_DOUBLE_TAP_TYPE, type.name());
        //    break;

        }
        editor.commit();

        MainApplication.postEvent(mContext, new OnConsumeBubblesChangedEvent());
    }

    public String getConsumeBubbleLabel(Constant.BubbleAction action) {
        switch (action) {
        case ConsumeLeft:
            return mSharedPreferences.getString(PREFERENCE_LEFT_CONSUME_BUBBLE_LABEL, null);

        case ConsumeRight:
            return mSharedPreferences.getString(PREFERENCE_RIGHT_CONSUME_BUBBLE_LABEL, null);

        //case LinkDoubleTap:
        //    return mSharedPreferences.getString(PREFERENCE_LINK_DOUBLE_TAP_LABEL, mContext.getString(R.string.not_set));
        }
        return null;
    }

    String getConsumeBubblePackageName(Constant.BubbleAction action) {
        switch (action) {
        case ConsumeLeft:
            return mSharedPreferences.getString(PREFERENCE_LEFT_CONSUME_BUBBLE_PACKAGE_NAME, null);

        case ConsumeRight:
            return mSharedPreferences.getString(PREFERENCE_RIGHT_CONSUME_BUBBLE_PACKAGE_NAME, null);

        //case LinkDoubleTap:
        //    return mSharedPreferences.getString(PREFERENCE_LINK_DOUBLE_TAP_PACKAGE_NAME, null);
        }
        return null;
    }

    String getConsumeBubbleActivityClassName(Constant.BubbleAction action) {
        switch (action) {
        case ConsumeLeft:
            return mSharedPreferences.getString(PREFERENCE_LEFT_CONSUME_BUBBLE_ACTIVITY_CLASS_NAME, null);

        case ConsumeRight:
            return mSharedPreferences.getString(PREFERENCE_RIGHT_CONSUME_BUBBLE_ACTIVITY_CLASS_NAME, null);

        //case LinkDoubleTap:
        //    return mSharedPreferences.getString(PREFERENCE_LINK_DOUBLE_TAP_ACTIVITY_CLASS_NAME, null);
        }
        return null;
    }

    Constant.ActionType getConsumeBubbleActionType(Constant.BubbleAction action) {
        String actionTypeAsString = null;
        switch (action) {
        case ConsumeLeft:
            actionTypeAsString = mSharedPreferences.getString(PREFERENCE_LEFT_CONSUME_BUBBLE_TYPE, null);
            break;

        case ConsumeRight:
            actionTypeAsString = mSharedPreferences.getString(PREFERENCE_RIGHT_CONSUME_BUBBLE_TYPE, null);
            break;

        //case LinkDoubleTap:
        //    actionTypeAsString = mSharedPreferences.getString(PREFERENCE_LINK_DOUBLE_TAP_TYPE, null);
        //    break;
        }

        if (actionTypeAsString != null) {
            if (actionTypeAsString.equals(Constant.ActionType.Share.name())) {
                return Constant.ActionType.Share;
            } else if (actionTypeAsString.equals(Constant.ActionType.View.name())) {
                return Constant.ActionType.View;
            }
        }

        return Constant.ActionType.Unknown;
    }

    public Drawable getConsumeBubbleIcon(Constant.BubbleAction action) {
        return getConsumeBubbleIcon(action, true);
    }

    public Drawable getConsumeBubbleIcon(Constant.BubbleAction action, boolean whiteShareIcon) {
        PackageManager packageManager = mContext.getPackageManager();
        try {
            String packageName = getConsumeBubblePackageName(action);
            String name = getConsumeBubbleActivityClassName(action);
            if (packageName != null && name != null) {
                if (name.equals(Constant.SHARE_PICKER_NAME)) {
                    return mContext.getResources().getDrawable(
                            whiteShareIcon ? R.drawable.ic_share_white_24dp : R.drawable.ic_share_grey600_24dp);
                }
                ComponentName componentName = new ComponentName(packageName, name);
                return packageManager.getActivityIcon(componentName);
            } else if (packageName != null) {
                // Try rendering the icon if we only have a packageName.
                ApplicationInfo app = packageManager.getApplicationInfo(packageName, 0);
                Drawable icon = packageManager.getApplicationIcon(app);
                return icon;
            }
        } catch (OutOfMemoryError ex) {
        } catch (PackageManager.NameNotFoundException e) {
        }
        return mContext.getResources().getDrawable(R.drawable.ic_launcher);
    }

    private void loadIgnoreLinksFromPackageNames() {
        if (mIgnoreLinksFromPackageNames == null) {
            mIgnoreLinksFromPackageNames = new ArrayList<String>();
        }
        mIgnoreLinksFromPackageNames.clear();

        // Ignore this on L. Hopefully Google reverse this decision...
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
            String string = mSharedPreferences.getString(PREFERENCE_IGNORE_LINKS_FROM, null);
            if (string != null) {
                String[] split = string.split(",");
                if (split != null && split.length > 0) {
                    for (String s : split) {
                        mIgnoreLinksFromPackageNames.add(s);
                    }
                }
            }
        }
    }

    public void setIgnoreLinksFromPackageNames(ArrayList<String> packageNames) {

        mIgnoreLinksFromPackageNames.clear();

        String result = "";
        if (packageNames != null && packageNames.size() > 0) {
            for (String packageName : packageNames) {
                result += packageName + ",";
                mIgnoreLinksFromPackageNames.add(packageName);
            }
        }

        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(PREFERENCE_IGNORE_LINKS_FROM, result);
        editor.commit();
    }

    public List<String> getIgnoreLinksFromPackageNames() {
        return mIgnoreLinksFromPackageNames;
    }

    public boolean ignoreLinkFromPackageName(String packageName) {
        for (String ignore : mIgnoreLinksFromPackageNames) {
            if (ignore.equals(packageName)) {
                return true;
            }
        }

        return false;
    }

    public boolean getAutoContentDisplayLinkLoaded() {
        return mSharedPreferences.getBoolean(PREFERENCE_AUTO_CONTENT_DISPLAY_LINK_LOADED,
                PREFERENCE_AUTO_CONTENT_DISPLAY_LINK_LOADED_DEFAULT);
    }

    public boolean isIncognitoMode() {
        return mSharedPreferences.getBoolean(PREFERENCE_INCOGNITO_MODE, false);
    }

    public boolean isHideBubbles() {
        return mSharedPreferences.getBoolean(PREFERENCE_HIDE_BUBBLES, true);
    }

    public boolean isTrackingProtectionEnabled() {
        return mSharedPreferences.getBoolean(PREFERENCE_TRACKINGPROTECTION_MODE, true);
    }

    public boolean isAdBlockEnabled() {
        return mSharedPreferences.getBoolean(PREFERENCE_ADBLOCK_MODE, true);
    }

    public boolean isBlock3PCookiesEnabled() {
        return mSharedPreferences.getBoolean(PREFERENCE_BLOCK_3P_COOKIES, true);
    }

    public boolean isHttpsEverywhereEnabled() {
        return mSharedPreferences.getBoolean(PREFERENCE_HTTPS_EVERYWHERE_MODE, false);
    }

    public void setWebViewBatterySaveMode(String mode) {
        if (mode.equals("aggressive")) {
            mWebViewBatterySaveMode = WebViewBatterySaveMode.Aggressive;
        } else if (mode.equals("off")) {
            mWebViewBatterySaveMode = WebViewBatterySaveMode.Off;
        } else {
            mWebViewBatterySaveMode = WebViewBatterySaveMode.Default;
        }
    }

    public void setWebViewBatterySaveMode(WebViewBatterySaveMode mode) {
        String value;
        switch (mode) {
        case Off:
            value = "off";
            break;

        case Aggressive:
            value = "aggressive";
            break;

        case Default:
        default:
            value = "default";
            break;
        }

        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(PREFERENCE_WEBVIEW_BATTERY_SAVING_MODE, value);
        editor.apply();

        setWebViewBatterySaveMode(value);
    }

    public WebViewBatterySaveMode getWebViewBatterySaveMode() {
        return mWebViewBatterySaveMode;
    }

    public String getUserAgentString() {
        String defaultUserAgent = mSharedPreferences.getString(PREFERENCE_USER_AGENT, "default");
        if (defaultUserAgent.equals("chrome_phone")) {
            return Constant.USER_AGENT_CHROME_PHONE;
        } else if (defaultUserAgent.equals("chrome_tablet")) {
            return Constant.USER_AGENT_CHROME_TABLET;
        } else if (defaultUserAgent.equals("chrome_desktop")) {
            return Constant.USER_AGENT_CHROME_DESKTOP;
        }

        return null;
    }

    public boolean isEnabled() {
        //return mSharedPreferences.getBoolean(PREFERENCE_ENABLED, false);
        return true;
    }

    public boolean checkForYouTubeEmbeds() {
        return mSharedPreferences.getBoolean(PREFERENCE_CHECK_FOR_YOUTUBE_EMBEDS, false);
    }

    public boolean getSayThanksClicked() {
        return mSharedPreferences.getBoolean(SAY_THANKS_CLICKED, false);
    }

    public void setSayThanksClicked(boolean value) {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putBoolean(SAY_THANKS_CLICKED, value);
        editor.commit();
    }

    void configureFallbackRedirectHosts(Set<String> items) {
        if (items != null && items.size() > 0) {
            // Make a copy to as documentation explicitly states not to trust the result
            // of getStringSet() call. http://stackoverflow.com/a/19949833/328679
            mFallbackRedirectHosts = new HashSet<>(items);
        } else {
            mFallbackRedirectHosts.clear();
        }
    }

    public void addFallbackRedirectHost(String host) {
        mFallbackRedirectHosts.add(host);
        saveFallbackRedirectHosts();
    }

    public void removeFallbackRedirectHost(String host) {
        mFallbackRedirectHosts.remove(host);
        saveFallbackRedirectHosts();
    }

    private void saveFallbackRedirectHosts() {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.remove(PREFERENCE_FALLBACK_REDIRECT_HOSTS); // always remove. See http://stackoverflow.com/a/21401062/328679
        if (mFallbackRedirectHosts.size() > 0) {
            editor.putStringSet(PREFERENCE_FALLBACK_REDIRECT_HOSTS, mFallbackRedirectHosts);
        }
        editor.apply();
    }

    public Set<String> getFallbackRedirectHosts() {
        return mFallbackRedirectHosts;
    }

    public boolean redirectUrlToBrowser(URL url) {
        String host = url.getHost();

        String hostAlt = host.contains("www.") ? host.replace("www.", "") : "www." + host;
        return mFallbackRedirectHosts.contains(host) || mFallbackRedirectHosts.contains(hostAlt);

        /*
         * Temporarily enable DownloadHandlerActivity to see if it might be used to handle this URL. If so, redirect the
         * URL to the default browser rather than allowing an app like ES File Explorer be set as a default app for a host. #338
         */

        /*
        boolean result = false;
            
        packageManager.setComponentEnabledSetting(mDownloadHandlerComponentName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
            
        mDownloadQueryIntent.setAction(Intent.ACTION_VIEW);
        mDownloadQueryIntent.setData(Uri.parse(url));
        List<ResolveInfo> resolveInfos = packageManager.queryIntentActivities(mDownloadQueryIntent, PackageManager.GET_RESOLVED_FILTER);
        if (resolveInfos != null && resolveInfos.size() > 0) {
        String className = DownloadHandlerActivity.class.getName();
        for (ResolveInfo resolveInfo : resolveInfos) {
            if (resolveInfo.activityInfo != null && resolveInfo.activityInfo.name.equals(className)) {
                result = true;
                break;
            }
        }
        }
            
        packageManager.setComponentEnabledSetting(mDownloadHandlerComponentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
            
        return result;
            */
    }

    public boolean getAutoArticleMode() {
        //return Constant.ARTICLE_MODE ? false : mSharedPreferences.getBoolean(PREFERENCE_AUTO_ARTICLE_MODE, false);
        return false;
    }

    public int getWebViewTextZoom() {
        return mSharedPreferences.getInt(PREFERENCE_WEBVIEW_TEXT_ZOOM, PREFERENCE_WEBVIEW_TEXT_ZOOM_DEFAULT);
    }

    public void setWebViewTextZoom(int zoom) {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putInt(PREFERENCE_WEBVIEW_TEXT_ZOOM, zoom);
        editor.commit();
    }

    public boolean getShowUndoCloseTab() {
        return mSharedPreferences.getBoolean(PREFERENCE_SHOW_UNDO_CLOSE_TAB, true);
    }

    public List<ResolveInfo> getAppsThatHandleUrl(String urlAsString, PackageManager packageManager) {

        List<Intent> browsers = getBrowsers();

        Intent intent = new Intent();
        intent.setAction(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(urlAsString));
        List<ResolveInfo> infos = packageManager.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);

        ArrayList<ResolveInfo> results = new ArrayList<ResolveInfo>();

        for (ResolveInfo info : infos) {
            IntentFilter filter = info.filter;
            if (filter != null && filter.hasAction(Intent.ACTION_VIEW)) {

                // Check if this item is a browser, and if so, ignore it
                boolean packageOk = true;
                for (Intent browser : browsers) {
                    if (info.activityInfo.packageName.equals(browser.getComponent().getPackageName())) {
                        packageOk = false;
                        break;
                    }
                }

                if (packageOk) {
                    // Ensure TapPath is always ignored
                    if (info.activityInfo.packageName.contains("com.digitalashes.tappath")) {
                        //Log.d("blerg", "ignore " + info.activityInfo.packageName);
                        packageOk = false;
                    } else {
                        // And some special case code for me to ignore alternate builds
                        if (BuildConfig.DEBUG) {
                            if (info.activityInfo.packageName.equals("com.linkbubble.playstore")
                                    || info.activityInfo.packageName.equals("com.brave.playstore")) {
                                //Log.d("blerg", "ignore " + info.activityInfo.packageName);
                                packageOk = false;
                            }
                        } else {
                            if (info.activityInfo.packageName.equals("com.linkbubble.playstore.dev")
                                    || info.activityInfo.packageName.equals("com.brave.playstore.dev")) {
                                //Log.d("blerg", "ignore " + info.activityInfo.packageName);
                                packageOk = false;
                            }
                        }
                    }
                }

                if (packageOk) {
                    results.add(info);
                    Log.d("appHandles", info.loadLabel(packageManager) + " for url:" + urlAsString);
                }
            }
        }

        if (results.size() > 0) {
            return results;
        }

        return null;
    }

    public ResolveInfo getDefaultAppForUrl(URL url, List<ResolveInfo> resolveInfos) {
        if (resolveInfos == null || resolveInfos.size() == 0) {
            return null;
        }

        String host = url.getHost();
        if (host.length() > 1) {
            String flattenedComponentName = mDefaultAppsMap.get(host);
            if (flattenedComponentName != null) {
                ComponentName componentName = ComponentName.unflattenFromString(flattenedComponentName);
                if (componentName != null) {
                    for (ResolveInfo resolveInfo : resolveInfos) {
                        // Handle crash: https://fabric.io/brave6/android/apps/com.linkbubble.playstore/issues/5623e787f5d3a7f76be5b166
                        if (resolveInfo == null || resolveInfo.activityInfo == null) {
                            CrashTracking.log("Null resolveInfo when getting default for app: " + resolveInfo);
                            continue;
                        }

                        if (resolveInfo.activityInfo.packageName.equals(componentName.getPackageName())
                                && resolveInfo.activityInfo.name.equals(componentName.getClassName())) {
                            return resolveInfo;
                        }
                    }

                    if (componentName.getPackageName().equals(mContext.getPackageName())) {
                        return mLinkBubbleEntryActivityResolveInfo;
                    }
                }
            }
        }

        return null;
    }

    public void setDefaultApp(String urlAsString, ResolveInfo resolveInfo) {
        setDefaultApp(urlAsString, resolveInfo, true);
    }

    public void setDefaultApp(String urlAsString, ResolveInfo resolveInfo, boolean save) {
        try {
            URL url = new URL(urlAsString);
            String host = url.getHost();
            if (host.length() > 1) {
                ComponentName componentName = new ComponentName(resolveInfo.activityInfo.packageName,
                        resolveInfo.activityInfo.name);
                addDefaultApp(host, componentName, save);
            }
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
    }

    private void addDefaultApp(String host, ComponentName componentName, boolean save) {

        mDefaultAppsMap.put(host, componentName.flattenToString());

        if (save) {
            saveDefaultApps();
        }
    }

    public void removeDefaultApp(String host) {
        if (mDefaultAppsMap.containsKey(host)) {
            mDefaultAppsMap.remove(host);
            saveDefaultApps();
        }
    }

    public Vector<String> loadCurrentTabs() {
        Vector<String> urls = new Vector<String>();
        String json = mSharedPreferences.getString(PREFERENCE_CURRENT_TABS, "[]");
        try {
            JSONArray jsonArray = new JSONArray(json);
            for (int i = 0; i < jsonArray.length(); ++i) {
                String urlAsString = jsonArray.getString(i);
                if (urlAsString.equals(Constant.WELCOME_MESSAGE_URL) == false) {
                    urls.add(urlAsString);
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return urls;
    }

    public void saveCurrentTabs(List<View> bubbles) {
        if (Constant.SAVE_CURRENT_TABS == false) {
            return;
        }

        JSONArray jsonArray = new JSONArray();
        if (bubbles != null) {
            for (View view : bubbles) {
                URL url = ((TabView) view).getUrl();
                if (url.toString().equals(Constant.NEW_TAB_URL) == false) {
                    jsonArray.put(url.toString());
                }
            }
        }

        String currentTabsString = mSharedPreferences.getString(PREFERENCE_CURRENT_TABS, "");
        String newTabsString = jsonArray.toString();

        if (!currentTabsString.equals(newTabsString)) {
            SharedPreferences.Editor editor = mSharedPreferences.edit();
            editor.putString(PREFERENCE_CURRENT_TABS, newTabsString);
            editor.commit();
        }
    }

    private void saveDefaultApps() {
        JSONArray jsonArray = new JSONArray();
        for (String key : mDefaultAppsMap.keySet()) {
            String component = mDefaultAppsMap.get(key);
            JSONObject object = new JSONObject();
            try {
                object.put(DEFAULT_APPS_MAP_KEY_HOST, key);
                object.put(DEFAULT_APPS_MAP_KEY_COMPONENT, component);
                jsonArray.put(object);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(PREFERENCE_DEFAULT_APPS, jsonArray.toString());
        editor.commit();
    }

    private void configureDefaultApps(String defaultAppsAsString) {
        try {
            if (defaultAppsAsString != null) {
                JSONArray defaultApps = new JSONArray(defaultAppsAsString);
                mDefaultAppsMap.clear();

                for (int i = 0; i < defaultApps.length(); i++) {
                    try {
                        JSONObject object = defaultApps.getJSONObject(i);
                        String host = object.getString(DEFAULT_APPS_MAP_KEY_HOST);
                        String flattenedName = object.getString(DEFAULT_APPS_MAP_KEY_COMPONENT);
                        mDefaultAppsMap.put(host, flattenedName);
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            } else {
                mDefaultAppsMap.clear();
            }
        } catch (JSONException e) {
            mDefaultAppsMap.clear();
        }
    }

    public TreeMap<String, ComponentName> getDefaultAppsMap() {
        if (mDefaultAppsMap != null && mDefaultAppsMap.size() > 0) {
            TreeMap<String, ComponentName> result = new TreeMap<String, ComponentName>();
            for (String host : mDefaultAppsMap.keySet()) {
                result.put(host, ComponentName.unflattenFromString(mDefaultAppsMap.get(host)));
            }
            return result;
        }

        return null;
    }

    public Point getBubbleRestingPoint() {
        mTempBubbleRestingPoint.x = mBubbleRestingPoint.x;
        if (mTempBubbleRestingPoint.x > Config.mScreenCenterX) {
            mTempBubbleRestingPoint.x = Config.mBubbleSnapRightX;
        } else {
            mTempBubbleRestingPoint.x = Config.mBubbleSnapLeftX;
        }

        mTempBubbleRestingPoint.y = mBubbleRestingPoint.y;
        int minYPosition = (int) (Config.mScreenHeight * .8f);
        if (mTempBubbleRestingPoint.y > minYPosition) {
            mTempBubbleRestingPoint.y = minYPosition;
        }
        return mTempBubbleRestingPoint;
    }

    public int getBubbleStartingX(Point bubbleRestingPoint) {
        float fromX;
        if (bubbleRestingPoint.x > Config.mScreenCenterX) {
            fromX = Config.mBubbleSnapRightX + Config.mBubbleWidth;
        } else {
            fromX = Config.mBubbleSnapLeftX - Config.mBubbleWidth;
        }
        return (int) fromX;
    }

    public void setBubbleRestingPoint(int x, int y) {
        mBubbleRestingPoint.set(x, y);
    }

    public void saveBubbleRestingPoint() {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putInt(BUBBLE_RESTING_X, mBubbleRestingPoint.x);
        editor.putInt(BUBBLE_RESTING_Y, mBubbleRestingPoint.y);
        editor.commit();
    }

    public void setWelcomeMessageDisplayed(boolean displayed) {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putBoolean(WELCOME_MESSAGE_DISPLAYED, displayed);
        editor.commit();
    }

    public boolean getWelcomeMessageDisplayed() {
        return mSharedPreferences.getBoolean(WELCOME_MESSAGE_DISPLAYED, false);
    }

    public void setTermsAccepted(boolean accepted) {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putBoolean(TERMS_ACCEPTED, accepted);
        editor.commit();
    }

    public boolean getTermsAccepted() {
        return mSharedPreferences.getBoolean(TERMS_ACCEPTED, false);
    }

    public boolean debugAutoLoadUrl() {
        return mSharedPreferences.getBoolean("auto_load_url", false);
    }

    public boolean getArticleModeEnabled() {
        return mSharedPreferences.getBoolean(KEY_ARTICLE_MODE_PREFERENCE, true);
    }

    public boolean getArticleModeOnWearEnabled() {
        return mSharedPreferences.getBoolean(KEY_ARTICLE_MODE_ON_WEAR_PREFERENCE, false);
    }

    private static final int APP_CHANGE_ANIM_TIME = 250;

    private static final String TOTAL_TIME_SAVED_KEY = "total_time_saved";
    private static final String TOTAL_LINKS_LOADED_KEY = "total_links_loaded";

    public static final String LOAD_TIME_TAG = "LoadTime";

    public enum LinkLoadType {
        PageLoad, // The page was loaded.
        AppRedirectInstant, // Redirect via MainController (before a ContentView instance is created for this link).
        AppRedirectBrowser, // Redirect via ContentView.
        ShareToOtherApp, // Adding to Pocket or sending to Twitter.
        OpenInOtherApp, // Opening in Chrome.
    }

    long mTotalTimeSaved;
    int mTotalLinksLoaded;

    public static class LinkLoadTimeStatsUpdatedEvent {
    }

    private LinkLoadTimeStatsUpdatedEvent mLinkLoadTimeStatsUpdatedEvent = new LinkLoadTimeStatsUpdatedEvent();

    public void trackLinkLoadTime(long timeSaved, LinkLoadType linkLoadType, String url) {
        switch (linkLoadType) {
        case AppRedirectInstant:
            // Add some time for the time taken to animate to the new app
            timeSaved += APP_CHANGE_ANIM_TIME;
            break;

        case AppRedirectBrowser:
            // Add some time for the time taken to load the browser initially
            timeSaved += APP_CHANGE_ANIM_TIME;
            // Add some time for the time taken to animate to the new app
            timeSaved += APP_CHANGE_ANIM_TIME;
            break;
        }

        mTotalTimeSaved += timeSaved;
        mTotalLinksLoaded++;

        Log.d(LOAD_TIME_TAG, "trackLinkLoadTime() - timeSaved:" + ((float) timeSaved / 1000.f) + " seconds, "
                + linkLoadType + ", " + url);

        MainApplication.postEvent(mContext, mLinkLoadTimeStatsUpdatedEvent);

        Analytics.trackTimeSaved(timeSaved);
    }

    public long getTotalTimeSaved() {
        return mTotalTimeSaved;
    }

    public long getTimeSavedPerLink() {
        if (mTotalLinksLoaded > 0) {
            return mTotalTimeSaved / mTotalLinksLoaded;
        }
        return -1;
    }

    public void saveLinkLoadStats() {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putLong(TOTAL_TIME_SAVED_KEY, mTotalTimeSaved);
        editor.putInt(TOTAL_LINKS_LOADED_KEY, mTotalLinksLoaded);
        editor.commit();
    }

    private void loadLinkLoadStats() {
        mTotalTimeSaved = mSharedPreferences.getLong(TOTAL_TIME_SAVED_KEY, 0);
        mTotalLinksLoaded = mSharedPreferences.getInt(TOTAL_LINKS_LOADED_KEY, 0);
    }

    private static final int RECENT_REDIRECT_TIME_DELTA = (int) (2.5f * 1000);

    private static final String LAST_APP_REDIRECTS = "last_app_redirects";
    private static final String LAST_APP_REDIRECT_KEY_TIME = "time";
    private static final String LAST_APP_REDIRECT_KEY_URL = "url";
    private static final int MAX_LAST_APP_REDIRECT_COUNT = 3;

    private static class LastAppRedirect {
        String mUrl;
        long mTime;
    }

    private List<LastAppRedirect> mLastAppRedirects = new ArrayList<LastAppRedirect>(MAX_LAST_APP_REDIRECT_COUNT);

    // Did we *just* redirect to this URL? We need to store this to fix #276
    public boolean didRecentlyRedirectToApp(String url) {
        long currentTime = System.currentTimeMillis();
        for (LastAppRedirect lastAppRedirect : mLastAppRedirects) {
            long timeDelta = currentTime - lastAppRedirect.mTime;
            if (timeDelta < RECENT_REDIRECT_TIME_DELTA && lastAppRedirect.mUrl.equals(url)) {
                return true;
            }
        }

        return false;
    }

    public void addRedirectToApp(String url) {

        LastAppRedirect record = null;

        // try and find a current record with this url
        for (LastAppRedirect i : mLastAppRedirects) {
            if (i.mUrl.equals(url)) {
                record = i;
                break;
            }
        }

        if (record == null && mLastAppRedirects.size() == MAX_LAST_APP_REDIRECT_COUNT) {
            // Get the oldest record in the list
            for (LastAppRedirect i : mLastAppRedirects) {
                if (record == null) {
                    record = i;
                } else if (i.mTime < record.mTime) {
                    record = i;
                }
            }
        }

        if (record == null) {
            record = new LastAppRedirect();
            mLastAppRedirects.add(record);
        }

        record.mUrl = url;
        record.mTime = System.currentTimeMillis();
    }

    private void saveRecentAppRedirects() {
        JSONArray jsonArray = new JSONArray();
        for (LastAppRedirect lastAppRedirect : mLastAppRedirects) {
            JSONObject object = new JSONObject();
            try {
                object.put(LAST_APP_REDIRECT_KEY_URL, lastAppRedirect.mUrl);
                object.put(LAST_APP_REDIRECT_KEY_TIME, lastAppRedirect.mTime);
                jsonArray.put(object);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putString(LAST_APP_REDIRECTS, jsonArray.toString());
        editor.commit();
    }

    private void loadRecentAppRedirects() {
        mLastAppRedirects.clear();
        String json = mSharedPreferences.getString(LAST_APP_REDIRECTS, "[]");
        try {
            JSONArray jsonArray = new JSONArray(json);
            for (int i = 0; i < jsonArray.length(); ++i) {
                try {
                    JSONObject object = jsonArray.getJSONObject(i);
                    LastAppRedirect lastAppRedirect = new LastAppRedirect();
                    lastAppRedirect.mUrl = object.getString(LAST_APP_REDIRECT_KEY_URL);
                    lastAppRedirect.mTime = object.getLong(LAST_APP_REDIRECT_KEY_TIME);
                    mLastAppRedirects.add(lastAppRedirect);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public boolean canFlushWebViewCache() {
        long lastEmptyTime = mSharedPreferences.getLong(LAST_FLUSH_WEBVIEW_CACHE_TIME, -1);
        if (lastEmptyTime > -1) {
            long delta = System.currentTimeMillis() - lastEmptyTime;
            if (delta > Constant.EMPTY_WEBVIEW_CACHE_INTERVAL) {
                return true;
            }
        }

        return false;
    }

    public void updateLastFlushWebViewCacheTime() {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putLong(LAST_FLUSH_WEBVIEW_CACHE_TIME, System.currentTimeMillis());
        editor.commit();
    }

    public boolean getDarkThemeEnabled() {
        return mSharedPreferences.getBoolean(PREFERENCE_THEME_DARK, false);
    }

    public void setDarkThemeEnabled(boolean value) {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putBoolean(PREFERENCE_THEME_DARK, value);
        editor.commit();
    }

    public boolean getColoredProgressIndicator() {
        return mSharedPreferences.getBoolean(PREFERENCE_COLORED_PROGRESS_INDICATOR, true);
    }

    public void setColoredProgressIndicator(boolean value) {
        SharedPreferences.Editor editor = mSharedPreferences.edit();
        editor.putBoolean(PREFERENCE_COLORED_PROGRESS_INDICATOR, value);
        editor.commit();
    }

    public int getThemedDefaultProgressColor() {
        if (getDarkThemeEnabled()) {
            return COLOR_PROGRESS_DARK;
        }
        return COLOR_PROGRESS_LIGHT;
    }

    public int getThemedContentViewColor() {
        if (getDarkThemeEnabled()) {
            return COLOR_CONTENT_VIEW_DARK;
        }
        return COLOR_CONTENT_VIEW_LIGHT;
    }

    public int getThemedTextColor() {
        if (getDarkThemeEnabled()) {
            return COLOR_TEXT_DARK;
        }
        return COLOR_TEXT_LIGHT;
    }

    public int getThemedLinkColor() {
        if (getDarkThemeEnabled()) {
            return COLOR_LINK_DARK;
        }
        return COLOR_LINK_LIGHT;
    }

    public boolean getThemeToolbar() {
        return mSharedPreferences.getBoolean(PREFERENCE_THEME_TOOLBAR, true);
    }
}