/*
* 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.engine;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* ContactListManager manages the creating, removing and retrieving contact
* lists.
*/
public abstract class ContactListManager {
/**
* ContactListManager state that indicates the contact list(s) has not been loaded.
*/
public static final int LISTS_NOT_LOADED = 0;
/**
* ContactListManager state that indicates the contact list(s) is loading.
*/
public static final int LISTS_LOADING = 1;
/**
* ContactListManager state that indicates the blocked list has been loaded.
*/
public static final int BLOCKED_LIST_LOADED = 2;
/**
* ContactListManager state that indicates the contact list(s) has been loaded.
*/
public static final int LISTS_LOADED = 3;
protected ContactList mDefaultContactList;
protected Vector<ContactList> mContactLists;
protected CopyOnWriteArrayList<ContactListListener> mContactListListeners;
protected SubscriptionRequestListener mSubscriptionRequestListener;
protected Vector<Contact> mBlockedList;
private int mState;
/**
* A pending list of blocking contacts which is used for checking duplicated
* block operation.
*/
private Vector<String> mBlockPending;
/**
* A pending list of deleting contacts which is used for checking duplicated
* delete operation.
*/
private Vector<Contact> mDeletePending;
/**
* Creates a new ContactListManager.
*
* @param conn The underlying protocol connection.
*/
protected ContactListManager() {
mContactLists = new Vector<ContactList>();
mContactListListeners = new CopyOnWriteArrayList<ContactListListener>();
mBlockedList = new Vector<Contact>();
mBlockPending = new Vector<String>(4);
mDeletePending = new Vector<Contact>(4);
mState = LISTS_NOT_LOADED;
}
/**
* Set the state of the ContactListManager
*
* @param state the new state
*/
protected synchronized void setState(int state) {
if (state < LISTS_NOT_LOADED || state > LISTS_LOADED) {
throw new IllegalArgumentException();
}
mState = state;
}
/**
* Get the state of the ContactListManager
*
* @return the current state of the manager
*/
public synchronized int getState() {
return mState;
}
/**
* Adds a listener to the manager so that it will be notified for contact
* list changed.
*
* @param listener the listener to add.
*/
public synchronized void addContactListListener(ContactListListener listener) {
if ((listener != null) && !mContactListListeners.contains(listener)) {
mContactListListeners.add(listener);
}
}
/**
* Removes a listener from this manager.
*
* @param listener the listener to remove.
*/
public synchronized void removeContactListListener(ContactListListener listener) {
mContactListListeners.remove(listener);
}
/**
* Sets the SubscriptionRequestListener to the manager so that it will be notified
* when a subscription request from another user is received.
*
* @param listener the ContactInvitationListener.
*/
public synchronized void setSubscriptionRequestListener(
SubscriptionRequestListener listener) {
mSubscriptionRequestListener = listener;
}
public synchronized SubscriptionRequestListener getSubscriptionRequestListener() {
return mSubscriptionRequestListener;
}
/**
* Gets a collection of the contact lists.
*
* @return a collection of the contact lists.
*/
public Collection<ContactList> getContactLists() {
return Collections.unmodifiableCollection(mContactLists);
}
/**
* Gets a contact by address.
*
* @param address the address of the Contact.
* @return the Contact or null if the Contact doesn't exist in any list.
*/
public Contact getContact(Address address) {
return getContact(address.getFullName());
}
public Contact getContact(String address) {
for (ContactList list : mContactLists) {
Contact c = list.getContact(address);
if( c != null) {
return c;
}
}
return null;
}
public abstract String normalizeAddress(String address);
/**
* Creates a temporary contact. It's usually used when we want to create
* a chat with someone not in the list.
*
* @param address the address of the temporary contact.
* @return the created temporary contact
*/
public abstract Contact createTemporaryContact(String address);
/**
* Tell whether the manager contains the specified contact
*
* @param contact the specified contact
* @return true if the contact is contained in the lists of the manager,
* otherwise, return false
*/
public boolean containsContact(Contact contact) {
for (ContactList list : mContactLists) {
if (list.containsContact(contact)) {
return true;
}
}
return false;
}
/**
* Gets a contact list by name.
*
* @param name the name of the contact list.
* @return the ContactList or null if the contact list doesn't exist.
*/
public ContactList getContactList(String name) {
for (ContactList list : mContactLists) {
if (list.getName() != null && list.getName().equals(name)) {
return list;
}
}
return null;
}
/**
* Get the contact list by the address
*
* @param address the address of the contact list
* @return the <code>ContactList</code> or null if the list doesn't exist
*/
public ContactList getContactList(Address address) {
for (ContactList list : mContactLists) {
if (list.getAddress().equals(address)) {
return list;
}
}
return null;
}
/**
* Gets the default contact list.
*
* @return the default contact list.
* @throws ImException
*/
public ContactList getDefaultContactList() throws ImException {
checkState();
return mDefaultContactList;
}
/**
* Create a contact list with the specified name asynchronously.
*
* @param name the specific name of the contact list
* @throws ImException
*/
public void createContactListAsync(String name) throws ImException {
createContactListAsync(name, null, false);
}
/**
* Create a contact list with specified name and whether it is to be
* created as the default list.
*
* @param name the specific name of the contact list
* @param isDefault whether the contact list is to be created as the
* default list
* @throws ImException
*/
public void createContactListAsync(String name, boolean isDefault) throws ImException {
createContactListAsync(name, null, isDefault);
}
/**
* Create a contact list with specified name and contacts asynchronously.
*
* @param name the specific name of the contact list
* @param contacts the initial contacts of the contact list
* @throws ImException
*/
public void createContactListAsync(String name,
Collection<Contact> contacts) throws ImException {
createContactListAsync(name, contacts, false);
}
/**
* Create a contact list with specified name and contacts asynchronously,
* and whether it is to be created as the default contact list.
*
* @param name the name of the contact list
* @param contacts the initial contacts of the list
* @param isDefault whether the contact list is the default list
* @throws ImException
*/
public synchronized void createContactListAsync(String name,
Collection<Contact> contacts, boolean isDefault) throws ImException {
checkState();
if (getContactList(name) != null) {
throw new ImException(ImErrorInfo.CONTACT_LIST_EXISTS,
"Contact list already exists");
}
if (mContactLists.isEmpty()) {
isDefault = true;
}
doCreateContactListAsync(name, contacts, isDefault);
}
/**
* Delete a contact list of the specified name asynchronously
* @param name the specific name of the contact list
* @throws ImException
*/
public void deleteContactListAsync(String name) throws ImException {
deleteContactListAsync(getContactList(name));
}
/**
* Delete a specified contact list asynchronously
* @param list the contact list to be deleted
* @throws ImException if any error raised
*/
public synchronized void deleteContactListAsync(ContactList list) throws ImException {
checkState();
if (null == list) {
throw new ImException(ImErrorInfo.CONTACT_LIST_NOT_FOUND,
"Contact list doesn't exist");
}
doDeleteContactListAsync(list);
}
public void blockContactAsync(Contact contact) throws ImException {
blockContactAsync(contact.getAddress().getFullName());
}
/**
* Blocks a certain Contact. The contact will be removed from any
* ContactList after be blocked. If the contact has already been blocked,
* the method does nothing.
*
* @param address the address of the contact to block.
* @throws ImException if an error occurs
*/
public void blockContactAsync(String address) throws ImException {
checkState();
if(isBlocked(address)){
return;
}
if (mBlockPending.contains(address)) {
return;
}
doBlockContactAsync(address, true);
}
public void unblockContactAsync(Contact contact) throws ImException {
unblockContactAsync(contact.getAddress().getFullName());
}
/**
* Unblock a certain contact. It will removes the contact from the blocked
* list and allows the contact to send message or invitation to the client
* again. If the contact is not blocked on the client, this method does
* nothing. Whether the unblocked contact will be added to the ContactList
* it belongs before blocked or not depends on the underlying protocol
* implementation.
*
* @param address the address of the contact to unblock.
* @throws ImException if the current state is illegal
*/
public void unblockContactAsync(String address) throws ImException {
checkState();
if(!isBlocked(address)) {
return;
}
doBlockContactAsync(address, false);
}
protected void addContactToListAsync(String address, ContactList list)
throws ImException {
checkState();
doAddContactToListAsync(address, list);
}
protected void removeContactFromListAsync(Contact contact, ContactList list)
throws ImException {
checkState();
if (mDeletePending.contains(contact)) {
return;
}
doRemoveContactFromListAsync(contact, list);
}
/**
* Gets a unmodifiable list of blocked contacts.
*
* @return a unmodifiable list of blocked contacts.
* @throws ImException
*/
public List<Contact> getBlockedList() throws ImException {
checkState();
return Collections.unmodifiableList(mBlockedList);
}
/**
* Checks if a contact is blocked.
*
* @param contact the contact.
* @return true if it's blocked, false otherwise.
* @throws ImException if contacts has not been loaded.
*/
public boolean isBlocked(Contact contact) throws ImException {
return isBlocked(contact.getAddress().getFullName());
}
/**
* Checks if a contact is blocked.
*
* @param address the address of the contact.
* @return true if it's blocked, false otherwise.
* @throws ImException if contacts has not been loaded.
*/
public synchronized boolean isBlocked(String address) throws ImException {
if(mState < BLOCKED_LIST_LOADED) {
throw new ImException(ImErrorInfo.ILLEGAL_CONTACT_LIST_MANAGER_STATE,
"Blocked list hasn't been loaded");
}
for(Contact c : mBlockedList) {
if(c.getAddress().getFullName().equals(address)){
return true;
}
}
return false;
}
/**
* Check the state of the ContactListManager. Only the LIST_LOADED state
* is permitted.
*
* @throws ImException if the current state is not LIST_LOADED
*/
protected void checkState() throws ImException {
if (getConnection().getState() != ImConnection.LOGGED_IN) {
throw new ImException(ImErrorInfo.CANT_CONNECT_TO_SERVER,
"Can't connect to server");
}
if (getState() != LISTS_LOADED) {
throw new ImException(ImErrorInfo.ILLEGAL_CONTACT_LIST_MANAGER_STATE,
"Illegal contact list manager state");
}
}
/**
* Load the contact lists from the server. This method will normally called
* after the user logged in to get the initial/saved contact lists from
* server. After called once, this method should not be called again.
*/
public abstract void loadContactListsAsync();
public abstract void approveSubscriptionRequest(String contact);
public abstract void declineSubscriptionRequest(String contact);
protected abstract ImConnection getConnection();
/**
* Block or unblock a contact.
*
* @param address
* the address of the contact to block or unblock.
* @param block
* <code>true</code> to block the contact; <code>false</code>
* to unblock the contact.
*/
protected abstract void doBlockContactAsync(String address, boolean block);
protected abstract void doCreateContactListAsync(String name,
Collection<Contact> contacts, boolean isDefault);
protected abstract void doDeleteContactListAsync(ContactList list);
/**
* Notify that the presence of the contact has been updated
*
* @param contacts the contacts who have updated presence information
*/
protected void notifyContactsPresenceUpdated(Contact[] contacts) {
for (ContactListListener listener : mContactListListeners) {
listener.onContactsPresenceUpdate(contacts);
}
}
/**
* Notify that a contact list related error has been raised.
*
* @param type the type of the error
* @param error the raised error
* @param listName the list name, if any, associated with the error
* @param contact the contact, if any, associated with the error
*/
protected void notifyContactError(int type, ImErrorInfo error,
String listName, Contact contact) {
if (type == ContactListListener.ERROR_REMOVING_CONTACT) {
mDeletePending.remove(contact);
} else if (type == ContactListListener.ERROR_BLOCKING_CONTACT) {
mBlockPending.remove(contact.getAddress().getFullName());
}
for (ContactListListener listener : mContactListListeners) {
listener.onContactError(type, error, listName, contact);
}
}
/**
* Notify that a contact list has been loaded
*
* @param list the loaded list
*/
protected void notifyContactListLoaded(ContactList list) {
for (ContactListListener listener : mContactListListeners) {
listener.onContactChange(ContactListListener.LIST_LOADED,
list, null);
}
}
/**
* Notify that all contact lists has been loaded
*/
protected void notifyContactListsLoaded() {
setState(LISTS_LOADED);
for (ContactListListener listener : mContactListListeners) {
listener.onAllContactListsLoaded();
}
}
/**
* Notify that a contact has been added to or removed from a list.
*
* @param list the updated contact list
* @param type the type of the update
* @param contact the involved contact, null if no contact involved.
*/
protected void notifyContactListUpdated(ContactList list, int type,
Contact contact) {
synchronized (this) {
if (type == ContactListListener.LIST_CONTACT_ADDED) {
list.insertToCache(contact);
} else if (type == ContactListListener.LIST_CONTACT_REMOVED) {
list.removeFromCache(contact);
mDeletePending.remove(contact);
}
}
for (ContactListListener listener : mContactListListeners) {
listener.onContactChange(type, list, contact);
}
}
/**
* Notify that the name of the specified contact list has been updated.
*
* @param list
* @param name the new name of the list
*/
protected void notifyContactListNameUpdated(ContactList list, String name) {
list.mName = name;
for (ContactListListener listener : mContactListListeners) {
listener.onContactChange(ContactListListener.LIST_RENAMED,
list, null);
}
}
/**
* Notify that a contact list has been created.
*
* @param list the created list
*/
protected void notifyContactListCreated(ContactList list) {
synchronized (this) {
if (list.isDefault()) {
for (ContactList l : mContactLists) {
l.setDefault(false);
}
mDefaultContactList = list;
}
mContactLists.add(list);
}
for (ContactListListener listener : mContactListListeners) {
listener.onContactChange(ContactListListener.LIST_CREATED,
list, null);
}
}
/**
* Notify that a contact list has been deleted
*
* @param list the deleted list
*/
protected void notifyContactListDeleted(ContactList list) {
synchronized(this) {
mContactLists.remove(list);
if (list.isDefault() && mContactLists.size() > 0) {
mContactLists.get(0).setDefault(true);
}
}
for (ContactListListener listener : mContactListListeners) {
listener.onContactChange(ContactListListener.LIST_DELETED,
list, null);
}
}
/**
* Notify that a contact has been blocked/unblocked.
*
* @param contact the blocked/unblocked contact
*/
protected void notifyBlockContact(Contact contact, boolean blocked) {
synchronized (this) {
if (blocked) {
mBlockedList.add(contact);
String addr = contact.getAddress().getFullName();
mBlockPending.remove(addr);
} else {
mBlockedList.remove(contact);
}
}
for (ContactListListener listener : mContactListListeners) {
listener.onContactChange(blocked ? ContactListListener.CONTACT_BLOCKED
: ContactListListener.CONTACT_UNBLOCKED, null, contact);
}
}
protected abstract void doAddContactToListAsync(String address,
ContactList list) throws ImException;
protected abstract void doRemoveContactFromListAsync(Contact contact, ContactList list);
protected abstract void setListNameAsync(String name, ContactList list);
}