Home | History | Annotate | Download | only in engine
      1 /*
      2  * Copyright (C) 2007-2008 Esmertec AG.
      3  * Copyright (C) 2007-2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.im.engine;
     19 
     20 import java.util.Collection;
     21 import java.util.Collections;
     22 import java.util.List;
     23 import java.util.Vector;
     24 import java.util.concurrent.CopyOnWriteArrayList;
     25 
     26 /**
     27  * ContactListManager manages the creating, removing and retrieving contact
     28  * lists.
     29  */
     30 public abstract class ContactListManager {
     31     /**
     32      * ContactListManager state that indicates the contact list(s) has not been loaded.
     33      */
     34     public static final int LISTS_NOT_LOADED = 0;
     35 
     36     /**
     37      * ContactListManager state that indicates the contact list(s) is loading.
     38      */
     39     public static final int LISTS_LOADING = 1;
     40 
     41     /**
     42      * ContactListManager state that indicates the blocked list has been loaded.
     43      */
     44     public static final int BLOCKED_LIST_LOADED = 2;
     45 
     46     /**
     47      * ContactListManager state that indicates the contact list(s) has been loaded.
     48      */
     49     public static final int LISTS_LOADED = 3;
     50 
     51     protected ContactList mDefaultContactList;
     52     protected Vector<ContactList> mContactLists;
     53 
     54     protected CopyOnWriteArrayList<ContactListListener> mContactListListeners;
     55     protected SubscriptionRequestListener mSubscriptionRequestListener;
     56 
     57     protected Vector<Contact> mBlockedList;
     58 
     59     private int mState;
     60 
     61     /**
     62      * A pending list of blocking contacts which is used for checking duplicated
     63      * block operation.
     64      */
     65     private Vector<String> mBlockPending;
     66     /**
     67      * A pending list of deleting contacts which is used for checking duplicated
     68      * delete operation.
     69      */
     70     private Vector<Contact> mDeletePending;
     71 
     72     /**
     73      * Creates a new ContactListManager.
     74      *
     75      * @param conn The underlying protocol connection.
     76      */
     77     protected ContactListManager() {
     78         mContactLists = new Vector<ContactList>();
     79         mContactListListeners = new CopyOnWriteArrayList<ContactListListener>();
     80         mBlockedList = new Vector<Contact>();
     81 
     82         mBlockPending = new Vector<String>(4);
     83         mDeletePending = new Vector<Contact>(4);
     84 
     85         mState = LISTS_NOT_LOADED;
     86     }
     87 
     88     /**
     89      * Set the state of the ContactListManager
     90      *
     91      * @param state the new state
     92      */
     93     protected synchronized void setState(int state) {
     94         if (state < LISTS_NOT_LOADED || state > LISTS_LOADED) {
     95             throw new IllegalArgumentException();
     96         }
     97 
     98         mState = state;
     99     }
    100 
    101     /**
    102      * Get the state of the ContactListManager
    103      *
    104      * @return the current state of the manager
    105      */
    106     public synchronized int getState() {
    107         return mState;
    108     }
    109 
    110     /**
    111      * Adds a listener to the manager so that it will be notified for contact
    112      * list changed.
    113      *
    114      * @param listener the listener to add.
    115      */
    116     public synchronized void addContactListListener(ContactListListener listener) {
    117         if ((listener != null) && !mContactListListeners.contains(listener)) {
    118             mContactListListeners.add(listener);
    119         }
    120     }
    121 
    122     /**
    123      * Removes a listener from this manager.
    124      *
    125      * @param listener the listener to remove.
    126      */
    127     public synchronized void removeContactListListener(ContactListListener listener) {
    128         mContactListListeners.remove(listener);
    129     }
    130 
    131     /**
    132      * Sets the SubscriptionRequestListener to the manager so that it will be notified
    133      * when a subscription request from another user is received.
    134      *
    135      * @param listener the ContactInvitationListener.
    136      */
    137     public synchronized void setSubscriptionRequestListener(
    138             SubscriptionRequestListener listener) {
    139         mSubscriptionRequestListener = listener;
    140     }
    141 
    142     public synchronized SubscriptionRequestListener getSubscriptionRequestListener() {
    143         return mSubscriptionRequestListener;
    144     }
    145 
    146     /**
    147      * Gets a collection of the contact lists.
    148      *
    149      * @return a collection of the contact lists.
    150      */
    151     public Collection<ContactList> getContactLists() {
    152         return Collections.unmodifiableCollection(mContactLists);
    153     }
    154 
    155     /**
    156      * Gets a contact by address.
    157      *
    158      * @param address the address of the Contact.
    159      * @return the Contact or null if the Contact doesn't exist in any list.
    160      */
    161     public Contact getContact(Address address) {
    162         return getContact(address.getFullName());
    163     }
    164 
    165     public Contact getContact(String address) {
    166         for (ContactList list : mContactLists) {
    167             Contact c = list.getContact(address);
    168             if( c != null) {
    169                 return c;
    170             }
    171         }
    172         return null;
    173     }
    174 
    175     public abstract String normalizeAddress(String address);
    176 
    177     /**
    178      * Creates a temporary contact. It's usually used when we want to create
    179      * a chat with someone not in the list.
    180      *
    181      * @param address the address of the temporary contact.
    182      * @return the created temporary contact
    183      */
    184     public abstract Contact createTemporaryContact(String address);
    185 
    186     /**
    187      * Tell whether the manager contains the specified contact
    188      *
    189      * @param contact the specified contact
    190      * @return true if the contact is contained in the lists of the manager,
    191      *          otherwise, return false
    192      */
    193     public boolean containsContact(Contact contact) {
    194         for (ContactList list : mContactLists) {
    195             if (list.containsContact(contact)) {
    196                 return true;
    197             }
    198         }
    199 
    200         return false;
    201     }
    202 
    203     /**
    204      * Gets a contact list by name.
    205      *
    206      * @param name the name of the contact list.
    207      * @return the ContactList or null if the contact list doesn't exist.
    208      */
    209     public ContactList getContactList(String name) {
    210         for (ContactList list : mContactLists) {
    211             if (list.getName() != null && list.getName().equals(name)) {
    212                 return list;
    213             }
    214         }
    215         return null;
    216     }
    217 
    218     /**
    219      * Get the contact list by the address
    220      *
    221      * @param address the address of the contact list
    222      * @return the <code>ContactList</code> or null if the list doesn't exist
    223      */
    224     public ContactList getContactList(Address address) {
    225         for (ContactList list : mContactLists) {
    226             if (list.getAddress().equals(address)) {
    227                 return list;
    228             }
    229         }
    230 
    231         return null;
    232     }
    233 
    234     /**
    235      * Gets the default contact list.
    236      *
    237      * @return the default contact list.
    238      * @throws ImException
    239      */
    240     public ContactList getDefaultContactList() throws ImException {
    241         checkState();
    242         return mDefaultContactList;
    243     }
    244 
    245     /**
    246      * Create a contact list with the specified name asynchronously.
    247      *
    248      * @param name the specific name of the contact list
    249      * @throws ImException
    250      */
    251     public void createContactListAsync(String name) throws ImException {
    252         createContactListAsync(name, null, false);
    253     }
    254 
    255     /**
    256      * Create a contact list with specified name and whether it is to be
    257      * created as the default list.
    258      *
    259      * @param name the specific name of the contact list
    260      * @param isDefault whether the contact list is to be created as the
    261      *                  default list
    262      * @throws ImException
    263      */
    264     public void createContactListAsync(String name, boolean isDefault) throws ImException {
    265         createContactListAsync(name, null, isDefault);
    266     }
    267 
    268     /**
    269      * Create a contact list with specified name and contacts asynchronously.
    270      *
    271      * @param name the specific name of the contact list
    272      * @param contacts the initial contacts of the contact list
    273      * @throws ImException
    274      */
    275     public void createContactListAsync(String name,
    276             Collection<Contact> contacts) throws ImException {
    277         createContactListAsync(name, contacts, false);
    278     }
    279 
    280     /**
    281      * Create a contact list with specified name and contacts asynchronously,
    282      * and whether it is to be created as the default contact list.
    283      *
    284      * @param name the name of the contact list
    285      * @param contacts the initial contacts of the list
    286      * @param isDefault whether the contact list is the default list
    287      * @throws ImException
    288      */
    289     public synchronized void createContactListAsync(String name,
    290             Collection<Contact> contacts, boolean isDefault) throws ImException {
    291         checkState();
    292 
    293         if (getContactList(name) != null) {
    294             throw new ImException(ImErrorInfo.CONTACT_LIST_EXISTS,
    295                     "Contact list already exists");
    296         }
    297 
    298         if (mContactLists.isEmpty()) {
    299             isDefault = true;
    300         }
    301 
    302         doCreateContactListAsync(name, contacts, isDefault);
    303     }
    304 
    305     /**
    306      * Delete a contact list of the specified name asynchronously
    307      * @param name the specific name of the contact list
    308      * @throws ImException
    309      */
    310     public void deleteContactListAsync(String name) throws ImException {
    311         deleteContactListAsync(getContactList(name));
    312     }
    313 
    314     /**
    315      * Delete a specified contact list asynchronously
    316      * @param list the contact list to be deleted
    317      * @throws ImException if any error raised
    318      */
    319     public synchronized void deleteContactListAsync(ContactList list) throws ImException {
    320         checkState();
    321 
    322         if (null == list) {
    323             throw new ImException(ImErrorInfo.CONTACT_LIST_NOT_FOUND,
    324                     "Contact list doesn't exist");
    325         }
    326 
    327         doDeleteContactListAsync(list);
    328     }
    329 
    330     public void blockContactAsync(Contact contact) throws ImException {
    331         blockContactAsync(contact.getAddress().getFullName());
    332     }
    333 
    334     /**
    335      * Blocks a certain Contact. The contact will be removed from any
    336      * ContactList after be blocked. If the contact has already been blocked,
    337      * the method does nothing.
    338      *
    339      * @param address the address of the contact to block.
    340      * @throws ImException if an error occurs
    341      */
    342     public void blockContactAsync(String address) throws ImException {
    343         checkState();
    344 
    345         if(isBlocked(address)){
    346             return;
    347         }
    348 
    349         if (mBlockPending.contains(address)) {
    350             return;
    351         }
    352         doBlockContactAsync(address, true);
    353     }
    354 
    355     public void unblockContactAsync(Contact contact) throws ImException {
    356         unblockContactAsync(contact.getAddress().getFullName());
    357     }
    358 
    359     /**
    360      * Unblock a certain contact. It will removes the contact from the blocked
    361      * list and allows the contact to send message or invitation to the client
    362      * again. If the contact is not blocked on the client, this method does
    363      * nothing. Whether the unblocked contact will be added to the ContactList
    364      * it belongs before blocked or not depends on the underlying protocol
    365      * implementation.
    366      *
    367      * @param address the address of the contact to unblock.
    368      * @throws ImException if the current state is illegal
    369      */
    370     public void unblockContactAsync(String address) throws ImException {
    371         checkState();
    372 
    373         if(!isBlocked(address)) {
    374             return;
    375         }
    376 
    377         doBlockContactAsync(address, false);
    378     }
    379 
    380     protected void addContactToListAsync(String address, ContactList list)
    381             throws ImException {
    382         checkState();
    383 
    384         doAddContactToListAsync(address, list);
    385     }
    386 
    387     protected void removeContactFromListAsync(Contact contact, ContactList list)
    388             throws ImException {
    389         checkState();
    390 
    391         if (mDeletePending.contains(contact)) {
    392             return;
    393         }
    394 
    395         doRemoveContactFromListAsync(contact, list);
    396     }
    397 
    398     /**
    399      * Gets a unmodifiable list of blocked contacts.
    400      *
    401      * @return a unmodifiable list of blocked contacts.
    402      * @throws ImException
    403      */
    404     public List<Contact> getBlockedList() throws ImException {
    405         checkState();
    406 
    407         return Collections.unmodifiableList(mBlockedList);
    408     }
    409 
    410     /**
    411      * Checks if a contact is blocked.
    412      *
    413      * @param contact the contact.
    414      * @return true if it's blocked, false otherwise.
    415      * @throws ImException if contacts has not been loaded.
    416      */
    417     public boolean isBlocked(Contact contact) throws ImException {
    418         return isBlocked(contact.getAddress().getFullName());
    419     }
    420 
    421     /**
    422      * Checks if a contact is blocked.
    423      *
    424      * @param address the address of the contact.
    425      * @return true if it's blocked, false otherwise.
    426      * @throws ImException if contacts has not been loaded.
    427      */
    428     public synchronized boolean isBlocked(String address) throws ImException {
    429         if(mState < BLOCKED_LIST_LOADED) {
    430             throw new ImException(ImErrorInfo.ILLEGAL_CONTACT_LIST_MANAGER_STATE,
    431                 "Blocked list hasn't been loaded");
    432         }
    433         for(Contact c : mBlockedList) {
    434             if(c.getAddress().getFullName().equals(address)){
    435                 return true;
    436             }
    437         }
    438         return false;
    439     }
    440 
    441     /**
    442      * Check the state of the ContactListManager. Only the LIST_LOADED state
    443      * is permitted.
    444      *
    445      * @throws ImException if the current state is not LIST_LOADED
    446      */
    447     protected void checkState() throws ImException {
    448         if (getConnection().getState() != ImConnection.LOGGED_IN) {
    449             throw new ImException(ImErrorInfo.CANT_CONNECT_TO_SERVER,
    450                     "Can't connect to server");
    451         }
    452 
    453         if (getState() != LISTS_LOADED) {
    454             throw new ImException(ImErrorInfo.ILLEGAL_CONTACT_LIST_MANAGER_STATE,
    455                     "Illegal contact list manager state");
    456         }
    457     }
    458 
    459     /**
    460      * Load the contact lists from the server. This method will normally called
    461      * after the user logged in to get the initial/saved contact lists from
    462      * server. After called once, this method should not be called again.
    463      */
    464     public abstract void loadContactListsAsync();
    465 
    466     public abstract void approveSubscriptionRequest(String contact);
    467     public abstract void declineSubscriptionRequest(String contact);
    468 
    469     protected abstract ImConnection getConnection();
    470 
    471     /**
    472      * Block or unblock a contact.
    473      *
    474      * @param address
    475      *            the address of the contact to block or unblock.
    476      * @param block
    477      *            <code>true</code> to block the contact; <code>false</code>
    478      *            to unblock the contact.
    479      */
    480     protected abstract void doBlockContactAsync(String address, boolean block);
    481     protected abstract void doCreateContactListAsync(String name,
    482             Collection<Contact> contacts, boolean isDefault);
    483     protected abstract void doDeleteContactListAsync(ContactList list);
    484 
    485     /**
    486      * Notify that the presence of the contact has been updated
    487      *
    488      * @param contacts the contacts who have updated presence information
    489      */
    490     protected void notifyContactsPresenceUpdated(Contact[] contacts) {
    491         for (ContactListListener listener : mContactListListeners) {
    492             listener.onContactsPresenceUpdate(contacts);
    493         }
    494     }
    495 
    496     /**
    497      * Notify that a contact list related error has been raised.
    498      *
    499      * @param type the type of the error
    500      * @param error the raised error
    501      * @param listName the list name, if any, associated with the error
    502      * @param contact the contact, if any, associated with the error
    503      */
    504     protected void notifyContactError(int type, ImErrorInfo error,
    505             String listName, Contact contact) {
    506         if (type == ContactListListener.ERROR_REMOVING_CONTACT) {
    507             mDeletePending.remove(contact);
    508         } else if (type == ContactListListener.ERROR_BLOCKING_CONTACT) {
    509             mBlockPending.remove(contact.getAddress().getFullName());
    510         }
    511         for (ContactListListener listener : mContactListListeners) {
    512             listener.onContactError(type, error, listName, contact);
    513         }
    514     }
    515 
    516     /**
    517      * Notify that a contact list has been loaded
    518      *
    519      * @param list the loaded list
    520      */
    521     protected void notifyContactListLoaded(ContactList list) {
    522         for (ContactListListener listener : mContactListListeners) {
    523             listener.onContactChange(ContactListListener.LIST_LOADED,
    524                     list, null);
    525         }
    526     }
    527 
    528     /**
    529      * Notify that all contact lists has been loaded
    530      */
    531     protected void notifyContactListsLoaded() {
    532         setState(LISTS_LOADED);
    533         for (ContactListListener listener : mContactListListeners) {
    534             listener.onAllContactListsLoaded();
    535         }
    536     }
    537 
    538     /**
    539      * Notify that a contact has been added to or removed from a list.
    540      *
    541      * @param list the updated contact list
    542      * @param type the type of the update
    543      * @param contact the involved contact, null if no contact involved.
    544      */
    545     protected void notifyContactListUpdated(ContactList list, int type,
    546             Contact contact) {
    547         synchronized (this) {
    548             if (type == ContactListListener.LIST_CONTACT_ADDED) {
    549                 list.insertToCache(contact);
    550             } else if (type == ContactListListener.LIST_CONTACT_REMOVED) {
    551                 list.removeFromCache(contact);
    552                 mDeletePending.remove(contact);
    553             }
    554         }
    555 
    556         for (ContactListListener listener : mContactListListeners) {
    557             listener.onContactChange(type, list, contact);
    558         }
    559     }
    560 
    561     /**
    562      * Notify that the name of the specified contact list has been updated.
    563      *
    564      * @param list
    565      * @param name the new name of the list
    566      */
    567     protected void notifyContactListNameUpdated(ContactList list, String name) {
    568         list.mName = name;
    569 
    570         for (ContactListListener listener : mContactListListeners) {
    571             listener.onContactChange(ContactListListener.LIST_RENAMED,
    572                     list, null);
    573         }
    574     }
    575 
    576     /**
    577      * Notify that a contact list has been created.
    578      *
    579      * @param list the created list
    580      */
    581     protected void notifyContactListCreated(ContactList list) {
    582         synchronized (this) {
    583             if (list.isDefault()) {
    584                 for (ContactList l : mContactLists) {
    585                     l.setDefault(false);
    586                 }
    587                 mDefaultContactList = list;
    588             }
    589             mContactLists.add(list);
    590         }
    591 
    592         for (ContactListListener listener : mContactListListeners) {
    593             listener.onContactChange(ContactListListener.LIST_CREATED,
    594                     list, null);
    595         }
    596     }
    597 
    598     /**
    599      * Notify that a contact list has been deleted
    600      *
    601      * @param list the deleted list
    602      */
    603     protected void notifyContactListDeleted(ContactList list) {
    604         synchronized(this) {
    605             mContactLists.remove(list);
    606             if (list.isDefault() && mContactLists.size() > 0) {
    607                 mContactLists.get(0).setDefault(true);
    608             }
    609         }
    610 
    611         for (ContactListListener listener : mContactListListeners) {
    612             listener.onContactChange(ContactListListener.LIST_DELETED,
    613                     list, null);
    614         }
    615     }
    616 
    617     /**
    618      * Notify that a contact has been blocked/unblocked.
    619      *
    620      * @param contact the blocked/unblocked contact
    621      */
    622     protected void notifyBlockContact(Contact contact, boolean blocked) {
    623         synchronized (this) {
    624             if (blocked) {
    625                 mBlockedList.add(contact);
    626                 String addr = contact.getAddress().getFullName();
    627                 mBlockPending.remove(addr);
    628             } else {
    629                 mBlockedList.remove(contact);
    630             }
    631         }
    632         for (ContactListListener listener : mContactListListeners) {
    633             listener.onContactChange(blocked ? ContactListListener.CONTACT_BLOCKED
    634                     : ContactListListener.CONTACT_UNBLOCKED, null, contact);
    635         }
    636     }
    637 
    638     protected abstract void doAddContactToListAsync(String address,
    639             ContactList list) throws ImException;
    640     protected abstract void doRemoveContactFromListAsync(Contact contact, ContactList list);
    641     protected abstract void setListNameAsync(String name, ContactList list);
    642 }
    643