com.beem.project.beem.service.XmppConnectionAdapter.java Source code

Java tutorial

Introduction

Here is the source code for com.beem.project.beem.service.XmppConnectionAdapter.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 com.beem.project.beem.service;

import java.util.Iterator;
import java.util.List;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.beem.project.beem.BeemApplication;
import com.beem.project.beem.BeemService;
import com.beem.project.beem.R;
import com.beem.project.beem.service.aidl.IBeemConnectionListener;
import com.beem.project.beem.service.aidl.IChatManager;
import com.beem.project.beem.service.aidl.IRoster;
import com.beem.project.beem.service.aidl.IXmppConnection;
import com.beem.project.beem.smack.avatar.AvatarCache;
import com.beem.project.beem.smack.avatar.AvatarListener;
import com.beem.project.beem.smack.avatar.AvatarMetadataExtension;
import com.beem.project.beem.smack.pep.PepSubManager;
import com.beem.project.beem.smack.ping.PingExtension;
import com.beem.project.beem.ui.ChangeStatus;
import com.beem.project.beem.ui.Subscription;
import com.beem.project.beem.utils.BeemBroadcastReceiver;
import com.beem.project.beem.utils.Status;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.PrivacyListManager;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketFilter;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.ChatStateManager;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
import org.jivesoftware.smackx.packet.DiscoverInfo;

/**
 * This class implements an adapter for XMPPConnection.
 * @author darisk
 */
public class XmppConnectionAdapter extends IXmppConnection.Stub {

    /**
     * Beem connection closed Intent name.
     */

    private static final int SMACK_PRIORITY_MIN = -128;
    private static final int SMACK_PRIORITY_MAX = 128;
    private static final String TAG = "XMPPConnectionAdapter";
    private final XMPPConnection mAdaptee;
    private IChatManager mChatManager;
    private final String mLogin;
    private final String mPassword;
    private String mResource;
    private String mErrorMsg;
    private RosterAdapter mRoster;
    private int mPreviousPriority;
    private int mPreviousMode;
    private String mPreviousStatus;
    private PrivacyListManagerAdapter mPrivacyListManager;
    private ChatStateManager mChatStateManager;
    private final BeemService mService;
    private BeemApplication mApplication;
    private BeemAvatarManager mAvatarManager;
    private PepSubManager mPepManager;
    private SharedPreferences mPref;
    private final RemoteCallbackList<IBeemConnectionListener> mRemoteConnListeners = new RemoteCallbackList<IBeemConnectionListener>();
    private final SubscribePacketListener mSubscribePacketListener = new SubscribePacketListener();
    private final PingListener mPingListener = new PingListener();

    private final ConnexionListenerAdapter mConListener = new ConnexionListenerAdapter();

    private UserInfo mUserInfo;
    private final UserInfoManager mUserInfoManager = new UserInfoManager();

    /**
     * Constructor.
     * @param config Configuration to use in order to connect
     * @param login login to use on connect
     * @param password password to use on connect
     * @param service the background service associated with the connection.
     */
    public XmppConnectionAdapter(final ConnectionConfiguration config, final String login, final String password,
            final BeemService service) {
        this(new XMPPConnection(config), login, password, service);
    }

    /**
     * Constructor.
     * @param serviceName name of the service to connect to
     * @param login login to use on connect
     * @param password password to use on connect
     * @param service the background service associated with the connection.
     */
    public XmppConnectionAdapter(final String serviceName, final String login, final String password,
            final BeemService service) {
        this(new XMPPConnection(serviceName), login, password, service);
    }

