co.beem.project.beem.FacebookTextService.java Source code

Java tutorial

Introduction

Here is the source code for co.beem.project.beem.FacebookTextService.java

Source

/*
BEEM is a videoconference application on the Android Platform.
    
Copyright (C) 2009 by Frederic-Charles Barthelery,
                      Jean-Manuel Da Silva,
                      Nikita Kozlov,
                      Philippe Lago,
                      Jean Baptiste Vergely,
                      Vincent Veronis.
    
This file is part of BEEM.
    
BEEM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
BEEM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with BEEM.  If not, see <http://www.gnu.org/licenses/>.
    
Please send bug reports with examples or suggestions to
contact@beem-project.com or http://dev.beem-project.com/
    
Epitech, hereby disclaims all copyright interest in the program "Beem"
written by Frederic-Charles Barthelery,
           Jean-Manuel Da Silva,
           Nikita Kozlov,
           Philippe Lago,
           Jean Baptiste Vergely,
           Vincent Veronis.
    
Nicolas Sadirac, November 26, 2009
President of Epitech.
    
Flavien Astraud, November 26, 2009
Head of the EIP Laboratory.
    
 */
package co.beem.project.beem;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;

import javax.net.ssl.SSLContext;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.Roster.SubscriptionMode;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.SmackAndroid;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.proxy.ProxyInfo;
import org.jivesoftware.smack.proxy.ProxyInfo.ProxyType;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
import org.jivesoftware.smackx.entitycaps.SimpleDirectoryPersistentCache;
import org.jivesoftware.smackx.entitycaps.packet.CapsExtension;
import org.jivesoftware.smackx.packet.ChatStateExtension;
import org.jivesoftware.smackx.provider.CapsExtensionProvider;
import org.jivesoftware.smackx.provider.DelayInfoProvider;
import org.jivesoftware.smackx.provider.DiscoverInfoProvider;
import org.jivesoftware.smackx.provider.DiscoverItemsProvider;
import org.jivesoftware.smackx.pubsub.provider.EventProvider;
import org.jivesoftware.smackx.pubsub.provider.ItemProvider;
import org.jivesoftware.smackx.pubsub.provider.ItemsProvider;
import org.jivesoftware.smackx.pubsub.provider.PubSubProvider;
import org.mango.facebooktext.FacebookTextApplication;
import org.mango.facebooktext.R;
import org.mango.facebooktext.connection.FacebookTextConnectionListener;
import org.mango.facebooktext.data.ChatMessage;
import org.mango.facebooktext.data.ChatMessageType;
import org.mango.facebooktext.data.ChatSession;
import org.mango.facebooktext.data.User;
import org.mango.facebooktext.data.UserState;
import org.mango.facebooktext.database.DatabaseHelper;
import org.mango.facebooktext.fbclient.AvatarFbReponseData;
import org.mango.facebooktext.fbclient.MMConnector;
import org.mango.facebooktext.ui.android.FbTextMainActivity;
import org.mango.facebooktext.ui.android.SessionManager;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.StrictMode;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;
import co.beem.project.beem.service.Contact;
import co.beem.project.beem.service.Message;
import co.beem.project.beem.service.XmppConnectionAdapter;
import co.beem.project.beem.service.XmppFacade;
import co.beem.project.beem.service.aidl.IXmppFacade;
import co.beem.project.beem.service.auth.PreferenceAuthenticator;
import co.beem.project.beem.smack.avatar.AvatarMetadataProvider;
import co.beem.project.beem.smack.avatar.AvatarProvider;
import co.beem.project.beem.smack.ping.PingExtension;
import co.beem.project.beem.smack.sasl.SASLGoogleOAuth2Mechanism;
import co.beem.project.beem.smack.sasl.ScramSaslMechanism;
import co.beem.project.beem.utils.BeemBroadcastReceiver;
import co.beem.project.beem.utils.BeemConnectivity;
import co.beem.project.beem.utils.Status;

import com.j256.ormlite.android.apptools.OpenHelperManager;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.stmt.UpdateBuilder;

import de.duenndns.ssl.MemorizingTrustManager;

/**
 * This class is for the Beem service. It must contains every global
 * informations needed to maintain the background service. The connection to the
 * xmpp server will be made asynchronously when the service will start.
 * 
 * @author darisk
 */
public class FacebookTextService extends Service implements FacebookTextConnectionListener {

    /** The id to use for status notification. */
    public static final int NOTIFICATION_STATUS_ID = 100;

    private static final String TAG = "FacebookTextService";
    private static final int DEFAULT_XMPP_PORT = 5222;

    private NotificationManager mNotificationManager;
    private XmppConnectionAdapter mConnection;
    private SharedPreferences mSettings;
    private String mLogin;
    private String mHost;
    private String mService;
    private int mPort;
    private ConnectionConfiguration mConnectionConfiguration;
    private IXmppFacade.Stub mBind;

