FileDocCategorySizeDatePackage
ImpsContactListManager.javaAPI DocAndroid 1.5 API38508Wed May 06 22:42:46 BST 2009com.android.im.imps

ImpsContactListManager.java

/*
 * Copyright (C) 2007-2008 Esmertec AG.
 * Copyright (C) 2007-2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.im.imps;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Vector;

import com.android.im.engine.Address;
import com.android.im.engine.Contact;
import com.android.im.engine.ContactList;
import com.android.im.engine.ContactListListener;
import com.android.im.engine.ContactListManager;
import com.android.im.engine.ImConnection;
import com.android.im.engine.ImErrorInfo;
import com.android.im.engine.ImException;
import com.android.im.engine.Presence;
import com.android.im.engine.SubscriptionRequestListener;
import com.android.im.imps.ImpsConstants.ImpsVersion;

/**
 * An implementation of ContactListManager of Wireless Village IMPS protocol.
 */
public class ImpsContactListManager extends ContactListManager
        implements ServerTransactionListener {
    private ImpsConnection mConnection;
    private String mDefaultDomain;
    ImpsTransactionManager mTransactionManager;
    ImpsConnectionConfig mConfig;

    boolean mAllowAutoSubscribe = true;

    ArrayList<Contact> mSubscriptionRequests;

    /**
     * Constructs the manager with specific connection.
     *
     * @param connection the connection related to the manager
     */
    ImpsContactListManager(ImpsConnection connection) {
        mConnection = connection;
        mConfig = connection.getConfig();
        mDefaultDomain = mConfig.getDefaultDomain();
        mTransactionManager = connection.getTransactionManager();

        mTransactionManager.setTransactionListener(
                ImpsTags.PresenceNotification_Request, this);
        mTransactionManager.setTransactionListener(
                ImpsTags.PresenceAuth_Request, this);
    }

    @Override
    public Contact createTemporaryContact(String address){
        ImpsAddress impsAddr = new ImpsUserAddress(normalizeAddress(address));
        return new Contact(impsAddr, impsAddr.getScreenName());
    }

    @Override
    public String normalizeAddress(String address) {
        String s = address.toLowerCase();
        if (!s.startsWith(ImpsConstants.ADDRESS_PREFIX)) {
            s = ImpsConstants.ADDRESS_PREFIX + s;
        }
        if (mDefaultDomain != null && s.indexOf('@') == -1) {
            s = s + "@" + mDefaultDomain;
        }
        return s;
    }

    @Override
    public synchronized void loadContactListsAsync() {
        if (getState() != LISTS_NOT_LOADED) {
            return;
        }

        setState(LISTS_LOADING);

        // load blocked list first
        Primitive request = new Primitive(ImpsTags.GetBlockedList_Request);

        AsyncTransaction tx = new AsyncTransaction(mTransactionManager) {
            @Override
            public void onResponseError(ImpsErrorInfo error) {
                // don't notify the 501 not implemented error
                if (error.getCode() != ImpsConstants.STATUS_NOT_IMPLEMENTED) {
                    notifyContactError(
                            ContactListListener.ERROR_LOADING_BLOCK_LIST,
                            error, null, null);
                }

                next();
            }

            @Override
            public void onResponseOk(Primitive response) {
                extractBlockedContacts(response);

                next();
            }

            private void next() {
                setState(BLOCKED_LIST_LOADED);
                new LoadContactsTransaction().startGetContactLists();
                //createDefaultAttributeListAsync();
            }
        };

        tx.sendRequest(request);
    }

    Vector<ImpsContactListAddress> extractListAddresses(Primitive response){
        Vector<ImpsContactListAddress> addresses = new Vector<ImpsContactListAddress>();

        for (PrimitiveElement child : response.getContentElement()
                .getChildren()) {
            if (child.getTagName().equals(ImpsTags.ContactList)) {
                // FIXME: ignore the PEP contact lists for now
                // PEP: "Presence Enhanced Phonebook and Instant Messaging
                // Application Category" specification from Nokia and SonyEricsson.
                //  ~IM_subscriptions
                //  ~pep1.0_privatelist
                //  ~pep1.0_blocklist
                //  ~pep1.0_friendlist
                //  ~pep1.0_subscriptions-*
                if (child.getContents().contains("/~pep1.0_")) {
                    continue;
                }
                addresses.add(new ImpsContactListAddress(child.getContents()));
            }
        }

        String defaultListAddress = response.getElementContents(ImpsTags.DefaultContactList);
        if (null != defaultListAddress) {
            addresses.add(new ImpsContactListAddress(defaultListAddress));
        }

        return addresses;
    }

    public void fetchPresence(ImpsAddress[] addresses) {
        if (addresses == null || addresses.length == 0) {
            return;
        }

        Primitive request = new Primitive(ImpsTags.GetPresence_Request);
        for (ImpsAddress addr : addresses) {
            request.addElement(addr.toPrimitiveElement());
        }
        AsyncTransaction tx = new AsyncTransaction(mTransactionManager){
            @Override
            public void onResponseError(ImpsErrorInfo error) {
                ImpsLog.logError("Failed to get presence:" + error.toString());
            }

            @Override
            public void onResponseOk(Primitive response) {
                extractAndNotifyPresence(response.getContentElement());
            }
        };
        tx.sendRequest(request);
    }

    public ImpsAddress[] getAllListAddress() {
        int count = mContactLists.size();
        ImpsAddress[] res = new ImpsContactListAddress[count];

        int index = 0;
        for (ContactList l : mContactLists) {
            res[index++] = (ImpsContactListAddress) l.getAddress();
        }

        return res;
    }

//    void createDefaultAttributeListAsync() {
//        Primitive request = new Primitive(ImpsTags.CreateAttributeList_Request);
//
//        PrimitiveElement presenceList = request.addElement(ImpsTags.PresenceSubList);
//        presenceList.setAttribute(ImpsTags.XMLNS, mConnection.getConfig().getPresenceNs());
//
//        for (String tagName : ImpsClientCapability.getSupportedPresenceAttribs()) {
//            presenceList.addChild(tagName);
//        }
//
//        request.addElement(ImpsTags.DefaultList, true);
//
//        AsyncTransaction tx = new AsyncTransaction(mTransactionManager) {
//            @Override
//            public void onResponseError(ImpsErrorInfo error) {
//                // don't notify the 501 not implemented error
//                if(error.getCode() != ImpsConstants.STATUS_NOT_IMPLEMENTED) {
//                    // TODO: not ERROR_RETRIEVING_PRESENCE exactly here...
//                    notifyContactError(
//                            ContactListListener.ERROR_RETRIEVING_PRESENCE,
//                            error, null, null);
//                }
//            }
//
//            @Override
//            public void onResponseOk(Primitive response) {}
//        };
//
//        tx.sendRequest(request);
//    }

    @Override
    public void approveSubscriptionRequest(String contact) {
        handleSubscriptionRequest(contact, true);
    }

    @Override
    public void declineSubscriptionRequest(String contact) {
        handleSubscriptionRequest(contact, false);
    }

    private void handleSubscriptionRequest(final String contact, final boolean accept) {

        Primitive request = new Primitive(ImpsTags.PresenceAuthUser);
        request.addElement(ImpsTags.UserID, contact);
        request.addElement(ImpsTags.Acceptance, accept);
        AsyncTransaction tx = new AsyncTransaction(mTransactionManager){
            @Override
            public void onResponseError(ImpsErrorInfo error) {
                SubscriptionRequestListener listener = getSubscriptionRequestListener();
                if (listener != null) {
                    if (accept) {
                        listener.onApproveSubScriptionError(contact, error);
                    } else {
                        listener.onDeclineSubScriptionError(contact, error);
                    }
                }
            }

            @Override
            public void onResponseOk(Primitive response) {
                SubscriptionRequestListener listener = getSubscriptionRequestListener();
                if (listener != null) {
                    if (accept) {
                        listener.onSubscriptionApproved(contact);
                    } else {
                        listener.onSubscriptionDeclined(contact);
                    }
                }
            }
        };
        tx.sendRequest(request);
    }

    void subscribeToAllListAsync() {
        AsyncCompletion completion = new AsyncCompletion(){
            public void onComplete() {
                // do nothing
            }
            public void onError(ImErrorInfo error) {
                notifyContactError(ContactListListener.ERROR_RETRIEVING_PRESENCE,
                        error, null, null);
            }
        };
        subscribeToListsAsync(mContactLists,completion);
    }

    void subscribeToListAsync(final ContactList list, final AsyncCompletion completion) {
        Vector<ContactList> lists = new Vector<ContactList>();

        lists.add(list);

        subscribeToListsAsync(lists, completion);
    }

    void subscribeToListsAsync(final Vector<ContactList> contactLists,
            final AsyncCompletion completion) {
        if (contactLists.isEmpty()) {
            return;
        }

        Primitive request = buildSubscribeToListsRequest(contactLists);

        AsyncTransaction tx = new AsyncTransaction(mTransactionManager) {
            @Override
            public void onResponseError(ImpsErrorInfo error) {
                if (error.getCode()
                        == ImpsConstants.STATUS_AUTO_SUBSCRIPTION_NOT_SUPPORTED) {
                    mAllowAutoSubscribe = false;
                    ArrayList<Contact> contacts = new ArrayList<Contact>();
                    for (ContactList list : contactLists) {
                        contacts.addAll(list.getContacts());
                    }

                    subscribeToContactsAsync(contacts, completion);
                } else {
                    if (completion != null) {
                        completion.onError(error);
                    }
                }
            }

            @Override
            public void onResponseOk(Primitive response) {
                if (completion != null) {
                    completion.onComplete();
                }
            }

        };

        tx.sendRequest(request);
    }

    void subscribeToContactsAsync(ArrayList<Contact> contacts, AsyncCompletion completion) {
        Primitive request = buildSubscribeToContactsRequest(contacts);

        SimpleAsyncTransaction tx = new SimpleAsyncTransaction(mTransactionManager, completion);

        tx.sendRequest(request);
    }

    void unsubscribeToListAsync(ContactList list, AsyncCompletion completion) {
        Primitive request = new Primitive(ImpsTags.UnsubscribePresence_Request);
        request.addElement(ImpsTags.ContactList, list.getAddress().getFullName());

        SimpleAsyncTransaction tx = new SimpleAsyncTransaction(
                mTransactionManager, completion);

        tx.sendRequest(request);
    }

    void unsubscribeToContactAsync(Contact contact, AsyncCompletion completion) {
        Primitive request = new Primitive(ImpsTags.UnsubscribePresence_Request);
        request.addElement(ImpsTags.User).addPropertyChild(ImpsTags.UserID,
                contact.getAddress().getFullName());

        SimpleAsyncTransaction tx = new SimpleAsyncTransaction(
                mTransactionManager, completion);

        tx.sendRequest(request);
    }

    private Primitive buildSubscribeToContactsRequest(ArrayList<Contact> contacts) {
        ArrayList<ImpsAddress> addresses = new ArrayList<ImpsAddress>();

        for (Contact contact : contacts) {
            addresses.add((ImpsAddress)contact.getAddress());
        }

        Primitive request = buildSubscribePresenceRequest(addresses);
        return request;
    }

    @Override
    protected void doCreateContactListAsync(final String name,
            Collection<Contact> contacts,
            final boolean isDefault) {
        ImpsAddress selfAddress = mConnection.getSession().getLoginUserAddress();
        ImpsAddress listAddress = new ImpsContactListAddress(selfAddress, name);

        final ContactList list = new ContactList(listAddress, name,
                isDefault, contacts, this);

        Primitive createListRequest = buildCreateListReq(name, contacts,
                isDefault, listAddress);

        AsyncTransaction tx = new AsyncTransaction(mTransactionManager) {

            @Override
            public void onResponseError(ImpsErrorInfo error) {
                notifyContactError(ContactListListener.ERROR_CREATING_LIST,
                        error, name, null);
            }

            @Override
            public void onResponseOk(Primitive response) {
                notifyContactListCreated(list);

                if (mConfig.usePrensencePolling()) {
                    getPresencePollingManager().resetPollingContacts();
                } else {
                    subscribeToListAsync(list, null);
                }
            }
        };

        tx.sendRequest(createListRequest);
    }

    private Primitive buildCreateListReq(String name,
            Collection<Contact> contacts, boolean isDefault,
            ImpsAddress listAddress) {
        Primitive createListRequest = new Primitive(ImpsTags.CreateList_Request);
        createListRequest.addElement(listAddress.toPrimitiveElement());

        // add initial contacts, if any
        if (null != contacts && !contacts.isEmpty()) {
            PrimitiveElement nickList = createListRequest.addElement(ImpsTags.NickList);

            for (Contact contact : contacts) {
                nickList.addChild(buildNickNameElem(contact));
            }
        }

        PrimitiveElement contactListProp = createListRequest.addElement(
                ImpsTags.ContactListProperties);

        contactListProp.addPropertyChild(ImpsConstants.DisplayName, name);
        contactListProp.addPropertyChild(ImpsConstants.Default,
                ImpsUtils.toImpsBool(isDefault));

        return createListRequest;
    }

    /**
     * Delete a specified contact list asyncLoginWrapper.
     *
     * @param list the contact list to be deleted
     */
    @Override
    public void doDeleteContactListAsync(final ContactList list) {
        Primitive delListRequest = buildDelListReq(list);

        AsyncTransaction tx = new AsyncTransaction(mTransactionManager) {

            @Override
            public void onResponseError(ImpsErrorInfo error) {
                notifyContactError(ContactListListener.ERROR_DELETING_LIST,
                        error, list.getName(), null);
            }

            @Override
            public void onResponseOk(Primitive response) {
                notifyContactListDeleted(list);
                if (mConfig.usePrensencePolling()) {
                    getPresencePollingManager().resetPollingContacts();
                } else if (!mAllowAutoSubscribe) {
                    unsubscribeToListAsync(list, new AsyncCompletion(){
                        public void onComplete() {}

                        public void onError(ImErrorInfo error) {
                            // don't bother to alert this error since the
                            // list has already been removed.
                            ImpsLog.log("Warning: unsubscribing list presence failed");
                        }
                    });
                }
            }
        };

        tx.sendRequest(delListRequest);
    }

    private Primitive buildDelListReq(final ContactList list) {
        Primitive delListRequest = new Primitive(ImpsTags.DeleteList_Request);
        delListRequest.addElement(((ImpsAddress)list.getAddress())
                .toPrimitiveElement());
        return delListRequest;
    }

    private Primitive buildListManageRequest(ContactList list, Collection<Contact> contactsToAdd,
            Collection<Contact> contactsToRemove, String listName) {
        // Create ListManage request
        Primitive req = new Primitive(ImpsTags.ListManage_Request);
        req.addElement(((ImpsAddress)list.getAddress()).toPrimitiveElement());
        req.addElement(ImpsTags.ReceiveList, false);

        // If there are any pending added contacts, add them to the addNickList
        if (contactsToAdd != null && !contactsToAdd.isEmpty()) {
            PrimitiveElement addList = req.addElement(ImpsTags.AddNickList);

            for (Contact c : contactsToAdd) {
                PrimitiveElement nickNameElem = addList.addChild(ImpsTags.NickName);
                nickNameElem.addChild(ImpsTags.Name, c.getName());
                nickNameElem.addChild(ImpsTags.UserID, c.getAddress().getFullName());
            }
        }

        // If there are any pending removed contacts, add them to the removeNickList
        if (contactsToRemove != null && !contactsToRemove.isEmpty()) {
            PrimitiveElement removeList = req.addElement(ImpsTags.RemoveNickList);

            for (Contact c : contactsToRemove) {
                removeList.addChild(ImpsTags.UserID, c.getAddress().getFullName());
            }
        }

        // Add the list properties
        if (listName != null) {
            PrimitiveElement requestProps = req.addElement(ImpsTags.ContactListProperties);
            requestProps.addPropertyChild(ImpsConstants.DisplayName, listName);
        }

        return req;
    }

    private Primitive buildSubscribeToListsRequest(Collection<ContactList> lists) {
        ArrayList<ImpsAddress> addresses = new ArrayList<ImpsAddress>();

        for (ContactList list : lists) {
            addresses.add((ImpsAddress)list.getAddress());
        }

        Primitive subscribePresenceRequest = buildSubscribePresenceRequest(addresses);

        subscribePresenceRequest.addElement(ImpsTags.AutoSubscribe, true);

        return subscribePresenceRequest;
    }

    private Primitive buildSubscribePresenceRequest(ArrayList<ImpsAddress> addresses) {
        Primitive request = new Primitive(ImpsTags.SubscribePresence_Request);

        // XXX: Workaround on OZ IMPS GTalk server which only supports a few
        // basic presence attributes. The PresenceSubList is optional and an
        // empty List or missing list indicates all available presence
        // attributes are desired but the OZ server doens't quite follow the
        // spec here. It won't send any PresenceNotification either when we
        // don't send PresenceSubList or we request more PA than it supports.
        if(mConfig.supportBasicPresenceOnly()){
            PrimitiveElement presenceList = request.addElement(ImpsTags.PresenceSubList);
            presenceList.setAttribute(ImpsTags.XMLNS, mConfig.getPresenceNs());
            for(String pa : ImpsClientCapability.getBasicPresenceAttributes()) {
                presenceList.addChild(pa);
            }
        }

        for (ImpsAddress address : addresses) {
            request.addElement(address.toPrimitiveElement());
        }

        return request;
    }

    public void notifyServerTransaction(ServerTransaction tx) {
        Primitive request = tx.getRequest();
        String type = request.getType();
        if (ImpsTags.PresenceNotification_Request.equals(type)) {
            tx.sendStatusResponse(ImpsConstants.SUCCESS_CODE);

            PrimitiveElement content = request.getContentElement();
            extractAndNotifyPresence(content);
        } else if (ImpsTags.PresenceAuth_Request.equals(type)) {
            tx.sendStatusResponse(ImpsConstants.SUCCESS_CODE);

            String userId = request.getElementContents(ImpsTags.UserID);
            Contact contact = getContact(userId);
            if (contact == null) {
                ImpsAddress address = new ImpsUserAddress(userId);
                contact = new Contact(address, address.getScreenName());
            }
            if (getState() < LISTS_LOADED) {
                if (mSubscriptionRequests == null) {
                    mSubscriptionRequests = new ArrayList<Contact>();
                }
                mSubscriptionRequests.add(contact);
            } else {
                SubscriptionRequestListener listener = getSubscriptionRequestListener();
                if (listener != null) {
                    listener.onSubScriptionRequest(contact);
                }
            }
        }
    }

    private void extractAndNotifyPresence(PrimitiveElement content) {
        ArrayList<Contact> updated = new ArrayList<Contact>();
        PresenceMapping presenceMapping = mConfig.getPresenceMapping();

        ArrayList<PrimitiveElement> presenceList = content.getChildren(ImpsTags.Presence);
        for (PrimitiveElement presenceElem : presenceList) {
            String userId = presenceElem.getChildContents(ImpsTags.UserID);
            if (userId == null) {
                continue;
            }
            PrimitiveElement presenceSubList = presenceElem.getChild(ImpsTags.PresenceSubList);
            Presence presence = ImpsPresenceUtils.extractPresence(presenceSubList, presenceMapping);
            // Find out the contact in all lists and update their presence
            for(ContactList list : mContactLists) {
                Contact contact = list.getContact(userId);
                if (contact != null) {
                    contact.setPresence(presence);
                    updated.add(contact);
                }
            }
        }
        if (!updated.isEmpty()) {
            notifyContactsPresenceUpdated(updated.toArray(new Contact[updated.size()]));
        }
    }

    void loadContactsOfListAsync(final ImpsAddress address, final
            AsyncCompletion completion) {
        Primitive listManageRequest = new Primitive(ImpsTags.ListManage_Request);

        listManageRequest.addElement(address.toPrimitiveElement());
        listManageRequest.addElement(ImpsTags.ReceiveList, true);

        AsyncTransaction tx = new AsyncTransaction(mTransactionManager) {
            @Override
            public void onResponseError(ImpsErrorInfo error) {
                completion.onError(error);
            }

            @Override
            public void onResponseOk(Primitive response) {
                final ContactList list = extractContactList(response, address);

                mContactLists.add(list);
                if (list.isDefault()) {
                    mDefaultContactList = list;
                }

                if (mConfig.usePrensencePolling()) {
                    completion.onComplete();
                } else {
                    subscribeToListAsync(list, completion);
                }
            }
        };

        tx.sendRequest(listManageRequest);
    }

    private Primitive buildBlockContactReq(String address, boolean block) {
        Primitive request = new Primitive(ImpsTags.BlockEntity_Request);
        ImpsVersion version = mConfig.getImpsVersion();

        if (version == ImpsVersion.IMPS_VERSION_13) {
            request.addElement(ImpsTags.BlockListInUse, true);
            request.addElement(ImpsTags.GrantListInUse, false);
        }

        PrimitiveElement blockList = request.addElement(ImpsTags.BlockList);
        if (version != ImpsVersion.IMPS_VERSION_13) {
            blockList.addChild(ImpsTags.InUse, true);
        }
        PrimitiveElement entityList = blockList.addChild(block ?
                ImpsTags.AddList : ImpsTags.RemoveList);
        entityList.addChild(ImpsTags.UserID, address);
        return request;
    }

    @Override
    protected void doBlockContactAsync(String address, final boolean block) {
        Primitive request = buildBlockContactReq(address, block);
        final Address contactAddress = new ImpsUserAddress(address);
        AsyncTransaction tx = new AsyncTransaction(mTransactionManager) {

            @Override
            public void onResponseError(ImpsErrorInfo error) {
                Contact c = getContact(contactAddress);
                if(c == null) {
                    c = new Contact(contactAddress, contactAddress.getScreenName());
                }
                notifyContactError(
                        block ? ContactListListener.ERROR_BLOCKING_CONTACT
                                : ContactListListener.ERROR_UNBLOCKING_CONTACT,
                        error, null, c);
            }

            @Override
            public void onResponseOk(Primitive response) {
                Contact c = getContact(contactAddress);
                if(c == null) {
                    c = new Contact(contactAddress, contactAddress.getScreenName());
                }
                notifyBlockContact(c, block);
            }
        };
        tx.sendRequest(request);
    }

    void extractBlockedContacts(Primitive response) {
        mBlockedList.clear();
        PrimitiveElement blockList = response.getElement(ImpsTags.BlockList);
        if(blockList == null) {
            return;
        }
        PrimitiveElement entityList = blockList.getChild(ImpsTags.EntityList);
        if(entityList == null) {
            return;
        }
        for (PrimitiveElement entity : entityList.getChildren()) {
            if (ImpsTags.UserID.equals(entity.getTagName())) {
                String userId = entity.getContents();
                if (userId == null || userId.length() == 0) {
                    ImpsLog.logError("Empty UserID in BlockList");
                    continue;
                }
                ImpsAddress userAddress = new ImpsUserAddress(entity.getContents());
                notifyBlockContact(new Contact(userAddress, userAddress.getScreenName()),
                        true);
            }
        }
    }

    /**
     * Generate NickName element for a specific contact.
     *
     * @param contact the contact which provides the info of the NickName elem
     * @return
     */
    private PrimitiveElement buildNickNameElem(Contact contact) {
        PrimitiveElement nickName = new PrimitiveElement(ImpsTags.NickName);

        nickName.addChild(ImpsTags.Name, contact.getName());
        nickName.addChild(((ImpsAddress)contact.getAddress()).toPrimitiveElement());

        return nickName;
    }

    ContactList extractContactList(Primitive response, final ImpsAddress address) {
        String screenName = address.getScreenName();
        boolean isDefault = false;
        PrimitiveElement propertyElem = response.getElement(ImpsTags.ContactListProperties);
        if (null != propertyElem) {
            for (PrimitiveElement elem : propertyElem.getChildren()) {
                if (elem.getTagName().equals(ImpsTags.Property)) {
                    String name = elem.getChildContents(ImpsTags.Name);
                    String value = elem.getChildContents(ImpsTags.Value);

                    if (name.equals(ImpsConstants.DisplayName)) {
                        screenName = value;
                    } else if (name.equals(ImpsTags.Default)) {
                        isDefault = ImpsUtils.isTrue(value);
                    }
                }
            }
        }

        PrimitiveElement nickListElem = response.getElement(ImpsTags.NickList);
        if (null == nickListElem) {
            return new ContactList(address, screenName, isDefault, null, this);
        }

        Vector<Contact> contacts = new Vector<Contact>();
        for (PrimitiveElement elem : nickListElem.getChildren()) {
            String id = null;
            String name = null;

            String tag = elem.getTagName();
            if (tag.equals(ImpsTags.NickName)) {
                id = elem.getChild(ImpsTags.UserID).getContents();
                name = elem.getChild(ImpsTags.Name).getContents();
            } else if (tag.equals(ImpsTags.UserID)){
                id = elem.getContents();
            }

            if (id != null) {
                Address contactAddress = new ImpsUserAddress(id);
                Contact c = getContact(contactAddress);
                if (c == null) {
                    if (name == null) {
                        name = contactAddress.getScreenName();
                    }
                    c = new Contact(contactAddress, name);
                }
                contacts.add(c);
            }
        }
        return new ContactList(address, screenName, isDefault, contacts, this);
    }

    private class LoadContactsTransaction extends AsyncTransaction {
        Vector<ImpsContactListAddress> mListAddresses;

        LoadContactsTransaction() {
            super(mTransactionManager);

            mListAddresses = new Vector<ImpsContactListAddress>();
        }

        @Override
        public void onResponseError(ImpsErrorInfo error) {
            notifyContactError(ContactListListener.ERROR_LOADING_LIST,
                    error, null, null);
        }

        @Override
        public void onResponseOk(Primitive response) {
            mContactLists.clear();

            mListAddresses = extractListAddresses(response);

            if (!mListAddresses.isEmpty()) {
                fetchContacts();
            } else {
                onContactListsLoaded();
            }
        }

        void startGetContactLists() {
            Primitive getListRequest = new Primitive(ImpsTags.GetList_Request);

            sendRequest(getListRequest);
        }

        private void fetchContacts() {
            ImpsContactListAddress fisrtListAddress = mListAddresses.firstElement();

            loadContactsOfListAsync(fisrtListAddress, new LoadListCompletion());
        }

        private final class LoadListCompletion implements AsyncCompletion {
            private int mListIndex;
            LoadListCompletion() {
                mListIndex = 0;
            }

            public void onComplete() {
                processResult(null);
            }

            public void onError(ImErrorInfo error) {
                processResult(error);
            }

            private void processResult(ImErrorInfo error) {
                ImpsAddress addr = mListAddresses.get(mListIndex);

                if (error == null) {
                    notifyContactListLoaded(getContactList(addr));
                } else {
                    notifyContactError(ContactListListener.ERROR_LOADING_LIST,
                            error, addr.getScreenName(), null);
                }

                mListIndex++;
                if (mListIndex < mListAddresses.size()) {
                    loadContactsOfListAsync(mListAddresses.get(mListIndex), this);
                } else {
                    onContactListsLoaded();
                }
            }
        }
    }

    void onContactListsLoaded() {
        notifyContactListsLoaded();

        if (mConfig.usePrensencePolling()) {
            fetchPresence(getAllListAddress());
        }

        // notify the pending subscription requests received before contact
        // lists has been loaded.
        SubscriptionRequestListener listener = getSubscriptionRequestListener();
        if (mSubscriptionRequests != null && listener != null) {
            for (Contact c : mSubscriptionRequests) {
                listener.onSubScriptionRequest(c);
            }
        }
        ((ImpsChatSessionManager) mConnection.getChatSessionManager()).start();
    }

    @Override
    protected void doAddContactToListAsync(String addressStr, ContactList list)
        throws ImException {
        ImpsUserAddress address = new ImpsUserAddress(addressStr);

        Contact contact;
        if (getContact(address) != null) {
            contact = getContact(address);
        } else {
            contact = new Contact(address, address.getScreenName());
        }

        if (isBlocked(contact)) {
            throw new ImException(ImErrorInfo.CANT_ADD_BLOCKED_CONTACT,
            "Contact has been blocked");
        }

        addContactToListAsync(contact, list);
    }

    private void addContactToListAsync(final Contact contact,
            final ContactList list) {
        final ArrayList<Contact> contacts = new ArrayList<Contact>();

        contacts.add(contact);
        updateContactListAsync(list, contacts, null, null, new AsyncCompletion(){
            public void onComplete() {
                notifyContactListUpdated(list,
                        ContactListListener.LIST_CONTACT_ADDED, contact);

                if (mConfig.usePrensencePolling()) {
                    fetchPresence(new ImpsAddress[]{
                            (ImpsAddress) contact.getAddress()});
                } else {
                    AsyncCompletion subscribeCompletion =  new AsyncCompletion(){
                        public void onComplete() {}

                        public void onError(ImErrorInfo error) {
                            notifyContactError(
                                    ContactListListener.ERROR_RETRIEVING_PRESENCE,
                                    error, list.getName(), contact);
                        }
                    };

                    if (mAllowAutoSubscribe) {
                        // XXX Send subscription again after add contact to make sure we
                        // can get the presence notification. Although the we set
                        // AutoSubscribe True when subscribe presence after load contacts,
                        // the server might not send presence notification.
                        subscribeToListAsync(list, subscribeCompletion);
                    } else {
                        subscribeToContactsAsync(contacts, subscribeCompletion);
                    }
                }
            }

            public void onError(ImErrorInfo error) {
                // XXX Workaround to convert 402 error to 531. Some
                // servers might return 402 - Bad parameter instead of
                // 531 - Unknown user if the user input an invalid user ID.
                if (error.getCode() == ImpsErrorInfo.BAD_PARAMETER) {
                    error = new ImErrorInfo(ImpsErrorInfo.UNKNOWN_USER,
                            error.getDescription());
                }
                notifyContactError(ContactListListener.ERROR_ADDING_CONTACT,
                        error, list.getName(), contact);
            }
        });
    }

    @Override
    protected void doRemoveContactFromListAsync(final Contact contact, final ContactList list) {
        ArrayList<Contact> contacts = new ArrayList<Contact>();

        contacts.add(contact);
        updateContactListAsync(list, null, contacts, null, new AsyncCompletion(){
            public void onComplete() {
                ImpsLog.log("removed contact");
                notifyContactListUpdated(list,
                        ContactListListener.LIST_CONTACT_REMOVED, contact);

                if (!mAllowAutoSubscribe) {
                    unsubscribeToContactAsync(contact, new AsyncCompletion(){
                        public void onComplete() {}

                        public void onError(ImErrorInfo error) {
                            // don't bother to alert this error since the
                            // contact has already been removed.
                            ImpsLog.log("Warning: unsubscribing contact presence failed");
                        }
                    });
                }
            }

            public void onError(ImErrorInfo error) {
                ImpsLog.log("remove contact error:" + error);
                notifyContactError(ContactListListener.ERROR_REMOVING_CONTACT,
                        error, list.getName(), contact);
            }
        });
    }

    @Override
    protected void setListNameAsync(final String name, final ContactList list) {
        updateContactListAsync(list, null, null, name, new AsyncCompletion(){
            public void onComplete() {
                notifyContactListNameUpdated(list, name);
            }

            public void onError(ImErrorInfo error) {
                notifyContactError(ContactListListener.ERROR_RENAMING_LIST,
                        error, list.getName(), null);
            }
        });
    }

    private void updateContactListAsync(final ContactList list, final ArrayList<Contact>
            contactsToAdd, final ArrayList<Contact> contactsToRemove,
            final String listName, AsyncCompletion completion) {
        Primitive request = buildListManageRequest(list, contactsToAdd,
                contactsToRemove, listName);

        SimpleAsyncTransaction tx = new SimpleAsyncTransaction(mTransactionManager, completion);
        tx.sendRequest(request);
    }

    String getPropertyValue(String propertyName, PrimitiveElement properties) {
        for (PrimitiveElement property : properties.getChildren(ImpsTags.Property)) {
            if (propertyName.equals(property.getChildContents(ImpsTags.Name))) {
                return property.getChildContents(ImpsTags.Value);
            }
        }

        return null;
    }

    void reset() {
        setState(LISTS_NOT_LOADED);
    }

    @Override
    protected ImConnection getConnection() {
        return mConnection;
    }

    private PresencePollingManager mPollingMgr;
    /*package*/PresencePollingManager getPresencePollingManager() {
        if (mPollingMgr == null) {
            mPollingMgr = new PresencePollingManager(this,
                    mConfig.getPresencePollInterval());
        }
        return mPollingMgr;
    }

}