com.xorcode.andtweet.AndTweetService.java Source code

Java tutorial

Introduction

Here is the source code for com.xorcode.andtweet.AndTweetService.java

Source

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

package com.xorcode.andtweet;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

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

import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetProvider;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteConstraintException;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.Log;

import com.xorcode.andtweet.TwitterUser.CredentialsVerified;
import com.xorcode.andtweet.appwidget.AndTweetAppWidgetProvider;
import com.xorcode.andtweet.data.AndTweetDatabase;
import com.xorcode.andtweet.data.MyPreferences;
import com.xorcode.andtweet.data.AndTweetDatabase.Tweets;
import com.xorcode.andtweet.net.ConnectionException;
import com.xorcode.andtweet.util.ForegroundCheckTask;
import com.xorcode.andtweet.util.I18n;
import com.xorcode.andtweet.util.MyLog;
import com.xorcode.andtweet.util.SharedPreferencesUtil;

/**
 * This is an application service that serves as a connection between Android
 * and Twitter. Other applications can interact with it via IPC.
 */
public class AndTweetService extends Service {
    private static final String TAG = AndTweetService.class.getSimpleName();

    /**
     * Use this tag to change logging level of the whole application Is used is
     * isLoggable(APPTAG, ... ) calls
     */
    public static final String APPTAG = "AndTweet";

    private static final String packageName = AndTweetService.class.getPackage().getName();

    /**
     * Prefix of all actions of this Service
     */
    private static final String ACTIONPREFIX = packageName + ".action.";

    /**
     * Intent with this action sent when it is time to update AndTweet
     * AppWidget.
     * <p>
     * This may be sent in response to some new information is ready for
     * notification (some changes...), or the system booting.
     * <p>
     * The intent will contain the following extras:
     * <ul>
     * <li>{@link #EXTRA_MSGTYPE}</li>
     * <li>{@link #EXTRA_NUMTWEETSMSGTYPE}</li>
     * <li>{@link android.appwidget.AppWidgetManager#EXTRA_APPWIDGET_IDS}<br/>
     * The appWidgetIds to update. This may be all of the AppWidgets created for
     * this provider, or just a subset. The system tries to send updates for as
     * few AppWidget instances as possible.</li>
     * 
     * @see AppWidgetProvider#onUpdate AppWidgetProvider.onUpdate(Context
     *      context, AppWidgetManager appWidgetManager, int[] appWidgetIds)
     */
    public static final String ACTION_APPWIDGET_UPDATE = ACTIONPREFIX + "APPWIDGET_UPDATE";

    /**
     * Repeating alarm was triggered.
     * @see AndTweetService#scheduleRepeatingAlarm()
     */
    public static final String ACTION_ALARM = ACTIONPREFIX + "ALARM";

    /**
     * This action is being sent by {@link AndTweetService} to notify that it
     * was stopped
     */
    public static final String ACTION_SERVICE_STOPPED = ACTIONPREFIX + "SERVICE_STOPPED";

    /**
     * This action is used in any intent sent to this service. Actual command to
     * perform by this service is in the {@link #EXTRA_MSGTYPE} extra of the
     * intent
     * 
     * @see CommandEnum
     */
    public static final String ACTION_GO = ACTIONPREFIX + "GO";

    /**
     * These names of extras are used in the Intent-notification of new Tweets
     * (e.g. to notify Widget).
     */

    /**
     * This extra is used as a command to perform by AndTweetService and
     * AndTweetAppWidgetProvider Value of this extra is string code of
     * CommandEnum (not serialized enum !)
     */
    public static final String EXTRA_MSGTYPE = packageName + ".MSGTYPE";

    /**
     * Command parameter: long - ID of the Tweet (or Message)
     */
    public static final String EXTRA_TWEETID = packageName + ".TWEETID";

    /**
     * Text of the status message
     */
    public static final String EXTRA_STATUS = packageName + ".STATUS";

    /**
     * User name
     */
    public static final String EXTRA_USERNAME = packageName + ".USERNAME";

    /**
     * Name of the preference to set
     */
    public static final String EXTRA_PREFERENCE_KEY = packageName + ".PREFERENCE_KEY";

    public static final String EXTRA_PREFERENCE_VALUE = packageName + ".PREFERENCE_VALUE";

    /**
     * Reply to
     */
    public static final String EXTRA_INREPLYTOID = packageName + ".INREPLYTOID";

    /**
     * Number of new tweets. Value is integer
     */
    public static final String EXTRA_NUMTWEETS = packageName + ".NUMTWEETS";

    /**
     * This extra is used to determine which timeline to show in
     * TimelineActivity Value is integer (TODO: enum...)
     */
    public static final String EXTRA_TIMELINE_TYPE = packageName + ".TIMELINE_TYPE";

    /**
     * The command to the AndTweetService or to AndTweetAppWidgetProvider as a
     * enum We use 'code' for persistence
     * 
     * @author yvolk
     */
    public enum CommandEnum {

        /**
         * The action is unknown
         */
        UNKNOWN("unknown"),
        /**
         * There is no action
         */
        EMPTY("empty"),
        /**
         * The action is being sent by recurring alarm to fetch the tweets,
         * replies and other information in the background
         */
        AUTOMATIC_UPDATE("automatic-update"),
        /**
         * Fetch the tweets, replies and other information.
         */
        FETCH_TIMELINE("fetch-timeline"),
        /**
         * Fetch messages
         */
        FETCH_MESSAGES("fetch-messages"),
        /**
         * The recurring alarm that is used to implement recurring tweet
         * downloads should be started.
         */
        START_ALARM("start-alarm"),
        /**
         * The recurring alarm that is used to implement recurring tweet
         * downloads should be stopped.
         */
        STOP_ALARM("stop-alarm"),
        /**
         * The recurring alarm that is used to implement recurring tweet
         * downloads should be restarted.
         */
        RESTART_ALARM("restart-alarm"),

        CREATE_FAVORITE("create-favorite"), DESTROY_FAVORITE("destroy-favorite"),

        UPDATE_STATUS("update-status"), DESTROY_STATUS("destroy-status"),

        RATE_LIMIT_STATUS("rate-limit-status"),

        /**
         * Notify User about commands in the Queue
         */
        NOTIFY_QUEUE("notify-queue"),

        /**
         * Commands to the Widget New tweets|messages were successfully loaded
         * from the server
         */
        NOTIFY_DIRECT_MESSAGE("notify-direct-message"), NOTIFY_TIMELINE("notify-timeline"), NOTIFY_REPLIES(
                "notify-replies"),
        /**
         * Clear previous notifications (because e.g. user open tweet list...)
         */
        NOTIFY_CLEAR("notify-clear"),

        /**
         * Reload all preferences...
         */
        PREFERENCES_CHANGED("preferences-changed"),

        /**
         * Save SharePreverence. We try to use it because sometimes Android
         * doesn't actually store these values to the disk... and the
         * preferences get lost. I think this is mainly because of several
         * processes using the same preferences
         */
        PUT_BOOLEAN_PREFERENCE("put-boolean-preference"), PUT_LONG_PREFERENCE(
                "put-long-preference"), PUT_STRING_PREFERENCE("put-string-preference");

