com.newsrob.EntryManager.java Source code

Java tutorial

Introduction

Here is the source code for com.newsrob.EntryManager.java

Source

package com.newsrob;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;

import org.apache.http.client.ClientProtocolException;

import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.os.Process;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.preference.Preference.OnPreferenceChangeListener;
import android.util.Log;
import android.widget.Toast;

import com.google.ads.AdSenseSpec;
import com.google.ads.AdSenseSpec.AdType;
import com.google.ads.AdSenseSpec.ExpandDirection;
import com.newsrob.DB.Entries;
import com.newsrob.DB.EntryLabelAssociations;
import com.newsrob.EntriesRetriever.AuthToken;
import com.newsrob.EntriesRetriever.StateChange;
import com.newsrob.EntriesRetriever.AuthToken.AuthType;
import com.newsrob.appwidget.UnreadWidgetProvider;
import com.newsrob.appwidget.WidgetPreferences;
import com.newsrob.auth.AccountManagementUtils;
import com.newsrob.auth.IAccountManagementUtils;
import com.newsrob.download.WebPageDownloadDirector;
import com.newsrob.jobs.Job;
import com.newsrob.jobs.ModelUpdateResult;
import com.newsrob.jobs.SwitchStorageProviderFailed;
import com.newsrob.jobs.SwitchStorageProviderResult;
import com.newsrob.locale.FireReceiver;
import com.newsrob.locale.MockEditSettingsActivity;
import com.newsrob.storage.IStorageAdapter;
import com.newsrob.storage.PhoneMemoryStorageAdapter;
import com.newsrob.storage.SdCardStorageAdapter;
import com.newsrob.util.Base64;
import com.newsrob.util.SDK9Helper;
import com.newsrob.util.SDKVersionUtil;
import com.newsrob.util.SingleValueStore;
import com.newsrob.util.Timing;

/**
 * Entry Manager is the public interface to entries. The views go through the
 * Entry Manager to query for Entries.
 * 
 * It used the EntriesRetriever to retrieve entries from the GoogleReader
 * service. It stores the retrieved entries in a SQLiteDatabase using the
 * DatabaseHelper. The Entries are instantiated by the DatabaseHelper.
 * 
 * @author mkamp
 * 
 */
