org.bombusim.lime.fragments.RosterFragment.java Source code

Java tutorial

Introduction

Here is the source code for org.bombusim.lime.fragments.RosterFragment.java

Source

/*
 * Copyright (c) 2005-2011, Eugene Stahov (evgs@bombus-im.org), 
 * http://bombus-im.org
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this software; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.bombusim.lime.fragments;

import java.util.ArrayList;
import java.util.Collections;

import org.bombusim.lime.Lime;
import org.bombusim.lime.R;
import org.bombusim.lime.activity.About;
import org.bombusim.lime.activity.AccountSettingsActivity;
import org.bombusim.lime.activity.ActiveChats;
import org.bombusim.lime.activity.ChatActivity;
import org.bombusim.lime.activity.EditContactActivity;
import org.bombusim.lime.activity.LoggerActivity;
import org.bombusim.lime.activity.PresenceActivity;
import org.bombusim.lime.activity.RosterActivity;
import org.bombusim.lime.activity.VCardActivity;
import org.bombusim.lime.activity.preferences.LimePrefs;
import org.bombusim.lime.activity.preferences.LimePrefsHC;
import org.bombusim.lime.data.Contact;
import org.bombusim.lime.data.Roster;
import org.bombusim.lime.data.RosterGroup;
import org.bombusim.lime.data.SelfContact;
import org.bombusim.lime.service.XmppService;
import org.bombusim.lime.service.XmppServiceBinding;
import org.bombusim.lime.widgets.AccountViewFactory;
import org.bombusim.lime.widgets.ContactViewFactory;
import org.bombusim.lime.widgets.GroupViewFactory;
import org.bombusim.xmpp.XmppAccount;
import org.bombusim.xmpp.handlers.IqRoster;
import org.bombusim.xmpp.stanza.XmppPresence;

import com.actionbarsherlock.app.SherlockListFragment;

import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ListFragment;
import android.util.Log;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;

public class RosterFragment extends SherlockListFragment {
    XmppServiceBinding sb;

    private Bitmap[] statusIcons;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setHasOptionsMenu(true);

        statusIcons = new Bitmap[] { BitmapFactory.decodeResource(getResources(), R.drawable.status_offline),
                BitmapFactory.decodeResource(getResources(), R.drawable.status_online),
                BitmapFactory.decodeResource(getResources(), R.drawable.status_chat),
                BitmapFactory.decodeResource(getResources(), R.drawable.status_away),
                BitmapFactory.decodeResource(getResources(), R.drawable.status_xa),
                BitmapFactory.decodeResource(getResources(), R.drawable.status_dnd),
                BitmapFactory.decodeResource(getResources(), R.drawable.status_ask),
                BitmapFactory.decodeResource(getResources(), R.drawable.status_unknown),
                BitmapFactory.decodeResource(getResources(), R.drawable.status_invisible) };

        ListAdapter adapter = new RosterAdapter(getActivity(), statusIcons);

        setListAdapter(adapter);

        sb = new XmppServiceBinding(getActivity());

        //temporary
        Lime.getInstance().saveBinding(sb);

    }

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        registerForContextMenu(getListView());
    }

    protected void showSslStatus(String certificateChain) {
        if (certificateChain == null)
            return;
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setTitle(R.string.sslInfo).setIcon(R.drawable.ssl_yes).setMessage(certificateChain)
                .setPositiveButton(R.string.close, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                });
        AlertDialog alert = builder.create();
        alert.setOwnerActivity(getActivity());
        alert.show();
    }

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        //TODO: collapse/expand account

        Object item = getListAdapter().getItem(position);

        if (item instanceof RosterGroup) {
            ((RosterGroup) item).toggleCollapsed();
            refreshVisualContent();
            return;
        }
        if (item instanceof XmppAccount) {
            ((XmppAccount) item).toggleCollapsed();
            refreshVisualContent();
            return;
        }
        if (item instanceof Contact) {
            Contact c = (Contact) item;
            openChatActivity(c);
        }
    }

    @Override
    public void onCreateOptionsMenu(com.actionbarsherlock.view.Menu menu,
            com.actionbarsherlock.view.MenuInflater inflater) {
        inflater.inflate(R.menu.roster_menu, menu);

        super.onCreateOptionsMenu(menu, inflater);

        //enable items available only if logged in
        //TODO: modify behavior if multiple account
        /*
        String rJid = Lime.getInstance().accounts.get(0).userJid;
        menu.setGroupEnabled(R.id.groupLoggedIn, sb.isLoggedIn(rJid) );
        */
    };

    @Override
    public boolean onOptionsItemSelected(com.actionbarsherlock.view.MenuItem item) {
        Context context = getActivity().getBaseContext();
        switch (item.getItemId()) {
        case R.id.cmdLogin:
            startActivityForResult(new Intent(context, PresenceActivity.class), 0);
            break;

        case R.id.cmdAddContact:
            openEditContactActivity(null);
            break;

        case R.id.cmdAccount:
            startActivityForResult(new Intent(context, AccountSettingsActivity.class), 0);
            break;

        case R.id.cmdChat: {
            ActiveChats chats = new ActiveChats();
            chats.showActiveChats(getActivity(), null);

            break;
        }

        case R.id.cmdLog:
            startActivity(new Intent(context, LoggerActivity.class));
            break;
        case R.id.cmdSettings: {
            if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.HONEYCOMB) {
                startActivity(new Intent(context, LimePrefs.class));
            } else {
                startActivity(new Intent(context, LimePrefsHC.class));
            }
            break;
        }
        case R.id.cmdAbout:
            About.showAboutDialog(getActivity());
            break;
        default:
            return true; // on submenu
        }

        return false;
    }

    Object contextItem;

    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {

        super.onCreateContextMenu(menu, v, menuInfo);

        int pos = ((AdapterContextMenuInfo) menuInfo).position;

        contextItem = getListAdapter().getItem(pos);

        if (contextItem instanceof RosterGroup) {
            //TODO: context menu for group
            return;
        }
        if (contextItem instanceof XmppAccount) {
            //TODO: context menu for account
            return;
        }

        if (contextItem instanceof Contact) {
            Contact c = (Contact) contextItem;
            menu.setHeaderTitle(c.getScreenName());

            Drawable icon = new BitmapDrawable(c.getAvatar());
            menu.setHeaderIcon(icon);

            MenuInflater inflater = getActivity().getMenuInflater();

            if (contextItem instanceof SelfContact) {
                inflater.inflate(R.menu.contact_self_menu, menu);
            } else {
                inflater.inflate(R.menu.contact_menu, menu);
            }

            //enable items available only if logged in
            menu.setGroupEnabled(R.id.groupLoggedIn, sb.isLoggedIn(c.getRosterJid()));
            return;
        }
    }

    public boolean onContextItemSelected(MenuItem item) {

        AdapterContextMenuInfo cmi = (AdapterContextMenuInfo) item.getMenuInfo();

        //contextItem = getListAdapter().getItem(cmi.position);

        if (contextItem instanceof Contact) {
            final Contact ctc = (Contact) contextItem;

            switch (item.getItemId()) {
            case R.id.cmdChat:
                openChatActivity(ctc);
                return true;
            case R.id.cmdVcard:
                openVCardActivity(ctc);
                return true;
            case R.id.cmdEdit:
                openEditContactActivity(ctc);
                return true;
            case R.id.cmdDelete:
                confirmDeleteContact(ctc);
                return true;

            case R.id.cmdSubscrRequestFrom:
                subscription(ctc, XmppPresence.PRESENCE_SUBSCRIBE);
                return true;
            case R.id.cmdSubscrSendTo:
                subscription(ctc, XmppPresence.PRESENCE_SUBSCRIBED);
                return true;
            case R.id.cmdSubscrRemove:
                subscription(ctc, XmppPresence.PRESENCE_UNSUBSCRIBED);
                return true;

            default:
                Toast.makeText(getActivity(), "Not implemented yet", Toast.LENGTH_SHORT).show();
            }
        }

        return super.onContextItemSelected(item);
    }

    private class RosterBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            String from = intent.getStringExtra("param");

            refreshVisualContent();
        }

    }

    void refreshSingleContact(Contact c) {
        ListView lv = getListView();

        int firstVisible = lv.getFirstVisiblePosition();
        int lastVisible = lv.getLastVisiblePosition();

        for (int position = firstVisible; position <= lastVisible; position++) {
            try {
                Contact clv = (Contact) lv.getItemAtPosition(position);
                if (c == clv) {

                    //TODO: remove this workaround
                    refreshVisualContent();
                    /*
                    View cv = lv.getChildAt(position);
                    if (cv !=null) { 
                    cv.invalidate();
                    } else {
                    //can't find view for contact, so refresh all
                    //TODO: is it real need to refresh all?
                    refreshVisualContent();
                    return;
                    }*/
                }
            } catch (ClassCastException e) {
            }
        }

    }

    //TODO: update only group if presence update 
    void refreshVisualContent() {
        //TODO: fix update
        //updateRosterTitle();

        RosterAdapter ra = (RosterAdapter) getListAdapter();
        ra.populateRosterObjects();

    }

    RosterBroadcastReceiver br;

    private boolean mFragmentActive = false;

    @Override
    public void onResume() {
        mFragmentActive = true;

        super.onResume();

        sb.setBindListener(new XmppServiceBinding.BindListener() {
            @Override
            public void onBindService(XmppService service) {
                //updateRosterTitle();
            }
        });

        sb.doBindService();
        //update view to actual state
        refreshVisualContent();
        br = new RosterBroadcastReceiver();
        getActivity().registerReceiver(br, new IntentFilter(Roster.UPDATE_CONTACT));
    }

    @Override
    public void onPause() {
        mFragmentActive = false;

        sb.doUnbindService();
        getActivity().unregisterReceiver(br);
        super.onPause();
    }

    //todo: move logic out of fragment
    public void openChatActivity(Contact c) {
        ((RosterActivity) getActivity()).openChat(c.getJid(), c.getRosterJid());
    }

    //todo: move logic out of fragment
    private void openVCardActivity(Contact c) {
        Intent openVcard = new Intent(getActivity(), VCardActivity.class);
        openVcard.putExtra(VCardActivity.MY_JID, c.getRosterJid());
        openVcard.putExtra(VCardActivity.JID, c.getJid());
        startActivity(openVcard);
    }

    //todo: move logic out of fragment
    private void openEditContactActivity(Contact c) {
        Intent openEditContact = new Intent(getActivity(), EditContactActivity.class);
        if (c != null) {
            openEditContact.putExtra(EditContactActivity.MY_JID, c.getRosterJid());
            openEditContact.putExtra(EditContactActivity.JID, c.getJid());
        } else {
            String rJid = Lime.getInstance().getActiveAccount().userJid;
            openEditContact.putExtra(EditContactActivity.MY_JID, rJid);
            //openEditContact.putExtra(EditContactActivity.JID,   c.getJid());
        }
        startActivity(openEditContact);
    }

    private void confirmDeleteContact(final Contact ctc) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
        builder.setTitle(ctc.getFullName()).setIcon(new BitmapDrawable(ctc.getAvatar()))
                .setMessage(R.string.confirmDelete)
                .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        deleteContact(ctc);
                    }
                }).setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                });
        AlertDialog alert = builder.create();
        alert.setOwnerActivity(getActivity());
        alert.show();
    }

    private void subscription(Contact contact, String subscriptionAction) {
        IqRoster.setSubscription(contact.getJid(), subscriptionAction, sb.getXmppStream(contact.getRosterJid()));
    }

    protected void deleteContact(Contact c) {
        IqRoster.deleteContact(c.getJid(), sb.getXmppStream(c.getRosterJid()));
    }

    private class RosterAdapter extends BaseAdapter {

        public final static int ITEM_ACCOUNT = 0;
        public final static int ITEM_CONTACT = 1;
        public final static int ITEM_GROUP = 2;

        public final static int ITEM_TYPECOUNT = 3;

        private ArrayList mRosterObjects;

        private ArrayList<RosterGroup> mGroups;

        private ContactViewFactory cvf;
        private GroupViewFactory gvf;
        private AccountViewFactory avf;

        public RosterAdapter(Context context, Bitmap[] statusIcons) {

            cvf = new ContactViewFactory(context, statusIcons);
            avf = new AccountViewFactory(context, statusIcons);
            gvf = new GroupViewFactory(context);

            mRosterObjects = new ArrayList();

            mGroups = new ArrayList<RosterGroup>();
        }

        @Override
        public int getCount() {
            return mRosterObjects.size();
        }

        @Override
        public Object getItem(int position) {
            return mRosterObjects.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public boolean hasStableIds() {
            return true;
        }

        @Override
        public int getViewTypeCount() {
            return ITEM_TYPECOUNT;
        }

        @Override
        public int getItemViewType(int position) {
            Object o = mRosterObjects.get(position);
            if (o instanceof XmppAccount)
                return ITEM_ACCOUNT;
            if (o instanceof RosterGroup)
                return ITEM_GROUP;
            if (o instanceof Contact)
                return ITEM_CONTACT;

            return -1;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            // TODO Auto-generated method stub
            Object o = mRosterObjects.get(position);

            if (o instanceof Contact)
                return cvf.getView(convertView, (Contact) o);
            if (o instanceof RosterGroup)
                return gvf.getView(convertView, (RosterGroup) o);
            if (o instanceof XmppAccount)
                return avf.getView(convertView, (XmppAccount) o);

            return null;
        }

        private void addContactToGroup(Contact contact, String groupName) {
            for (RosterGroup g : mGroups) {
                if (!g.rJid.equals(contact.getRosterJid()))
                    continue;

                if (g.groupName.equals(groupName)) {

                    g.contacts.add(contact);

                    if (contact.isAvailable())
                        g.onlineCount++;

                    return;
                }
            }

            RosterGroup ng = new RosterGroup(groupName, contact.getRosterJid());
            ng.contacts.add(contact);
            mGroups.add(ng);
        }

        private final static long PRESENCE_GROUPING_TIME_MS = 500;

        private long mLastUpdate;
        private int mUpdateLock;
        private Object mUpdateSyncObject = new Object();

        public void populateRosterObjects() {

            synchronized (mUpdateSyncObject) {
                long now = System.currentTimeMillis();

                mUpdateLock++;
                if (now - mLastUpdate < PRESENCE_GROUPING_TIME_MS) {
                    mUpdateLock++;
                }

                Log.d("Roster", "Triggered: " + mUpdateLock);

                mLastUpdate = now;

                if (mUpdateLock > 3)
                    return;

            }

            new PresenceUpdateBunch().execute();
        }

        void releaseLock() {
            synchronized (mUpdateSyncObject) {
                Log.d("Roster", "Updates: " + mUpdateLock);
                mUpdateLock--;
            }
        }

        /**
         * Asynchronous refreshing roster structure
         * @author evgs
         *
         */

        private class PresenceUpdateBunch extends AsyncTask<Void, Integer, ArrayList> {

            @Override
            protected ArrayList doInBackground(Void... params) {

                if (mUpdateLock > 1)
                    try {
                        Thread.sleep(PRESENCE_GROUPING_TIME_MS);
                    } catch (InterruptedException e) {
                        /* normal case */ }

                synchronized (mUpdateSyncObject) {
                    mUpdateLock = 1;
                }

                boolean hideOfflines = Lime.getInstance().prefs.hideOfflines;

                ArrayList rosterObjects = new ArrayList();

                ArrayList<XmppAccount> accounts = Lime.getInstance().getAccounts();

                for (XmppAccount a : accounts) {

                    rosterObjects.add(a);

                    if (a.collapsed) {
                        // next account
                        continue;
                    }

                    if (a.userJid == null) {
                        //TODO: remove this temporary workaround for empty account
                        releaseLock();
                        return rosterObjects;
                    }

                    synchronized (mGroups) {

                        Roster roster = Lime.getInstance().getRoster();

                        //0.1 add selfcontact

                        Contact self = roster.getSelfContact(a.userJid);
                        if (Lime.getInstance().prefs.selfContact || self.getOnlineResourceCount() > 1)
                            rosterObjects.add(self);

                        ArrayList<Contact> contacts = roster.getContactsCopy();

                        //1. reset groups
                        for (RosterGroup group : mGroups) {
                            if (group.rJid.equals(a.userJid))
                                group.clear();
                        }

                        //2. populate groups with contacts

                        for (Contact contact : contacts) {

                            if (contact instanceof SelfContact)
                                continue;

                            // collating roster by rJid
                            if (!contact.getRosterJid().equals(a.userJid))
                                continue;

                            String allGroups = contact.getAllGroups();
                            if (allGroups == null) {
                                //TODO: group sorting indexes
                                addContactToGroup(contact, "- No group");
                                continue;
                            }

                            String cgroups[] = allGroups.split("\t");
                            for (String cg : cgroups) {
                                addContactToGroup(contact, cg);
                            }
                        }

                        //3. remove empty groups
                        //3.1 sort non-empty groups
                        int i = 0;
                        while (i < mGroups.size()) {
                            if (mGroups.get(i).contacts.isEmpty()) {
                                mGroups.remove(i);
                            } else {
                                mGroups.get(i).sort();
                                i++;
                            }
                        }

                        //4. order groups
                        //TODO: optimize sorting
                        Collections.sort(mGroups);

                        //5. add groups to roster
                        //TODO 5.1 check if account collapsed

                        for (RosterGroup group : mGroups) {

                            if (!group.rJid.equals(a.userJid))
                                continue;

                            rosterObjects.add(group);

                            //skip contacts if group collapsed
                            if (group.collapsed)
                                continue;

                            for (Contact contact : group.contacts) {
                                // skip offlines
                                if (hideOfflines)
                                    if (!contact.isAvailable())
                                        continue;

                                rosterObjects.add(contact);
                            }
                        }

                    } //synchronized (mgroups
                } // accounts loop

                //TODO: add MUC
                releaseLock();
                return rosterObjects;
            }

            @Override
            protected void onPostExecute(ArrayList result) {
                if (result == null)
                    return;

                if (!mFragmentActive)
                    return;

                ListView lv = getListView();

                lv.setVisibility(View.GONE);

                mRosterObjects = result;

                lv.invalidate();
                notifyDataSetChanged();

                lv.setVisibility(View.VISIBLE);
            }
        }

    }

}