Project: andtweet
/* 
 * 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.AndTweetPreferences; 
import com.xorcode.andtweet.data.FriendTimeline; 
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; 
 
/**
 * 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); 
        } 
 
        /**
         * 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()); 
        } 
    } 
 
    /**
     * 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; 
 
    /**
     * Commands queue to be processed by the Service 
     */
 
    private BlockingQueue<CommandData> mCommands = new ArrayBlockingQueue<CommandData>(100true); 
 
    /**
     * Retry Commands queue 
     */
 
    private BlockingQueue<CommandData> mRetryQueue = new ArrayBlockingQueue<CommandData>(100true); 
 
    /**
     * 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 AndTweetPreferences.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 AndTweetPreferences.getSharedPreferences(TAG, MODE_PRIVATE); 
    } 
     
    @Override 
    public void onCreate() { 
        AndTweetPreferences.initialize(thisthis); 
        preferencesChangeTime = AndTweetPreferences.getDefaultSharedPreferences().getLong(PreferencesActivity.KEY_PREFERENCES_CHANGE_TIME, 0); 
        preferencesExamineTime = getServiceSp().getLong(PreferencesActivity.KEY_PREFERENCES_EXAMINE_TIME, 0); 
        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) { 
            v(TAG, "onReceive " + intent.toString()); 
            receiveCommand(intent); 
        } 
 
    }; 
 
    @Override 
    public void onDestroy() { 
        sendBroadcast(new Intent(ACTION_SERVICE_STOPPED)); 
         
        // Unregister all callbacks. 
        mCallbacks.kill(); 
 
        unregisterReceiver(intentReceiver); 
 
        // Clear notifications if any 
        int count = notifyOfQueue(true); 
         
        AndTweetPreferences.forget(); 
 
        d(TAG, "Service destroyed" + (count>0 ? ", " + count + " msg in the Queue" : "")); 
    } 
 
    @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); 
        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 = AndTweetPreferences.getDefaultSharedPreferences().getLong( 
                PreferencesActivity.KEY_PREFERENCES_CHANGE_TIME, 0); 
        if (preferencesChangeTime != preferencesChangeTimeNew 
                || preferencesExamineTime < preferencesChangeTimeNew) { 
            examinePreferences(); 
        } 
         
        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)) { 
                d(TAG, "Duplicated " + commandData); 
            } else { 
                d(TAG, "Adding to the queue " + commandData); 
                if (!mCommands.offer(commandData)) { 
                    Log.e(TAG, "mCommands is full?"); 
                } 
            } 
        } 
 
        // Start Executor if necessary 
        startEndStuff(truenullnull); 
    } 
 
    /**
     * @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); 
                    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); 
                    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); 
                    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) { 
                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 = AndTweetPreferences.getDefaultSharedPreferences().getLong(PreferencesActivity.KEY_PREFERENCES_CHANGE_TIME, 0); 
        long preferencesExamineTimeNew = java.lang.System.currentTimeMillis(); 
         
        if (preferencesChangeTimeNew > preferencesExamineTime) { 
            d(TAG, "Examine at=" + preferencesExamineTimeNew + " Preferences changed at=" + preferencesChangeTimeNew); 
        } else if (preferencesChangeTimeNew > preferencesChangeTime) { 
            d(TAG, "Preferences changed at=" + preferencesChangeTimeNew); 
        } else if (preferencesChangeTimeNew == preferencesChangeTime) { 
            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(PreferencesActivity.KEY_PREFERENCES_EXAMINE_TIME, preferencesExamineTime).commit(); 
 
        // Forget and reload preferences... 
        AndTweetPreferences.forget(); 
        AndTweetPreferences.initialize(thisthis); 
 
        // Stop existing alarm in any case 
        ok = cancelRepeatingAlarm(); 
 
        SharedPreferences sp = AndTweetPreferences.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) { 
                            d(TAG, logMsg); 
                        } 
                        mExecutors.add(executor); 
                        if (mExecutors.size() == 1) { 
                            mWakeLock = getWakeLock(); 
                            mBroadcastListenerCount = mCallbacks.beginBroadcast(); 
                            d(TAG, "No other threads running so starting new broadcast for " 
                                    + mBroadcastListenerCount + " listeners"); 
                        } 
                        executor.execute(); 
                    } 
                } else { 
                    notifyOfQueue(false); 
                } 
            } 
        } else { 
            // Stop 
            if (logMsg != null) { 
                d(TAG, logMsg); 
            } 
            mExecutors.remove(executorIn); 
            if (mExecutors.size() == 0) { 
                d(TAG, "Ending last thread so also ending broadcast."); 
                mWakeLock.release(); 
                mCallbacks.finishBroadcast(); 
                if (notifyOfQueue(false) == 0) { 
                    if (! ForegroundCheckTask.isAppOnForeground(AndTweetPreferences.getContext())) { 
                        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) { 
                d(TAG, mRetryQueue.size() + " commands in Retry Queue."); 
            } 
            if (mCommands.size() > 0) { 
                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(this0new 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; 
 
        @Override 
        protected void onPreExecute() { 
 
        } 
 
        @Override 
        protected JSONObject doInBackground(Void... arg0) { 
            JSONObject jso = null
 
            int what = 0
            String message = ""
            d(TAG, "CommandExecutor, " + mCommands.size() + " commands to process"); 
 
            do { 
                boolean ok = false
                // Get commands from the Queue one by one and execute them 
                // The queue is Blocking, so we can do this 
                CommandData commandData = mCommands.poll(); 
                if (commandData == null) { 
                    // All work is done 
                    break
                } 
 
                commandData.retriesLeft -= 1
                boolean retry = false
                d(TAG, "Executing " + commandData); 
 
                switch (commandData.command) { 
                    case AUTOMATIC_UPDATE: 
                        d(TAG, "Getting tweets, replies and messages"); 
                        ok = loadTimeline(truetrue); 
                        break
                    case FETCH_TIMELINE: 
                        d(TAG, "Getting tweets and replies"); 
                        ok = loadTimeline(truefalse); 
                        break
                    case FETCH_MESSAGES: 
                        d(TAG, "Getting messages"); 
                        ok = loadTimeline(falsetrue); 
                        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); 
                } 
                d(TAG, (ok ? "Succeeded" : "Failed") + " " + commandData); 
                if (retry) { 
                    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) { 
                        // 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, "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(); 
                } 
            } 
            startEndStuff(falsethis, 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) { 
                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 
                    }, nullnullnull); 
                    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()); 
                } 
            } 
 
            // TODO: Maybe we need to notify the caller about the result?! 
 
            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) { 
                Log.e(TAG, "destroyStatus Connection Exception: " + e.toString()); 
            } 
 
            if (ok) { 
                // 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()); 
                } 
            } 
 
            // TODO: Maybe we need to notify the caller about the result?! 
 
            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) { 
                // The tweet was sent successfully 
                FriendTimeline fl = new FriendTimeline( 
                        AndTweetService.this.getApplicationContext(), 
                        AndTweetDatabase.Tweets.TIMELINE_TYPE_FRIENDS); 
                try { 
                    fl.insertFromJSONObject(result, true); 
                } catch (JSONException e) { 
                    Log.e(TAG, "updateStatus JSONException: " + e.toString()); 
                } 
            } 
            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
 
            if (TwitterUser.getTwitterUser().getCredentialsVerified() == CredentialsVerified.SUCCEEDED) { 
                // Only if User was authenticated already 
                String descr = "(starting)"
                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) { 
                            descr = "loading Friends"
                            fl = new FriendTimeline(AndTweetService.this.getApplicationContext(), 
                                    AndTweetDatabase.Tweets.TIMELINE_TYPE_FRIENDS); 
                            ok = fl.loadTimeline(); 
                            aNewTweets = fl.newCount(); 
                            aReplyCount += fl.replyCount(); 
                        } 
 
                        fl.pruneOldRecords(); 
                    } 
 
                    if (ok && loadMessages) { 
                        descr = "loading Messages"
                        fl = new FriendTimeline(AndTweetService.this.getApplicationContext(), 
                                AndTweetDatabase.Tweets.TIMELINE_TYPE_MESSAGES); 
                        ok = fl.loadTimeline(); 
                        aNewMessages = fl.newCount(); 
                        fl.pruneOldRecords(); 
                    } 
                } catch (ConnectionException e) { 
                    Log.e(TAG, descr + ", Connection Exception: " + e.toString()); 
                } catch (SQLiteConstraintException e) { 
                    Log.e(TAG, descr + ", SQLite Exception: " + e.toString()); 
                } 
            } 
 
            if (ok) { 
                notifyOfUpdatedTimeline(aNewTweets, aReplyCount, aNewMessages); 
            } 
 
            String message = (ok ? "Succeeded" : "Failed") + " getting "
            if (loadTweets) { 
                message += aNewTweets + " tweets, " + aReplyCount + " replies"
            } 
            if (loadMessages) { 
                if (loadTweets) { 
                    message += " and "
                } 
                message += aNewMessages + " messages"
            } 
            AndTweetService.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 { 
                    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) { 
            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(PreferencesActivity.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[] { 
                        200300200300 
                }; 
            } 
 
            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) { 
                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) { 
                        d(TAG, e.toString()); 
                    } catch (JSONException e) { 
                        Log.e(TAG, e.toString()); 
                    } 
                } 
            } 
            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); 
        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); 
        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(this0, 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 { 
            v(TAG, "Internet Connection Not Present"); 
            return false
        } 
    } 
 
    /**
     * Shortcut for debugging messages of the application 
     */
 
    public static int d(String tag, String msg) { 
        int i = 0
        if (Log.isLoggable(AndTweetService.APPTAG, Log.DEBUG)) { 
            i = Log.d(tag, msg); 
        } 
        return i; 
    } 
 
    /**
     * Shortcut for debugging messages of the application 
     */
 
    public static int d(String tag, String msg, Throwable tr) { 
        int i = 0
        if (Log.isLoggable(AndTweetService.APPTAG, Log.DEBUG)) { 
            i = Log.d(tag, msg, tr); 
        } 
        return i; 
    } 
 
    /**
     * Shortcut for verbose messages of the application 
     */
 
    public static int v(String tag, String msg) { 
        int i = 0
        if (Log.isLoggable(AndTweetService.APPTAG, Log.VERBOSE)) { 
            i = Log.v(tag, msg); 
        } 
        return i; 
    } 
}