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 */ ContactListManager()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 */ setState(int state)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 */ getState()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 */ addContactListListener(ContactListListener listener)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 */ removeContactListListener(ContactListListener listener)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 */ setSubscriptionRequestListener( SubscriptionRequestListener listener)137 public synchronized void setSubscriptionRequestListener( 138 SubscriptionRequestListener listener) { 139 mSubscriptionRequestListener = listener; 140 } 141 getSubscriptionRequestListener()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 */ getContactLists()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 */ getContact(Address address)161 public Contact getContact(Address address) { 162 return getContact(address.getFullName()); 163 } 164 getContact(String address)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 normalizeAddress(String address)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 */ createTemporaryContact(String address)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 */ containsContact(Contact contact)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 */ getContactList(String name)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 */ getContactList(Address address)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 */ getDefaultContactList()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 */ createContactListAsync(String name)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 */ createContactListAsync(String name, boolean isDefault)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 */ createContactListAsync(String name, Collection<Contact> contacts)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 */ createContactListAsync(String name, Collection<Contact> contacts, boolean isDefault)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 */ deleteContactListAsync(String name)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 */ deleteContactListAsync(ContactList list)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 blockContactAsync(Contact contact)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 */ blockContactAsync(String address)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 unblockContactAsync(Contact contact)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 */ unblockContactAsync(String address)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 addContactToListAsync(String address, ContactList list)380 protected void addContactToListAsync(String address, ContactList list) 381 throws ImException { 382 checkState(); 383 384 doAddContactToListAsync(address, list); 385 } 386 removeContactFromListAsync(Contact contact, ContactList list)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 */ getBlockedList()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 */ isBlocked(Contact contact)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 */ isBlocked(String address)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 */ checkState()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 */ loadContactListsAsync()464 public abstract void loadContactListsAsync(); 465 approveSubscriptionRequest(String contact)466 public abstract void approveSubscriptionRequest(String contact); declineSubscriptionRequest(String contact)467 public abstract void declineSubscriptionRequest(String contact); 468 getConnection()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 */ doBlockContactAsync(String address, boolean block)480 protected abstract void doBlockContactAsync(String address, boolean block); doCreateContactListAsync(String name, Collection<Contact> contacts, boolean isDefault)481 protected abstract void doCreateContactListAsync(String name, 482 Collection<Contact> contacts, boolean isDefault); doDeleteContactListAsync(ContactList list)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 */ notifyContactsPresenceUpdated(Contact[] contacts)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 */ notifyContactError(int type, ImErrorInfo error, String listName, Contact contact)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 */ notifyContactListLoaded(ContactList list)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 */ notifyContactListsLoaded()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 */ notifyContactListUpdated(ContactList list, int type, Contact contact)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 */ notifyContactListNameUpdated(ContactList list, String name)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 */ notifyContactListCreated(ContactList list)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 */ notifyContactListDeleted(ContactList list)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 */ notifyBlockContact(Contact contact, boolean blocked)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 doAddContactToListAsync(String address, ContactList list)638 protected abstract void doAddContactToListAsync(String address, 639 ContactList list) throws ImException; doRemoveContactFromListAsync(Contact contact, ContactList list)640 protected abstract void doRemoveContactFromListAsync(Contact contact, ContactList list); setListNameAsync(String name, ContactList list)641 protected abstract void setListNameAsync(String name, ContactList list); 642 } 643