com.vodafone.twitter.service.TwitterService.java Source code

Java tutorial

Introduction

Here is the source code for com.vodafone.twitter.service.TwitterService.java

Source

/*
 * Copyright (C) 2011 Vodafone Group Duesseldorf
 *
 * 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.vodafone.twitter.service;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.LinkedList;
import java.util.HashMap;
import java.net.HttpURLConnection;
import java.net.URL;
import java.io.InputStream;
import java.text.SimpleDateFormat;

import android.util.Config;
import android.util.Log;
import android.app.Service;
import android.app.IntentService;
import android.app.PendingIntent;
import android.app.Notification;
import android.app.AlarmManager;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.BroadcastReceiver;
import android.widget.RemoteViews;
import android.text.format.Time;
import android.text.Html;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.wifi.WifiManager;
import android.net.Uri;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Binder;
import android.os.IBinder;
import android.os.BatteryManager;
import android.os.PowerManager;
import android.os.Vibrator;

import twitter4j.*;
import twitter4j.conf.*;
import twitter4j.auth.*;

import org.timur.glticker.EntryTopic;
import org.timur.glticker.TickerServiceAbstract;

public class TwitterService extends TwitterServiceAbstract {

    static final String LOGTAG = "TwitterService";
    static final String PREFS_NAME = "com.vodafone.twitterservice";

    static boolean unpluggedWifiWakeupActivated = false;
    static Boolean wifiEnabled = null;
    static Boolean powerPlugged = null;
    static LinkedList<EntryTopic> messageList = new LinkedList<EntryTopic>();
    static int totalNumberOfQueuedMessages = 0;
    static volatile int numberOfQueuedMessagesSinceLastClientActivity = 0;
    static volatile boolean activityPaused = false;
    static int maxQueueMessages = 40;
    static PowerManager powerManager = null;
    static PowerManager.WakeLock wakeLockNoScreen = null;
    static Vibrator vibrator = null;
    static SharedPreferences preferences = null;
    static PendingIntent pendingIntent = null;
    static AlarmManager alarmManager = null;
    static BroadcastReceiver onWifiChanged = null;
    static BroadcastReceiver onBatteryChanged = null;
    static Intent tickerNewEntryBroadcastIntent = null;
    static volatile ConnectThread connectThread = null;
    static twitter4j.Twitter twitter = null;
    static RequestToken requestToken = null;
    static AccessToken accessToken = null;
    static TwitterStream twitterStream = null;
    static UserStreamListener statusListener = null;
    static volatile String errMsg = null;
    static volatile int totalNumberOfBrodcastsSend = 0;
    static volatile ActivityUpdateThread activityUpdateThread = null;
    static volatile boolean activityUpdatePending = false;
    static boolean fetchHomeTimelineDone = false;
    static boolean linkifyMessages = false;
    //static UserStream userStream = null;
    private String messageLink = null;
    //private String twitterConsumerKey, twitterConsumerSecret;

    @Override
    public void onCreate() {
        super.onCreate();
        if (Config.LOGD)
            Log.i(LOGTAG, "onCreate()");
        powerManager = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
        wakeLockNoScreen = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOGTAG);
        preferences = getSharedPreferences(PREFS_NAME, MODE_WORLD_WRITEABLE);

        // retrieve wifiEnabled state
        ConnectivityManager connectivityManager = (ConnectivityManager) this
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        if (connectivityManager != null) {
            int networkType = 0; // mobile=0, wifi=1
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            if (networkInfo != null) {
                boolean newWifiEnabled = false;
                if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI)
                    newWifiEnabled = true;
                if (wifiEnabled == null || newWifiEnabled != wifiEnabled) {
                    wifiEnabled = new Boolean(newWifiEnabled);
                    if (wifiEnabled) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, "onCreate() WIFI_STATE_ENABLED");
                    } else {
                        if (Config.LOGD)
                            Log.i(LOGTAG, "onCreate() WIFI_STATE_DISABLED");
                    }
                    checkRunningOnUnpluggedWifi();
                }
            }
        }

        // we need event handlers for onWifiChanged and onBatteryChanged
        // so we'll know when we run on battery + wifi, in which case we must manually wake the device
        // as soon as we run on battery + wifi, checkRunningOnUnpluggedWifi() will setup RepeatingAlarmReceiver
        // RepeatingAlarmReceiver will wake the device partially every 8 minutes for a few seconds
        onWifiChanged = new BroadcastReceiver() {
            public void onReceive(Context context, Intent intent) {
                int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, -1);
                switch (state) {
                case WifiManager.WIFI_STATE_DISABLED:
                    if (Config.LOGD)
                        Log.i(LOGTAG, "onWifiChanged WIFI_STATE_DISABLED");
                    wifiEnabled = new Boolean(false);
                    checkRunningOnUnpluggedWifi();
                    break;

                case WifiManager.WIFI_STATE_ENABLED:
                    if (Config.LOGD)
                        Log.i(LOGTAG, "onWifiChanged WIFI_STATE_ENABLED");
                    wifiEnabled = new Boolean(true);
                    checkRunningOnUnpluggedWifi();
                    break;

                case WifiManager.WIFI_STATE_UNKNOWN:
                    if (Config.LOGD)
                        Log.i(LOGTAG, "onWifiChanged WIFI_STATE_UNKNOWN");
                    //wifiEnabled = false;
                    break;
                }
            }
        };

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        intentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
        intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
        registerReceiver(onWifiChanged, intentFilter);

        onBatteryChanged = new BroadcastReceiver() {
            public void onReceive(Context context, Intent intent) {
                int plugged = intent.getIntExtra("plugged", -1);
                boolean newPowerPlugged = false;
                if (plugged == BatteryManager.BATTERY_PLUGGED_AC || plugged == BatteryManager.BATTERY_PLUGGED_USB)
                    newPowerPlugged = true;
                if (powerPlugged == null || newPowerPlugged != powerPlugged) {
                    powerPlugged = new Boolean(newPowerPlugged);
                    //if(Config.LOGD) Log.i(LOGTAG, "onBatteryChanged powerPlugged="+powerPlugged);
                    checkRunningOnUnpluggedWifi();
                }
            }
        };
        registerReceiver(onBatteryChanged, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));

        // sample code: check hasVibrator
        // PackageManager pm = getPackageManager();
        // boolean hasCompass = pm.hasSystemFeature(PackageManager.FEATURE_VIBRATOR_SERVICE);
        // vibrator = (Vibrator)this.getSystemService(Context.VIBRATOR_SERVICE);

        numberOfQueuedMessagesSinceLastClientActivity = 0;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // called by the system every time a client explicitly starts the service by calling startService(Intent),
        connectThread = null;
        numberOfQueuedMessagesSinceLastClientActivity = 0;

        // store consumer key + secret in system properties for twitter4j to use
        System.setProperty("twitter4j.oauth.consumerKey", intent.getStringExtra("consumerKey"));
        System.setProperty("twitter4j.oauth.consumerSecret", intent.getStringExtra("consumerSecret"));
        System.setProperty("streamBaseURL", "https://stream.twitter.com/");
        //twitterConsumerKey=intent.getStringExtra("consumerKey");
        //twitterConsumerSecret=intent.getStringExtra("consumerSecret");

        linkifyMessages = false;
        String linkifySwitch = intent.getStringExtra("linkify");
        if (linkifySwitch != null && linkifySwitch.equals("true"))
            linkifyMessages = true;

        // create a specific BroadcastIntent based on intentFilter string, given via intent, to notify activity of new data
        String tickerNewEntryBroadcastIntentString = intent.getStringExtra("intentFilter");
        tickerNewEntryBroadcastIntent = new Intent(tickerNewEntryBroadcastIntentString);
        if (Config.LOGD)
            Log.i(LOGTAG,
                    String.format(
                            "onStartCommand() flags=%d startId=%d isConnected=%b tickerNewEntryBroadcastIntent=%s",
                            flags, startId, isConnected(), tickerNewEntryBroadcastIntentString));

        (connectThread = new ConnectThread(this)).start();

        return START_NOT_STICKY;
    }

    @Override
    public void onLowMemory() {
        if (Config.LOGD)
            Log.i(LOGTAG, "onLowMemory()");
        synchronized (messageList) {
            while (messageList.size() > maxQueueMessages / 2) {
                messageList.removeLast();
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        if (twitterStream != null) {
            if (Config.LOGD)
                Log.i(LOGTAG, "onDestroy() twitterStream.cleanUp()");
            twitterStream.cleanUp();
            if (Config.LOGD)
                Log.i(LOGTAG, "onDestroy() twitterStream.shutdown()");
            twitterStream.shutdown();
            twitterStream = null;
        }

        if (onWifiChanged != null)
            unregisterReceiver(onWifiChanged);

        if (onBatteryChanged != null)
            unregisterReceiver(onBatteryChanged);
    }

    // public TickerService specific methods

    public class LocalBinder extends TickerServiceAbstract.LocalBinder {
        public TwitterService getService() {
            return TwitterService.this;
        }
    }

    final Binder localBinder = new LocalBinder();

    @Override
    public IBinder onBind(Intent intent) {
        return localBinder;
    }

    public void onPause() {
        activityPaused = true;
    }

    public void onResume() {
        activityPaused = false;
        numberOfQueuedMessagesSinceLastClientActivity = 0;

        if (!isConnected()) {
            if (connectThread == null) {
                if (Config.LOGD)
                    Log.i(LOGTAG, "onResume() not connected - new ConnectThread()...");
                (connectThread = new ConnectThread(this)).start();
            } else {
                if (Config.LOGD)
                    Log.i(LOGTAG,
                            "onResume() not connected - another ConnectThread() is still in process - do nothing");
            }
        }
    }

    public boolean isConnected() {
        if (connectThread != null && twitter != null && twitterStream != null)
            return true;
        return false;
    }

    public boolean isConnecting() { // actually this is isConnectingOrConnected()
        if (connectThread != null)
            return true;
        return false;
    }

    public String getErrMsg() {
        String retStr = errMsg;
        errMsg = null;
        return retStr;
    }

    public synchronized List<EntryTopic> getMessageListLatestAfterMS(long lastNewestMessageMS, int maxCount) {
        List<EntryTopic> retlist = new LinkedList<EntryTopic>();
        int count = 0;
        synchronized (messageList) {
            while (count < maxCount && count < messageList.size()
                    && messageList.get(count).createTimeMs > lastNewestMessageMS)
                count++;
            //retlist = Collections.synchronizedList(messageList.subList(0,count));
            List<EntryTopic> sublist = messageList.subList(0, count);
            int sublistSize = sublist.size();
            for (int i = 0; i < sublistSize; i++)
                retlist.add(sublist.get(i));
        }
        if (Config.LOGD)
            Log.i(LOGTAG, String.format("getMessageListLatestAfterMS(%d) count=%d retlist.size()=%d",
                    lastNewestMessageMS, count, retlist.size()));
        return retlist;
    }

    public synchronized List<EntryTopic> getMessageListLatest(int maxCount) {
        List<EntryTopic> retlist = new LinkedList<EntryTopic>();
        synchronized (messageList) {
            if (maxCount > messageList.size())
                maxCount = messageList.size();
            //retlist = Collections.synchronizedList(messageList.subList(0,maxCount));
            List<EntryTopic> sublist = messageList.subList(0, maxCount);
            int sublistSize = sublist.size();
            for (int i = 0; i < sublistSize; i++)
                retlist.add(sublist.get(i));
        }
        if (Config.LOGD)
            Log.i(LOGTAG, String.format("getMessageListLatest() maxCount=%d retlist.size()=%d", maxCount,
                    retlist.size()));
        return retlist;
    }

    // public TwitterService specific methods

    public Twitter getTwitterObject() {
        return twitter;
    }

    public String linkify(String msgString, String linkName, boolean twitterMode) {
        // parse through 'msgString' and convert any [http://....] to [<a href="http://...">http://...</a>]
        int startOffset = 0;
        int numberOfCharactersTillNextSpace = 0;
        String search = "http://";
        messageLink = null;
        String link2 = null;

        int offset = msgString.indexOf(search);
        while (offset >= 0) {
            // search pattern starts on the beginning of the string or it has a leading whitespace
            messageLink = msgString.substring(offset);
            if (messageLink == null)
                break;
            numberOfCharactersTillNextSpace = messageLink.indexOf(" ");
            //if(Config.LOGD) Log.i(LOGTAG, String.format("linkify() offset=%d link=%s numberOfCharactersTillNextSpace=%d",offset,messageLink,numberOfCharactersTillNextSpace));
            if (numberOfCharactersTillNextSpace > 0)
                messageLink = messageLink.substring(0, numberOfCharactersTillNextSpace);
            else
                numberOfCharactersTillNextSpace = messageLink.length();

            int idxTwitpic = messageLink.indexOf("//twitpic.com/");
            if (idxTwitpic >= 0) {
                String id = messageLink.substring(idxTwitpic + 14);
                String markup = "<img width='300' height='300' style='margin-left:auto;margin-right:auto;display:block;' onload='window.onImageLoad(this)' src='http://twitpic.com/show/thumb/"
                        + id + "' />"; // oiginal size = 150x150
                //Log.i(LOGTAG,"linkify() twitpic markup="+markup);
                msgString = msgString.substring(0, offset) + markup;
                startOffset = offset + markup.length();
            } else {
                String displayLink = messageLink;
                if (linkName != null)
                    displayLink = linkName;

                // todo: java.lang.StringIndexOutOfBoundsException:
                String markup2 = "<a href=\"" + messageLink + "\">" + displayLink + "</a> ";
                if (offset + numberOfCharactersTillNextSpace < msgString.length())
                    markup2 += msgString.substring(offset + numberOfCharactersTillNextSpace);
                msgString = msgString.substring(0, offset) + markup2;
                startOffset = offset + markup2.length();
            }

            int nextOffset = msgString.substring(startOffset).indexOf(search); // todo: java.lang.StringIndexOutOfBoundsException
            if (nextOffset < 0)
                break;
            offset = startOffset + nextOffset;
        }

        search = "https://";
        offset = msgString.indexOf(search);
        while (offset >= 0) {
            // search pattern starts on the beginning of the string or it has a leading whitespace
            messageLink = msgString.substring(offset);
            numberOfCharactersTillNextSpace = messageLink.indexOf(" ");
            if (numberOfCharactersTillNextSpace > 0)
                messageLink = messageLink.substring(0, numberOfCharactersTillNextSpace);
            else
                numberOfCharactersTillNextSpace = messageLink.length();

            String displayLink = messageLink;
            if (linkName != null)
                displayLink = linkName;
            msgString = msgString.substring(0, offset) + "<a href=\"" + messageLink + "\">" + displayLink + "</a> "
                    + msgString.substring(offset + numberOfCharactersTillNextSpace);
            startOffset = offset + 9 + numberOfCharactersTillNextSpace + 2 + displayLink.length() + 5;

            //Log.i(LOGTAG,"http2link msgString="+msgString+" length="+msgString.length()+" startOffset="+startOffset+" search="+search);
            int nextOffset = msgString.substring(startOffset).indexOf(search);
            //Log.i(LOGTAG,"http2link numberOfCharactersTillNextSpace="+numberOfCharactersTillNextSpace+" offset="+offset+" startOffset="+startOffset+" nextOffset="+nextOffset+" link="+messageLink);
            if (nextOffset < 0)
                break;
            offset = startOffset + nextOffset;
        }

        if (twitterMode) {
            // linkify "#...."
            search = "#";
            offset = msgString.indexOf(search);
            while (offset >= 0) {
                link2 = msgString.substring(offset);
                numberOfCharactersTillNextSpace = link2.indexOf(" ");
                if (numberOfCharactersTillNextSpace > 0)
                    link2 = link2.substring(0, numberOfCharactersTillNextSpace);
                else
                    numberOfCharactersTillNextSpace = link2.length();

                msgString = msgString.substring(0, offset) + "<a href=\"http://twitter.com/search?q=%23" + link2
                        + "\">" + link2 + "</a> " + msgString.substring(offset + numberOfCharactersTillNextSpace);
                startOffset = offset + 40 + numberOfCharactersTillNextSpace + 2 + numberOfCharactersTillNextSpace
                        + 5;

                int nextOffset = msgString.substring(startOffset).indexOf(search);
                //Log.i(LOGTAG,"http2link # numberOfCharactersTillNextSpace="+numberOfCharactersTillNextSpace+" offset="+offset+" startOffset="+startOffset+" nextOffset="+nextOffset+" link2="+link2);
                if (nextOffset < 0)
                    break;
                offset = startOffset + nextOffset;
            }
            if (messageLink == null && link2 != null)
                messageLink = link2;

            // linkify "@...."
            search = "@";
            offset = msgString.indexOf(search);
            while (offset >= 0) {
                link2 = msgString.substring(offset);
                numberOfCharactersTillNextSpace = link2.indexOf(" ");
                if (numberOfCharactersTillNextSpace > 0)
                    link2 = link2.substring(0, numberOfCharactersTillNextSpace);
                else
                    numberOfCharactersTillNextSpace = link2.length();

                msgString = msgString.substring(0, offset) + "<a href=\"http://twitter.com/" + link2 + "\">" + link2
                        + "</a> " + msgString.substring(offset + numberOfCharactersTillNextSpace);
                startOffset = offset + 28 + numberOfCharactersTillNextSpace + 2 + numberOfCharactersTillNextSpace
                        + 5;

                int nextOffset = msgString.substring(startOffset).indexOf(search);
                //Log.i(LOGTAG,"http2link @ numberOfCharactersTillNextSpace="+numberOfCharactersTillNextSpace+" offset="+offset+" startOffset="+startOffset+" nextOffset="+nextOffset+" link2="+link2);
                if (nextOffset < 0)
                    break;
                offset = startOffset + nextOffset;
            }
            if (messageLink == null && link2 != null)
                messageLink = link2;
        }

        return msgString;
    }

    public String linkifyLink() {
        return messageLink;
    }

    public void twitterLogin(String pin) {
        // called by OAuthActivity
        if (Config.LOGD)
            Log.i(LOGTAG, String.format("twitterLogin pin=%s", pin));

        if (pin == null || pin.length() == 0) {
            // pin-entry was aborted or failed for other reason
            String errorText = "twitterLogin pin entry has failed";
            if (Config.LOGD)
                Log.i(LOGTAG, errorText);
            errMsg = errorText;
            connectThread = null;
            return;
        }

        // 2011-05-18: twitter has changed the position of the pin in the HTML
        int idxCode = pin.indexOf("<code>");
        if (idxCode > 0) {
            pin = pin.substring(idxCode + 6);
            int idxCodeEnd = pin.indexOf("</code>");
            if (idxCodeEnd > 0) {
                pin = pin.substring(0, idxCodeEnd);
                if (Config.LOGD)
                    Log.i(LOGTAG, String.format("twitterLogin pin fixed=%s", pin));
            }
        }

        accessToken = null;
        if (twitter != null) {
            try {
                if (pin.length() > 0 && requestToken != null) {
                    if (Config.LOGD)
                        Log.i(LOGTAG, String
                                .format("twitterLogin .getOAuthAccessToken(" + requestToken + "," + pin + ")"));
                    accessToken = twitter.getOAuthAccessToken(requestToken, pin);
                } else {
                    if (Config.LOGD)
                        Log.i(LOGTAG, String.format("twitterLogin .getOAuthAccessToken()"));
                    accessToken = twitter.getOAuthAccessToken();
                }
                if (Config.LOGD)
                    Log.i(LOGTAG, "twitter.verifyCredentials().getId()=" + twitter.verifyCredentials().getId());
            } catch (twitter4j.TwitterException twex) {
                accessToken = null;
            }
        }

        if (accessToken == null) {
            if (Config.LOGD)
                Log.e(LOGTAG, String.format("twitterLogin pin=%s accessToken==null ###############", pin));
            errMsg = "no accessToken";
            connectThread = null;
            return;
        }

        // store accessToken + secret in preferences (read by ConnectThread run() from now on)
        if (Config.LOGD)
            Log.i(LOGTAG, String.format("twitterLogin storePrefs() accessToken=%s / %s", accessToken.getToken(),
                    accessToken.getTokenSecret()));
        SharedPreferences.Editor editor = preferences.edit();
        editor.putString("oauth.accessToken", accessToken.getToken());
        editor.putString("oauth.accessTokenSecret", accessToken.getTokenSecret());
        editor.commit();

        (connectThread = new ConnectThread(this)).start();
    }

    public void clearTwitterLogin() {
        if (Config.LOGD)
            Log.i(LOGTAG, "clearTwitterLogin()");
        if (twitterStream != null) {
            if (Config.LOGD)
                Log.i(LOGTAG, "clearTwitterLogin() twitterStream.cleanUp()");
            twitterStream.cleanUp();
            if (Config.LOGD)
                Log.i(LOGTAG, "clearTwitterLogin() twitterStream.shutdown()");
            twitterStream.shutdown();
            twitterStream = null;
        }
        SharedPreferences.Editor editor = preferences.edit();
        editor.putString("oauth.accessToken", "");
        editor.putString("oauth.accessTokenSecret", "");
        editor.commit();
    }

    // private methods

    private boolean checkRunningOnUnpluggedWifi() {
        boolean newUnpluggedWifiWakeupActivated = false;
        if (wifiEnabled != null && wifiEnabled && powerPlugged != null && !powerPlugged)
            newUnpluggedWifiWakeupActivated = true;

        if (newUnpluggedWifiWakeupActivated != unpluggedWifiWakeupActivated) {
            unpluggedWifiWakeupActivated = newUnpluggedWifiWakeupActivated;
            if (unpluggedWifiWakeupActivated) {
                // device has gone into: WIFI + UNPLUGGED, we need to activate automatic "unplugged-Wifi" wakeups
                if (Config.LOGD)
                    Log.i(LOGTAG, "checkRunningOnUnpluggedWifi() NOW RUNNING IN UNPLUGGED+WIFI MODE");

                if (wakeLockNoScreen == null) {
                    Log.e(LOGTAG,
                            "checkRunningOnUnpluggedWifi() wakeLockNoScreen NOT AVAILABLE, cannot create RepeatingAlarmReceiver");
                } else {
                    Intent intent = new Intent(TwitterService.this, RepeatingAlarmReceiver.class);
                    pendingIntent = PendingIntent.getBroadcast(TwitterService.this, 0, intent, 0);
                    if (alarmManager == null)
                        alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
                    alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (600 * 1000), // 1st wakeup in 10 minutes (=600*1000) from now
                            480 * 1000, // followed up every 8 minutes (=480*1000)
                            pendingIntent);

                    if (Config.LOGD)
                        Log.i(LOGTAG, "checkRunningOnUnpluggedWifi() ACTIVATED unpluggedWifiWakeup");
                }
            } else {
                // just now, the device has gone OUT of: WIFI + UNPLUGGED  (it's now either: 3G+unplugged, or WiFi+plugged, or 3G+plugged)
                if (Config.LOGD)
                    Log.i(LOGTAG, "checkRunningOnUnpluggedWifi() WE DON'T RUN IN UNPLUGGED+WIFI ANYMORE");

                // deactivate unplugged-Wifi-Wakeup
                if (alarmManager != null) {
                    if (pendingIntent != null) {
                        alarmManager.cancel(pendingIntent);
                        pendingIntent = null;
                        if (Config.LOGD)
                            Log.i(LOGTAG, "checkRunningOnUnpluggedWifi() DE-ACTIVATED unpluggedWifiWakeup");
                    } else if (Config.LOGD)
                        Log.i(LOGTAG,
                                "checkRunningOnUnpluggedWifi() DE-ACTIVATED unpluggedWifiWakeup pendingIntent==null");
                } else if (Config.LOGD)
                    Log.i(LOGTAG,
                            "checkRunningOnUnpluggedWifi() DE-ACTIVATED unpluggedWifiWakeup alarmManager==null");
            }
        }

        return unpluggedWifiWakeupActivated;
    }

    public static class RepeatingAlarmReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            // device partly and briefly waking up from sleep
            Date date = new Date();
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat();
            if (wakeLockNoScreen != null) {
                // we need to stay awake enough time to let wifi re-connect
                if (Config.LOGD)
                    Log.i(LOGTAG, "RepeatingAlarmReceiver UBPLUGGED+WIFI KEEP WAKE 10 secs starting at "
                            + simpleDateFormat.format(date));
                wakeLockNoScreen.acquire(10000l);
            } else {
                if (Config.LOGD)
                    Log.i(LOGTAG, "RepeatingAlarmReceiver no wakeLockNoScreen, cannot activateWiFiConnection "
                            + simpleDateFormat.format(date));
            }
        }
    }

    private int findIdxOfMsgWithSameTimeMs(long timeMs) {
        int idx = 0;
        synchronized (messageList) {
            while (idx < messageList.size() && messageList.get(idx).createTimeMs != timeMs)
                idx++;
            if (idx >= messageList.size())
                idx = -1;
        }
        return idx;
    }

    private EntryTopic msgWithSameId(long id) {
        EntryTopic retEntryTopic = null;
        int idx = 0;
        synchronized (messageList) {
            while (idx < messageList.size() && messageList.get(idx).id != id)
                idx++;
            if (idx < messageList.size())
                retEntryTopic = messageList.get(idx);
        }
        return retEntryTopic;
    }

    private int findIdxOfFirstOlderMsg(EntryTopic msgObject) {
        int idx = 0;
        synchronized (messageList) {
            while (idx < messageList.size() && messageList.get(idx).createTimeMs > msgObject.createTimeMs)
                idx++;
        }
        return idx;
    }

    private boolean processStatus(Status status) {
        if (msgWithSameId(status.getId()) != null) {
            if (Config.LOGD)
                Log.i(LOGTAG, "processStatus() found msgWithSameId " + status.getId() + " - don't process");
            return false;
        }

        boolean newMsgReceived = false;
        User user = status.getUser();
        String channelImageString = null;
        try {
            java.net.URI uri = user.getProfileImageURL().toURI();
            channelImageString = uri.toString();
        } catch (java.net.URISyntaxException uriex) {
            Log.e(LOGTAG, String.format("ConnectThread processStatus() URISyntaxException %s ex=%s",
                    user.getProfileImageURL().toString(), uriex));
            errMsg = uriex.getMessage();
        }

        String title = status.getText();
        if (linkifyMessages) {
            title = linkify(title, null, true);
            // messageLink will contain the link-url
        }

        long timeMs = status.getCreatedAt().getTime();
        // make timeMs unique in our messageList
        while (findIdxOfMsgWithSameTimeMs(timeMs) >= 0)
            timeMs++;

        EntryTopic feedEntry = new EntryTopic(0, 0, user.getName(), title, null, messageLink, timeMs,
                status.getId(), channelImageString);
        feedEntry.shortName = user.getScreenName();
        synchronized (messageList) {
            // messageList is always sorted with the newest items on top
            int findIdxOfFirstOlder = findIdxOfFirstOlderMsg(feedEntry);
            if (findIdxOfFirstOlder < maxQueueMessages) {
                messageList.add(findIdxOfFirstOlder, feedEntry);
                newMsgReceived = true;
                totalNumberOfQueuedMessages++;
                if (activityPaused)
                    numberOfQueuedMessagesSinceLastClientActivity++;

                // debug: for every regular msg, create 5 additional dummy messages
                //for(int i=1; i<=5; i++) {
                //  feedEntry = new EntryTopic(0,                                   // region
                //                             0,                                   // prio
                //                             "dummy",
                //                             "test message "+i,
                //                             null,                                // description
                //                             messageLink,
                //                             timeMs+i*100,
                //                             status.getId()+i,                    // todo: make sure the id was ot yet stored in messageList
                //                             channelImageString);                 // todo: must make use of this in MyWebView/JsObject/script.js
                //  messageList.add(findIdxOfFirstOlder,feedEntry);
                //  totalNumberOfQueuedMessages++;
                //  if(activityPaused)
                //    numberOfQueuedMessagesSinceLastClientActivity++;
                //}

                // if there are now more than 'maxQueueMessages' entrys in the queue, remove the oldest...
                while (messageList.size() > maxQueueMessages)
                    messageList.removeLast();
            } else {
                if (Config.LOGD)
                    Log.i(LOGTAG, "processStatus() not findIdxOfFirstOlder<maxQueueMessages - don't process");
            }
        }
        return newMsgReceived;
    }

    private void notifyClient() {
        if (activityPaused) {
            if (numberOfQueuedMessagesSinceLastClientActivity >= maxQueueMessages - 10) {
                if (Config.LOGD)
                    Log.i(LOGTAG, "notifyClient() numberOfQueuedMessagesSinceLastClientActivity="
                            + numberOfQueuedMessagesSinceLastClientActivity + " BUZZ ##########");
                if (vibrator != null) {
                    vibrator.vibrate(600);
                }
                numberOfQueuedMessagesSinceLastClientActivity = 0;
            }
        } else {
            //if(Config.LOGD) Log.i(LOGTAG, "ConnectThread before sendBroadcast() numberOfQueuedMessagesSinceLastClientActivity="+numberOfQueuedMessagesSinceLastClientActivity+" activityUpdatePending="+activityUpdatePending);
            //sendBroadcast(tickerNewEntryBroadcastIntent); // this ends up in ServiceClient BroadcastReceiver() onReceive()
            // this broadcast better not be sent more often than once per second - therefor we use ActivityUpdateThread to delay the broadcast...
            if (!activityUpdatePending) {
                activityUpdatePending = true;
                //if(Config.LOGD) Log.i(LOGTAG, "notifyClient() new ActivityUpdateThread()");
                activityUpdateThread = new ActivityUpdateThread();
                activityUpdateThread.start();
            }
        }
    }

    private int fetchHomeTimeline() {
        // retrieve the latest up to maxQueueMessages twt-msgs
        int count = 0, countProcessed = 0;
        if (twitter != null) {
            List<Status> statuses = null;
            try {
                int messageListSize = 0;

                Paging paging = new Paging();
                paging.setCount(maxQueueMessages);
                if (Config.LOGD)
                    Log.i(LOGTAG, String.format("fetchHomeTimeline() twitter.getHomeTimeline() maxQueueMessages=%d",
                            maxQueueMessages));
                statuses = twitter.getHomeTimeline(paging);

                if (statuses != null) {
                    count = statuses.size();

                    for (Status status : statuses) {
                        if (processStatus(status))
                            countProcessed++;
                    }

                    synchronized (messageList) {
                        messageListSize = messageList.size();
                    }
                    if (Config.LOGD)
                        Log.i(LOGTAG,
                                String.format("fetchHomeTimeline() countProcessed=%d, messageList.size()=%d done",
                                        countProcessed, messageListSize));

                    if (countProcessed > 0)
                        notifyClient();
                } else {
                    if (Config.LOGD)
                        Log.i(LOGTAG, "fetchHomeTimeline() no statuses");
                }
            } catch (TwitterException twex) {
                Log.e(LOGTAG, "fetchHomeTimeline() failed to get twitter timeline: " + twex);
                errMsg = twex.getMessage();
            } catch (java.lang.IllegalStateException illstaex) {
                Log.e(LOGTAG, "fetchHomeTimeline() IllegalStateException illstaex=" + illstaex);
                errMsg = "IllegalStateException on .getHomeTimeline()";
            }
        }
        return count;
    }

    private class ConnectThread extends Thread {
        Context context = null;

        ConnectThread(Context context) {
            this.context = context;
            if (Config.LOGD)
                Log.i(LOGTAG, "ConnectThread constructor..");
        }

        public void run() {
            if (Config.LOGD)
                Log.i(LOGTAG, "ConnectThread run() ...");

            if (twitterStream != null) {
                if (Config.LOGD)
                    Log.i(LOGTAG, "ConnectThread run() TwitterStreamFactory().cleanup()");
                twitterStream.cleanUp();
                if (Config.LOGD)
                    Log.i(LOGTAG, "ConnectThread run() TwitterStreamFactory().shutdown()");
                twitterStream.shutdown();
                twitterStream = null;
            }

            accessToken = null;
            // create accessToken from preferences "oauth.accessToken" + "oauth.accessTokenSecret"
            String oauthAccessToken = preferences.getString("oauth.accessToken", "");
            if (oauthAccessToken != null && oauthAccessToken.length() > 0) {
                String oauthAccessTokenSecret = preferences.getString("oauth.accessTokenSecret", "");
                if (Config.LOGD)
                    Log.i(LOGTAG, String.format("ConnectThread run() preferences oauth.accessTokenSecret=[%s/%s]",
                            oauthAccessToken, oauthAccessTokenSecret));
                if (oauthAccessTokenSecret != null && oauthAccessTokenSecret.length() > 0) {
                    accessToken = new AccessToken(oauthAccessToken, oauthAccessTokenSecret);
                }
            }

            if (accessToken == null) {
                if (Config.LOGD)
                    Log.i(LOGTAG, "ConnectThread run() UNAUTHORIZED TwitterFactory().getInstance() ...");
                twitter = new TwitterFactory().getInstance();
                if (twitter == null) {
                    if (Config.LOGD)
                        Log.e(LOGTAG, "ConnectThread run() failed to instantiate TwitterFactory()");
                    errMsg = "no twitter object from factory without accessToken";
                    connectThread = null;
                    return;
                }

                // startActivity OAuthActivity:  open oauthAuthorizationURL in an embedded browser and let the user login
                try {
                    if (Config.LOGD)
                        Log.i(LOGTAG, "ConnectThread run() twitter.getOAuthRequestToken() ...");
                    requestToken = twitter.getOAuthRequestToken();
                    if (requestToken != null) {
                        String oauthLoginActivityName = "com.vodafone.twitter.service.OAuthActivity";
                        if (Config.LOGD)
                            Log.i(LOGTAG, "ConnectThread run() requestToken.getAuthorizationURL() ...");
                        String oauthAuthorizationURL = requestToken.getAuthorizationURL();
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread run() start %s url=%s",
                                    oauthLoginActivityName, oauthAuthorizationURL));
                        Intent intent = new Intent(getBaseContext(), Class.forName(oauthLoginActivityName));
                        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        intent.putExtra("argAuthorizationURL", oauthAuthorizationURL);
                        getApplication().startActivity(intent); // -> OAuthActivity -> twitterLogin() -> new ConnectThread(this).start()
                        if (Config.LOGD)
                            Log.i(LOGTAG,
                                    "ConnectThread run() OAuthActivity to call us back via twitterLogin(pin) ...");
                    } else {
                        Log.e(LOGTAG, "ConnectThread run() got no requestToken = twitter.getOAuthRequestToken()");
                        connectThread = null;
                    }
                } catch (twitter4j.TwitterException twex) {
                    Log.e(LOGTAG, "ConnectThread run() TwitterException=" + twex);
                    // "Received authentication challenge is null" - thrown if the server replies with a 401 - most likely due to clock setting
                    errMsg = twex.getMessage() + " - please check your system clock";
                    connectThread = null;
                } catch (java.lang.ClassNotFoundException cnfex) {
                    Log.e(LOGTAG, "ConnectThread run() ClassNotFoundException=" + cnfex);
                    errMsg = cnfex.getMessage();
                    connectThread = null;
                } catch (java.lang.IllegalStateException istex) {
                    Log.e(LOGTAG, "ConnectThread run() IllegalStateException=" + istex);
                    errMsg = istex.getMessage();
                    connectThread = null;
                } catch (Exception ex) {
                    Log.e(LOGTAG, "ConnectThread run() Exception=" + ex);
                    errMsg = ex.getMessage();
                    connectThread = null;
                }
                return;
            }

            // got our accessToken alright
            if (Config.LOGD)
                Log.i(LOGTAG, "ConnectThread run() TwitterFactory().getInstance(accessToken) ...");
            twitter = new TwitterFactory().getInstance(accessToken); // may take a little time...
            if (twitter == null) {
                if (Config.LOGD)
                    Log.e(LOGTAG, "ConnectThread run() failed to instantiate TwitterFactory()");
                errMsg = "no twitter object from factory with accessToken=" + accessToken;
                connectThread = null;
                return;
            }

            if (!fetchHomeTimelineDone) {
                // fetch HomeTimeline only once after the service has established a connection
                int count = fetchHomeTimeline();
                fetchHomeTimelineDone = true;
                if (Config.LOGD)
                    Log.i(LOGTAG, "ConnectThread run() received count=" + count + " from fetchHomeTimeline()");
            }

            numberOfQueuedMessagesSinceLastClientActivity = 0;
            connectStream();
        }

        void connectStream() {
            if (Config.LOGD)
                Log.i(LOGTAG, "connectStream()...");

            if (statusListener == null) {
                statusListener = new UserStreamListener() {
                    public void onStatus(Status status) {
                        if (Config.LOGD)
                            Log.i(LOGTAG,
                                    String.format(
                                            "onStatus() twt=%s: %s activityUpdatePending=%b activityPaused=%b",
                                            status.getUser().getName(), status.getText(), activityUpdatePending,
                                            activityPaused));
                        if (processStatus(status)) {
                            notifyClient();
                        } else {
                            if (Config.LOGD)
                                Log.i(LOGTAG, "onStatus() msg not processed");
                        }
                    }

                    public void onDeletionNotice(StatusDeletionNotice statusDeletionNotice) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onDeletionNotice"));
                    }

                    public void onTrackLimitationNotice(int numberOfLimitedStatuses) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format(
                                    "ConnectThread StatusListener limit notice trigger numberOfLimitedStatuses=%d",
                                    numberOfLimitedStatuses));
                    }

                    public void onScrubGeo(long userId, long upToStatusId) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onScrubGeo"));
                    }

                    public void onException(Exception ex) {
                        int statusCode = ((TwitterException) ex).getStatusCode();
                        switch (statusCode) {
                        case -1: // Stream closed
                            Log.e(LOGTAG, "ConnectThread StatusListener onException -1: Stream closed");
                            errMsg = "reconnecting...";
                            //try { Thread.sleep(20000); } catch(Exception ex2) {};
                            fetchHomeTimelineDone = false;
                            break;

                        case 401: // Authentication credentials were missing or incorrect
                            Log.e(LOGTAG,
                                    "ConnectThread StatusListener onException 401: Authentication credentials were missing or incorrect");
                            errMsg = "Authentication credentials missing or incorrect (401)";
                            try {
                                Thread.sleep(3000);
                            } catch (Exception ex2) {
                            }
                            ;

                            // todo: ???
                            connectStream();
                            // todo: it won't be enough to start a new connectStream() ???
                            break;

                        case 420: // The number of requests you have made exceeds the quota afforded by your assigned rate limit
                            Log.e(LOGTAG, "ConnectThread StatusListener onException " + ex);
                            errMsg = "too many requests (420)";
                            try {
                                Thread.sleep(20000);
                            } catch (Exception ex2) {
                            }
                            ;
                            // todo: ???
                            break;

                        default:
                            Log.e(LOGTAG, "ConnectThread StatusListener onException " + ex);
                            errMsg = ex.getMessage();
                            // todo: ???
                            break;
                        }

                        Log.e(LOGTAG, String.format("ConnectThread StatusListener onException statusCode=%d done",
                                statusCode));
                    }

                    public void onBlock(User source, User blockedUser) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onBlock"));
                    };

                    public void onDeletionNotice(long directMessageId, long userId) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onDeletionNotice"));
                    };

                    public void onDirectMessage(DirectMessage directMessage) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onDirectMessage"));
                        // todo: create a new EntryTopic?
                    };

                    public void onFavorite(User source, User target, Status favoritedStatus) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onFavorite"));
                    };

                    public void onFollow(User source, User followedUser) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onFollow"));
                    };

                    public void onFriendList(long[] friendIds) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onFriendList"));
                    };

                    public void onRetweet(User source, User target, Status retweetedStatus) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onRetweet"));
                    };

                    public void onUnblock(User source, User unblockedUser) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUnblock"));
                    };

                    public void onUnfavorite(User source, User target, Status unfavoritedStatus) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUnfavorite"));
                    };

                    public void onUserListCreation(User listOwner, UserList list) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUserListCreation"));
                    };

                    public void onUserListDeletion(User listOwner, UserList list) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUserListDeletion"));
                    };

                    public void onUserListMemberAddition(User addedMember, User listOwner, UserList list) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUserListMemberAddition"));
                    };

                    public void onUserListMemberDeletion(User deletedMember, User listOwner, UserList list) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUserListMemberDeletion"));
                    };

                    public void onUserListSubscription(User subscriber, User listOwner, UserList list) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUserListSubscription"));
                    };

                    public void onUserListUnsubscription(User subscriber, User listOwner, UserList list) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUserListUnsubscription"));
                    };

                    public void onUserListUpdate(User listOwner, UserList list) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUserListUpdate"));
                    };

                    public void onUserProfileUpdate(User updatedUser) {
                        if (Config.LOGD)
                            Log.i(LOGTAG, String.format("ConnectThread StatusListener onUserProfileUpdate"));
                    };
                };
            }

            if (Config.LOGD)
                Log.i(LOGTAG, "ConnectThread connectStream() TwitterStreamFactory().getInstance(accessToken)");
            twitterStream = new TwitterStreamFactory().getInstance(accessToken);
            if (twitterStream != null) {
                if (Config.LOGD)
                    Log.i(LOGTAG, "ConnectThread connectStream() got twitterStream");
                twitterStream.addListener(statusListener);
                if (Config.LOGD)
                    Log.i(LOGTAG, "ConnectThread connectStream() StatusListener added");

                try {
                    if (Config.LOGD)
                        Log.i(LOGTAG, "ConnectThread connectStream() twitterStream.getUserStream() ...");
                    //userStream = twitterStream.getUserStream(); // simply doesn't work
                    twitterStream.user();
                    //} catch(TwitterException twitterException) {
                    //  Log.e(LOGTAG, "ConnectThread connectStream() TwitterException twitterException="+twitterException);
                } catch (java.lang.IllegalStateException illstaex) {
                    Log.e(LOGTAG, "ConnectThread connectStream() IllegalStateException illstaex=" + illstaex);
                    errMsg = "IllegalStateException on .user()";
                    connectThread = null;
                    twitterStream = null;
                }
            } else {
                Log.e(LOGTAG, "ConnectThread connectStream() got no twitterStream");
                errMsg = "no twitterStream instance";
                connectThread = null;
            }
        }
    }

    private class ActivityUpdateThread extends Thread {
        // this helper thread sends delayed broadcasts to our activity
        // the delay is used to prevent multiple broadcasts invoked by multiple tweets being received 'at once'
        int delayMS = 1200;

        ActivityUpdateThread() {
        }

        void setDelayMS(int delayMS) {
            this.delayMS = delayMS;
        }

        public void run() {
            int broadcastNumber = totalNumberOfBrodcastsSend;
            if (totalNumberOfBrodcastsSend > 0) {
                try {
                    Thread.sleep(delayMS);
                } catch (java.lang.InterruptedException iex) {
                }
            }

            // this will arrive in the activity's BroadcastReceiver onReceive()
            sendBroadcast(tickerNewEntryBroadcastIntent);

            totalNumberOfBrodcastsSend++;
            activityUpdatePending = false;
        }
    }
}