    private BlockingQueue<co.beem.project.beem.service.Message> savingMessageQueue;
    private BlockingQueue<String> loadingUserAvatarQueue;
    private BlockingQueue<User> stateChangeQueue;
    public static boolean isRunning;
    public static DatabaseHelper databaseHelper;
    private Dao<ChatMessage, Integer> chatMessageDao;
    private Dao<User, String> userDao;
    private Dao<ChatSession, Integer> chatSessionDao;
    private SessionManager sessionManager;

    private BeemBroadcastReceiver mReceiver = new BeemBroadcastReceiver();
    private FacebookTextServiceBroadcastReceiver mOnOffReceiver = new FacebookTextServiceBroadcastReceiver();
    private FacebookTextServicePreferenceListener mPreferenceListener = new FacebookTextServicePreferenceListener();

    private boolean mOnOffReceiverIsRegistered;
    private boolean isConnected;

    private SSLContext sslContext;
    private Handler handler;
    private SmackAndroid smackAndroid;

    /**
     * Constructor.
     */
    public FacebookTextService() {

    }

    /**
     * Initialize the connection.
     */
    private void initConnectionConfig() {
        Log.d(TAG, "FacebookTextService innitiation ...");

        // TODO add an option for this ?
        // SmackConfiguration.setPacketReplyTimeout(30000);
        ProxyInfo proxyInfo = getProxyConfiguration();
        boolean useSystemAccount = mSettings.getBoolean(FacebookTextApplication.USE_SYSTEM_ACCOUNT_KEY, false);

        if (!useSystemAccount || mConnectionConfiguration == null) {
            /* StrictMode.ThreadPolicy policy = new
             StrictMode.ThreadPolicy.Builder().permitAll().build();
             StrictMode.setThreadPolicy(policy);*/
            SASLAuthentication.unsupportSASLMechanism(SASLGoogleOAuth2Mechanism.MECHANISM_NAME);
            SASLAuthentication.supportSASLMechanism("PLAIN");
            if (mSettings.getBoolean(FacebookTextApplication.ACCOUNT_SPECIFIC_SERVER_KEY, false))
                mConnectionConfiguration = new ConnectionConfiguration(mHost, mPort, mService, proxyInfo);
            else
                mConnectionConfiguration = new ConnectionConfiguration(mService, proxyInfo);
            mConnectionConfiguration.setCallbackHandler(new PreferenceAuthenticator(this));
        }

        if (mSettings.getBoolean("settings_key_xmpp_tls_use", false)
                || mSettings.getBoolean("settings_key_gmail", false)) {
            mConnectionConfiguration.setSecurityMode(SecurityMode.required);
        }
        if (mSettings.getBoolean(FacebookTextApplication.SMACK_DEBUG_KEY, true))

            mConnectionConfiguration.setDebuggerEnabled(true);
        mConnectionConfiguration.setSendPresence(false);
        mConnectionConfiguration.setRosterLoadedAtLogin(false);
        // maybe not the universal path, but it works on most devices (Samsung
        // Galaxy, Google Nexus One)
        mConnectionConfiguration.setTruststoreType("BKS");
        mConnectionConfiguration.setTruststorePath("/system/etc/security/cacerts.bks");
        if (sslContext != null)
            mConnectionConfiguration.setCustomSSLContext(sslContext);
    }