    /**
     * Constructor.
     * @param con The connection to adapt
     * @param login The login to use
     * @param password The password to use
     * @param service the background service associated with the connection.
     */
    public XmppConnectionAdapter(final XMPPConnection con, final String login, final String password,
            final BeemService service) {
        mAdaptee = con;
        PrivacyListManager.getInstanceFor(mAdaptee);
        mLogin = login;
        mPassword = password;
        mService = service;
        Context ctx = mService.getApplicationContext();
        if (ctx instanceof BeemApplication) {
            mApplication = (BeemApplication) ctx;
        }
        mPref = mService.getServicePreference();
        try {
            mPreviousPriority = Integer.parseInt(mPref.getString(BeemApplication.CONNECTION_PRIORITY_KEY, "0"));
        } catch (NumberFormatException ex) {
            mPreviousPriority = 0;
        }
        mResource = mPref.getString(BeemApplication.CONNECTION_RESOURCE_KEY, "Beem");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void addConnectionListener(IBeemConnectionListener listen) throws RemoteException {
        if (listen != null)
            mRemoteConnListeners.register(listen);
    }

    @Override
    public boolean connect() throws RemoteException {
        if (mAdaptee.isConnected())
            return true;
        else {
            try {
                mAdaptee.connect();
                mAdaptee.addConnectionListener(mConListener);
                return true;
            } catch (XMPPException e) {
                Log.e(TAG, "Error while connecting", e);
                try {
                    //TODO NIKITA DOES SOME SHIT !!! Fix this monstruosity
                    String str = mService.getResources().getString(mService.getResources().getIdentifier(
                            e.getXMPPError().getCondition().replace("-", "_"), "string", "com.beem.project.beem"));
                    mErrorMsg = str;
                } catch (NullPointerException e2) {
                    if (!"".equals(e.getMessage()))
                        mErrorMsg = e.getMessage();
                    else
                        mErrorMsg = e.toString();
                }
            }
            return false;
        }
    }

    @Override
    public boolean login() throws RemoteException {
        if (mAdaptee.isAuthenticated())
            return true;
        if (!mAdaptee.isConnected())
            return false;
        try {

            this.initFeatures(); // pour declarer les features xmpp qu'on
            // supporte

            PacketFilter filter = new PacketFilter() {

                @Override
                public boolean accept(Packet packet) {
                    if (packet instanceof Presence) {
                        Presence pres = (Presence) packet;
                        if (pres.getType() == Presence.Type.subscribe)
                            return true;
                    }
                    return false;
                }
            };

            mAdaptee.addPacketListener(mSubscribePacketListener, filter);

            filter = new PacketTypeFilter(PingExtension.class);
            mAdaptee.addPacketListener(mPingListener, filter);

            mAdaptee.login(mLogin, mPassword, mResource);
            mUserInfo = new UserInfo(mAdaptee.getUser());
            discoverServerFeatures();

            mChatManager = new BeemChatManager(mAdaptee.getChatManager(), mService, mAdaptee.getRoster());
            //nikita: I commented this line because of the logs provided in http://www.beem-project.com/issues/321
            //Also, since the privacylistmanager isn't finished and used, it will be safer to not initialize it
            //mPrivacyListManager = new PrivacyListManagerAdapter(PrivacyListManager.getInstanceFor(mAdaptee));
            mService.initJingle(mAdaptee);

            mApplication.setConnected(true);
            int mode = mPref.getInt(BeemApplication.STATUS_KEY, 0);
            String status = mPref.getString(BeemApplication.STATUS_TEXT_KEY, "");
            changeStatus(mode, status);
            return true;
        } catch (XMPPException e) {
            Log.e(TAG, "Error while connecting", e);
            mErrorMsg = mService.getString(R.string.error_login_authentication);
            return false;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void connectAsync() throws RemoteException {
        if (mAdaptee.isConnected() || mAdaptee.isAuthenticated())
            return;
        Thread t = new Thread(new Runnable() {

            @Override
            public void run() {
                try {
                    connectSync();
                } catch (RemoteException e) {
                    Log.e(TAG, "Error while connecting asynchronously", e);
                }
            }
        });
        t.start();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean connectSync() throws RemoteException {
        if (connect())
            return login();
        return false;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void changeStatusAndPriority(int status, String msg, int priority) {
        Presence pres = new Presence(Presence.Type.available);
        String m;
        if (msg != null)
            m = msg;
        else
            m = mPreviousStatus;
        pres.setStatus(m);
        mPreviousStatus = m;
        Presence.Mode mode = Status.getPresenceModeFromStatus(status);
        if (mode != null) {
            pres.setMode(mode);
            mPreviousMode = status;
        } else {
            pres.setMode(Status.getPresenceModeFromStatus(mPreviousMode));
        }
        int p = priority;
        if (priority < SMACK_PRIORITY_MIN)
            p = SMACK_PRIORITY_MIN;
        if (priority > SMACK_PRIORITY_MAX)
            p = SMACK_PRIORITY_MAX;
        mPreviousPriority = p;
        pres.setPriority(p);
        mAdaptee.sendPacket(pres);
        updateNotification(Status.getStatusFromPresence(pres), m);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void changeStatus(int status, String msg) {
        changeStatusAndPriority(status, msg, mPreviousPriority);
    }

    /**
     * Get the AvatarManager of this connection.
     * @return the AvatarManager or null if there is not
     */
    public BeemAvatarManager getAvatarManager() {
        return mAvatarManager;
    }

    /**
     * get the previous status.
     * @return previous status.
     */
    public String getPreviousStatus() {
        return mPreviousStatus;
    }

    /**
     * get the previous mode.
     * @return previous mode.
     */
    public int getPreviousMode() {
        return mPreviousMode;
    }

    /**
     * Update the notification for the Beem status.
     * @param status the status to display.
     * @param text the text to display.
     */
    private void updateNotification(int status, String text) {
        Notification mStatusNotification;
        mStatusNotification = new Notification(Status.getIconBarFromStatus(status), text,
                System.currentTimeMillis());
        mStatusNotification.defaults = Notification.DEFAULT_LIGHTS;
        mStatusNotification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT;

        mStatusNotification.setLatestEventInfo(mService, "Beem Status", text,
                PendingIntent.getActivity(mService, 0, new Intent(mService, ChangeStatus.class), 0));
        // bypass the preferences for notification
        mService.getNotificationManager().notify(BeemService.NOTIFICATION_STATUS_ID, mStatusNotification);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean disconnect() {
        if (mAdaptee != null && mAdaptee.isConnected())
            mAdaptee.disconnect();
        return true;
    }

    /**
     * Get the Smack XmppConnection.
     * @return Smack XmppConnection
     */
    public XMPPConnection getAdaptee() {
        return mAdaptee;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IChatManager getChatManager() throws RemoteException {
        return mChatManager;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public IRoster getRoster() throws RemoteException {
        if (mRoster != null)
            return mRoster;
        Roster adap = mAdaptee.getRoster();
        if (adap == null)
            return null;
        mRoster = new RosterAdapter(adap, mService, mAvatarManager);
        return mRoster;
    }

    /**
     * Get the user informations.
     * @return the user infos or null if not logged
     */
    public UserInfo getUserInfo() {
        return mUserInfo;
    }

    /**
     * Returns true if currently authenticated by successfully calling the login method.
     * @return true when successfully authenticated
     */
    public boolean isAuthentificated() {
        return mAdaptee.isAuthenticated();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeConnectionListener(IBeemConnectionListener listen) throws RemoteException {
        if (listen != null)
            mRemoteConnListeners.unregister(listen);
    }

    /**
     * PrivacyListManagerAdapter mutator.
     * @param privacyListManager the privacy list manager
     */
    public void setPrivacyListManager(PrivacyListManagerAdapter privacyListManager) {
        this.mPrivacyListManager = privacyListManager;
    }

    /**
     * PrivacyListManagerAdapter accessor.
     * @return the mPrivacyList
     */
    public PrivacyListManagerAdapter getPrivacyListManager() {
        return mPrivacyListManager;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getErrorMessage() {
        return mErrorMsg;
    }

    /**
     * Initialize the features provided by beem.
     */
    private void initFeatures() {
        ServiceDiscoveryManager.setIdentityName("Beem");
        ServiceDiscoveryManager.setIdentityType("phone");
        ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(mAdaptee);
        if (sdm == null)
            sdm = new ServiceDiscoveryManager(mAdaptee);

        sdm.addFeature("http://jabber.org/protocol/disco#info");
        //nikita: must be uncommented when the feature will be enabled
        //sdm.addFeature("jabber:iq:privacy");
        sdm.addFeature("http://jabber.org/protocol/caps");
        sdm.addFeature("urn:xmpp:avatar:metadata");
        sdm.addFeature("urn:xmpp:avatar:metadata+notify");
        sdm.addFeature("urn:xmpp:avatar:data");
        sdm.addFeature("http://jabber.org/protocol/nick");
        sdm.addFeature("http://jabber.org/protocol/nick+notify");
        sdm.addFeature(PingExtension.NAMESPACE);

        mChatStateManager = ChatStateManager.getInstance(mAdaptee);

        EntityCapsManager em = sdm.getEntityCapsManager();
        em.setNode("http://www.beem-project.com");
    }

    /**
     * Discover the features provided by the server.
     */
    private void discoverServerFeatures() {
        try {
            // jid et server
            ServiceDiscoveryManager sdm = ServiceDiscoveryManager.getInstanceFor(mAdaptee);
            DiscoverInfo info = sdm.discoverInfo(mAdaptee.getServiceName());
            Iterator<DiscoverInfo.Identity> it = info.getIdentities();
            while (it.hasNext()) {
                DiscoverInfo.Identity identity = it.next();
                if ("pubsub".equals(identity.getCategory()) && "pep".equals(identity.getType())) {
                    initPEP();
                }
            }
        } catch (XMPPException e) {
            Log.w(TAG, "Unable to discover server features", e);
        }
    }

    /**
     * Initialize PEP.
     */
    private void initPEP() {
        // Enable pep sending
        Log.d(TAG, "Pep enabled");
        // API 8
        // mService.getExternalCacheDir()
        mPepManager = new PepSubManager(mAdaptee, StringUtils.parseBareAddress(mAdaptee.getUser()));
        AvatarCache avatarCache = new BeemAvatarCache(mService);
        mAvatarManager = new BeemAvatarManager(mService, mAdaptee, mPepManager, avatarCache, true);
        mAvatarManager.addAvatarListener(mUserInfoManager);
        mApplication.setPepEnabled(true);
    }

    /**
     * Reset the application state.
     */
    private void resetApplication() {
        mApplication.setConnected(false);
        mApplication.setPepEnabled(false);
    }

    /**
     * Listener for XMPP connection events. It will calls the remote listeners for connection events.
     */
    private class ConnexionListenerAdapter implements ConnectionListener {

        /**
         * Defaut constructor.
         */
        public ConnexionListenerAdapter() {
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void connectionClosed() {
            Log.d(TAG, "closing connection");
            mRoster = null;
            Intent intent = new Intent(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED);
            intent.putExtra("message", mService.getString(R.string.BeemBroadcastReceiverDisconnect));
            intent.putExtra("normally", true);
            mService.sendBroadcast(intent);
            mService.stopSelf();
            resetApplication();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void connectionClosedOnError(Exception exception) {
            Log.d(TAG, "connectionClosedOnError");
            mRoster = null;
            Intent intent = new Intent(BeemBroadcastReceiver.BEEM_CONNECTION_CLOSED);
            intent.putExtra("message", exception.getMessage());
            mService.sendBroadcast(intent);
            mService.stopSelf();
            resetApplication();
        }

        /**
         * Connection failed callback.
         * @param errorMsg smack failure message
         */
        public void connectionFailed(String errorMsg) {
            Log.d(TAG, "Connection Failed");
            final int n = mRemoteConnListeners.beginBroadcast();

            for (int i = 0; i < n; i++) {
                IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
                try {
                    if (listener != null)
                        listener.connectionFailed(errorMsg);
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing the
                    // dead listeners.
                    Log.w(TAG, "Error while triggering remote connection listeners", e);
                }
            }
            mRemoteConnListeners.finishBroadcast();
            mService.stopSelf();
            resetApplication();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void reconnectingIn(int arg0) {
            Log.d(TAG, "reconnectingIn");
            final int n = mRemoteConnListeners.beginBroadcast();

            for (int i = 0; i < n; i++) {
                IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
                try {
                    if (listener != null)
                        listener.reconnectingIn(arg0);
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing the
                    // dead listeners.
                    Log.w(TAG, "Error while triggering remote connection listeners", e);
                }
            }
            mRemoteConnListeners.finishBroadcast();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void reconnectionFailed(Exception arg0) {
            Log.d(TAG, "reconnectionFailed");
            final int r = mRemoteConnListeners.beginBroadcast();

            for (int i = 0; i < r; i++) {
                IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
                try {
                    if (listener != null)
                        listener.reconnectionFailed();
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing the
                    // dead listeners.
                    Log.w(TAG, "Error while triggering remote connection listeners", e);
                }
            }
            mRemoteConnListeners.finishBroadcast();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void reconnectionSuccessful() {
            Log.d(TAG, "reconnectionSuccessful");
            mApplication.setConnected(true);
            PacketFilter filter = new PacketFilter() {

                @Override
                public boolean accept(Packet packet) {
                    if (packet instanceof Presence) {
                        Presence pres = (Presence) packet;
                        if (pres.getType() == Presence.Type.subscribe)
                            return true;
                    }
                    return false;
                }
            };

            mAdaptee.addPacketListener(mSubscribePacketListener, filter);

            final int n = mRemoteConnListeners.beginBroadcast();

            for (int i = 0; i < n; i++) {
                IBeemConnectionListener listener = mRemoteConnListeners.getBroadcastItem(i);
                try {
                    if (listener != null)
                        listener.reconnectionSuccessful();
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing the
                    // dead listeners.
                    Log.w(TAG, "Error while triggering remote connection listeners", e);
                }
            }
            mRemoteConnListeners.finishBroadcast();
        }
    }

    /**
     * This PacketListener will set a notification when you got a subscribtion request.
     * @author Da Risk <da_risk@elyzion.net>
     */
    private class SubscribePacketListener implements PacketListener {

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

        @Override
        public void processPacket(Packet packet) {
            String from = packet.getFrom();

            NotificationCompat.Builder notif = new NotificationCompat.Builder(mService);
            String title = mService.getString(R.string.AcceptContactRequest, from);
            String text = mService.getString(R.string.AcceptContactRequestFrom, from);
            notif.setTicker(title).setContentTitle(title);
            notif.setContentText(text);
            notif.setSmallIcon(R.drawable.beem_status_icon_gray);
            notif.setAutoCancel(true).setWhen(System.currentTimeMillis());

            Intent intent = new Intent(mService, Subscription.class);
            intent.setData(Contact.makeXmppUri(from));
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            PendingIntent notifIntent = PendingIntent.getActivity(mService, 0, intent, PendingIntent.FLAG_ONE_SHOT);
            notif.setContentIntent(notifIntent);

            int id = packet.hashCode();
            mService.sendNotification(id, notif.getNotification());
        }
    }

    /**
     * The UserInfoManager listen to XMPP events and update the user information accoldingly.
     */
    private class UserInfoManager implements AvatarListener {

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

        @Override
        public void onAvatarChange(String from, String avatarId, List<AvatarMetadataExtension.Info> avatarInfos) {
            String jid = StringUtils.parseBareAddress(mUserInfo.getJid());
            String mfrom = StringUtils.parseBareAddress(from);
            if (jid.equalsIgnoreCase(mfrom)) {
                mUserInfo.setAvatarId(avatarId);
            }
        }
    }

    /**
     * Listener for Ping request. It will respond with a Pong.
     */
    private class PingListener implements PacketListener {

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

        }

        @Override
        public void processPacket(Packet packet) {
            if (!(packet instanceof PingExtension))
                return;
            PingExtension p = (PingExtension) packet;
            if (p.getType() == IQ.Type.GET) {
                PingExtension pong = new PingExtension();
                pong.setType(IQ.Type.RESULT);
                pong.setTo(p.getFrom());
                pong.setPacketID(p.getPacketID());
                mAdaptee.sendPacket(pong);
            }
        }
    }

}