        /**
         * code of the enum that is used in messages
         */
        private String code;

        private CommandEnum(String codeIn) {
            code = codeIn;
        }

        /**
         * String code for the Command to be used in messages
         */
        public String save() {
            return code;
        }

        /**
         * Returns the enum for a String action code or UNKNOWN
         */
        public static CommandEnum load(String strCode) {
            for (CommandEnum serviceCommand : CommandEnum.values()) {
                if (serviceCommand.code.equals(strCode)) {
                    return serviceCommand;
                }
            }
            return UNKNOWN;
        }

    }

    /**
     * Command data store (message...)
     * 
     * @author yvolk
     */
    public static class CommandData {
        public CommandEnum command;

        public long itemId = 0;

        /**
         * Other command parameters
         */
        public Bundle bundle = new Bundle();

        private int hashcode = 0;

        /**
         * Number of retries left
         */
        public int retriesLeft = 0;

        public CommandData(CommandEnum commandIn) {
            command = commandIn;
        }

        public CommandData(CommandEnum commandIn, long itemIdIn) {
            command = commandIn;
            itemId = itemIdIn;
        }

        /**
         * Initialize command to put SharedPreference
         * 
         * @param preferenceKey
         * @param value
         * @param username - preferences for this user, or null if Global
         *            preferences
         */
        public CommandData(String preferenceKey, boolean value, String username) {
            command = CommandEnum.PUT_BOOLEAN_PREFERENCE;
            bundle.putString(EXTRA_PREFERENCE_KEY, preferenceKey);
            bundle.putBoolean(EXTRA_PREFERENCE_VALUE, value);
            if (username != null) {
                bundle.putString(EXTRA_USERNAME, username);
            }
        }

        /**
         * Initialize command to put SharedPreference
         * 
         * @param preferenceKey
         * @param value
         * @param username - preferences for this user, or null if Global
         *            preferences
         */
        public CommandData(String preferenceKey, long value, String username) {
            command = CommandEnum.PUT_LONG_PREFERENCE;
            bundle.putString(EXTRA_PREFERENCE_KEY, preferenceKey);
            bundle.putLong(EXTRA_PREFERENCE_VALUE, value);
            if (username != null) {
                bundle.putString(EXTRA_USERNAME, username);
            }
        }

        /**
         * Initialize command to put SharedPreference
         * 
         * @param preferenceKey
         * @param value
         * @param username - preferences for this user
         */
        public CommandData(String preferenceKey, String value, String username) {
            command = CommandEnum.PUT_STRING_PREFERENCE;
            bundle.putString(EXTRA_PREFERENCE_KEY, preferenceKey);
            bundle.putString(EXTRA_PREFERENCE_VALUE, value);
            if (username != null) {
                bundle.putString(EXTRA_USERNAME, username);
            }
        }

        /**
         * Used to decode command from the Intent upon receiving it
         * 
         * @param intent
         */
        public CommandData(Intent intent) {
            bundle = intent.getExtras();
            // Decode command
            String strCommand = "(no command)";
            if (bundle != null) {
                strCommand = bundle.getString(EXTRA_MSGTYPE);
                itemId = bundle.getLong(EXTRA_TWEETID);
            }
            command = CommandEnum.load(strCommand);
        }

        /**
         * Restore the object from the SharedPreferences 
         * @param sp
         * @param index Index of the preference's name to be used
         */
        public CommandData(SharedPreferences sp, int index) {
            bundle = new Bundle();
            String si = Integer.toString(index);
            // Decode command
            String strCommand = sp.getString(EXTRA_MSGTYPE + si, CommandEnum.UNKNOWN.save());
            itemId = sp.getLong(EXTRA_TWEETID + si, 0);
            command = CommandEnum.load(strCommand);

            switch (command) {
            case UPDATE_STATUS:
                bundle.putString(EXTRA_STATUS, sp.getString(EXTRA_STATUS + si, ""));
                bundle.putLong(EXTRA_INREPLYTOID, sp.getLong(EXTRA_INREPLYTOID + si, 0));
                break;
            }

            MyLog.v(TAG, "Restored command " + (EXTRA_MSGTYPE + si) + " = " + strCommand);
        }

        /**
         * It's used in equals() method We need to distinguish duplicated
         * commands
         */
        @Override
        public int hashCode() {
            if (hashcode == 0) {
                String text = Long.toString(command.ordinal());
                if (itemId != 0) {
                    text += Long.toString(itemId);
                }
                switch (command) {
                case UPDATE_STATUS:
                    text += bundle.getString(EXTRA_STATUS);
                    break;
                case PUT_BOOLEAN_PREFERENCE:
                    text += bundle.getString(EXTRA_PREFERENCE_KEY) + bundle.getString(EXTRA_USERNAME)
                            + bundle.getBoolean(EXTRA_PREFERENCE_VALUE);
                    break;
                case PUT_LONG_PREFERENCE:
                    text += bundle.getString(EXTRA_PREFERENCE_KEY) + bundle.getString(EXTRA_USERNAME)
                            + bundle.getLong(EXTRA_PREFERENCE_VALUE);
                    break;
                case PUT_STRING_PREFERENCE:
                    text += bundle.getString(EXTRA_PREFERENCE_KEY) + bundle.getString(EXTRA_USERNAME)
                            + bundle.getString(EXTRA_PREFERENCE_VALUE);
                    break;
                }
                hashcode = text.hashCode();
            }
            return hashcode;
        }

        /**
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return "CommandData [" + "command=" + command.save() + (itemId == 0 ? "" : "; id=" + itemId)
                    + ", hashCode=" + hashCode() + "]";
        }

        /**
         * @return Intent to be sent to this.AndTweetService
         */
        public Intent toIntent() {
            return toIntent(null);
        }

        /**
         * @return Intent to be sent to this.AndTweetService
         */
        public Intent toIntent(Intent intent_in) {
            Intent intent = intent_in;
            if (intent == null) {
                intent = new Intent(AndTweetService.ACTION_GO);
            }
            if (bundle == null) {
                bundle = new Bundle();
            }
            bundle.putString(AndTweetService.EXTRA_MSGTYPE, command.save());
            if (itemId != 0) {
                bundle.putLong(AndTweetService.EXTRA_TWEETID, itemId);
            }
            intent.putExtras(bundle);
            return intent;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj == null) {
                return false;
            }
            if (obj == this) {
                return true;
            }
            if (obj.getClass() != getClass()) {
                return false;
            }
            return (this.hashCode() == ((CommandData) obj).hashCode());
        }