    /**
     * Get the save proxy configuration.
     * 
     * @return the proxy configuration
     */
    private ProxyInfo getProxyConfiguration() {
        boolean useProxy = mSettings.getBoolean(FacebookTextApplication.PROXY_USE_KEY, false);
        if (useProxy) {
            String stype = mSettings.getString(FacebookTextApplication.PROXY_TYPE_KEY, "HTTP");
            String phost = mSettings.getString(FacebookTextApplication.PROXY_SERVER_KEY, "");
            String puser = mSettings.getString(FacebookTextApplication.PROXY_USERNAME_KEY, "");
            String ppass = mSettings.getString(FacebookTextApplication.PROXY_PASSWORD_KEY, "");
            int pport = Integer.parseInt(mSettings.getString(FacebookTextApplication.PROXY_PORT_KEY, "1080"));
            ProxyInfo.ProxyType type = ProxyType.valueOf(stype);
            return new ProxyInfo(type, phost, pport, puser, ppass);
        } else {
            return ProxyInfo.forNoProxy();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IBinder onBind(Intent intent) {
        Log.d(TAG, "ONBIND()");
        /*
         * if (mConnection == null || mConnection.getAdaptee() == null ||
         * !mConnection.getAdaptee().isConnected()) { createConnectAsync(); } if
         * (databaseHelper == null) getHelper();
         */
        return mBind;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "ONUNBIND()");
        if (mConnection != null && !mConnection.getAdaptee().isConnected()) {
            // this.stopSelf();
            Log.d(TAG, "connection to server stopped");
        }
        return true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onCreate() {
        super.onCreate();
        smackAndroid = SmackAndroid.init(FacebookTextService.this);
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);
        savingMessageQueue = new LinkedBlockingQueue<co.beem.project.beem.service.Message>();
        loadingUserAvatarQueue = new LinkedBlockingDeque<String>();
        stateChangeQueue = new LinkedBlockingQueue<User>();
        databaseHelper = getHelper();
        try {
            setupDatabaseConnection();
        } catch (Exception e) {
            e.printStackTrace();
        }
        isRunning = true;
        sessionManager = new SessionManager(FacebookTextService.this);
        savingMessageOnBackgroundThread(new SavingNewMessageTask());
        savingMessageOnBackgroundThread(new UpdateUserStateTask());
        handler = new Handler();
        registerReceiver(mReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
        registerReceiver(mOnOffReceiver, new IntentFilter(FacebookTextApplication.GET_AVATAR));
        registerReceiver(mOnOffReceiver, new IntentFilter(FacebookTextApplication.UPDATE_USER_STATE));
        registerReceiver(mOnOffReceiver,
                new IntentFilter(FacebookTextApplication.PUSH_NOTIFICATION_FAVORITE_ONLINE));
        mSettings = PreferenceManager.getDefaultSharedPreferences(this);
        mSettings.registerOnSharedPreferenceChangeListener(mPreferenceListener);
        if (mSettings.getBoolean(FacebookTextApplication.USE_AUTO_AWAY_KEY, false)) {
            mOnOffReceiverIsRegistered = true;
            registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
            registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));
            // registerReceiver(sma, filter)
        }
        String tmpJid = mSettings.getString(FacebookTextApplication.ACCOUNT_USERNAME_KEY, "").trim();
        mLogin = StringUtils.parseName(tmpJid);
        boolean useSystemAccount = mSettings.getBoolean(FacebookTextApplication.USE_SYSTEM_ACCOUNT_KEY, false);
        mPort = DEFAULT_XMPP_PORT;
        mService = StringUtils.parseServer(tmpJid);
        mHost = mService;
        initMemorizingTrustManager();

        if (mSettings.getBoolean(FacebookTextApplication.ACCOUNT_SPECIFIC_SERVER_KEY, false)) {
            mHost = mSettings.getString(FacebookTextApplication.ACCOUNT_SPECIFIC_SERVER_HOST_KEY, "").trim();
            if ("".equals(mHost))
                mHost = mService;
            String tmpPort = mSettings.getString(FacebookTextApplication.ACCOUNT_SPECIFIC_SERVER_PORT_KEY, "5222");
            if (!"".equals(tmpPort))
                mPort = Integer.parseInt(tmpPort);
        }
        if (mSettings.getBoolean(FacebookTextApplication.FULL_JID_LOGIN_KEY, false) || "gmail.com".equals(mService)
                || "googlemail.com".equals(mService) || useSystemAccount) {
            mLogin = tmpJid;
        }

        configure(ProviderManager.getInstance());

        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        Roster.setDefaultSubscriptionMode(SubscriptionMode.manual);
        mBind = new XmppFacade(this);
        savingMessageOnBackgroundThread(new DownloadAvatarTask());
        Log.d(TAG, "Create FacebookTextService \t id: " + mLogin + " \t host: " + mHost + "\tmPort" + mPort
                + "\t service" + mService);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        isRunning = false;
        try {
            Message message = new Message("");
            message.setFrom("quite_right_away");
            savingMessageQueue.put(message);

            loadingUserAvatarQueue.add("quite_right_away");
            stateChangeQueue.add(new User("quite_right_away"));

            // TODO set all friend state to offline
        } catch (InterruptedException e) {
            Log.v(TAG, "Could not stop the saving message queue");
            e.printStackTrace();
        }
        mNotificationManager.cancelAll();
        unregisterReceiver(mReceiver);
        mSettings.unregisterOnSharedPreferenceChangeListener(mPreferenceListener);
        if (mOnOffReceiverIsRegistered)
            unregisterReceiver(mOnOffReceiver);
        if (mConnection != null && mConnection.isAuthentificated() && BeemConnectivity.isConnected(this))
            mConnection.disconnect();

        if (databaseHelper != null) {
            databaseHelper = null;
            OpenHelperManager.releaseHelper();
        }
        try {
            smackAndroid.exit();
        } catch (Exception e) {
            e.printStackTrace();
        }
        Log.i(TAG, "Stopping the service");
    }

    /**
     * {@inheritDoc}
     */
    /*
     * @Override public void onStart(Intent intent, int startId) {
     * super.onStart(intent, startId); Log.d(TAG,
     * "onStart - where start connection"); createConnectAsync(); }
     */

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        /*if (intent.getBooleanExtra(FacebookTextApplication.INTENT_NO_RESTART, false)){
           return START_STICKY;
        }*/