public class EntryManager
        implements SharedPreferences.OnSharedPreferenceChangeListener, OnPreferenceChangeListener {
    public static final String SETTINGS_SYNC_NEWSROB_ONLY_ENABLED = "settings_sync_newsrob_only_enabled";

    private static final String DEVICE_MODEL_DESIRE = "HTC Desire";
    private static final String DEVICE_MODEL_EVO = "PC36100";
    private static final String DEVICE_MODEL_DROID_INCREDIBLE = "ADR6300";
    private static final String DEVICE_MODEL_DELL_STREAK = "Dell Streak";
    private static final String DEVICE_MODEL_ARCHOS_7 = "A70HB";

    private static final String SYNC_TYPE_VALUE_UNREAD_ARTICLES_ONLY = "unread_articles_only";

    public static final String THEME_LIGHT = "Light";
    public static final String THEME_DARK = "Dark";

    public static final String ACTION_BAR_TOP = "top";
    public static final String ACTION_BAR_BOTTOM = "bottom";
    public static final String ACTION_BAR_GONE = "gone";

    private static final String SETTINGS_SYNC_TYPE = "settings_sync_type";

    static final String DOWNLOAD_NO = "no";

    public static final String DOWNLOAD_YES = "yes";

    static final String DOWNLOAD_WIFI_ONLY = "wifi-only";

    private static final String TAG = EntryManager.class.getSimpleName();

    public static final String SETTINGS_AUTOMATIC_REFRESHING_ENABLED = "settings_automatic_refreshing_enabled2";
    static final String SETTINGS_ENTRY_MANAGER_CAPACITY = "settings_entry_manager_entries_capacity";
    public static final String SETTINGS_AUTOMATIC_REFRESH_INTERVAL = "settings_automatic_refresh_interval";
    static final String SETTINGS_HIDE_READ_ITEMS = "settings_hide_read_items";
    static final String SETTINGS_LAST_SYNCED_SUBSCRIPTIONS = "last_synced_subscriptions";

    public static final String SETTINGS_STORAGE_ASSET_DOWNLOAD = "storage_asset_download";
    public static final String SETTINGS_STORAGE_PROVIDER_KEY = "settings_storage_provider";

    private static final String SETTINGS_MARK_ALL_READ_CONFIRMATION_DIALOG_THRESHOLD = "settings_ui_mark_all_read_confirmation_threshold2";

    static final String SETTINGS_GLOBAL_DOWNLOAD_PREF_KEY = "settings_global_download_pref";
    public static final String SETTINGS_PASS = "settings_last_used_u";
    static final String SETTINGS_REMEMBER_PASSWORD = "settings_remember_password";

    static final String SETTINGS_INSTALLED_AT = "com.newsrob.installed_at";

    public static final String SETTINGS_NEXT_SCHEDULED_SYNC_TIME = "next_scheduled_sync_time";
    public static final String SETTINGS_LAST_SYNC_TIME = "last_sync_time";
    public static final String SETTINGS_LAST_SYNC_COMPLETE = "last_sync_complete";

    private static final String SETTINGS_OPENED_PROGRESS_REPORT = "settings_has_opened_progress_report";

    static final String SETTINGS_LICENSE_ACCEPTED = "settings_license_accepted";
    static final String SETTINGS_ELLIPSIZE_TITLES_ENABLED = "settings_ellipsize_titles_enabled2";

    public static final String SETTINGS_HOVERING_ZOOM_CONTROLS_ENABLED = "settings_hovering_zoom_controls_enabled";
    static final String SETTINGS_HOVERING_BUTTONS_NAVIGATION_ENABLED = "settings_hovering_buttons_navigation_enabled";
    static final String SETTINGS_VOLUME_CONTROL_NAVIGATION_ENABLED = "settings_volume_control_navigation_enabled";
    static final String SETTINGS_CAMERA_BUTTON_CONTROLS_READ_STATE_ENABLED = "settings_camera_button_controls_read_state_enabled";

    static final String SETTINGS_ALWAYS_USE_SSL = "settings_always_use_ssl";

    static final String STORAGE_PROVIDER_SD_CARD = "sdcard";
    static final String STORAGE_PROVIDER_PHONE = "phone";

    private static final String SETTINGS_GOOGLE_USER_ID = "settings_google_user_id";

    public static final int LAST_VERSION_CHECK_INTERVAL_MINUTES = 24 * 60;

    static final String SETTINGS_AUTH_TOKEN = "settings_auth_token";
    static final String SETTINGS_AUTH_TYPE = "settings_auth_type";

    static final String SETTINGS_EMAIL = "email";

    private static final String SETTINGS_GR_UPDATED_KEY = "com.newsrob.gr_updated";

    public static final String SETTINGS_KEEP_STARRED = "settings_keep_starred";
    public static final String SETTINGS_KEEP_SHARED = "settings_keep_shared";
    public static final String SETTINGS_KEEP_NOTES = "settings_keep_notes";

    public static final String SETTINGS_INCREMENTAL_SYNC_ENABLED = "settings_incremental_syncing_enabled";

    public static final String SETTINGS_MOBILIZER = "settings_mobilizer";
    public static final String SETTINGS_PLUGINS = "settings_plugins";

    public static final String SETTINGS_UI_THEME = "settings_ui_theme";
    public static final String SETTINGS_UI_RICH_ARTICLE_LIST_ENABLED = "settings_ui_rich_articles_enabled";

    private static final String SETTINGS_UI_SORT_DEFAULT_NEWEST_FIRST = "settings_ui_sort_newest_first";

    private static final String SETTINGS_VIBRATE_FIRST_LAST_ENABLED = "settings_vibrate_first_last_enabled";

    private static final int MAX_ARTICLES_IN_ARTICLE_LIST = 250;

    private final DB databaseHelper;

    private EntriesRetriever grf;

    private static EntryManager instance;

    private final Context ctx;

    private boolean isModelCurrentlyUpdated = false;

    private final Collection<IEntryModelUpdateListener> listeners = new ArrayList<IEntryModelUpdateListener>(1);

    private IStorageAdapter fileContextAdapter;

    private final NewsRobNotificationManager newsRobNotificationManager;

    private final SharedPreferences sharedPreferences;

    protected Job currentRunningJob;

    private boolean cancelRequested;

    Thread runningThread;

    private final NewsRobScheduler scheduler;

    long modelLockedAt;
    String modelLockedBy;

    private ConnectivityManager connectivityManager;

    private Boolean proVersion;

    private Boolean isAndroidMarketInstalled;

    private TimerTask updateWidgetsTimerTask;
    private Timer updateWidgetsTimer;
    private Long lastUpdateWidgetUpdate;

    public static final String PRO_PACKAGE_NAME = "com.newsrob.pro";
    public static final String LEGACY_PACKAGE_NAME = "com.newsrob.threetosix";
    public static final String MARKET_PACKAGE_NAME = "com.android.vending";

    public static final String EXTRA_LOGIN_EXPIRED = "EXTRA_LOGIN_EXPIRED";

    public static final String EXTRA_CAPTCHA_TOKEN = "EXTRA_CAPTCHA_TOKEN";
    public static final String EXTRA_CAPTCHA_URL = "EXTRA_CAPTCHA_URL";

    private static final String SETTINGS_UI_TEXT_SIZE = "settings_ui_text_size";

    private static final String SETTING_SWIPE_ARTICLE_DETAIL_VIEW = "settings_swipe_article_detail_view";
    private static final String SETTING_SWIPE_ARTICLE_LIST = "settings_swipe_article_list";

    public static final String SETTINGS_SYNC_IN_PROGRESS_NOTIFICATION = "settings_sync_in_progress_enabled";

    private static final String SETTINGS_LAST_SUCCESSFUL_LOGIN = "settings_last_successful_login";

    private static final String SETTINGS_FIRST_INSTALLED_VERSION = "settings_first_installed_version";

    public static final String SETTINGS_UI_ACTION_BAR_LOCATION = "settings_ui_action_bar";

    private static final String SETTINGS_USAGE_DATA_COLLECTION_PERMISSION_VERSION = "usage_data_collection_version";
    public static final String SETTINGS_USAGE_DATA_PERMISSION_GRANTED = "usage_data_permission_granted";

    private static final String SETTINGS_ALWAYS_EXACT_SYNC = "settings_always_exact_sync";

    private int currentThemeId = 0;

    private Map<DBQuery, Boolean> isMarkAllReadPossibleCache;

    private SingleValueStore singleValueStore;

    private AdSenseSpec adSenseSpec;

    private Integer articleCountCache;

    private HashMap<DBQuery, Integer> contentCountCache;

    private EntryManager(final Context context) {

        singleValueStore = new SingleValueStore(context);

        isMarkAllReadPossibleCache = new HashMap<DBQuery, Boolean>();
        contentCountCache = new HashMap<DBQuery, Integer>();

        sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
        ctx = context;

        databaseHelper = new DB(context, NewsRob.getDebugProperties(context).getProperty("databasePath", null));

        String storageProviderPreference = getSharedPreferences().getString(SETTINGS_STORAGE_PROVIDER_KEY, null);
        if (storageProviderPreference == null) {// no default set yet
            storageProviderPreference = SdCardStorageAdapter.isAdvisable() ? STORAGE_PROVIDER_SD_CARD
                    : STORAGE_PROVIDER_PHONE;

            SDK9Helper.apply(getSharedPreferences().edit().putString(SETTINGS_STORAGE_PROVIDER_KEY,
                    storageProviderPreference));
        }

        fileContextAdapter = STORAGE_PROVIDER_SD_CARD.equals(storageProviderPreference)
                ? new SdCardStorageAdapter(context)
                : new PhoneMemoryStorageAdapter(context);

        getSharedPreferences().registerOnSharedPreferenceChangeListener(this);

        scheduler = new NewsRobScheduler(context, this);

        newsRobNotificationManager = new NewsRobNotificationManager(ctx);
        addListener(newsRobNotificationManager);
        setUpAdSenseSpec();

    }

    private void setUpAdSenseSpec() {
        // ("ca-mb-app-pub-2595622705667578") ca-mb-app-test expandedvideotest
        // android news technology tech google reader rss atom mobile
        adSenseSpec = new AdSenseSpec("ca-mb-app-pub-2595622705667578").setCompanyName("Mariano Kamp")
                .setAppName("NewsRob").setChannel("7593515733").setAdType(AdType.TEXT_IMAGE)
                .setExpandDirection(ExpandDirection.TOP)
                .setKeywords(
                        "android news technology tech finance vip gossip photography friends google reader newsreader rss atom mobile offline")
                .setAdTestEnabled(false);

    }

    public AdSenseSpec getAdSenseSpec() {
        return adSenseSpec;
    }

    public int getMyVersionCode() {
        PackageInfo packageInfo;
        try {
            packageInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0);
        } catch (final NameNotFoundException e) {
            throw new IllegalStateException(
                    ctx.getPackageName() + " was not found when quering the Package Manager.", e);
        }
        return packageInfo.versionCode;

    }

    public String getMyVersionName() {
        PackageInfo packageInfo;
        try {
            packageInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(), 0);
        } catch (final NameNotFoundException e) {
            throw new IllegalStateException(
                    ctx.getPackageName() + " was not found when quering the Package Manager.", e);
        }
        return packageInfo.versionName + (isProVersion() ? " Pro" : "");
    }

    public synchronized EntriesRetriever getEntriesRetriever() {
        if (grf == null)
            grf = new EntriesRetriever(getContext());
        return grf;
    }

    private void switchStorageProvider() {

        Log.d(TAG, "Switch Storage Provider");

        if (isModelCurrentlyUpdated())
            return;

        final String newPrefValue = getSharedPreferences().getString(SETTINGS_STORAGE_PROVIDER_KEY, null);
        final String oldStorageProviderClass = fileContextAdapter.getClass().getName();

        final String newStorageProviderClass = STORAGE_PROVIDER_SD_CARD.equals(newPrefValue)
                ? SdCardStorageAdapter.class.getName()
                : PhoneMemoryStorageAdapter.class.getName();
        if (!oldStorageProviderClass.equals(newStorageProviderClass)) {

            runningThread = new Thread(new Runnable() {

                public void run() {
                    final PowerManager pm = (PowerManager) ctx.getSystemService(Context.POWER_SERVICE);
                    final PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
                    Log.i(TAG, "Wake lock acquired at " + new Date().toString() + ".");
                    wl.acquire();
                    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                    final Timing t = new Timing("Storage Provider Switch", ctx);
                    ModelUpdateResult result = null;
                    if (isModelCurrentlyUpdated())
                        return;
                    try {
                        lockModel("EM.switchStorageProvider.run");
                    } catch (final IllegalStateException ise) {
                        return;
                    }
                    try {

                        Log.i(TAG, "Switching storage providers started at " + new Date().toString() + ".");

                        fireModelUpdateStarted("Switching storage providers", false, true);
                        Log.d(TAG, "Change of storage provider detected.");

                        final List<Job> jobList = new LinkedList<Job>();

                        final Job clearOldStorageProvider = new Job("Clearing Old Storage Provider",
                                EntryManager.this) {

                            @Override
                            public void run() {
                                Log.d(TAG, "Clearing the old storage provider.");
                                doClearCache();
                                if (fileContextAdapter.canWrite())
                                    WebPageDownloadDirector.removeAllAssets(fileContextAdapter);
                            }

                        };
                        jobList.add(clearOldStorageProvider);
                        final Job switchStorageProviders = new Job("Switching Storage Providers",
                                EntryManager.this) {

                            @Override
                            public void run() throws Exception {
                                Log.d(TAG, "Establishing new storage provider: " + newStorageProviderClass);
                                fileContextAdapter = newStorageProviderClass.equals(
                                        SdCardStorageAdapter.class.getName()) ? new SdCardStorageAdapter(ctx)
                                                : new PhoneMemoryStorageAdapter(ctx);

                                Log.d(TAG, "New storage provider established.");
                            }

                        };
                        jobList.add(switchStorageProviders);

                        final Job clearNewStorageProvider = new Job("Clearing New Storage Provider",
                                EntryManager.this) {

                            @Override
                            public void run() {
                                Log.d(TAG, "Clearing the new storage provider.");
                                doClearCache();
                                if (fileContextAdapter.canWrite())
                                    WebPageDownloadDirector.removeAllAssets(fileContextAdapter);
                            }

                        };
                        jobList.add(clearNewStorageProvider);

                        runJobs(jobList);

                        result = new SwitchStorageProviderResult();

                    } catch (final Throwable throwable) {
                        result = new SwitchStorageProviderFailed(throwable);
                        Log.d(TAG, "Problem during switching storage providers.", throwable);
                        t.stop();
                    } finally {
                        unlockModel("EM.switchStorageProvider.run");
                        clearCancelState();
                        fireModelUpdateFinished(result);
                        fireStatusUpdated();
                        Log.i(TAG, "Switching storage providers finished at " + new Date().toString() + ".");

                        wl.release();
                        t.stop();
                    }
                }

            }, "Storage Provider Switch Worker");
            runningThread.start();
        }
    }

    void runJob(final Job job) throws Throwable {
        final ArrayList<Job> jobList = new ArrayList<Job>(1);
        jobList.add(job);
        runJobs(jobList);
    }

    void runJobs(final List<Job> jobList) throws Throwable {
        Throwable caughtThrowable = null;
        for (final Job job : jobList) {
            try {
                if (isCancelRequested())
                    break;
                Log.d(TAG, "Started job: " + job.getJobDescription());
                EntryManager.this.currentRunningJob = job;
                fireStatusUpdated();
                if (NewsRob.isDebuggingEnabled(ctx))
                    PL.log("Existing Articles (before " + job.getJobDescription() + ")="
                            + EntryManager.this.getArticleCount(), ctx);
                job.run();

                if (NewsRob.isDebuggingEnabled(ctx))
                    PL.log("Existing Articles (after " + job.getJobDescription() + ")="
                            + EntryManager.this.getArticleCount(), ctx);

            } catch (final Throwable throwable) {
                Log.d(TAG, "Caught throwable.", throwable);
                if (caughtThrowable != null) {
                    Log.d(TAG, "Rethrowing it!");
                    throw throwable;
                } else {
                    caughtThrowable = throwable;
                    Log.d(TAG, "Stashing it.");
                }
            } finally {
                Log.d(TAG, "Finished job: " + job.getJobDescription());
                EntryManager.this.currentRunningJob = null;
                fireStatusUpdated();
            }
        }
        if (caughtThrowable != null)
            throw caughtThrowable;
    }

    public boolean canRefresh() {
        return !isModelCurrentlyUpdated() && fileContextAdapter.canWrite();
    }

    public boolean needsSession() {
        String token = getSharedPreferences().getString(SETTINGS_AUTH_TOKEN, null);
        return (token == null || "EXPIRED".equals(token));
    }

    public void doLogin(final Context context, final String email, final String password, String captchaToken,
            String captchaAnswer) throws ClientProtocolException, IOException, AuthenticationFailedException {
        getEntriesRetriever().authenticate(context, email, password, captchaToken, captchaAnswer);
    }

    public void doLogin(String googleUserId, String authToken) {
        // googleUserId is currently already set in onSuccess of LoginActivity
        // LATER
        saveAuthToken(new EntriesRetriever.AuthToken(AuthType.AUTH, authToken));
    }

    public void expireAuthToken() {
        AuthToken authToken = getAuthToken();
        IAccountManagementUtils accountManagementUtils = AccountManagementUtils.getAccountManagementUtils(ctx);
        if (accountManagementUtils != null && authToken != null
                && authToken.getAuthType() == EntriesRetriever.AuthToken.AuthType.AUTH) {
            AccountManagementUtils.getAccountManagementUtils(ctx).invalidateAuthToken(ctx,
                    authToken.getAuthToken());
        }
        SDK9Helper.apply(getSharedPreferences().edit().putString(SETTINGS_AUTH_TOKEN, "EXPIRED"));
    }

    public void clearAuthToken() {
        AuthToken authToken = getAuthToken();
        IAccountManagementUtils accountManagementUtils = AccountManagementUtils.getAccountManagementUtils(ctx);

        if (accountManagementUtils != null && authToken != null
                && authToken.getAuthType() == EntriesRetriever.AuthToken.AuthType.AUTH) {
            AccountManagementUtils.getAccountManagementUtils(ctx).invalidateAuthToken(ctx,
                    authToken.getAuthToken());
        }
        SharedPreferences.Editor editor = getSharedPreferences().edit().remove(SETTINGS_AUTH_TOKEN);
        editor.remove(SETTINGS_AUTH_TYPE);
        SDK9Helper.apply(editor);

    }

    public void saveAuthToken(AuthToken authToken) {
        // Android 2.0 token doesn't need to be saved, can be re-aquired easily
        // update: or so I thought
        /*
         * if (authToken != null && authToken.getAuthType() ==
         * EntriesRetriever.AuthToken.AuthType.AUTH) return;
         */
        PL.log("EM.saveAuthToken token=" + authToken, ctx);
        String authType = String.valueOf(authToken.getAuthType());
        String newAuthToken = authToken.getAuthToken();

        SharedPreferences.Editor editor = getSharedPreferences().edit().putString(SETTINGS_AUTH_TOKEN,
                newAuthToken);
        editor.putString(SETTINGS_AUTH_TYPE, authType);
        SDK9Helper.apply(editor);

        PL.log("EM.saveAuthToken(2)", ctx);

    }

    public void regetAuthToken() throws OperationCanceledException, AuthenticatorException, IOException {

        PL.log("EM.regetAuthToken()", ctx);
        IAccountManagementUtils accountManagementUtils = AccountManagementUtils.getAccountManagementUtils(ctx);
        AuthToken authToken = new AuthToken(AuthType.AUTH,
                accountManagementUtils.blockingGetAuthToken(ctx, getEmail()));
        saveAuthToken(authToken);

    }

    public AuthToken getAuthToken() {

        // no registered auth token with the Android 2.0 account management
        // so let's try the old schemes then
        String authToken = getSharedPreferences().getString(SETTINGS_AUTH_TOKEN, null);
        String authType = getSharedPreferences().getString(SETTINGS_AUTH_TYPE, null);

        PL.log("EM.getAuthToken() token=" + authToken.substring(0, 4) + " type=" + authType, ctx);
        if (authToken != null && authType != null) {
            AuthToken token = new EntriesRetriever.AuthToken(EntriesRetriever.AuthToken.AuthType.valueOf(authType),
                    authToken);
            PL.log("EM.getAuthToken()2 ton=" + token, ctx);
            if ("EXPIRED".equals(token.getAuthToken())) {
                try {
                    regetAuthToken();
                } catch (Exception e) {
                    e.printStackTrace();
                    return null;
                }
                token = new EntriesRetriever.AuthToken(EntriesRetriever.AuthToken.AuthType.valueOf(authType),
                        authToken);
            }
            return token;
        }

        return null; // no new-auth, old-auth or sid
    }

    public void addListener(final IEntryModelUpdateListener newListener) {
        listeners.add(newListener);
    }

    boolean entryExists(final String entryAtomId) {
        // Timing t = new Timing("EntryManager.entryExists");
        final boolean result = databaseHelper.findEntryByAtomId(entryAtomId) != null;
        // t.stop();
        return result;
    }

    public Entry findArticleById(Long id) {
        return databaseHelper.findArticleById(id);
    }

    public Entry findEntryByAtomId(final String entryAtomId) {
        // Timing t = new Timing("EntryManager.findEntryByAtomId "+entryAtomId);
        final Entry result = databaseHelper.findEntryByAtomId(entryAtomId);
        // t.stop();
        return result;
    }

    public void fireStatusUpdated() {
        try {
            for (final IEntryModelUpdateListener listener : new ArrayList<IEntryModelUpdateListener>(listeners))
                listener.statusUpdated();
        } catch (final RuntimeException nsee) {
            // ignored
        }
    }

    void fireModelUpdateFinished(final ModelUpdateResult result) {
        PL.log("MODEL_UPDATE(" + result != null ? result.getClass().getName()
                : "null" + "): Finished. Result: " + (result != null ? result.getMessage() : "Result was null"),
                ctx);
        try {
            for (final IEntryModelUpdateListener listener : new ArrayList<IEntryModelUpdateListener>(listeners)) {
                listener.modelUpdateFinished(result);
            }
        } catch (final RuntimeException nsee) {
            // ignored
        }
        updateWidgets();
    }

    void fireModelUpdateStarted(final String message, final boolean fastSyncOnly, final boolean manualSync) {
        PL.log("MODEL_UPDATE_STARTED (" + message + "): Started fastSync=" + fastSyncOnly + " manualSync="
                + manualSync, ctx);

        try {
            for (final IEntryModelUpdateListener listener : new ArrayList<IEntryModelUpdateListener>(listeners)) {
                listener.modelUpdateStarted(fastSyncOnly);
            }

        } catch (final RuntimeException nsee) {
            // ignored
        }
    }

    public void fireModelUpdated() {

        fireModelUpdated(null);

    }

    void fireModelUpdated(final String atomId) {
        synchronized (this) {
            if (NewsRob.fireModelUpdateInProgress)
                return;
            else
                NewsRob.fireModelUpdateInProgress = true;
        }
        try {

            // reset the cache when something
            // has changed.
            isMarkAllReadPossibleCache.clear();
            articleCountCache = null;
            contentCountCache.clear();

            try {
                for (final IEntryModelUpdateListener listener : new ArrayList<IEntryModelUpdateListener>(
                        listeners)) {
                    if (atomId != null)
                        listener.modelUpdated(atomId);
                    else
                        listener.modelUpdated();
                }
            } catch (final RuntimeException nsee) {
                // ignored
            }
            updateWidgets();
        } finally {
            NewsRob.fireModelUpdateInProgress = false;
        }
    }

    private synchronized void updateWidgets() {

        if (updateWidgetsTimer == null)
            updateWidgetsTimer = new Timer("UpdateWidgetsTimer", false);

        if (updateWidgetsTimerTask != null) {
            updateWidgetsTimerTask.cancel();
            updateWidgetsTimerTask = null;
        }

        if (lastUpdateWidgetUpdate == null
                || (lastUpdateWidgetUpdate + (3 * 60 * 1000)) < System.currentTimeMillis()) {
            UnreadWidgetProvider.requestWidgetsUpdate(ctx);
            lastUpdateWidgetUpdate = System.currentTimeMillis();
        } else {
            updateWidgetsTimerTask = new TimerTask() {

                @Override
                public void run() {
                    UnreadWidgetProvider.requestWidgetsUpdate(ctx);
                    lastUpdateWidgetUpdate = System.currentTimeMillis();

                    // ADW Notification
                    if (isADWLauncherInterestedInNotifications()) {

                        Intent i = new Intent();
                        i.setAction("org.adw.launcher.counter.SEND");
                        i.putExtra("PNAME", "com.newsrob");
                        i.putExtra("COUNT", getUnreadArticleCount());
                        ctx.sendBroadcast(i);
                        PL.log("EntryManager.updateWidgets sent notification to ADW.", ctx);
                    }

                }

            };
            updateWidgetsTimer.schedule(updateWidgetsTimerTask, 35 * 1000);
        }
    }

    public Context getContext() {
        return ctx;
    }

    public void insert(List<Entry> entries) {
        databaseHelper.insert(entries);
    }

    public void insert(final Entry entry) {
        databaseHelper.insert(entry);
    }

    /** make an article more read */
    public void increaseReadLevel(Entry entry) {

        switch (entry.getReadState()) {

        case READ:
            break;

        default:
            updateReadState(entry, ReadState.READ);
            return;
        }

    }

    public void increaseUnreadLevel(Entry entry) {
        switch (entry.getReadState()) {
        case READ:
            updateReadState(entry, ReadState.UNREAD);
            break;
        case UNREAD:
            if (isProVersion())
                updateReadState(entry, ReadState.PINNED);
            break;
        default:
            // stay pinned
        }

    }

    void updateReadState(final Entry entry, final ReadState newReadState, final boolean isReadStatePending) {
        Timing t = new Timing("EntryManager.updateReadState(3) - total", ctx);
        try {
            if (entry.getReadState() == newReadState)
                return;
            Timing t2 = new Timing("EntryManager.updateReadState(3) - updateReadState", ctx);

            entry.setReadState(newReadState);
            entry.setReadStatePending(isReadStatePending);
            databaseHelper.updateReadState(entry);
            t2.stop();

            Timing t3 = new Timing("EntryManager.updateReadState(3) - fireModelUpdated", ctx);

            fireModelUpdated();
            t3.stop();

            Timing t4 = new Timing("EntryManager.updateReadState(3) - scheduleUploadOnlySync", ctx);

            if (isReadStatePending)
                scheduler.scheduleUploadOnlySynchonization();

            t4.stop();
        } finally {
            t.stop();
        }
    }

    public void updateReadState(Entry entry, ReadState newReadState) {

        ReadState existingReadState = entry.getReadState();

        // GR state unread on both sides or read on both sides?
        boolean existingUnread = existingReadState == ReadState.UNREAD || existingReadState == ReadState.PINNED;
        boolean newUnread = newReadState == ReadState.UNREAD || newReadState == ReadState.PINNED;

        boolean stateChanged = existingUnread != newUnread;

        boolean existingReadStatePending = entry.isReadStatePending();
        boolean newReadStatePending = stateChanged ? !existingReadStatePending : existingReadStatePending;

        updateReadState(entry, newReadState, newReadStatePending);
    }

    public void updateSharedState(final Entry entry, final boolean isShared) {
        updateSharedState(entry, isShared, !entry.isSharedStatePending());
    }

    void updateStarredState(final Entry entry, final boolean isStarred, final boolean isStarredStatePending) {
        if (entry.isStarred() == isStarred)
            return;

        entry.setStarred(isStarred);
        entry.setStarredStatePending(isStarredStatePending);
        databaseHelper.updateStarredState(entry);
        fireModelUpdated();
        if (isStarredStatePending)
            scheduler.scheduleUploadOnlySynchonization();

    }

    void updateLikedState(final Entry entry, final boolean isLiked, final boolean isLikedStatePending) {
        if (entry.isLiked() == isLiked)
            return;

        entry.setLiked(isLiked);
        entry.setLikedStatePending(isLikedStatePending);
        databaseHelper.updateLikedState(entry);
        fireModelUpdated();
        if (isLikedStatePending)
            scheduler.scheduleUploadOnlySynchonization();

    }

    void updateSharedState(final Entry entry, final boolean isShared, final boolean isSharedStatePending) {
        if (entry.isShared() == isShared)
            return;

        entry.setShared(isShared);
        entry.setSharedStatePending(isSharedStatePending);
        databaseHelper.updateSharedState(entry);
        fireModelUpdated();
        if (isSharedStatePending)
            scheduler.scheduleUploadOnlySynchonization();

    }

    public void updateFriendsSharedState(final Entry existingEntry, final boolean isSharedByFriends) {
        existingEntry.setFriendsShared(isSharedByFriends);
        databaseHelper.updateFriendsSharedState(existingEntry);
        fireModelUpdated();
    }

    public void updateStarredState(final Entry entry, final boolean isStarred) {
        updateStarredState(entry, isStarred, !entry.isStarredStatePending());
    }

    public void updateLikedState(final Entry entry, final boolean isLiked) {
        updateLikedState(entry, isLiked, !entry.isLikedStatePending());
    }

    public boolean isModelCurrentlyUpdated() {
        return isModelCurrentlyUpdated;
    }

    public void removeListener(final IEntryModelUpdateListener listenerToRemove) {
        listeners.remove(listenerToRemove);
    }

    public boolean isLicenseAccepted() {
        return getSharedPreferences().getBoolean(SETTINGS_LICENSE_ACCEPTED, false);
    }

    public void acceptLicense() {
        SDK9Helper.apply(getSharedPreferences().edit().putBoolean(SETTINGS_LICENSE_ACCEPTED, true));
        Log.d(TAG, "License accepted!");
    }

    synchronized void lockModel(final String lockedBy) {
        if (isModelCurrentlyUpdated)
            throw new IllegalStateException(String.format(
                    "Trying to lock locked model by %s. Model was already locked by %s since %s seconds.", lockedBy,
                    modelLockedBy, String.valueOf((System.currentTimeMillis() - modelLockedAt) / 1000)));
        isModelCurrentlyUpdated = true;

        modelLockedAt = System.currentTimeMillis();
        modelLockedBy = lockedBy;
        fireStatusUpdated();
    }

    synchronized void unlockModel(final String unlockedBy) {
        if (!isModelCurrentlyUpdated)
            Log.w(TAG, "Unlocking model, but model wasn't locked! unlockedBy=" + unlockedBy);
        isModelCurrentlyUpdated = false;
        modelLockedAt = -1;
        modelLockedBy = null; // NOPMD by mkamp on 1/18/10 8:29 PM
        fireStatusUpdated();
    }

    public void requestClearCacheAndSync() {
        try {
            runJob(new Job("Clearing Cache", EntryManager.this) {

                @Override
                public void run() throws Exception {

                    try {
                        Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);

                        synchronized (this) {
                            if (isModelCurrentlyUpdated())
                                return;
                            lockModel("EM.requestClearCacheAndSync");
                        }
                        doClearCache();
                    } finally {
                        synchronized (this) {
                            unlockModel("EM.requestClearCacheAndSync");
                        }
                    }
                    fireModelUpdated();
                    requestSynchronization(false);
                }

            });
        } catch (final Throwable e) {
            showExceptionToast("Clear Cache And Sync", e);
        }

    }

    public void requestSynchronization(final boolean uploadOnly) {
        if (isModelCurrentlyUpdated()) {
            PL.log("Model is currently updated. Returning. Locked by " + modelLockedBy + " at " + modelLockedAt,
                    ctx);
            return;
        }

        final Intent i = new Intent(ctx, SynchronizationService.class);
        if (uploadOnly)
            i.setAction(SynchronizationService.ACTION_SYNC_UPLOAD_ONLY);
        i.putExtra(SynchronizationService.EXTRA_MANUAL_SYNC, true);
        SynchronizationService.acquireWakeLock(ctx);
        ctx.startService(i);

    }

    public void requestMarkAllAsRead(final DBQuery dbq) {
        new Thread(new Runnable() {
            public void run() {
                doMarkAllRead(dbq);
            }
        }).start();
    }

    protected void doMarkAllRead(final DBQuery dbq) {
        if (NewsRob.isDebuggingEnabled(ctx))
            PL.log("Mark All Read: dbq=" + dbq, ctx);
        if (NewsRob.isDebuggingEnabled(ctx)) {
            PL.log("Before mark all read: " + getUnreadArticleCount() + "/" + getArticleCount(), ctx);
            PL.log("Articles with pending read state: " + getPendingReadStateArticleCount(), ctx);
        }
        databaseHelper.markAllRead(dbq);
        if (NewsRob.isDebuggingEnabled(ctx)) {
            PL.log("After mark all read: " + getUnreadArticleCount() + "/" + getArticleCount(), ctx);
            PL.log("Articles with pending read state: " + getPendingReadStateArticleCount(), ctx);
        }
        fireModelUpdated();

        scheduler.scheduleUploadOnlySynchonization();
    }

    public void requestClearCache(final Handler handler) {
        runSimpleJob("Clear Cache", new Runnable() {
            public void run() {
                doClearCache();
            }
        }, handler);
    }

    private void runSimpleJob(final String name, final Runnable runnable, final Handler handler) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    runJob(new Job(name, EntryManager.this) {

                        @Override
                        public void run() throws Exception {

                            try {
                                Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);

                                synchronized (this) {
                                    if (isModelCurrentlyUpdated())
                                        return;
                                    lockModel(name);
                                }
                                runnable.run();
                            } finally {
                                synchronized (this) {
                                    unlockModel(name);
                                }
                            }
                        }
                    });
                } catch (final Throwable e) {
                    if (handler != null) {
                        handler.post(new Runnable() {

                            public void run() {
                                showExceptionToast(name, e);
                            }
                        });
                    }
                }

            }
        }).start();
    }

    public void showExceptionToast(final String message, final Throwable e) {
        try {
            Toast.makeText(ctx, message + e.getClass() + ":" + e.getMessage(), Toast.LENGTH_LONG).show(); // I18N
        } catch (final Exception ex) {
            Log.e(TAG, "Caught exception when trying to show an error Toast.", e);
        }

    }

    // LATER public b/o testing
    public void doClearCache() {
        fireStatusUpdated();
        setGRUpdated(-1l);
        updateLastSyncedSubscriptions(-1l);
        databaseHelper.deleteAll();
        final int noOfAssetsDeleted = WebPageDownloadDirector.removeAllAssets(fileContextAdapter);
        Log.d(TAG, noOfAssetsDeleted + " assets deleted.");
        fireModelUpdated();
        fireStatusUpdated();
    }

    void setLastSync(final boolean complete) {
        SDK9Helper.apply(getSharedPreferences().edit().putBoolean(SETTINGS_LAST_SYNC_COMPLETE, complete)
                .putLong(SETTINGS_LAST_SYNC_TIME, System.currentTimeMillis()));
    }

    public static synchronized EntryManager getInstance(final Context context) {
        if (instance == null)
            instance = new EntryManager(context.getApplicationContext());
        return instance;
    }

    List<Entry> findAllStatePendingEntries(final String column, final String desiredState) {
        return databaseHelper.findAllByPendingState(column, desiredState);
    }

    boolean delete(final Entry entry) {

        if (!fileContextAdapter.canWrite())
            return false;

        final int noOfAssetsDeleted = WebPageDownloadDirector.removeAssetsForId(entry.getAtomId(),
                fileContextAdapter);
        Log.d(TAG, noOfAssetsDeleted + " assets deleted for entry " + entry.getAtomId() + ".");

        return databaseHelper.deleteEntry(entry) == 1 ? true : false;
    }

    void resetPendingStates(final Entry entry) {
        databaseHelper.updateReadState(entry);
        databaseHelper.updateStarredState(entry);
    }

    public int getStorageCapacity() {
        return Integer.parseInt(sharedPreferences.getString(SETTINGS_ENTRY_MANAGER_CAPACITY, "50"));

    }

    public SharedPreferences getSharedPreferences() {
        return sharedPreferences;
    }

    public int deleteReadArticles(final SyncJob job) {
        return deleteArticles(job, databaseHelper.getReadArticlesIdsForDeletion(getNoOfStarredArticlesToKeep(),
                getNoOfSharedArticlesToKeep(), getNoOfNotesToKeep()));
    }

    public int getNoOfStarredArticlesToKeep() {
        return getNoOfArticlesToKeep(SETTINGS_KEEP_STARRED, 20);
    }

    public int getNoOfSharedArticlesToKeep() {
        return getNoOfArticlesToKeep(SETTINGS_KEEP_SHARED, 20);
    }

    public int getNoOfNotesToKeep() {
        return getNoOfArticlesToKeep(SETTINGS_KEEP_NOTES, 5);
    }

    public boolean shouldHWZoomControlsBeDisabled() {

        if (DEVICE_MODEL_DROID_INCREDIBLE.equals(Build.MODEL) && SDKVersionUtil.getVersion() < 8)
            return true;

        if (DEVICE_MODEL_EVO.equals(Build.MODEL) && SDKVersionUtil.getVersion() < 8)
            return true;

        if (DEVICE_MODEL_DESIRE.equals(Build.MODEL) && SDKVersionUtil.getVersion() < 8)
            return true;

        return false;
    }

    public boolean shouldSyncInProgressNotificationBeDisabled() {

        if (true)
            return false;
        if (DEVICE_MODEL_DROID_INCREDIBLE.equals(Build.MODEL) && SDKVersionUtil.getVersion() < 8)
            return true;

        if (DEVICE_MODEL_DELL_STREAK.equals(Build.MODEL) && SDKVersionUtil.getVersion() < 7)
            return true;

        if (DEVICE_MODEL_EVO.equals(Build.MODEL) && SDKVersionUtil.getVersion() < 8)
            return true;

        if (DEVICE_MODEL_DESIRE.equals(Build.MODEL) && SDKVersionUtil.getVersion() < 8)
            return true;

        return false;

    }

    private int getNoOfArticlesToKeep(final String setting, final int def) {
        if (!isProVersion())
            return 0;
        return Integer.parseInt(getSharedPreferences().getString(setting, String.valueOf(def)));
    }

    int reduceToCapacity(final SyncJob job) {
        return deleteArticles(job, databaseHelper.getOverCapacityIds(getStorageCapacity(),
                getNoOfStarredArticlesToKeep(), getNoOfSharedArticlesToKeep(), getNoOfNotesToKeep()));
    }

    private int deleteArticles(final SyncJob job, final Cursor idsOfEntriesToDeleteCursor) {
        Timing t = new Timing("DeleteArticles's assets", ctx);

        final List<String> articleIdsToDeleteInDatabase = new ArrayList<String>(
                idsOfEntriesToDeleteCursor.getCount());
        int noOfEntriesDeleted = 0;

        try {

            job.target = idsOfEntriesToDeleteCursor.getCount(); // TODO too
            // eager?

            // Debug.startMethodTracing("delete");

            if (!fileContextAdapter.canWrite()) {
                Log.w(TAG,
                        "EntryManager.deleteArticles even though fileContext.canWrite() returns false. Skipping.");
            } else {

                while (idsOfEntriesToDeleteCursor.moveToNext()) {
                    if (job.isCancelled())
                        return noOfEntriesDeleted;
                    if (!fileContextAdapter.canWrite())
                        return noOfEntriesDeleted;

                    // PERFORMANCE ooh. performance
                    // problem. skip
                    // this step. don't load an entry first
                    // to delete it LATER

                    String mostRecentArticleAtomId = getMostRecentArticleAtomId();

                    String atomId = idsOfEntriesToDeleteCursor.getString(1);

                    // don't delete the article currently viewed by the user
                    if (mostRecentArticleAtomId == null || !atomId.endsWith(mostRecentArticleAtomId)) {
                        WebPageDownloadDirector.removeAssetsForId(atomId, fileContextAdapter);
                        articleIdsToDeleteInDatabase.add(idsOfEntriesToDeleteCursor.getString(0));
                        noOfEntriesDeleted++;
                    }

                    job.actual = noOfEntriesDeleted;
                    if (job.actual % 5 == 0)
                        fireStatusUpdated();
                }
                fireModelUpdated();
                Log.d(TAG, "EntryManager.deleteArticles() cleaned up " + noOfEntriesDeleted + " articles' assets.");
            }
            // Debug.stopMethodTracing();

        } finally {
            idsOfEntriesToDeleteCursor.close();
            t.stop();
        }

        deleteArticlesFromDb(job, articleIdsToDeleteInDatabase);

        return noOfEntriesDeleted;
    }

    private void deleteArticlesFromDb(final SyncJob job, final List<String> articleIdsToDeleteInDatabase) {
        if (articleIdsToDeleteInDatabase.isEmpty())
            return;

        Timing t2 = new Timing("Delete Articles From Db", ctx);

        job.setJobDescription("Cleaning up database");
        job.target = articleIdsToDeleteInDatabase.size();
        job.actual = 0;
        fireStatusUpdated();

        SQLiteDatabase db = databaseHelper.getDb();

        final String sql1 = "DELETE FROM " + Entries.TABLE_NAME + " WHERE " + Entries.__ID + "=?;";
        final String sql2 = "DELETE FROM " + EntryLabelAssociations.TABLE_NAME + " WHERE "
                + EntryLabelAssociations.ENTRY_ID + "=?;";

        final SQLiteStatement stmt1 = db.compileStatement(sql1);
        final SQLiteStatement stmt2 = db.compileStatement(sql2);

        try {

            // outter loop does the chunking and holds the transaction context
            while (!articleIdsToDeleteInDatabase.isEmpty()) {

                db.beginTransaction();

                while (!articleIdsToDeleteInDatabase.isEmpty()) {

                    String id = articleIdsToDeleteInDatabase.remove(0);
                    stmt1.bindString(1, id);
                    stmt1.execute();
                    stmt2.bindString(1, id);
                    stmt2.execute();

                    job.actual++;

                    if (job.actual % 10 == 0)
                        fireStatusUpdated();

                    // commit every 35 articles
                    if (job.actual >= 35)
                        break;
                }

                db.setTransactionSuccessful();
                db.endTransaction();
            }
        } finally {
            stmt1.close();
            stmt2.close();
        }
        fireStatusUpdated();
        t2.stop();
    }

    void removePendingStateMarkers(final Collection<String> atomIds, final String column) {
        databaseHelper.removePendingStateMarkers(atomIds, column);
        fireModelUpdated();
    }

    public void logout() {
        getEntriesRetriever().logout();
    }

    public IStorageAdapter getStorageAdapter() {
        return fileContextAdapter;
    }

    public Job getCurrentRunningJob() {
        return currentRunningJob;
    }

    void clearCancelState() {
        cancelRequested = false;
    }

    public boolean isCancelRequested() {
        return cancelRequested;
    }

    public void cancel() {
        cancelRequested = true;

        if (currentRunningJob != null)
            currentRunningJob.cancel();
        fireStatusUpdated();

        if (runningThread != null) {
            final Thread t = new Thread(new Runnable() {

                public void run() {
                    try {

                        if (!runningThread.isAlive())
                            return;

                        runningThread.join(8000);
                        if (!runningThread.isAlive())
                            return;

                        runningThread.interrupt();
                        runningThread.join(2500);

                        if (!runningThread.isAlive())
                            return;

                    } catch (final Throwable t) {
                        PL.log("CANCEL: " + t.getMessage() + " " + t.getClass(), ctx);
                    } finally {
                        if (runningThread == null || !runningThread.isAlive()) {
                            runningThread = null; // NOPMD by mkamp on 1/18/10
                            // 8:29 PM
                            clearCancelState();

                        } else {
                            PL.log("CANCEL: Didn't work out ;-( " + runningThread.getState(), ctx);

                            for (final StackTraceElement ste : new ArrayList<StackTraceElement>(
                                    Arrays.asList(runningThread.getStackTrace()))) {
                                PL.log("CANCEL: " + ste.toString(), ctx);

                            }

                        }
                    }
                }
            });
            t.start();
        }
    }

    static class SyncJobStatus {
        int noOfEntriesUpdated = 0;
        int noOfEntriesFetched = 0;
    }

    public NewsRobNotificationManager getNewsRobNotificationManager() {
        return newsRobNotificationManager;
    }

    NewsRobScheduler getScheduler() {
        return scheduler;
    }

    Collection<Long> findAllArticleIds2Download() {
        return databaseHelper.findAllArticleIdsToDownload();
    }

    boolean updatedDownloaded(final Entry entry) {
        return databaseHelper.updateDownloaded(entry);
    }

    public Feed findFeedById(final long feedId) {
        return databaseHelper.findFeedById(feedId);
    }

    public boolean updateFeed(final Feed feed) {
        return databaseHelper.updateFeed(feed);
    }

    public int getDefaultDownloadPref() {
        return new Integer(getSharedPreferences().getString(SETTINGS_GLOBAL_DOWNLOAD_PREF_KEY,
                String.valueOf(Feed.DOWNLOAD_PREF_FEED_ONLY)));
    }

    public boolean downloadContentCurrentlyEnabled(boolean manualSync) {
        return downloadOrSyncCurrentlyEnabled(
                getSharedPreferences().getString(EntryManager.SETTINGS_STORAGE_ASSET_DOWNLOAD, DOWNLOAD_YES),
                manualSync);
    }

    public boolean syncCurrentlyEnabled(final boolean manualSync) {
        return downloadOrSyncCurrentlyEnabled(
                getSharedPreferences().getString(EntryManager.SETTINGS_AUTOMATIC_REFRESHING_ENABLED, DOWNLOAD_YES),
                manualSync);
    }

    private boolean downloadOrSyncCurrentlyEnabled(final String syncPref, final boolean manualSync) {

        if (isInAirplaneMode()) {
            PL.log("Sync/Download: Airplane mode is turned on by the user. Will result in the sync/download to be interrupted.",
                    ctx);

            // Toast.makeText(ctx,
            // "Device in Airplane mode.\nNo sync or download possible.",
            // Toast.LENGTH_LONG).show();
            return false;
        }

        if (manualSync)
            return true;

        if (DOWNLOAD_NO.equals(syncPref))
            return false;

        if (DOWNLOAD_WIFI_ONLY.equals(syncPref) && !isOnWiFi()) {
            PL.log("Sync/Downlod: WiFi requested, but not available. Will result in the sync/download to be interrupted.",
                    ctx);

            return false;
        }

        // DOWNLOAD_YES OR (DOWNLOAD_WIFI and WiFi is available)

        boolean backgroundDataEnabled = isBackgroundDataEnabled() && isSystemwideSyncEnabled();

        if (!backgroundDataEnabled)
            PL.log("Sync/Download: Background data or systemwide syncing turned off by the user. Will result in the sync/download to be interrupted.",
                    ctx);

        if (!backgroundDataEnabled)
            return false;

        return true;
    }

    boolean isOnWiFi() {
        if (connectivityManager == null)
            connectivityManager = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);

        if (connectivityManager == null) {
            PL.log("EntryManager. Wasn't able to get CONNECTIVITY_SERVICE.", ctx);
            WifiManager wifiManager = (WifiManager) ctx.getSystemService(Context.WIFI_SERVICE);
            WifiInfo wifiInfo = wifiManager.getConnectionInfo();
            if (wifiInfo != null)
                PL.log("WiFi Info=" + wifiInfo + " SSID=" + wifiInfo.getSSID(), ctx);
            return false;
        }

        final NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        if (networkInfo == null) {
            PL.log("EntryManager. Wasn't able to get Network Info.", ctx);
            return false;

        }

        if (ConnectivityManager.TYPE_WIFI != networkInfo.getType()) {
            PL.log("EntryManager. Network Info Type was not WiFi, but " + networkInfo.getType() + ".", ctx);
            return false;
        }

        return true;
    }

    public boolean isNetworkConnected(Context ctx) {

        if (connectivityManager == null)
            connectivityManager = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo ni = connectivityManager.getActiveNetworkInfo();
        if (ni == null) {
            PL.log("EntryManager. Wasn't able to get NI.", ctx);
            return false;
        }

        if (NewsRob.isDebuggingEnabled(ctx)) {
            PL.log("ActiveNetwork: " + ni.getTypeName(), ctx);
            PL.log("ActiveNetworkState: " + ni.getDetailedState(), ctx);
            PL.log("isNetworkConnected? " + ni.isConnected(), ctx);
        }

        return ni.isConnected();
    }

    public boolean isBackgroundDataEnabled() {
        if (connectivityManager == null)
            connectivityManager = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
        return connectivityManager.getBackgroundDataSetting();
    }

    public boolean isSystemwideSyncEnabled() {
        Method m;
        try {
            m = ContentResolver.class.getMethod("getMasterSyncAutomatically", new Class[] {});
            if (m != null) {

                Object o = m.invoke(ContentResolver.class, new Object[] {});

                if (o != null) {
                    return (Boolean) o;
                }
            }
        } catch (Exception e) {
        }

        return true;
    }

    /** in Airplane Mode and Wifi is included? */
    public boolean isInAirplaneMode() {
        return false;
    }

    public Cursor getDashboardContentCursor(DBQuery dbq) {
        return databaseHelper.getDashboardContentCursor(dbq);
    }

    public Cursor getContentCursor(final DBQuery query) {
        return databaseHelper.getContentCursor(query);
    }

    public Cursor getFeedListContentCursor(final DBQuery query) {
        return databaseHelper.getFeedListContentCursor(query);
    }

    public boolean onPreferenceChange(final Preference preference, final Object newValue) {
        final String key = preference.getKey();
        onSharedPreferenceChanged(sharedPreferences, key);
        return true;
    }

    public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {

        if (SETTINGS_ENTRY_MANAGER_CAPACITY.equals(key) || SETTINGS_KEEP_SHARED.equals(key)
                || SETTINGS_KEEP_STARRED.equals(key) || SETTINGS_SYNC_TYPE.equals(key)) {
            setGRUpdated(-1l);
        } else if (SETTINGS_STORAGE_PROVIDER_KEY.equals(key)) {
            setGRUpdated(-1l);
            switchStorageProvider();
        } else if (SETTINGS_INCREMENTAL_SYNC_ENABLED.equals(key))
            setGRUpdated(-1l);

        // When the setting is changed from the settings dialog also maintain
        // the version code here
        if (SETTINGS_USAGE_DATA_PERMISSION_GRANTED.equals(key)) {
            SDK9Helper.apply(getSharedPreferences().edit().putInt(SETTINGS_USAGE_DATA_COLLECTION_PERMISSION_VERSION,
                    getMyVersionCode()));

            PL.log("EntryManager: Usage Data Collection Permission changed from the settings dialog. New setting="
                    + getSharedPreferences().getBoolean(SETTINGS_USAGE_DATA_PERMISSION_GRANTED, false) + " version="
                    + getSharedPreferences().getInt(SETTINGS_USAGE_DATA_COLLECTION_PERMISSION_VERSION, -99)
                    + " collectionPermitted=" + isUsageDataCollectionPermitted(), ctx);

        }

        if (SETTINGS_AUTOMATIC_REFRESHING_ENABLED.equals(key)) {
            maintainBootReceiverState();
        }
        if (SETTINGS_UI_THEME.equals(key)) {
            getContext().setTheme(getCurrentThemeResourceId());
        }
        currentThemeId = 0; // reset in case this has been changed
    }

    public void maintainBootReceiverState() {
        final ComponentName cName = new ComponentName(ctx, BootReceiver.class);
        final PackageManager pm = ctx.getPackageManager();

        final int newComponentState = isAutoSyncEnabled() ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
                : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;

        if (pm.getComponentEnabledSetting(cName) != newComponentState) {
            Log.d(TAG, "Setting new component enabled state on BootReceiver: " + isAutoSyncEnabled());
            pm.setComponentEnabledSetting(cName, newComponentState, PackageManager.DONT_KILL_APP);
        }
        PL.log("EntryManager.maintainBootReceiverState(): Component enabled="
                + pm.getComponentEnabledSetting(cName), ctx);

    }

    @SuppressWarnings("unchecked")
    public void maintainPremiumDependencies() {

        this.proVersion = null;

        if (false) {
            final int desiredComponentState = isProVersion() ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
                    : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;

            final Class[] componentsToDeactivate = { FireReceiver.class, MockEditSettingsActivity.class };

            final PackageManager pm = ctx.getPackageManager();
            for (final Class cls : componentsToDeactivate) {
                final ComponentName cName = new ComponentName(ctx, cls);
                pm.setComponentEnabledSetting(cName, desiredComponentState, PackageManager.DONT_KILL_APP);
            }
        }
    }

    public boolean shouldHideReadItems() {
        return getSharedPreferences().getBoolean(EntryManager.SETTINGS_HIDE_READ_ITEMS, false);
    }

    public int getDaysInstalled() {

        long l = getSharedPreferences().getLong(SETTINGS_INSTALLED_AT, 0l);

        if (l == 0l) {
            l = System.currentTimeMillis();
            SDK9Helper.apply(getSharedPreferences().edit().putLong(SETTINGS_INSTALLED_AT, l));
        }

        final int daysInstalled = (int) ((System.currentTimeMillis() - l) / (1000 * 60 * 60 * 24));

        return daysInstalled;
    }

    public boolean isAutoSyncEnabled() {
        final String pref = sharedPreferences.getString(EntryManager.SETTINGS_AUTOMATIC_REFRESHING_ENABLED,
                EntryManager.DOWNLOAD_YES);
        if (EntryManager.DOWNLOAD_YES.equals(pref) || EntryManager.DOWNLOAD_WIFI_ONLY.equals(pref))
            return true;
        return false;
    }

    public int getNoOfNewArticlesSinceLastUsed(final long lastUsed) {

        final DBQuery dbq = new DBQuery(this, null, null);
        dbq.setShouldHideReadItemsWithoutUpdatingThePreference(true);
        dbq.setStartDate(lastUsed);

        return getContentCount(dbq);
    }

    public boolean shouldTitlesBeEllipsized() {
        return getSharedPreferences().getBoolean(SETTINGS_ELLIPSIZE_TITLES_ENABLED, false);
    }

    public void setGRUpdated(final long lastUpdated) {
        if (NewsRob.isDebuggingEnabled(ctx))
            PL.log("setGRUpdated=" + lastUpdated, ctx);

        SDK9Helper.apply(getSharedPreferences().edit().putLong(SETTINGS_GR_UPDATED_KEY, lastUpdated));
    }

    public long getGRUpdated() {
        return getSharedPreferences().getLong(SETTINGS_GR_UPDATED_KEY, -1l);
    }

    public void updateStates(final Collection<StateChange> stateChanges) {
        final Timing t = new Timing("EntryManager.updateStates()", ctx);
        try {
            if (databaseHelper.updateStates(stateChanges))
                fireStatusUpdated();
        } finally {
            t.stop();
        }
    }

    public void updateStatesFromTempTable(ArticleDbState articleDbState) {
        databaseHelper.updateStatesFromTempTable(articleDbState);
        fireStatusUpdated();
    }

    public String getGoogleUserId() {
        return getSharedPreferences().getString(SETTINGS_GOOGLE_USER_ID, null);
    }

    public void setGoogleUserId(final String newUserId) {

        if (newUserId == null)
            SDK9Helper.apply(getSharedPreferences().edit().remove(SETTINGS_GOOGLE_USER_ID));
        else
            SDK9Helper.apply(getSharedPreferences().edit().putString(SETTINGS_GOOGLE_USER_ID, newUserId));

    }

    public boolean isLightColorSchemeSelected() {
        return THEME_LIGHT.equals(getThemeColorScheme());
    }

    private String getThemeColorScheme() {
        return getSharedPreferences().getString(SETTINGS_UI_THEME, THEME_LIGHT);
    }

    public void toggleTheme() {

        SDK9Helper.apply(getSharedPreferences().edit().putString(SETTINGS_UI_THEME,
                isLightColorSchemeSelected() ? THEME_DARK : THEME_LIGHT));
        currentThemeId = 0;

    }

    public final int getCurrentThemeResourceId() {
        // Caching the theme resolution
        if (currentThemeId == 0)
            currentThemeId = getThemeResourceId(getThemeColorScheme());

        return currentThemeId;
    }

    public final int getThemeResourceId(String colorScheme) {
        String key = "Theme.NewsRob_" + colorScheme + "_Normal";
        return ctx.getResources().getIdentifier(key, "style", "com.newsrob.threetosix");
    }

    public Cursor searchFullText(final String query) {
        return databaseHelper.findByFullText(query);
    }

    public Cursor getArticleAsCursor(final String atomId) {
        return databaseHelper.findCursorByQueryString("ATOM_ID = \'" + atomId + "\'");
    }

    public int getUnreadArticleCount() {
        return databaseHelper.getUnreadArticleCount();
    }

    public long getNextScheduledSyncTime() {
        return getSharedPreferences().getLong(SETTINGS_NEXT_SCHEDULED_SYNC_TIME, -1l);
    }

    // Only called for the bookkeeping
    public void updateNextScheduledSyncTime(final long nextSyncTime) {
        SDK9Helper.apply(getSharedPreferences().edit().putLong(SETTINGS_NEXT_SCHEDULED_SYNC_TIME, nextSyncTime));
        PL.log("EntryManager.updateNextScheduledSyncTime() = " + new Date(nextSyncTime), ctx);
    }

    public int getMarkAllReadConfirmationDialogThreshold() {
        return Integer.parseInt(
                getSharedPreferences().getString(SETTINGS_MARK_ALL_READ_CONFIRMATION_DIALOG_THRESHOLD, "0"));
    }

    public void populateTempTable(final long[] articleIds) {
        databaseHelper.populateTempIds(articleIds);
    }

    public String getMostRecentArticleAtomId() {
        return singleValueStore.getString("current_article");
    }

    public void setMostRecentArticleAtomId(final String atomId) {
        try {
            singleValueStore.putString("current_article", atomId.substring(33));
        } catch (RuntimeException re) {
            re.printStackTrace();
        }
    }

    public void removeLocallyExistingArticlesFromTempTable() {
        databaseHelper.removeLocallyExistingArticlesFromTempTable();
    }

    public List<String> getNewArticleIdsToFetch(final int noOfArticles2Fetch) {
        return databaseHelper.getNewArticleAtomIdsToFetch(noOfArticles2Fetch);
    }

    public int getTempIdsCount() {
        return databaseHelper.getTempIdsCount();
    }

    public boolean shouldReadItemsBeDeleted() {
        return (SYNC_TYPE_VALUE_UNREAD_ARTICLES_ONLY.equals(
                getSharedPreferences().getString(SETTINGS_SYNC_TYPE, SYNC_TYPE_VALUE_UNREAD_ARTICLES_ONLY)));
    }

    public boolean shouldOnlyUnreadArticlesBeDownloaded() {
        return (SYNC_TYPE_VALUE_UNREAD_ARTICLES_ONLY.equals(
                getSharedPreferences().getString(SETTINGS_SYNC_TYPE, SYNC_TYPE_VALUE_UNREAD_ARTICLES_ONLY)));
    }

    public boolean areHoveringZoomControlsEnabled() {
        return !shouldHWZoomControlsBeDisabled()
                && getSharedPreferences().getBoolean(SETTINGS_HOVERING_ZOOM_CONTROLS_ENABLED, false);
    }

    public boolean isRichArticleListEnabled() {
        return getSharedPreferences().getBoolean(SETTINGS_UI_RICH_ARTICLE_LIST_ENABLED, true);
    }

    public boolean isHoveringButtonsNavigationEnabled() {
        return getSharedPreferences().getBoolean(SETTINGS_HOVERING_BUTTONS_NAVIGATION_ENABLED, true);
    }

    public boolean isVolumeControlNavigationEnabled() {
        return getSharedPreferences().getBoolean(SETTINGS_VOLUME_CONTROL_NAVIGATION_ENABLED, true);
    }

    public boolean isCameraButtonControllingReadStateEnabled() {
        return getSharedPreferences().getBoolean(SETTINGS_CAMERA_BUTTON_CONTROLS_READ_STATE_ENABLED, false);
    }

    public List<Feed> findAllFeeds() {
        return databaseHelper.findAllFeeds();
    }

    public int getFeedCount() {
        return databaseHelper.getFeedCount();
    }

    public long insert(final Feed f) {
        return databaseHelper.insert(f);
    }

    public void updateFeedNames(final Map<String, String> remoteFeedAtomIdsAndFeedTitles) {
        databaseHelper.updateFeedNames(remoteFeedAtomIdsAndFeedTitles);
    }

    public void updateLastUsed() {
        singleValueStore.putLong("last_used", System.currentTimeMillis());
        getNewsRobNotificationManager().cancelNewArticlesNotification();
    }

    public long getLastUsed() {
        return singleValueStore.getLong("last_used", 0l);
    }

    public void maintainLastTimeProposedReinstall() {
        singleValueStore.putLong("last_proposed_reinstall", System.currentTimeMillis());
    }

    public long getLastTimeProposedReinstall() {
        return singleValueStore.getLong("last_proposed_reinstall", 0l);
    }

    public void updateLastExactSync() {
        singleValueStore.putLong("last_exact_sync", System.currentTimeMillis());
        getNewsRobNotificationManager().cancelNewArticlesNotification();
    }

    public long getLastExactSync() {
        return singleValueStore.getLong("last_exact_sync", 0l);
    }

    public void updateLastShownThatSyncIsNotPossible() {
        singleValueStore.putLong("sync_not_possible_shown", System.currentTimeMillis());
        getNewsRobNotificationManager().cancelNewArticlesNotification();
    }

    public long getLastShownThatSyncIsNotPossible() {
        return singleValueStore.getLong("sync_not_possible_shown", 0l);
    }

    public boolean shouldVibrateOnFirstLast() {
        return getSharedPreferences().getBoolean(SETTINGS_VIBRATE_FIRST_LAST_ENABLED, true);
    }

    public void updateLastSyncedSubscriptions(final long timestamp) {
        SDK9Helper.apply(getSharedPreferences().edit().putLong(SETTINGS_LAST_SYNCED_SUBSCRIPTIONS, timestamp));
    }

    public long getLastSyncedSubscriptions() {
        return getSharedPreferences().getLong(SETTINGS_LAST_SYNCED_SUBSCRIPTIONS, -1l);
    }

    public boolean isMarkAllReadPossible(final DBQuery dbq) {
        Boolean b = isMarkAllReadPossibleCache.get(dbq);
        if (b == null) {
            b = databaseHelper.isMarkAllReadPossible(dbq);
            isMarkAllReadPossibleCache.put(dbq, b);
        }
        return b;
    }

    public int getContentCount(DBQuery dbq) {
        Integer count = contentCountCache.get(dbq);
        if (count == null) {
            count = databaseHelper.getContentCount(dbq);
            contentCountCache.put(dbq, count);
        }
        return count;
    }

    public int getArticleCount() {
        Integer count = articleCountCache;
        if (count == null) {
            count = databaseHelper.getArticleCount();
            articleCountCache = count;
        }

        return count;
    }

    public long getMarkAllReadCount(final DBQuery dbq) {
        return databaseHelper.getMarkAllReadCount(dbq);
    }

    public int getPendingReadStateArticleCount() {
        return databaseHelper.getPendingReadStateArticleCount();
    }

    public boolean shouldAlwaysUseSsl() {
        return getSharedPreferences().getBoolean(SETTINGS_ALWAYS_USE_SSL, true);
    }

    public final boolean isProVersion() {

        if (proVersion == null) {
            final int checkSignature = ctx.getPackageManager().checkSignatures(LEGACY_PACKAGE_NAME,
                    EntryManager.PRO_PACKAGE_NAME);
            proVersion = checkSignature == PackageManager.SIGNATURE_MATCH
                    || checkSignature == PackageManager.SIGNATURE_NEITHER_SIGNED;
        }
        return proVersion;
    }

    public boolean isAndroidMarketInstalled() {
        if (isAndroidMarketInstalled == null) {
            try {
                ctx.getPackageManager().getPackageInfo(MARKET_PACKAGE_NAME, 0);
                isAndroidMarketInstalled = true;
            } catch (NameNotFoundException nnfe) {
                isAndroidMarketInstalled = false;
            }
            Log.d(TAG, "Android Market is " + (isAndroidMarketInstalled ? "" : "not ") + "installed.");
        }
        return isAndroidMarketInstalled;

    }

    public boolean shouldAdsBeShown() {

        final boolean shouldAdsBeShown = !isProVersion()
                || "1".equals(NewsRob.getDebugProperties(ctx).getProperty("enableAds", "0"));
        if (false)
            PL.log("EntryManager.shouldAdsBeShown() isProVersion=" + isProVersion() + " debugEnabledAds="
                    + NewsRob.getDebugProperties(ctx).getProperty("enableAds", "0") + " result=" + shouldAdsBeShown,
                    ctx);
        return shouldAdsBeShown;
    }

    public Long findNotesFeedId() {
        return databaseHelper.findNotesFeedId(getGoogleUserId());
    }

    public void update(final Entry entry) {
        databaseHelper.update(entry);
        fireModelUpdated(entry.getAtomId());
        if (entry.getNote() != null && !entry.isNoteSubmitted())
            scheduler.scheduleUploadOnlySynchonization();
    }

    public List<Entry> getEntriesWithNotesToBeSubmitted() {
        return databaseHelper.findEntriesWithNotesToBeSubmitted();
    }

    public void clearNotesSubmissionStateForAllSubmittedNotes() {
        databaseHelper.clearNotesSubmissionStateForAllSubmittedNotes();
    }

    public void removeDeletedNotes() {
        databaseHelper.removeDeletedNotes();
    }

    private static String encryptPassword(String clearTextPassword) {
        String encryptedPassword = null;

        if (clearTextPassword == null || clearTextPassword.length() == 0)
            return null;

        try {
            Cipher cipher = Cipher.getInstance("DES");
            cipher.init(Cipher.ENCRYPT_MODE, getSecretKey());

            encryptedPassword = new String(
                    com.newsrob.util.Base64.encodeBytes((cipher.doFinal(clearTextPassword.getBytes("UTF-8")))));

        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptedPassword;
    }

    private static String decryptPassword(String encryptedPw) {
        String clearTextPassword = null;

        if (encryptedPw == null || encryptedPw.length() == 0)
            return null;

        try {
            Cipher cipher = Cipher.getInstance("DES");
            cipher.init(Cipher.DECRYPT_MODE, getSecretKey());

            clearTextPassword = new String(cipher.doFinal(Base64.decode(encryptedPw)), "UTF-8");

        } catch (Exception e) {
            e.printStackTrace();
        }
        return clearTextPassword;
    }

    private static SecretKey getSecretKey() throws InvalidKeyException, UnsupportedEncodingException,
            NoSuchAlgorithmException, InvalidKeySpecException {
        DESKeySpec keySpec = new DESKeySpec("EntryManager.class".getBytes("UTF8"));
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES");
        SecretKey secretKey = keyFactory.generateSecret(keySpec);
        return secretKey;
    }

    public boolean shouldRememberPassword() {
        return getSharedPreferences().getBoolean(SETTINGS_REMEMBER_PASSWORD, false);
    }

    public String getMobilizer() {
        return getSharedPreferences().getString(SETTINGS_MOBILIZER, "gwt");
    }

    public String getPlugins() {
        return getSharedPreferences().getString(SETTINGS_PLUGINS, "OFF");
    }

    public void setRememberPassword(boolean enabled) {
        SDK9Helper.apply(getSharedPreferences().edit().putBoolean(SETTINGS_REMEMBER_PASSWORD, enabled));
        if (!enabled)
            storePassword(null);
    }

    public String getStoredPassword() {
        String encryptedPw = getSharedPreferences().getString(SETTINGS_PASS, null);
        if (encryptedPw == null || encryptedPw.length() == 0)
            return null;

        return decryptPassword(encryptedPw);
    }

    public void storePassword(String password) {
        SDK9Helper.apply(getSharedPreferences().edit().putString(SETTINGS_PASS, encryptPassword(password)));
    }

    public String getDefaultTextSize() {
        return getSharedPreferences().getString(SETTINGS_UI_TEXT_SIZE, "NORMAL");
    }

    public boolean isSyncInProgressNotificationEnabled() {
        return shouldSyncInProgressNotificationBeDisabled() ? false
                : getSharedPreferences().getBoolean(SETTINGS_SYNC_IN_PROGRESS_NOTIFICATION, false);
    }

    public String getEmail() {
        return getSharedPreferences().getString(SETTINGS_EMAIL, null);
    }

    public void saveEmail(String googleAccount) {
        SDK9Helper.apply(getSharedPreferences().edit().putString(SETTINGS_EMAIL, googleAccount));
    }

    public Cursor getAllFeedsCursor() {
        return databaseHelper.getAllFeedsCursor();
    }

    public Cursor getAllLabelsCursor() {
        return databaseHelper.getAllLabelsCursor();
    }

    public WidgetPreferences getWidgetPreferences(int appWidgetId) {
        WidgetPreferences wp = new WidgetPreferences();

        wp.setLabel(getSharedPreferences().getString(getWidgetPreferenceKey(appWidgetId, "label"), null));
        wp.setStartingActivityName(
                getSharedPreferences().getString(getWidgetPreferenceKey(appWidgetId, "startingActivity"), null));
        try {
            InputStream is = null;
            try {
                is = ctx.openFileInput(getWidgetPreferenceKey(appWidgetId, "query"));
            } catch (IOException ioe) {
                is = ctx.openFileInput(getWidgetPreferenceKey(appWidgetId, "label"));
            }
            wp.setDBQuery(DBQuery.restore(this, is));
            is.close();
        } catch (IOException ioe) {
            Log.d(TAG, "Couldn't load dbq for widget " + appWidgetId + ".");
            // ioe.printStackTrace();
            wp = null;
        }
        return wp;
    }

    public void saveWidgetPreferences(int appWidgetId, WidgetPreferences wp) {
        SDK9Helper.apply(getSharedPreferences().edit().putString(getWidgetPreferenceKey(appWidgetId, "label"),
                wp.getLabel()));

        SDK9Helper.apply(getSharedPreferences().edit()
                .putString(getWidgetPreferenceKey(appWidgetId, "startingActivity"), wp.getStartingActivityName()));

        try {
            if (wp.getDBQuery() != null) {
                OutputStream os = null;
                os = ctx.openFileOutput(getWidgetPreferenceKey(appWidgetId, "query"), Context.MODE_PRIVATE);
                wp.getDBQuery().store(os);
                os.close();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    private String getWidgetPreferenceKey(int appWidgetId, String key) {
        return "widget" + appWidgetId + key;
    }

    public void clearWidgetPreferences(int appWidgetId) {
        SDK9Helper.apply(getSharedPreferences().edit().remove(getWidgetPreferenceKey(appWidgetId, "label")));
        SDK9Helper.apply(getSharedPreferences().edit().remove(getWidgetPreferenceKey(appWidgetId, "query")));
    }

    public boolean isSwipeOnArticleDetailViewEnabled() {
        return getSharedPreferences().getBoolean(SETTING_SWIPE_ARTICLE_DETAIL_VIEW, true);
    }

    public boolean isSwipeOnArticleListEnabled() {
        return getSharedPreferences().getBoolean(SETTING_SWIPE_ARTICLE_LIST, true);
    }

    public boolean isNewsRobOnlySyncingEnabled() {
        return isProVersion() && getSharedPreferences().getBoolean(SETTINGS_SYNC_NEWSROB_ONLY_ENABLED, false);
    }

    public boolean isFriendsArticlesSyncingEnabled() {
        return getSharedPreferences().getBoolean("settings_sync_broadcast_friends_enabled", true);
    }

    public void saveLastSuccessfulLogin() {
        SDK9Helper
                .apply(getSharedPreferences().edit().putLong(SETTINGS_LAST_SUCCESSFUL_LOGIN, new Date().getTime()));
    }

    public Date getLastSuccessfulLogin() {
        return new Date(getSharedPreferences().getLong(SETTINGS_LAST_SUCCESSFUL_LOGIN, 0l));
    }

    public boolean shouldShowNewestArticlesFirst() {
        return getSharedPreferences().getBoolean(SETTINGS_UI_SORT_DEFAULT_NEWEST_FIRST, true);
    }

    public int getFirstInstalledVersion() {
        return getSharedPreferences().getInt(SETTINGS_FIRST_INSTALLED_VERSION, -1);
    }

    public void maintainFirstInstalledVersion() {
        if (getSharedPreferences().contains(SETTINGS_FIRST_INSTALLED_VERSION))
            return;
        int versionCode = getMyVersionCode();
        // set to a previous version when the app
        // was already installed
        if (getDaysInstalled() > 0)
            versionCode--;
        SDK9Helper.apply(getSharedPreferences().edit().putInt(SETTINGS_FIRST_INSTALLED_VERSION, versionCode));
    }

    public void migrateSyncType() {
        if (!getSharedPreferences().contains(SETTINGS_SYNC_TYPE)) {
            String newValue = SYNC_TYPE_VALUE_UNREAD_ARTICLES_ONLY;
            if (getFirstInstalledVersion() < 390)
                newValue = "all_articles";
            SDK9Helper.apply(getSharedPreferences().edit().putString(SETTINGS_SYNC_TYPE, newValue));
        }
    }

    public void showReleaseNotes() {
        // don't show release notes after install
        if (getMyVersionCode() == getFirstInstalledVersion())
            return;

        int shortVersionCode = getMyVersionCode() / 10;

        String releaseNotesKey = "release_notes_seen." + shortVersionCode;
        if (getSharedPreferences().contains(releaseNotesKey))
            return;

        Uri uri = Uri.parse("http://bit.ly/nr_" + shortVersionCode);
        if (false) {
            Intent i = new Intent(Intent.ACTION_VIEW, uri);
            i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            ctx.startActivity(i);
        } else {
            getNewsRobNotificationManager().createCheckReleaseNotesNotification(uri);

        }

        SDK9Helper.apply(getSharedPreferences().edit().putBoolean(releaseNotesKey, true));
    }

    public void removeArticleContent(Entry entry) {
        if (!fileContextAdapter.canWrite())
            return;

        final int noOfAssetsDeleted = WebPageDownloadDirector.removeAssetsForId(entry.getAtomId(),
                fileContextAdapter);
        Log.d(TAG, noOfAssetsDeleted + " assets deleted for entry " + entry.getAtomId() + ".");
        entry.setDownloaded(0);
        entry.setError(null);
        updatedDownloaded(entry);
        fireModelUpdated(entry.getAtomId());
    }

    public Object getFeeds2UnsubscribeCount() {
        return databaseHelper.getFeeds2UnsubscribeCount();
    }

    public Cursor getFeeds2UnsubscribeCursor() {
        return databaseHelper.getFeeds2UnsubscribeCursor();
    }

    public void requestUnsubscribeFeed(final String feedAtomId) {
        // TODO remove this whole method if it doesn't do anything but run the
        // job

        try {
            runJob(new Job("UnsubscribingFeed", EntryManager.this) {

                @Override
                public void run() throws Exception {
                    doUnsubscribeFeed(feedAtomId);
                }
            });
        } catch (Throwable e) {
            e.printStackTrace();
        }

    }

    public void doUnsubscribeFeed(String feedAtomId) {
        try {
            Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);

            synchronized (this) {
                if (isModelCurrentlyUpdated())
                    return;
                lockModel("EM.requestUnsubscribeFeed");
            }
            if (canFeedBeUnsubscribed(feedAtomId)) {
                databaseHelper.addFeed2Unsubscribe(feedAtomId);
                DBQuery dbq = prepareMarkFeedReadQuery(feedAtomId);
                if (dbq != null)
                    doMarkAllRead(dbq);
            }
        } finally {
            synchronized (this) {
                unlockModel("EM.requestUnsubscribeFeed");
            }
        }

    }

    public boolean canFeedBeUnsubscribed(String feedAtomId) {
        if (databaseHelper.isFeedMarkedToBeUnsubscribed(feedAtomId))
            return false;

        if (!databaseHelper.doesFeedExist(feedAtomId))
            return false;

        return true;
    }

    public long getArticleCountThatWouldBeMarkedAsReadWhenFeedWouldBeUnsubscribed(String feedAtomId) {
        DBQuery dbq = prepareMarkFeedReadQuery(feedAtomId);
        if (dbq == null)
            return -1;
        return getMarkAllReadCount(dbq);
    }

    private DBQuery prepareMarkFeedReadQuery(String feedAtomId) {
        long feedId = databaseHelper.findFeedIdByFeedAtomId(feedAtomId);
        if (feedId == -1)
            return null;
        DBQuery dbq = new DBQuery(this, null, feedId);
        dbq.setShouldHideReadItemsWithoutUpdatingThePreference(true);
        return dbq;
    }

    public void removeFeedFromFeeds2Unsubscribe(String feedAtomId) {
        databaseHelper.removeFeedFromFeeds2Unsubscribe(feedAtomId);
    }

    public int getMaxArticlesInArticleList() {
        if (DEVICE_MODEL_ARCHOS_7.equals(Build.MODEL) && SDKVersionUtil.getVersion() < 4
                || "HTC Gratia A6380".equals(Build.MODEL))
            return 200;
        return MAX_ARTICLES_IN_ARTICLE_LIST;
    }

    public boolean shouldActionBarLocationOnlyAllowGone() {

        int sdkLevel = SDKVersionUtil.getVersion();
        if (sdkLevel < 4 || sdkLevel == 5 || sdkLevel == 6)
            return true;

        // Tiny screens
        if (true)
            return false;

        Configuration cfg = ctx.getResources().getConfiguration();
        int screenLayout = -1;
        try {
            screenLayout = (Integer) cfg.getClass().getField("screenLayout").get(cfg);
        } catch (Exception e) {
            return false;
        }
        // if ((cfg.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) ==
        // Configuration.SCREENLAYOUT_SIZE_SMALL)
        if ((screenLayout & 0x0000000f) == 0x00000001)
            return true;

        return false;
    }

    public String getActionBarLocation() {

        if (shouldActionBarLocationOnlyAllowGone())
            return ACTION_BAR_GONE;

        return getSharedPreferences().getString(SETTINGS_UI_ACTION_BAR_LOCATION, ACTION_BAR_TOP);
    }

    public boolean shouldAskForUsageDataCollectionPermission() {
        SharedPreferences sp = getSharedPreferences();
        if (!sp.contains(SETTINGS_USAGE_DATA_COLLECTION_PERMISSION_VERSION))
            return true;

        return false;

    }

    public boolean isUsageDataCollectionPermitted() {
        return getSharedPreferences().getBoolean(SETTINGS_USAGE_DATA_PERMISSION_GRANTED, false);
    }

    public void saveUsageDataCollectionPermission(boolean permitted) {

        Editor editor = getSharedPreferences().edit();
        editor.putInt(SETTINGS_USAGE_DATA_COLLECTION_PERMISSION_VERSION, getMyVersionCode());
        editor.putBoolean(SETTINGS_USAGE_DATA_PERMISSION_GRANTED, permitted);
        SDK9Helper.apply(editor);

        Log.d(TAG, "User Data Collection preference saved: Permitted? " + permitted);
    }

    public boolean hasProgressReportBeenOpened() {
        return getSharedPreferences().contains(SETTINGS_OPENED_PROGRESS_REPORT);
    }

    public void updateProgressReportBeenOpened() {
        SDK9Helper.apply(getSharedPreferences().edit().putBoolean(SETTINGS_OPENED_PROGRESS_REPORT, true));
    }

    public int getUnreadArticleCountExact() {
        return databaseHelper.getUnreadArticleCountExact();
    }

    public int getReadArticleCount() {
        return databaseHelper.getReadArticleCount();
    }

    public int getPinnedArticleCount() {
        return databaseHelper.getPinnedArticleCount();
    }

    public int getStarredArticleCount() {
        return databaseHelper.getStarredArticleCount();
    }

    public int getSharedArticleCount() {
        return databaseHelper.getSharedArticleCount();
    }

    public int getNotesCount() {
        return databaseHelper.getNotesCount();
    }

    public int getChangedArticleCount() {
        return databaseHelper.getChangedArticleCount();
    }

    public boolean isADWLauncherInterestedInNotifications() {

        boolean adwInstalled;
        try {
            adwInstalled = (ctx.getPackageManager().getPackageInfo("org.adw.launcher.notifications", 0) != null);
        } catch (NameNotFoundException e) {
            return false;
        }
        return adwInstalled;
    }

    public boolean shouldAlwaysExactSync() {
        return getSharedPreferences().getBoolean(SETTINGS_ALWAYS_EXACT_SYNC, false);
    }

    public long findFeedIdByFeedUrl(String feedUrl) {
        return databaseHelper.findFeedIdByFeedUrl(feedUrl);
    }

}