        /**
         * Persist the object to the SharedPreferences 
         * We're not storing all types of commands here because not all commands
         *   go to the queue
         * @param sp
         * @param index Index of the preference's name to be used
         */
        public void save(SharedPreferences sp, int index) {
            String si = Integer.toString(index);

            android.content.SharedPreferences.Editor ed = sp.edit();
            ed.putString(EXTRA_MSGTYPE + si, command.save());
            if (itemId != 0) {
                ed.putLong(EXTRA_TWEETID + si, itemId);
            }
            switch (command) {
            case UPDATE_STATUS:
                ed.putString(EXTRA_STATUS + si, bundle.getString(EXTRA_STATUS));
                ed.putLong(EXTRA_INREPLYTOID + si, bundle.getLong(EXTRA_INREPLYTOID));
                break;
            }
            ed.commit();
        }
    }

    /**
     * This is a list of callbacks that have been registered with the service.
     */
    final RemoteCallbackList<IAndTweetServiceCallback> mCallbacks = new RemoteCallbackList<IAndTweetServiceCallback>();

    private static final int MILLISECONDS = 1000;

    /**
     * Send broadcast to Widgets even if there are no new tweets
     */
    // TODO: Maybe this should be additional setting...
    public static boolean updateWidgetsOnEveryUpdate = true;

    private boolean mNotificationsEnabled;

    private boolean mNotificationsVibrate;
    /**
     * Flag to control the Service state persistence
     */
    private boolean mStateRestored = false;

    /**
     * Commands queue to be processed by the Service
     */
    private BlockingQueue<CommandData> mCommands = new ArrayBlockingQueue<CommandData>(100, true);

    /**
     * Retry Commands queue
     */
    private BlockingQueue<CommandData> mRetryQueue = new ArrayBlockingQueue<CommandData>(100, true);

    /**
     * The set of threads that are currently executing commands For now let's
     * have only ONE working thread (it seems there is some problem in parallel
     * execution...)
     */
    private Set<CommandExecutor> mExecutors = new HashSet<CommandExecutor>();

    /**
     * The number of listeners returned by the last broadcast start call.
     */
    private volatile int mBroadcastListenerCount = 0;

    /**
     * The reference to the wake lock used to keep the CPU from stopping during
     * downloads.
     */
    private volatile PowerManager.WakeLock mWakeLock = null;

    /**
     * Time when shared preferences where changed
     */
    protected long preferencesChangeTime = 0;
    /**
     * Time when shared preferences where analyzed
     */
    protected long preferencesExamineTime = 0;

    /**
     * @return Single instance of Default SharedPreferences is returned, this is why we
     *         may synchronize on the object
     */
    private SharedPreferences getSp() {
        return MyPreferences.getDefaultSharedPreferences();
    }

    /**
     * The idea is to have SharePreferences, that are being edited by
     * the service process only (to avoid problems of concurrent access.
     * @return Single instance of SharedPreferences, specific to the class
     */
    private SharedPreferences getServiceSp() {
        return MyPreferences.getSharedPreferences(TAG, MODE_PRIVATE);
    }

    @Override
    public void onCreate() {
        MyPreferences.initialize(this, this);
        preferencesChangeTime = MyPreferences.getDefaultSharedPreferences()
                .getLong(MyPreferences.KEY_PREFERENCES_CHANGE_TIME, 0);
        preferencesExamineTime = getServiceSp().getLong(MyPreferences.KEY_PREFERENCES_EXAMINE_TIME, 0);
        MyLog.d(TAG, "Service created, preferencesChangeTime=" + preferencesChangeTime + ", examined="
                + preferencesExamineTime);

        registerReceiver(intentReceiver, new IntentFilter(ACTION_GO));
    }

    private BroadcastReceiver intentReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context arg0, Intent intent) {
            MyLog.v(TAG, "onReceive " + intent.toString());
            receiveCommand(intent);
        }

    };

    @Override
    public void onDestroy() {
        synchronized (this) {
            sendBroadcast(new Intent(ACTION_SERVICE_STOPPED));

            // Unregister all callbacks.
            mCallbacks.kill();

            unregisterReceiver(intentReceiver);

            // Clear notifications if any
            int count = notifyOfQueue(true);
            saveState();

            MyLog.d(TAG, "Service destroyed" + (count > 0 ? ", " + count + " msg in the Queue" : ""));
            MyPreferences.forget();
        }
    }

    /**
     * Persist everything that we'll need on next Service creation.
     */
    private void saveState() {
        if (mStateRestored) {
            int count = 0;
            // TODO: Save Queues
            count += saveQueue(mCommands, TAG + "_" + "mCommands");
            count += saveQueue(mRetryQueue, TAG + "_" + "mRetryQueue");
            MyLog.d(TAG, "State saved" + (count > 0 ? ", " + count + " msg in the Queues" : ""));
        }
        mStateRestored = false;
    }

    private int saveQueue(BlockingQueue<CommandData> q, String prefsFileName) {
        Context context = MyPreferences.getContext();
        int count = 0;
        // Delete any existing saved queue
        SharedPreferencesUtil.delete(context, prefsFileName);
        if (q.size() > 0) {
            SharedPreferences sp = MyPreferences.getSharedPreferences(prefsFileName, MODE_PRIVATE);
            while (q.size() > 0) {
                CommandData cd = q.poll();
                cd.save(sp, count);
                MyLog.v(TAG, "Command saved: " + cd.toString());
                count += 1;
            }
            MyLog.d(TAG, "Queue saved to " + prefsFileName + ", " + count + " msgs");
        }
        return count;
    }

    /**
     * Restore state if it was not restored yet
     */
    private void restoreState() {
        synchronized (this) {
            if (!mStateRestored) {
                int count = 0;
                // TODO: Restore Queues
                count += restoreQueue(mCommands, TAG + "_" + "mCommands");
                count += restoreQueue(mRetryQueue, TAG + "_" + "mRetryQueue");
                MyLog.d(TAG, "State restored" + (count > 0 ? ", " + count + " msg in the Queues" : ""));
            }
            mStateRestored = true;
        }
    }

    private int restoreQueue(BlockingQueue<CommandData> q, String prefsFileName) {
        Context context = MyPreferences.getContext();
        int count = 0;
        if (SharedPreferencesUtil.exists(context, prefsFileName)) {
            boolean done = false;
            SharedPreferences sp = MyPreferences.getSharedPreferences(prefsFileName, MODE_PRIVATE);
            do {
                CommandData cd = new CommandData(sp, count);
                if (cd.command == CommandEnum.UNKNOWN) {
                    done = true;
                } else {
                    try {
                        q.put(cd);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    MyLog.v(TAG, "Command restored: " + cd.toString());
                    count += 1;
                }
            } while (!done);
            sp = null;
            // Delete this saved queue
            SharedPreferencesUtil.delete(context, prefsFileName);
            MyLog.d(TAG, "Queue restored from " + prefsFileName + ", " + count + " msgs");
        }
        return count;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Select the interface to return. If your service only implements
        // a single interface, you can just return it here without checking
        // the Intent.
        if (IAndTweetService.class.getName().equals(intent.getAction())) {
            return mBinder;
        }
        return null;
    }

    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        MyLog.d(TAG, "onStart(): startid: " + startId);
        receiveCommand(intent);
    }

    /**
     * Put Intent to the Command's queue and Start Execution thread if none is
     * already running
     * 
     * @param Intent containing command and it's parameters. It may be null to
     *            initialize execution only.
     */
    private synchronized void receiveCommand(Intent intent) {

        long preferencesChangeTimeNew = MyPreferences.getDefaultSharedPreferences()
                .getLong(MyPreferences.KEY_PREFERENCES_CHANGE_TIME, 0);
        if (preferencesChangeTime != preferencesChangeTimeNew
                || preferencesExamineTime < preferencesChangeTimeNew) {
            examinePreferences();
        }

        restoreState();

        if (mCommands.isEmpty()) {
            // This is a good place to send commands from retry Queue
            while (!mRetryQueue.isEmpty()) {
                CommandData commandData = mRetryQueue.poll();
                if (!mCommands.contains(commandData)) {
                    if (!mCommands.offer(commandData)) {
                        Log.e(TAG, "mCommands is full?");
                    }
                }
            }
        }

        if (intent != null) {
            CommandData commandData = new CommandData(intent);
            if (commandData.command == CommandEnum.UNKNOWN) {
                // Ignore unknown commands

                // Maybe this command may be processed synchronously without
                // Internet connection?
            } else if (processCommandImmediately(commandData)) {
                // Don't add to the queue
            } else if (mCommands.contains(commandData)) {
                MyLog.d(TAG, "Duplicated " + commandData);
            } else {
                MyLog.d(TAG, "Adding to the queue " + commandData);
                if (!mCommands.offer(commandData)) {
                    Log.e(TAG, "mCommands is full?");
                }
            }
        }

        // Start Executor if necessary
        startEndStuff(true, null, null);
    }

    /**
     * @param commandData
     * @return true if the command was processed (either successfully or not...)
     */
    private boolean processCommandImmediately(CommandData commandData) {
        boolean processed = false;
        // Processed successfully?
        boolean ok = true;
        boolean skipped = false;

        /**
         * Flag for debugging. It looks like for now we don't need to edit
         * SharedPreferences from this part of code
         */
        boolean putPreferences = false;

        processed = (commandData == null);
        if (!processed) {
            processed = true;
            switch (commandData.command) {

            // TODO: Do we really need these three commands?
            case START_ALARM:
                ok = scheduleRepeatingAlarm();
                break;
            case STOP_ALARM:
                ok = cancelRepeatingAlarm();
                break;
            case RESTART_ALARM:
                ok = cancelRepeatingAlarm();
                ok = scheduleRepeatingAlarm();
                break;

            case UNKNOWN:
            case EMPTY:
                // Nothing to do
                break;
            case PREFERENCES_CHANGED:
                examinePreferences();
                break;

            case PUT_BOOLEAN_PREFERENCE:
                if (!putPreferences) {
                    skipped = true;
                    break;
                }
                String key = commandData.bundle.getString(EXTRA_PREFERENCE_KEY);
                boolean boolValue = commandData.bundle.getBoolean(EXTRA_PREFERENCE_VALUE);
                String username = commandData.bundle.getString(EXTRA_USERNAME);
                MyLog.v(TAG, "Put boolean Preference '" + key + "'=" + boolValue
                        + ((username != null) ? " user='" + username + "'" : " global"));
                SharedPreferences sp = null;
                if (username != null) {
                    sp = TwitterUser.getTwitterUser(username).getSharedPreferences();
                } else {
                    sp = getSp();
                }
                synchronized (sp) {
                    sp.edit().putBoolean(key, boolValue).commit();
                }
                break;
            case PUT_LONG_PREFERENCE:
                if (!putPreferences) {
                    skipped = true;
                    break;
                }
                key = commandData.bundle.getString(EXTRA_PREFERENCE_KEY);
                long longValue = commandData.bundle.getLong(EXTRA_PREFERENCE_VALUE);
                username = commandData.bundle.getString(EXTRA_USERNAME);
                MyLog.v(TAG, "Put long Preference '" + key + "'=" + longValue
                        + ((username != null) ? " user='" + username + "'" : " global"));
                if (username != null) {
                    sp = TwitterUser.getTwitterUser(username).getSharedPreferences();
                } else {
                    sp = getSp();
                }
                synchronized (sp) {
                    sp.edit().putLong(key, longValue).commit();
                }
                break;
            case PUT_STRING_PREFERENCE:
                if (!putPreferences) {
                    skipped = true;
                    break;
                }
                key = commandData.bundle.getString(EXTRA_PREFERENCE_KEY);
                String stringValue = commandData.bundle.getString(EXTRA_PREFERENCE_VALUE);
                username = commandData.bundle.getString(EXTRA_USERNAME);
                MyLog.v(TAG, "Put String Preference '" + key + "'=" + stringValue
                        + ((username != null) ? " user='" + username + "'" : " global"));
                if (username != null) {
                    sp = TwitterUser.getTwitterUser(username).getSharedPreferences();
                } else {
                    sp = getSp();
                }
                synchronized (sp) {
                    sp.edit().putString(key, stringValue).commit();
                }
                break;
            default:
                processed = false;
                break;
            }
            if (processed) {
                MyLog.d(TAG, (skipped ? "Skipped" : (ok ? "Succeeded" : "Failed")) + " " + commandData);
            }
        }
        return processed;
    }

    /**
     * Examine changed preferences and behave accordingly
     * Clear all (including static) members, that depend on preferences
     * and need to be reread...
     */
    private boolean examinePreferences() {
        boolean ok = true;

        // Check when preferences were changed
        long preferencesChangeTimeNew = MyPreferences.getDefaultSharedPreferences()
                .getLong(MyPreferences.KEY_PREFERENCES_CHANGE_TIME, 0);
        long preferencesExamineTimeNew = java.lang.System.currentTimeMillis();

        if (preferencesChangeTimeNew > preferencesExamineTime) {
            MyLog.d(TAG, "Examine at=" + preferencesExamineTimeNew + " Preferences changed at="
                    + preferencesChangeTimeNew);
        } else if (preferencesChangeTimeNew > preferencesChangeTime) {
            MyLog.d(TAG, "Preferences changed at=" + preferencesChangeTimeNew);
        } else if (preferencesChangeTimeNew == preferencesChangeTime) {
            MyLog.d(TAG, "Preferences didn't change, still at=" + preferencesChangeTimeNew);
        } else {
            Log.e(TAG, "Preferences change time error, time=" + preferencesChangeTimeNew);
        }
        preferencesChangeTime = preferencesChangeTimeNew;
        preferencesExamineTime = preferencesExamineTimeNew;
        getServiceSp().edit().putLong(MyPreferences.KEY_PREFERENCES_EXAMINE_TIME, preferencesExamineTime).commit();

        // Forget and reload preferences...
        MyPreferences.forget();
        MyPreferences.initialize(this, this);

        // Stop existing alarm in any case
        ok = cancelRepeatingAlarm();

        SharedPreferences sp = MyPreferences.getDefaultSharedPreferences();
        if (sp.contains("automatic_updates") && sp.getBoolean("automatic_updates", false)) {
            /**
             * Schedule Automatic updates according to the preferences.
             */
            ok = scheduleRepeatingAlarm();
        }
        return ok;
    }

    /**
     * Start Execution thread if none is already running or stop execution
     * 
     * @param start true: start, false: stop
     * @param executor - existing executor or null (if starting new executor)
     * @param logMsg a log message to include for debugging
     */
    private synchronized void startEndStuff(boolean start, CommandExecutor executorIn, String logMsg) {
        if (start) {
            SharedPreferences sp = getSp();
            mNotificationsEnabled = sp.getBoolean("notifications_enabled", false);
            mNotificationsVibrate = sp.getBoolean("vibration", false);
            sp = null;

            if (!mCommands.isEmpty()) {
                // Don't even launch executor if we're not online
                if (isOnline()) {
                    // only one Executing thread for now...
                    if (mExecutors.isEmpty()) {
                        CommandExecutor executor;
                        if (executorIn != null) {
                            executor = executorIn;
                        } else {
                            executor = new CommandExecutor();
                        }
                        if (logMsg != null) {
                            MyLog.d(TAG, logMsg);
                        }
                        mExecutors.add(executor);
                        if (mExecutors.size() == 1) {
                            mWakeLock = getWakeLock();
                            mBroadcastListenerCount = mCallbacks.beginBroadcast();
                            MyLog.d(TAG, "No other threads running so starting new broadcast for "
                                    + mBroadcastListenerCount + " listeners");
                        }
                        executor.execute();
                    }
                } else {
                    notifyOfQueue(false);
                }
            }
        } else {
            // Stop
            if (logMsg != null) {
                MyLog.d(TAG, logMsg);
            }
            mExecutors.remove(executorIn);
            if (mExecutors.size() == 0) {
                MyLog.d(TAG, "Ending last thread so also ending broadcast.");
                mWakeLock.release();
                mCallbacks.finishBroadcast();
                if (notifyOfQueue(false) == 0) {
                    if (!ForegroundCheckTask.isAppOnForeground(MyPreferences.getContext())) {
                        MyLog.d(TAG, "App is on Background so stop this Service");
                        stopSelf();
                    }
                }
            }
        }
    }

    /**
     * Notify user of the commands Queue size
     * 
     * @return total size of Queues
     */
    private int notifyOfQueue(boolean clearNotification) {
        int count = mRetryQueue.size() + mCommands.size();
        NotificationManager nM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
        if (count == 0 || clearNotification) {
            // Clear notification
            nM.cancel(CommandEnum.NOTIFY_QUEUE.ordinal());
        } else if (mNotificationsEnabled) {
            if (mRetryQueue.size() > 0) {
                MyLog.d(TAG, mRetryQueue.size() + " commands in Retry Queue.");
            }
            if (mCommands.size() > 0) {
                MyLog.d(TAG, mCommands.size() + " commands in Main Queue.");
            }

            // Set up the notification to display to the user
            Notification notification = new Notification(R.drawable.notification_icon,
                    (String) getText(R.string.notification_title), System.currentTimeMillis());

            int messageTitle;
            String aMessage = "";

            aMessage = I18n.formatQuantityMessage(getApplicationContext(), R.string.notification_queue_format,
                    count, R.array.notification_queue_patterns, R.array.notification_queue_formats);
            messageTitle = R.string.notification_title_queue;

            // Set up the scrolling message of the notification
            notification.tickerText = aMessage;

            /**
             * Set the latest event information and send the notification
             * Actually don't start any intent
             * 
             * @see http
             *      ://stackoverflow.com/questions/4232006/android-notification
             *      -pendingintent-problem
             */
            // PendingIntent pi = PendingIntent.getActivity(this, 0, null, 0);

            /**
             * Kick the commands queue by sending empty command
             */
            PendingIntent pi = PendingIntent.getBroadcast(this, 0, new CommandData(CommandEnum.EMPTY).toIntent(),
                    0);

            notification.setLatestEventInfo(this, getText(messageTitle), aMessage, pi);
            nM.notify(CommandEnum.NOTIFY_QUEUE.ordinal(), notification);
        }
        return count;
    }

    /**
     * Command executor
     * 
     * @author yvolk
     */
    private class CommandExecutor extends AsyncTask<Void, Void, JSONObject> {
        // private boolean skip = false;
        private final String SERVICE_NOT_RESTORED_TEXT = "AndTweetService state is not restored";

        @Override
        protected void onPreExecute() {

        }

        @Override
        protected JSONObject doInBackground(Void... arg0) {
            JSONObject jso = null;

            int what = 0;
            String message = "";
            MyLog.d(TAG, "CommandExecutor, " + mCommands.size() + " commands to process");

            do {
                boolean ok = false;
                CommandData commandData = null;
                synchronized (AndTweetService.this) {
                    if (mStateRestored) {
                        // Get commands from the Queue one by one and execute them
                        // The queue is Blocking, so we can do this
                        commandData = mCommands.poll();
                    }
                }
                if (commandData == null) {
                    // All work is done
                    break;
                }

                commandData.retriesLeft -= 1;
                boolean retry = false;
                MyLog.d(TAG, "Executing " + commandData);

                switch (commandData.command) {
                case AUTOMATIC_UPDATE:
                    MyLog.d(TAG, "Getting tweets, replies and messages");
                    ok = loadTimeline(true, true);
                    break;
                case FETCH_TIMELINE:
                    MyLog.d(TAG, "Getting tweets and replies");
                    ok = loadTimeline(true, false);
                    break;
                case FETCH_MESSAGES:
                    MyLog.d(TAG, "Getting messages");
                    ok = loadTimeline(false, true);
                    break;
                case CREATE_FAVORITE:
                case DESTROY_FAVORITE:
                    ok = createOrDestroyFavorite(commandData.command == CommandEnum.CREATE_FAVORITE,
                            commandData.itemId);
                    // Retry in a case of an error
                    retry = !ok;
                    break;
                case UPDATE_STATUS:
                    String status = commandData.bundle.getString(EXTRA_STATUS).trim();
                    long inReplyToId = commandData.bundle.getLong(EXTRA_INREPLYTOID);
                    ok = updateStatus(status, inReplyToId);
                    retry = !ok;
                    break;
                case DESTROY_STATUS:
                    ok = destroyStatus(commandData.itemId);
                    // Retry in a case of an error
                    retry = !ok;
                    break;
                case RATE_LIMIT_STATUS:
                    ok = rateLimitStatus();
                    break;
                default:
                    Log.e(TAG, "Unexpected command here " + commandData);
                }
                MyLog.d(TAG, (ok ? "Succeeded" : "Failed") + " " + commandData);

                if (retry) {
                    boolean ok2 = true;
                    if (commandData.retriesLeft < 0) {
                        // This means that retriesLeft was not set yet,
                        // so let's set it to some default value, the same for
                        // any command
                        // that needs to be retried...
                        commandData.retriesLeft = 9;
                    }
                    // Check if any retries left (actually 0 means this was the
                    // last retry)
                    if (commandData.retriesLeft > 0) {
                        synchronized (AndTweetService.this) {
                            if (mStateRestored) {
                                // Put the command to the retry queue
                                if (!mRetryQueue.contains(commandData)) {
                                    if (!mRetryQueue.offer(commandData)) {
                                        Log.e(TAG, "mRetryQueue is full?");
                                    }
                                }
                            } else {
                                Log.e(TAG, SERVICE_NOT_RESTORED_TEXT);
                                ok2 = false;
                            }
                        }
                    } else {
                        ok2 = false;
                    }
                    if (!ok2) {
                        Log.e(TAG, "Couldn't execute " + commandData);
                    }
                }
                if (!ok && !isOnline()) {
                    // Don't bother with other commands if we're not Online :-)
                    break;
                }
            } while (true);

            try {
                jso = new JSONObject();
                jso.put("what", what);
                jso.put("message", message);
            } catch (JSONException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return jso;
        }

        /**
         * TODO: Delete unnecessary lines... This is in the UI thread, so we can
         * mess with the UI
         * 
         * @return ok
         */
        protected void onPostExecute(JSONObject jso) {
            // boolean succeeded = false;
            String message = null;
            if (jso != null) {
                try {
                    int what = jso.getInt("what");
                    message = jso.getString("message");

                    switch (what) {
                    case 0:

                        // succeeded = true;
                        break;
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
            synchronized (AndTweetService.this) {
                if (mStateRestored) {
                    startEndStuff(false, this, message);
                }
            }
        }

        /**
         * @param create true - create, false - destroy
         * @param statusId
         * @return boolean ok
         */
        private boolean createOrDestroyFavorite(boolean create, long statusId) {
            boolean ok = false;
            JSONObject result = new JSONObject();
            try {
                if (create) {
                    result = TwitterUser.getTwitterUser().getConnection().createFavorite(statusId);
                } else {
                    result = TwitterUser.getTwitterUser().getConnection().destroyFavorite(statusId);
                }
                ok = (result != null);
            } catch (ConnectionException e) {
                Log.e(TAG, (create ? "create" : "destroy") + "Favorite Connection Exception: " + e.toString());
            }
            if (ok) {
                synchronized (AndTweetService.this) {
                    if (mStateRestored) {
                        try {
                            boolean favorited = result.getBoolean("favorited");
                            if (favorited != create) {
                                /** 
                                 * yvolk: 2011-09-27 Twitter docs state that this may happen
                                 *  due to asynchronous nature of the process,
                                 *  see https://dev.twitter.com/docs/api/1/post/favorites/create/%3Aid
                                 */
                                if (create) {
                                    // For the case we created favorite, let's change
                                    // the flag manually.
                                    result.put("favorited", create);

                                    MyLog.d(TAG, (create ? "create" : "destroy")
                                            + ". Favorited flag didn't change yet.");

                                    // Let's try to assume that everything was Ok:
                                    ok = true;
                                } else {
                                    // yvolk: 2011-09-27 Sometimes this twitter.com 'async' process doesn't work
                                    // so let's try another time...
                                    // This is safe, because "delete favorite" works
                                    // even for "Unfavorited" tweet :-)
                                    ok = false;

                                    Log.e(TAG, (create ? "create" : "destroy")
                                            + ". Favorited flag didn't change yet.");
                                }
                            }
                        } catch (JSONException e) {
                            Log.e(TAG, (create ? "create" : "destroy") + ". Checking resulted favorited flag: "
                                    + e.toString());
                        }

                        if (ok) {
                            try {
                                Uri uri = ContentUris.withAppendedId(Tweets.CONTENT_URI, result.getLong("id"));
                                Cursor c = getContentResolver().query(uri,
                                        new String[] { Tweets._ID, Tweets.AUTHOR_ID, Tweets.TWEET_TYPE }, null,
                                        null, null);
                                try {
                                    c.moveToFirst();
                                    FriendTimeline fl = new FriendTimeline(
                                            AndTweetService.this.getApplicationContext(),
                                            c.getInt(c.getColumnIndex(Tweets.TWEET_TYPE)));
                                    fl.insertFromJSONObject(result, true);
                                } catch (Exception e) {
                                    Log.e(TAG, "e: " + e.toString());
                                } finally {
                                    if (c != null && !c.isClosed())
                                        c.close();
                                }
                            } catch (JSONException e) {
                                Log.e(TAG,
                                        "Error marking as " + (create ? "" : "not ") + "favorite: " + e.toString());
                            }
                        }
                    } else {
                        Log.e(TAG, (create ? "create" : "destroy") + "Favorite - " + SERVICE_NOT_RESTORED_TEXT);
                    }
                }

            }

            // TODO: Maybe we need to notify the caller about the result?!

            MyLog.d(TAG, (create ? "Creating" : "Destroying") + " favorite " + (ok ? "succeded" : "failed")
                    + ", id=" + statusId);
            return ok;
        }

        /**
         * @param statusId
         * @return boolean ok
         */
        private boolean destroyStatus(long statusId) {
            boolean ok = false;
            JSONObject result = new JSONObject();
            try {
                result = TwitterUser.getTwitterUser().getConnection().destroyStatus(statusId);
                ok = (result != null);
            } catch (ConnectionException e) {
                if (e.getStatusCode() == 404) {
                    // This means that there is no such "Status", so we may assume that it's Ok!
                    ok = true;
                } else {
                    Log.e(TAG, "destroyStatus Connection Exception: " + e.toString());
                }
            }

            if (ok) {
                synchronized (AndTweetService.this) {
                    if (mStateRestored) {
                        // And delete the status from the local storage
                        try {
                            FriendTimeline fl = new FriendTimeline(AndTweetService.this.getApplicationContext(),
                                    AndTweetDatabase.Tweets.TIMELINE_TYPE_FRIENDS);
                            fl.destroyStatus(statusId);
                        } catch (Exception e) {
                            Log.e(TAG, "Error destroying status locally: " + e.toString());
                        }
                    } else {
                        Log.e(TAG, "destroyStatus - " + SERVICE_NOT_RESTORED_TEXT);
                    }
                }
            }

            // TODO: Maybe we need to notify the caller about the result?!

            MyLog.d(TAG, "Destroying status " + (ok ? "succeded" : "failed") + ", id=" + statusId);
            return ok;
        }

        /**
         * @param status
         * @param inReplyToId
         * @return ok
         */
        private boolean updateStatus(String status, long inReplyToId) {
            boolean ok = false;
            JSONObject result = new JSONObject();
            try {
                result = TwitterUser.getTwitterUser().getConnection().updateStatus(status.trim(), inReplyToId);
                ok = (result != null);
            } catch (ConnectionException e) {
                Log.e(TAG, "updateStatus Exception: " + e.toString());
            }
            if (ok) {
                synchronized (AndTweetService.this) {
                    if (mStateRestored) {
                        try {
                            // The tweet was sent successfully
                            FriendTimeline fl = new FriendTimeline(AndTweetService.this.getApplicationContext(),
                                    AndTweetDatabase.Tweets.TIMELINE_TYPE_FRIENDS);

                            fl.insertFromJSONObject(result, true);
                        } catch (JSONException e) {
                            Log.e(TAG, "updateStatus JSONException: " + e.toString());
                        }
                    } else {
                        Log.e(TAG, "updateStatus - " + SERVICE_NOT_RESTORED_TEXT);
                    }
                }
            }
            return ok;
        }

        /**
         * @param loadTweets - Should we load tweets
         * @param loadMessages - Should we load messages
         * @return ok
         */
        public boolean loadTimeline(boolean loadTweets, boolean loadMessages) {

            // TODO: Cycle for all users...
            boolean ok = false;
            int aNewTweets = 0;
            int aReplyCount = 0;
            int aNewMessages = 0;
            String descr = "(starting)";

            if (TwitterUser.getTwitterUser().getCredentialsVerified() == CredentialsVerified.SUCCEEDED) {
                // Only if User was authenticated already
                try {
                    FriendTimeline fl = null;
                    ok = true;
                    if (ok && loadTweets) {
                        descr = "loading Mentions";
                        fl = new FriendTimeline(AndTweetService.this.getApplicationContext(),
                                AndTweetDatabase.Tweets.TIMELINE_TYPE_MENTIONS);
                        ok = fl.loadTimeline();
                        aReplyCount = fl.replyCount();
                        if (ok) {
                            synchronized (AndTweetService.this) {
                                if (!mStateRestored) {
                                    Log.i(TAG, descr + " - " + SERVICE_NOT_RESTORED_TEXT);
                                    ok = false;
                                }
                            }
                        }

                        if (ok) {
                            descr = "loading Friends";
                            fl = new FriendTimeline(AndTweetService.this.getApplicationContext(),
                                    AndTweetDatabase.Tweets.TIMELINE_TYPE_FRIENDS);
                            ok = fl.loadTimeline();
                            aNewTweets = fl.newCount();
                            aReplyCount += fl.replyCount();
                        }
                        if (ok) {
                            synchronized (AndTweetService.this) {
                                if (mStateRestored) {
                                    fl.pruneOldRecords();
                                } else {
                                    Log.i(TAG, descr + " - " + SERVICE_NOT_RESTORED_TEXT);
                                    ok = false;
                                }
                            }
                        }
                    }

                    if (ok && loadMessages) {
                        descr = "loading Messages";
                        fl = new FriendTimeline(AndTweetService.this.getApplicationContext(),
                                AndTweetDatabase.Tweets.TIMELINE_TYPE_MESSAGES);
                        ok = fl.loadTimeline();
                        aNewMessages = fl.newCount();
                        if (ok) {
                            synchronized (AndTweetService.this) {
                                if (mStateRestored) {
                                    fl.pruneOldRecords();
                                } else {
                                    Log.i(TAG, descr + " - " + SERVICE_NOT_RESTORED_TEXT);
                                    ok = false;
                                }
                            }
                        }
                    }
                } catch (ConnectionException e) {
                    Log.e(TAG, descr + ", Connection Exception: " + e.toString());
                } catch (SQLiteConstraintException e) {
                    Log.e(TAG, descr + ", SQLite Exception: " + e.toString());
                }
            }

            if (ok) {
                descr = "notifying";
                synchronized (AndTweetService.this) {
                    if (mStateRestored) {
                        notifyOfUpdatedTimeline(aNewTweets, aReplyCount, aNewMessages);
                    } else {
                        Log.i(TAG, descr + " - " + SERVICE_NOT_RESTORED_TEXT);
                        ok = false;
                    }
                }
            }

            String message = (ok ? "Succeeded" : "Failed") + " getting ";
            if (loadTweets) {
                message += aNewTweets + " tweets, " + aReplyCount + " replies";
            }
            if (loadMessages) {
                if (loadTweets) {
                    message += " and ";
                }
                message += aNewMessages + " messages";
            }
            MyLog.d(TAG, message);

            return ok;
        }

        private void notifyOfUpdatedTimeline(int tweetsChanged, int repliesChanged, int messagesChanged) {

            // TODO: It's not so simple... I think :-)
            int N = mBroadcastListenerCount;

            for (int i = 0; i < N; i++) {
                try {
                    MyLog.d(TAG, "finishUpdateTimeline, Notifying callback no. " + i);
                    IAndTweetServiceCallback cb = mCallbacks.getBroadcastItem(i);
                    if (cb != null) {
                        if (tweetsChanged > 0) {
                            cb.tweetsChanged(tweetsChanged);
                        }
                        if (repliesChanged > 0) {
                            cb.repliesChanged(repliesChanged);
                        }
                        if (messagesChanged > 0) {
                            cb.messagesChanged(messagesChanged);
                        }
                        cb.dataLoading(0);
                    }
                } catch (RemoteException e) {
                    Log.e(TAG, e.toString());
                }
            }
            boolean notified = false;
            if (repliesChanged > 0) {
                notifyOfNewTweets(repliesChanged, CommandEnum.NOTIFY_REPLIES);
                notified = true;
            }
            if (messagesChanged > 0) {
                notifyOfNewTweets(messagesChanged, CommandEnum.NOTIFY_DIRECT_MESSAGE);
                notified = true;
            }
            if (tweetsChanged > 0 || !notified) {
                notifyOfNewTweets(tweetsChanged, CommandEnum.NOTIFY_TIMELINE);
                notified = true;
            }
        }

        /**
         * Notify the user of new tweets.
         * 
         * @param numTweets
         */
        private void notifyOfNewTweets(int numTweets, CommandEnum msgType) {
            MyLog.d(TAG, "notifyOfNewTweets n=" + numTweets + "; msgType=" + msgType);

            if (updateWidgetsOnEveryUpdate) {
                // Notify widgets even about the fact, that update occurred
                // even if there was nothing new
                updateWidgets(numTweets, msgType);
            }

            // If no notifications are enabled, return
            if (!mNotificationsEnabled || numTweets == 0) {
                return;
            }

            boolean notificationsMessages = false;
            boolean notificationsReplies = false;
            boolean notificationsTimeline = false;
            String ringtone = null;
            SharedPreferences sp = getSp();
            synchronized (sp) {
                notificationsMessages = sp.getBoolean("notifications_messages", false);
                notificationsReplies = sp.getBoolean("notifications_mentions", false);
                notificationsTimeline = sp.getBoolean("notifications_timeline", false);
                ringtone = sp.getString(MyPreferences.KEY_RINGTONE_PREFERENCE, null);
            }
            sp = null;

            // Make sure that notifications haven't been turned off for the
            // message
            // type
            switch (msgType) {
            case NOTIFY_REPLIES:
                if (!notificationsReplies)
                    return;
                break;
            case NOTIFY_DIRECT_MESSAGE:
                if (!notificationsMessages)
                    return;
                break;
            case NOTIFY_TIMELINE:
                if (!notificationsTimeline)
                    return;
                break;
            }

            // Set up the notification to display to the user
            Notification notification = new Notification(R.drawable.notification_icon,
                    (String) getText(R.string.notification_title), System.currentTimeMillis());

            notification.vibrate = null;
            if (mNotificationsVibrate) {
                notification.vibrate = new long[] { 200, 300, 200, 300 };
            }

            notification.flags = Notification.FLAG_SHOW_LIGHTS | Notification.FLAG_AUTO_CANCEL;
            notification.ledOffMS = 1000;
            notification.ledOnMS = 500;
            notification.ledARGB = Color.GREEN;

            if ("".equals(ringtone) || ringtone == null) {
                notification.sound = null;
            } else {
                Uri ringtoneUri = Uri.parse(ringtone);
                notification.sound = ringtoneUri;
            }

            // Set up the pending intent
            PendingIntent contentIntent;

            int messageTitle;
            Intent intent;
            String aMessage = "";

            // Prepare "intent" to launch timeline activities exactly like in
            // com.xorcode.andtweet.TimelineActivity.onOptionsItemSelected
            switch (msgType) {
            case NOTIFY_REPLIES:
                aMessage = I18n.formatQuantityMessage(getApplicationContext(),
                        R.string.notification_new_mention_format, numTweets, R.array.notification_mention_patterns,
                        R.array.notification_mention_formats);
                messageTitle = R.string.notification_title_mentions;
                intent = new Intent(getApplicationContext(), TweetListActivity.class);
                intent.putExtra(AndTweetService.EXTRA_TIMELINE_TYPE,
                        AndTweetDatabase.Tweets.TIMELINE_TYPE_MENTIONS);
                contentIntent = PendingIntent.getActivity(getApplicationContext(), numTweets, intent, 0);
                break;

            case NOTIFY_DIRECT_MESSAGE:
                aMessage = I18n.formatQuantityMessage(getApplicationContext(),
                        R.string.notification_new_message_format, numTweets, R.array.notification_message_patterns,
                        R.array.notification_message_formats);
                messageTitle = R.string.notification_title_messages;
                intent = new Intent(getApplicationContext(), MessageListActivity.class);
                intent.putExtra(AndTweetService.EXTRA_TIMELINE_TYPE,
                        AndTweetDatabase.Tweets.TIMELINE_TYPE_MESSAGES);
                contentIntent = PendingIntent.getActivity(getApplicationContext(), numTweets, intent, 0);
                break;

            case NOTIFY_TIMELINE:
            default:
                aMessage = I18n.formatQuantityMessage(getApplicationContext(),
                        R.string.notification_new_tweet_format, numTweets, R.array.notification_tweet_patterns,
                        R.array.notification_tweet_formats);
                messageTitle = R.string.notification_title;
                intent = new Intent(getApplicationContext(), TweetListActivity.class);
                intent.putExtra(AndTweetService.EXTRA_TIMELINE_TYPE, AndTweetDatabase.Tweets.TIMELINE_TYPE_FRIENDS);
                contentIntent = PendingIntent.getActivity(getApplicationContext(), numTweets, intent, 0);
                break;
            }

            // Set up the scrolling message of the notification
            notification.tickerText = aMessage;

            // Set the latest event information and send the notification
            notification.setLatestEventInfo(AndTweetService.this, getText(messageTitle), aMessage, contentIntent);
            NotificationManager nM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
            nM.notify(msgType.ordinal(), notification);
        }

        /**
         * Send Update intent to AndTweet Widget(s), if there are some
         * installed... (e.g. on the Home screen...)
         * 
         * @see AndTweetAppWidgetProvider
         */
        private void updateWidgets(int numTweets, CommandEnum msgType) {
            Intent intent = new Intent(ACTION_APPWIDGET_UPDATE);
            intent.putExtra(EXTRA_MSGTYPE, msgType.save());
            intent.putExtra(EXTRA_NUMTWEETS, numTweets);
            sendBroadcast(intent);
        }

        /**
         * Ask the the Twitter service of how many more requests are allowed:
         * number of remaining API calls.
         * 
         * @return ok
         */
        private boolean rateLimitStatus() {
            boolean ok = false;
            JSONObject result = new JSONObject();
            try {
                result = TwitterUser.getTwitterUser().getConnection().rateLimitStatus();
                ok = (result != null);
            } catch (ConnectionException e) {
                Log.e(TAG, "rateLimitStatus Exception: " + e.toString());
            }

            if (ok) {
                synchronized (AndTweetService.this) {
                    if (mStateRestored) {
                        for (int i = 0; i < mBroadcastListenerCount; i++) {
                            try {
                                IAndTweetServiceCallback cb = mCallbacks.getBroadcastItem(i);
                                if (cb != null) {
                                    cb.rateLimitStatus(result.getInt("remaining_hits"),
                                            result.getInt("hourly_limit"));
                                }
                            } catch (RemoteException e) {
                                MyLog.d(TAG, e.toString());
                            } catch (JSONException e) {
                                Log.e(TAG, e.toString());
                            }
                        }
                    } else {
                        Log.e(TAG, "rateLimitStatus - " + SERVICE_NOT_RESTORED_TEXT);
                    }
                }
            }
            return ok;
        }
    }

    private PowerManager.WakeLock getWakeLock() {
        PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
        PowerManager.WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        wakeLock.acquire();
        return wakeLock;
    }

    /**
     * Returns the number of milliseconds between two fetch actions.
     * 
     * @return the number of milliseconds
     */
    private int getFetchFrequencyS() {
        int frequencyS = Integer.parseInt(getSp().getString("fetch_frequency", "180"));
        return (frequencyS * MILLISECONDS);
    }

    /**
     * Starts the repeating Alarm that sends the fetch Intent.
     */
    private boolean scheduleRepeatingAlarm() {
        final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        final PendingIntent pIntent = getRepeatingIntent();
        final int frequencyMs = getFetchFrequencyS();
        final long firstTime = SystemClock.elapsedRealtime() + frequencyMs;
        am.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, firstTime, frequencyMs, pIntent);
        MyLog.d(TAG, "Started repeating alarm in a " + frequencyMs + "ms rhythm.");
        return true;
    }

    /**
     * Cancels the repeating Alarm that sends the fetch Intent.
     */
    private boolean cancelRepeatingAlarm() {
        final AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        final PendingIntent pIntent = getRepeatingIntent();
        am.cancel(pIntent);
        MyLog.d(TAG, "Cancelled repeating alarm.");
        return true;
    }

    /**
     * Returns Intent to be send with Repeating Alarm.
     * This alarm will be received by {@link AndTweetServiceManager}
     * @return the Intent
     */
    private PendingIntent getRepeatingIntent() {
        Intent intent = new Intent(ACTION_ALARM);
        intent.putExtra(AndTweetService.EXTRA_MSGTYPE, CommandEnum.AUTOMATIC_UPDATE.save());
        PendingIntent pIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
        return pIntent;
    }

    /**
     * The IAndTweetService is defined through IDL
     */
    private final IAndTweetService.Stub mBinder = new IAndTweetService.Stub() {
        public void registerCallback(IAndTweetServiceCallback cb) {
            if (cb != null)
                mCallbacks.register(cb);
        }

        public void unregisterCallback(IAndTweetServiceCallback cb) {
            if (cb != null)
                mCallbacks.unregister(cb);
        }
    };

    /**
     * We use this function before actual requests of Internet services Based on
     * http
     * ://stackoverflow.com/questions/1560788/how-to-check-internet-access-on
     * -android-inetaddress-never-timeouts
     */
    public boolean isOnline() {
        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        // test for connection
        if (cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isAvailable()
                && cm.getActiveNetworkInfo().isConnected()) {
            return true;
        } else {
            MyLog.v(TAG, "Internet Connection Not Present");
            return false;
        }
    }
}