        Log.d(TAG, "onStart - where start connection");
        if (mConnection == null || mConnection.getAdaptee() == null || !mConnection.getAdaptee().isConnected()) {
            createConnectAsync();
        }
        if (databaseHelper == null)
            getHelper();
        return START_STICKY;
    }

    /**
     * Create the XmppConnectionAdapter. This method makes a network request so
     * it must not be called on the main thread.
     * 
     * @return the connection
     */
    public XmppConnectionAdapter createConnection() {
        if (mConnection == null) {
            initConnectionConfig();
            mConnection = new XmppConnectionAdapter(mConnectionConfiguration, mLogin, null, this);
            Log.d(TAG, "created new connection");
        }
        return mConnection;
    }

    /*
     * get database helper
     */
    public DatabaseHelper getHelper() {
        if (databaseHelper == null)
            databaseHelper = OpenHelperManager.getHelper(FacebookTextService.this, DatabaseHelper.class);
        return databaseHelper;
    }

    /*
     * Set up database connection
     */

    public void setupDatabaseConnection() throws SQLException {
        userDao = databaseHelper.getUserDao();
        chatMessageDao = databaseHelper.getMessageDao();
        chatSessionDao = databaseHelper.getChatSessionDao();
    }

    /**
     * Show a notification using the preference of the user.
     * 
     * @param id
     *            the id of the notification.
     * @param notif
     *            the notification to show
     */
    public void sendNotification(int id, Notification notif) {
        if (mSettings.getBoolean(FacebookTextApplication.NOTIFICATION_VIBRATE_KEY, true))
            notif.defaults |= Notification.DEFAULT_VIBRATE;
        notif.ledARGB = 0xff0000ff; // Blue color
        notif.ledOnMS = 1000;
        notif.ledOffMS = 1000;
        notif.flags |= Notification.FLAG_SHOW_LIGHTS;
        String ringtoneStr = mSettings.getString(FacebookTextApplication.NOTIFICATION_SOUND_KEY,
                Settings.System.DEFAULT_NOTIFICATION_URI.toString());
        notif.sound = Uri.parse(ringtoneStr);
        if (mSettings.getBoolean("notifications_new_message", true))
            mNotificationManager.notify(id, notif);
    }

    /**
     * Delete a notification.
     * 
     * @param id
     *            the id of the notification
     */
    public void deleteNotification(int id) {
        mNotificationManager.cancel(id);
    }

    /**
     * Reset the status to online after a disconnect.
     */
    public void resetStatus() {
        Editor edit = mSettings.edit();
        edit.putInt(FacebookTextApplication.STATUS_KEY, 1);
        edit.commit();
    }

    /**
     * Initialize Jingle from an XmppConnectionAdapter.
     * 
     * @param adaptee
     *            XmppConnection used for jingle.
     */
    public void initJingle(XMPPConnection adaptee) {
    }

    /**
     * Return a bind to an XmppFacade instance.
     * 
     * @return IXmppFacade a bind to an XmppFacade instance
     */
    public IXmppFacade getBind() {
        return mBind;
    }

    /**
     * Get the preference of the service.
     * 
     * @return the preference
     */
    public SharedPreferences getServicePreference() {
        return mSettings;
    }

    /**
     * Get the notification manager system service.
     * 
     * @return the notification manager service.
     */
    public NotificationManager getNotificationManager() {
        return mNotificationManager;
    }

    /**
     * Utility method to create and make a connection asynchronously.
     */
    private synchronized void createConnectAsync() {
        if (mConnection == null) {
            new Thread(new Runnable() {

                @Override
                public void run() {
                    createConnection();
                    connectAsync();
                }
            }).start();
        } else
            connectAsync();
        Log.v(TAG, "starting connection");
    }

    /**
     * Utility method to connect asynchronously.
     */
    private void connectAsync() {
        try {
            mConnection.connectAsync();
        } catch (RemoteException e) {
            Log.w(TAG, "unable to connect", e);
        }
    }

    /**
     * Get the specified Android account.
     * 
     * @param accountName
     *            the account name
     * @param accountType
     *            the account type
     * 
     * @return the account or null if it does not exist
     */
    private Account getAccount(String accountName, String accountType) {
        AccountManager am = AccountManager.get(this);
        for (Account a : am.getAccountsByType(accountType)) {
            if (a.name.equals(accountName)) {
                return a;
            }
        }
        return null;
    }

    /**
     * Install the MemorizingTrustManager in the ConnectionConfiguration of
     * Smack.
     */
    private void initMemorizingTrustManager() {
        try {
            sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, MemorizingTrustManager.getInstanceList(this), new java.security.SecureRandom());
        } catch (GeneralSecurityException e) {
            Log.w(TAG, "Unable to use MemorizingTrustManager", e);
        }
    }

    /**
     * A sort of patch from this thread:
     * http://www.igniterealtime.org/community/thread/31118. Avoid
     * ClassCastException by bypassing the classloading shit of Smack.
     * 
     * @param pm
     *            The ProviderManager.
     */
    private void configure(ProviderManager pm) {
        Log.d(TAG, "configure");
        // Service Discovery # Items
        pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
        // Service Discovery # Info
        pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());

        // Privacy
        // pm.addIQProvider("query", "jabber:iq:privacy", new
        // PrivacyProvider());
        // Delayed Delivery only the new version
        pm.addExtensionProvider("delay", "urn:xmpp:delay", new DelayInfoProvider());

        // Service Discovery # Items
        pm.addIQProvider("query", "http://jabber.org/protocol/disco#items", new DiscoverItemsProvider());
        // Service Discovery # Info
        pm.addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());

        // Chat State
        ChatStateExtension.Provider chatState = new ChatStateExtension.Provider();
        pm.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", chatState);
        pm.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates", chatState);
        pm.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", chatState);
        pm.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", chatState);
        pm.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", chatState);
        // capabilities
        pm.addExtensionProvider(CapsExtension.NODE_NAME, CapsExtension.XMLNS, new CapsExtensionProvider());

        // Pubsub
        pm.addIQProvider("pubsub", "http://jabber.org/protocol/pubsub", new PubSubProvider());
        pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub", new ItemsProvider());
        pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub", new ItemsProvider());
        pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub", new ItemProvider());

        pm.addExtensionProvider("items", "http://jabber.org/protocol/pubsub#event", new ItemsProvider());
        pm.addExtensionProvider("item", "http://jabber.org/protocol/pubsub#event", new ItemProvider());
        pm.addExtensionProvider("event", "http://jabber.org/protocol/pubsub#event", new EventProvider());
        // TODO rajouter les manquants pour du full pubsub

        // PEP avatar
        pm.addExtensionProvider("metadata", "urn:xmpp:avatar:metadata", new AvatarMetadataProvider());
        pm.addExtensionProvider("data", "urn:xmpp:avatar:data", new AvatarProvider());

        // PEPProvider pep = new PEPProvider();
        // AvatarMetadataProvider avaMeta = new AvatarMetadataProvider();
        // pep.registerPEPParserExtension("urn:xmpp:avatar:metadata", avaMeta);
        // pm.addExtensionProvider("event",
        // "http://jabber.org/protocol/pubsub#event", pep);

        // ping
        pm.addIQProvider(PingExtension.ELEMENT, PingExtension.NAMESPACE, PingExtension.class);

        /*
         * // Private Data Storage pm.addIQProvider("query",
         * "jabber:iq:private", new PrivateDataManager.PrivateDataIQProvider());
         * // Time try { pm.addIQProvider("query", "jabber:iq:time",
         * Class.forName("org.jivesoftware.smackx.packet.Time")); } catch
         * (ClassNotFoundException e) { Log.w("TestClient",
         * "Can't load class for org.jivesoftware.smackx.packet.Time"); } //
         * Roster Exchange pm.addExtensionProvider("x", "jabber:x:roster", new
         * RosterExchangeProvider()); // Message Events
         * pm.addExtensionProvider("x", "jabber:x:event", new
         * MessageEventProvider()); // XHTML pm.addExtensionProvider("html",
         * "http://jabber.org/protocol/xhtml-im", new XHTMLExtensionProvider());
         * // Group Chat Invitations pm.addExtensionProvider("x",
         * "jabber:x:conference", new GroupChatInvitation.Provider()); // Data
         * Forms pm.addExtensionProvider("x", "jabber:x:data", new
         * DataFormProvider()); // MUC User pm.addExtensionProvider("x",
         * "http://jabber.org/protocol/muc#user", new MUCUserProvider()); // MUC
         * Admin pm.addIQProvider("query",
         * "http://jabber.org/protocol/muc#admin", new MUCAdminProvider()); //
         * MUC Owner pm.addIQProvider("query",
         * "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider()); //
         * Version try { pm.addIQProvider("query", "jabber:iq:version",
         * Class.forName("org.jivesoftware.smackx.packet.Version")); } catch
         * (ClassNotFoundException e) { // Not sure what's happening here.
         * Log.w("TestClient",
         * "Can't load class for org.jivesoftware.smackx.packet.Version"); } //
         * VCard pm.addIQProvider("vCard", "vcard-temp", new VCardProvider());
         * // Offline Message Requests pm.addIQProvider("offline",
         * "http://jabber.org/protocol/offline", new
         * OfflineMessageRequest.Provider()); // Offline Message Indicator
         * pm.addExtensionProvider("offline",
         * "http://jabber.org/protocol/offline", new
         * OfflineMessageInfo.Provider()); // Last Activity
         * pm.addIQProvider("query", "jabber:iq:last", new
         * LastActivity.Provider()); // User Search pm.addIQProvider("query",
         * "jabber:iq:search", new UserSearch.Provider()); // SharedGroupsInfo
         * pm.addIQProvider("sharedgroup",
         * "http://www.jivesoftware.org/protocol/sharedgroup", new
         * SharedGroupsInfo.Provider()); // JEP-33: Extended Stanza Addressing
         * pm.addExtensionProvider("addresses",
         * "http://jabber.org/protocol/address", new
         * MultipleAddressesProvider()); // FileTransfer pm.addIQProvider("si",
         * "http://jabber.org/protocol/si", new StreamInitiationProvider());
         * pm.addIQProvider("query", "http://jabber.org/protocol/bytestreams",
         * new BytestreamsProvider()); pm.addIQProvider("open",
         * "http://jabber.org/protocol/ibb", new IBBProviders.Open());
         * pm.addIQProvider("close", "http://jabber.org/protocol/ibb", new
         * IBBProviders.Close()); pm.addExtensionProvider("data",
         * "http://jabber.org/protocol/ibb", new IBBProviders.Data());
         * 
         * pm.addIQProvider("command", COMMAND_NAMESPACE, new
         * AdHocCommandDataProvider());
         * pm.addExtensionProvider("malformed-action", COMMAND_NAMESPACE, new
         * AdHocCommandDataProvider.MalformedActionError());
         * pm.addExtensionProvider("bad-locale", COMMAND_NAMESPACE, new
         * AdHocCommandDataProvider.BadLocaleError());
         * pm.addExtensionProvider("bad-payload", COMMAND_NAMESPACE, new
         * AdHocCommandDataProvider.BadPayloadError());
         * pm.addExtensionProvider("bad-sessionid", COMMAND_NAMESPACE, new
         * AdHocCommandDataProvider.BadSessionIDError());
         * pm.addExtensionProvider("session-expired", COMMAND_NAMESPACE, new
         * AdHocCommandDataProvider.SessionExpiredError());
         */

        /* register additionnals sasl mechanisms */
        SASLAuthentication.registerSASLMechanism(SASLGoogleOAuth2Mechanism.MECHANISM_NAME,
                SASLGoogleOAuth2Mechanism.class);
        SASLAuthentication.registerSASLMechanism(ScramSaslMechanism.MECHANISM_NAME, ScramSaslMechanism.class);

        SASLAuthentication.supportSASLMechanism(ScramSaslMechanism.MECHANISM_NAME);
        // Configure entity caps manager. This must be done only once
        File f = new File(getCacheDir(), "entityCaps");
        f.mkdirs();
        try {
            EntityCapsManager.setPersistentCache(new SimpleDirectoryPersistentCache(f));
        } catch (IllegalStateException e) {
            Log.v(TAG, "EntityCapsManager already initialized", e);
        } catch (IOException e) {
            Log.w(TAG, "EntityCapsManager not able to reuse persistent cache");
        }
    }

    /**
     * Listen on preference changes.
     */
    private class FacebookTextServicePreferenceListener
            implements SharedPreferences.OnSharedPreferenceChangeListener {

        /**
         * ctor.
         */
        public FacebookTextServicePreferenceListener() {
        }

        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
            if (FacebookTextApplication.USE_AUTO_AWAY_KEY.equals(key)) {
                if (sharedPreferences.getBoolean(FacebookTextApplication.USE_AUTO_AWAY_KEY, false)) {
                    mOnOffReceiverIsRegistered = true;
                    registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
                    registerReceiver(mOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));
                } else {
                    mOnOffReceiverIsRegistered = false;
                    unregisterReceiver(mOnOffReceiver);
                }
            }
        }
    }

    /**
     * Listen on some Intent broadcast, ScreenOn and ScreenOff.
     */
    private class FacebookTextServiceBroadcastReceiver extends BroadcastReceiver {

        private String mOldStatus;
        private int mOldMode;

        /**
         * Constructor.
         */
        public FacebookTextServiceBroadcastReceiver() {
        }

        @Override
        public void onReceive(final Context context, final Intent intent) {
            String intentAction = intent.getAction();
            if (intentAction.equals(Intent.ACTION_SCREEN_OFF) && mConnection != null) {
                mOldMode = mConnection.getPreviousMode();
                mOldStatus = mConnection.getPreviousStatus();

                if (mConnection.isAuthentificated())
                    mConnection.changeStatus(Status.CONTACT_STATUS_AWAY,
                            mSettings.getString(FacebookTextApplication.AUTO_AWAY_MSG_KEY, "Away"));
            }

            else if (FacebookTextApplication.GET_AVATAR.equals(intentAction)) {
                putUserToGetAvatar(intent.getStringExtra(User.USER_JABBER_ID));
            }

            else if (FacebookTextApplication.UPDATE_USER_STATE.equals(intentAction)) {
                putUserStateChanged(new User(intent.getStringExtra(User.USER_JABBER_ID),
                        intent.getIntExtra(User.USER_STATE_FIELD, 0)));
            } else if (FacebookTextApplication.PUSH_NOTIFICATION_FAVORITE_ONLINE.equals(intentAction)) {
                makeNotificationForFavorite(new User(intent.getStringExtra(User.USER_JABBER_ID),
                        intent.getStringExtra(User.USER_NAME_FIELD), ""));
            }

            else if (intentAction.equals(Intent.ACTION_SCREEN_ON) && mConnection != null) {
                if (mConnection.isAuthentificated())
                    mConnection.changeStatus(mOldMode, mOldStatus);
            }

        }
    }

    //
    private class SavingNewMessageTask implements Runnable {

        @Override
        public void run() {

            outter: while (isRunning)
                try {
                    co.beem.project.beem.service.Message newMessage = savingMessageQueue.take();
                    String jId = newMessage.getFrom();
                    if (jId.equalsIgnoreCase("quite_right_away"))
                        break outter;
                    try {
                        User user = userDao.queryForId(jId);
                        ChatSession thisSession = null;
                        List<ChatSession> listSessionsWithThisFriend = null;
                        try {
                            listSessionsWithThisFriend = chatSessionDao.query(chatSessionDao.queryBuilder().where()
                                    .eq(ChatSession.USER_NAME_FIELD, user).prepare());
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }

                        /*
                         * if there no session chat exists, this is the first
                         * time chat with this contact We create new one
                         */
                        if (listSessionsWithThisFriend == null || listSessionsWithThisFriend.size() == 0) {
                            thisSession = new ChatSession(user);
                            try {
                                chatSessionDao.create(thisSession);
                            } catch (SQLException e) {
                                e.printStackTrace();
                            }
                        } // else take the first session found with this friend
                        else {
                            thisSession = listSessionsWithThisFriend.get(0);
                        }

                        ChatMessage chatMessage = null;

                        //check image message
                        if (newMessage.getBody().contains("fbcdn-sphotos")) {
                            for (String imageLink : newMessage.getBody().split("\\s+")) {
                                if (imageLink.contains("fbcdn-sphotos-h-a.akamai")) {
                                    ChatMessage event = new ChatMessage("", new Date().getTime(), user,
                                            ChatMessageType.image, false);
                                    event.setSession(thisSession);
                                    event.setImagePath(imageLink);
                                    chatMessageDao.create(event);
                                    thisSession.setLastMessage(event);
                                    user.increaseMEssageCount();
                                }

                            }
                            chatSessionDao.update(thisSession);
                        }

                        else { // text message
                            chatMessage = new ChatMessage(newMessage.getBody(), newMessage.getTimestamp().getTime(),
                                    user, ChatMessageType.text, false);
                            chatMessage.setSession(thisSession);
                            chatMessageDao.create(chatMessage);
                            thisSession.setLastMessage(chatMessage);
                            chatSessionDao.update(thisSession);
                            user.increaseMEssageCount();
                        }

                        //update message count for user
                        UpdateBuilder<User, String> updateBuilder = userDao.updateBuilder();
                        updateBuilder.updateColumnValue(User.USER_MESSAGE_COUNT_FIELD, user.getMessageCount());
                        updateBuilder.where().eq(User.USER_JABBER_ID, user.getJabberId());

                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                    // find the session for this message first

                } catch (InterruptedException e) {
                    Log.v(TAG, "Could not save this new message");
                    e.printStackTrace();
                }

            Log.v(TAG, "SavingNewMessageTask is closed");

        }

    }

    /*
      * 
      * 
      * */
    public class DownloadAvatarTask implements Runnable {

        @Override
        public void run() {

            try {
                Thread.sleep(20000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            String myUid = PreferenceManager.getDefaultSharedPreferences(FacebookTextService.this)
                    .getString(FacebookTextApplication.ACCOUNT_USERNAME_KEY, "");
            if (myUid != null) {
                loadingUserAvatarQueue.add("-" + myUid);
            }

            try {
                // load user list
                Log.v(TAG, "Task download avatar start");

                outer: while (isRunning) {
                    String userId = loadingUserAvatarQueue.take();
                    // set up connector and download avatar for this user
                    MMConnector connector = new MMConnector();
                    try {
                        if (mSettings.getBoolean(FacebookTextApplication.LOAD_AVATAR_KEY, true)) {
                            String id = userId.substring(1).split("@")[0];

                            // check file downloaded or not
                            File f = getFileStreamPath(id + ".png");
                            if (f.length() > 2000) {
                                throw new Exception("file downloaded");
                            }

                            if (userId.equals("quite_right_away"))
                                break outer;

                            AvatarFbReponseData responseData = connector.sendRequest(id);
                            if (responseData != null && responseData.data != null) {
                                // start to down load

                                InputStream input = null;
                                OutputStream output = null;
                                HttpURLConnection connection = null;
                                try {
                                    URL url = new URL(responseData.data.url);
                                    connection = (HttpURLConnection) url.openConnection();
                                    connection.connect();

                                    // expect HTTP 200 OK, so we don't
                                    // mistakenly save error report
                                    // instead of the file
                                    if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                                        Log.w(TAG, "Server returned HTTP " + connection.getResponseCode() + " "
                                                + connection.getResponseMessage());
                                        throw new Exception("cannot download avatar for this user");
                                    }

                                    // this will be useful to display download
                                    // percentage
                                    // might be -1: server did not report the
                                    // length
                                    int fileLength = connection.getContentLength();

                                    // download the file
                                    output = openFileOutput(id + ".png", MODE_PRIVATE);
                                    input = connection.getInputStream();
                                    Bitmap bmp = BitmapFactory.decodeStream(input);
                                    bmp.compress(Bitmap.CompressFormat.PNG, 100, output); // 100-best quality

                                    /*
                                     * byte data[] = new byte[4096]; long total
                                     * = 0; int count; while ((count =
                                     * input.read(data)) != -1) { total +=
                                     * count; output.write(data, 0, count); }
                                     */

                                } catch (Exception e) {
                                    e.printStackTrace();
                                } finally {
                                    try {
                                        if (output != null)
                                            output.close();
                                        if (input != null)
                                            input.close();
                                    } catch (IOException ignored) {
                                    }

                                    if (connection != null)
                                        connection.disconnect();
                                }
                            }
                            Thread.sleep(100);

                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } // ending of task downloading for one user

                }

                Log.v(TAG, "DownloadAvatarTask is closed");

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

        }

    }

    /*
     * 
     * Change user state in database
     */
    public class UpdateUserStateTask implements Runnable {

        @Override
        public void run() {

            outer: while (isRunning) {

                try {
                    User user = stateChangeQueue.take();
                    UserState state = user.getState();
                    if (user.getJabberId().equals("quite_right_away"))
                        break outer;

                    UpdateBuilder<User, String> builder = userDao.updateBuilder();
                    builder.updateColumnValue(User.USER_STATE_FIELD, user.getState());
                    builder.where().eq(User.USER_JABBER_ID, user.getJabberId());

                    int affterRows = userDao.update(builder.prepare());
                    Log.v(TAG, "update affted " + affterRows + " rows: " + user.getJabberId());

                    user = userDao.queryForId(user.getJabberId());
                    String currentId = mSettings.getString(FacebookTextApplication.CURRENT_CHAT_ID, "");
                    if (user != null && user.isFavorite() && state == UserState.available
                            && !user.getJabberId().equals(currentId)) {
                        // push notification here
                        makeNotificationForFavorite(user);
                        toastMessage(user.getName() + " is now online");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (SQLException e) {
                    e.printStackTrace();
                }

            } // end of endless while
            Log.v(TAG, "UpdateUserStateTask is closed");
        }

    }

    public boolean putMessageToSave(co.beem.project.beem.service.Message message) {
        if (message == null)
            return false;
        return savingMessageQueue.add(message);
    }

    public boolean putUserToGetAvatar(String userId) {
        if (userId == null)
            return false;
        else
            return loadingUserAvatarQueue.add(userId);
    }

    public boolean putUserStateChanged(User user) {
        if (user == null)
            return false;
        else
            return stateChangeQueue.add(user);
    }

    /*
     * Execute thread to retrieve message when available this may be hard work
     * for mobile *
     */

    public static Thread savingMessageOnBackgroundThread(final Runnable runnable) {
        final Thread t = new Thread() {
            @Override
            public void run() {
                try {
                    runnable.run();
                } finally {

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

    public void toastMessage(final String message) {
        handler.post(new Runnable() {

            @Override
            public void run() {
                Toast.makeText(FacebookTextService.this, message, Toast.LENGTH_SHORT).show();
                ;

            }
        });
    }

    public void makeNotificationForFavorite(User user) {

        NotificationCompat.Builder notif = new NotificationCompat.Builder(FacebookTextService.this);
        try {
            String contactJid = user.getJabberId();
            Contact c = getBind().getRoster().getContact(contactJid);
            String contactName = contactJid;
            if (c != null) {
                contactName = c.getName();
                Bitmap avatar = getAvatar(c);
                notif.setLargeIcon(avatar);
            }
            notif.setTicker(contactName).setContentTitle(contactName);
            notif.setContentText(getString(R.string.is_now_online));
            notif.setSmallIcon(R.drawable.ic_stat_ic_launcher_fbtext);
            notif.setContentIntent(makeChatIntent(user));
            notif.setAutoCancel(true).setWhen(System.currentTimeMillis());
            sendNotification(user.getJabberId().hashCode(), notif.getNotification());
        } catch (RemoteException e) {
            Log.e(TAG, e.getMessage());
        }

    }

    private PendingIntent makeChatIntent(User user) {
        Intent chatIntent = new Intent(FacebookTextService.this, FbTextMainActivity.class);
        chatIntent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_SINGLE_TOP
                | Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            chatIntent.setData(user.toUri());
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }
        PendingIntent contentIntent = PendingIntent.getActivity(FacebookTextService.this, 0, chatIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        return contentIntent;
    }

    /**
     * Get the avatar of a contact.
     * 
     * @param c
     *            the contact
     * @return the avatar of c or null if avatar is not defined
     */
    private Bitmap getAvatar(Contact c) {
        String id = c.getAvatarId();
        if (id == null)
            id = "";

        File internalFile = getFileStreamPath(c.getJID().substring(1).split("@")[0] + ".png");
        Uri uri = Uri.fromFile(internalFile);

        try {
            InputStream in = getContentResolver().openInputStream(uri);
            return BitmapFactory.decodeStream(in);
        } catch (FileNotFoundException e) {
            Log.d(TAG, "Error loading avatar id: " + id, e);
            return null;
        }
    }

    public void onConnected() {

    }

    public void onDisconnect() {

    }

    public void onConnecting() {

    }

    public void onNoInternetConnection() {

    }

    @Override
    public void onConnectionError() {
        // TODO Auto-generated method stub

    }

}