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.service; 19 20 import java.util.ArrayList; 21 import java.util.Collection; 22 import java.util.HashMap; 23 import java.util.HashSet; 24 import java.util.Iterator; 25 import java.util.List; 26 import java.util.Vector; 27 28 import android.content.ContentResolver; 29 import android.content.ContentUris; 30 import android.content.ContentValues; 31 import android.content.Intent; 32 import android.database.Cursor; 33 import android.net.Uri; 34 import android.os.RemoteCallbackList; 35 import android.os.RemoteException; 36 import android.provider.Im; 37 import android.util.Log; 38 import android.widget.Toast; 39 40 import com.android.im.IChatListener; 41 import com.android.im.IContactList; 42 import com.android.im.IContactListListener; 43 import com.android.im.IContactListManager; 44 import com.android.im.ISubscriptionListener; 45 import com.android.im.R; 46 import com.android.im.engine.Address; 47 import com.android.im.engine.Contact; 48 import com.android.im.engine.ContactList; 49 import com.android.im.engine.ContactListListener; 50 import com.android.im.engine.ContactListManager; 51 import com.android.im.engine.ImErrorInfo; 52 import com.android.im.engine.ImException; 53 import com.android.im.engine.Presence; 54 import com.android.im.engine.SubscriptionRequestListener; 55 56 public class ContactListManagerAdapter extends IContactListManager.Stub { 57 static final String TAG = RemoteImService.TAG; 58 59 ImConnectionAdapter mConn; 60 ContentResolver mResolver; 61 62 private ContactListManager mAdaptee; 63 private ContactListListenerAdapter mContactListListenerAdapter; 64 private SubscriptionRequestListenerAdapter mSubscriptionListenerAdapter; 65 66 final RemoteCallbackList<IContactListListener> mRemoteContactListeners 67 = new RemoteCallbackList<IContactListListener>(); 68 final RemoteCallbackList<ISubscriptionListener> mRemoteSubscriptionListeners 69 = new RemoteCallbackList<ISubscriptionListener>(); 70 71 HashMap<Address, ContactListAdapter> mContactLists; 72 HashMap<String, Contact> mTemporaryContacts; 73 74 HashSet<String> mValidatedContactLists; 75 HashSet<String> mValidatedContacts; 76 HashSet<String> mValidatedBlockedContacts; 77 78 private long mAccountId; 79 private long mProviderId; 80 81 private Uri mAvatarUrl; 82 private Uri mContactUrl; 83 84 static final long FAKE_TEMPORARY_LIST_ID = -1; 85 static final String[] CONTACT_LIST_ID_PROJECTION = { Im.ContactList._ID }; 86 87 RemoteImService mContext; 88 ContactListManagerAdapter(ImConnectionAdapter conn)89 public ContactListManagerAdapter(ImConnectionAdapter conn) { 90 mAdaptee = conn.getAdaptee().getContactListManager(); 91 mConn = conn; 92 mContext = conn.getContext(); 93 mResolver = mContext.getContentResolver(); 94 95 mContactListListenerAdapter = new ContactListListenerAdapter(); 96 mSubscriptionListenerAdapter = new SubscriptionRequestListenerAdapter(); 97 mContactLists = new HashMap<Address, ContactListAdapter>(); 98 mTemporaryContacts = new HashMap<String, Contact>(); 99 mValidatedContacts = new HashSet<String>(); 100 mValidatedContactLists = new HashSet<String>(); 101 mValidatedBlockedContacts = new HashSet<String>(); 102 103 mAdaptee.addContactListListener(mContactListListenerAdapter); 104 mAdaptee.setSubscriptionRequestListener(mSubscriptionListenerAdapter); 105 106 mAccountId = mConn.getAccountId(); 107 mProviderId = mConn.getProviderId(); 108 109 Uri.Builder builder = Im.Avatars.CONTENT_URI_AVATARS_BY.buildUpon(); 110 ContentUris.appendId(builder, mProviderId); 111 ContentUris.appendId(builder, mAccountId); 112 113 mAvatarUrl = builder.build(); 114 115 builder = Im.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon(); 116 ContentUris.appendId(builder, mProviderId); 117 ContentUris.appendId(builder, mAccountId); 118 119 mContactUrl = builder.build(); 120 } 121 createContactList(String name, List<Contact> contacts)122 public int createContactList(String name, List<Contact> contacts) { 123 try { 124 mAdaptee.createContactListAsync(name, contacts); 125 } catch (ImException e) { 126 return e.getImError().getCode(); 127 } 128 129 return ImErrorInfo.NO_ERROR; 130 } 131 deleteContactList(String name)132 public int deleteContactList(String name) { 133 try { 134 mAdaptee.deleteContactListAsync(name); 135 } catch (ImException e) { 136 return e.getImError().getCode(); 137 } 138 139 return ImErrorInfo.NO_ERROR; 140 } 141 getContactLists()142 public List getContactLists() { 143 synchronized (mContactLists) { 144 return new ArrayList<ContactListAdapter>(mContactLists.values()); 145 } 146 } 147 removeContact(String address)148 public int removeContact(String address) { 149 if(isTemporary(address)) { 150 // For temporary contact, just close the session and delete him in 151 // database. 152 closeChatSession(address); 153 154 String selection = Im.Contacts.USERNAME + "=?"; 155 String[] selectionArgs = { address }; 156 mResolver.delete(mContactUrl, selection, selectionArgs); 157 synchronized (mTemporaryContacts) { 158 mTemporaryContacts.remove(address); 159 } 160 } else { 161 synchronized (mContactLists) { 162 for(ContactListAdapter list : mContactLists.values()) { 163 int resCode = list.removeContact(address); 164 if (ImErrorInfo.ILLEGAL_CONTACT_ADDRESS == resCode) { 165 // Did not find in this list, continue to remove from 166 // other list. 167 continue; 168 } 169 if (ImErrorInfo.NO_ERROR != resCode) { 170 return resCode; 171 } 172 } 173 } 174 } 175 176 return ImErrorInfo.NO_ERROR; 177 } 178 approveSubscription(String address)179 public void approveSubscription(String address) { 180 mAdaptee.approveSubscriptionRequest(address); 181 } 182 declineSubscription(String address)183 public void declineSubscription(String address) { 184 mAdaptee.declineSubscriptionRequest(address); 185 } 186 blockContact(String address)187 public int blockContact(String address) { 188 try { 189 mAdaptee.blockContactAsync(address); 190 } catch (ImException e) { 191 return e.getImError().getCode(); 192 } 193 194 return ImErrorInfo.NO_ERROR; 195 } 196 unBlockContact(String address)197 public int unBlockContact(String address) { 198 try { 199 mAdaptee.unblockContactAsync(address); 200 } catch (ImException e) { 201 Log.e(TAG, e.getMessage()); 202 return e.getImError().getCode(); 203 } 204 205 return ImErrorInfo.NO_ERROR; 206 } 207 isBlocked(String address)208 public boolean isBlocked(String address) { 209 try { 210 return mAdaptee.isBlocked(address); 211 } catch (ImException e) { 212 Log.e(TAG, e.getMessage()); 213 return false; 214 } 215 } 216 registerContactListListener(IContactListListener listener)217 public void registerContactListListener(IContactListListener listener) { 218 if (listener != null) { 219 mRemoteContactListeners.register(listener); 220 } 221 } 222 unregisterContactListListener(IContactListListener listener)223 public void unregisterContactListListener(IContactListListener listener) { 224 if (listener != null) { 225 mRemoteContactListeners.unregister(listener); 226 } 227 } 228 registerSubscriptionListener(ISubscriptionListener listener)229 public void registerSubscriptionListener(ISubscriptionListener listener) { 230 if (listener != null) { 231 mRemoteSubscriptionListeners.register(listener); 232 } 233 } 234 unregisterSubscriptionListener(ISubscriptionListener listener)235 public void unregisterSubscriptionListener(ISubscriptionListener listener) { 236 if (listener != null) { 237 mRemoteSubscriptionListeners.unregister(listener); 238 } 239 } 240 getContactList(String name)241 public IContactList getContactList(String name) { 242 return getContactListAdapter(name); 243 } 244 loadContactLists()245 public void loadContactLists() { 246 if(mAdaptee.getState() == ContactListManager.LISTS_NOT_LOADED){ 247 clearValidatedContactsAndLists(); 248 mAdaptee.loadContactListsAsync(); 249 } 250 } 251 getState()252 public int getState() { 253 return mAdaptee.getState(); 254 } 255 getContactByAddress(String address)256 public Contact getContactByAddress(String address) { 257 Contact c = mAdaptee.getContact(address); 258 if(c == null) { 259 synchronized (mTemporaryContacts) { 260 return mTemporaryContacts.get(address); 261 } 262 } else { 263 return c; 264 } 265 } 266 createTemporaryContact(String address)267 public Contact createTemporaryContact(String address) { 268 Contact c = mAdaptee.createTemporaryContact(address); 269 insertTemporary(c); 270 return c; 271 } 272 queryOrInsertContact(Contact c)273 public long queryOrInsertContact(Contact c) { 274 long result; 275 276 String username = c.getAddress().getFullName(); 277 String selection = Im.Contacts.USERNAME + "=?"; 278 String[] selectionArgs = { username }; 279 String[] projection = {Im.Contacts._ID}; 280 281 Cursor cursor = mResolver.query(mContactUrl, projection, selection, 282 selectionArgs, null); 283 284 if(cursor != null && cursor.moveToFirst()) { 285 result = cursor.getLong(0); 286 } else { 287 result = insertTemporary(c); 288 } 289 290 if(cursor != null) { 291 cursor.close(); 292 } 293 return result; 294 } 295 insertTemporary(Contact c)296 private long insertTemporary(Contact c) { 297 synchronized (mTemporaryContacts) { 298 mTemporaryContacts.put(c.getAddress().getFullName(), c); 299 } 300 Uri uri = insertContactContent(c, FAKE_TEMPORARY_LIST_ID); 301 return ContentUris.parseId(uri); 302 } 303 304 /** 305 * Tells if a contact is a temporary one which is not in the list of 306 * contacts that we subscribe presence for. Usually created because of the 307 * user is having a chat session with this contact. 308 * 309 * @param address 310 * the address of the contact. 311 * @return <code>true</code> if it's a temporary contact; 312 * <code>false</code> otherwise. 313 */ isTemporary(String address)314 public boolean isTemporary(String address) { 315 synchronized (mTemporaryContacts) { 316 return mTemporaryContacts.containsKey(address); 317 } 318 } 319 getContactListAdapter(String name)320 ContactListAdapter getContactListAdapter(String name) { 321 synchronized (mContactLists) { 322 for (ContactListAdapter list : mContactLists.values()) { 323 if (name.equals(list.getName())) { 324 return list; 325 } 326 } 327 328 return null; 329 } 330 } 331 getContactListAdapter(Address address)332 ContactListAdapter getContactListAdapter(Address address) { 333 synchronized (mContactLists) { 334 return mContactLists.get(address); 335 } 336 } 337 338 private class Exclusion { 339 private StringBuilder mSelection; 340 private List mSelectionArgs; 341 private String mExclusionColumn; 342 Exclusion(String exclusionColumn, Collection<String> items)343 Exclusion(String exclusionColumn, Collection<String> items) { 344 mSelection = new StringBuilder(); 345 mSelectionArgs = new ArrayList(); 346 mExclusionColumn = exclusionColumn; 347 for (String s : items) { 348 add(s); 349 } 350 } 351 add(String exclusionItem)352 public void add(String exclusionItem) { 353 if (mSelection.length()==0) { 354 mSelection.append(mExclusionColumn + "!=?"); 355 } else { 356 mSelection.append(" AND " + mExclusionColumn + "!=?"); 357 } 358 mSelectionArgs.add(exclusionItem); 359 } 360 getSelection()361 public String getSelection() { 362 return mSelection.toString(); 363 } 364 getSelectionArgs()365 public String[] getSelectionArgs() { 366 return (String []) mSelectionArgs.toArray(new String[0]); 367 } 368 } 369 removeObsoleteContactsAndLists()370 private void removeObsoleteContactsAndLists() { 371 // remove all contacts for this provider & account which have not been 372 // added since login, yet still exist in db from a prior login 373 Exclusion exclusion = new Exclusion(Im.Contacts.USERNAME, mValidatedContacts); 374 mResolver.delete(mContactUrl, exclusion.getSelection(), exclusion.getSelectionArgs()); 375 376 // remove all blocked contacts for this provider & account which have not been 377 // added since login, yet still exist in db from a prior login 378 exclusion = new Exclusion(Im.BlockedList.USERNAME, mValidatedBlockedContacts); 379 Uri.Builder builder = Im.BlockedList.CONTENT_URI.buildUpon(); 380 ContentUris.appendId(builder, mProviderId); 381 ContentUris.appendId(builder, mAccountId); 382 Uri uri = builder.build(); 383 mResolver.delete(uri, exclusion.getSelection(), exclusion.getSelectionArgs()); 384 385 // remove all contact lists for this provider & account which have not been 386 // added since login, yet still exist in db from a prior login 387 exclusion = new Exclusion(Im.ContactList.NAME, mValidatedContactLists); 388 builder = Im.ContactList.CONTENT_URI.buildUpon(); 389 ContentUris.appendId(builder, mProviderId); 390 ContentUris.appendId(builder, mAccountId); 391 uri = builder.build(); 392 mResolver.delete(uri, exclusion.getSelection(), exclusion.getSelectionArgs()); 393 394 } 395 396 final class ContactListListenerAdapter implements ContactListListener { 397 private boolean mAllContactsLoaded; 398 399 // class to hold contact changes made before mAllContactsLoaded 400 private class StoredContactChange { 401 int mType; 402 ContactList mList; 403 Contact mContact; 404 StoredContactChange(int type, ContactList list, Contact contact)405 StoredContactChange(int type, ContactList list, Contact contact) { 406 mType = type; 407 mList = list; 408 mContact = contact; 409 } 410 } 411 private Vector<StoredContactChange> mDelayedContactChanges = 412 new Vector<StoredContactChange>(); 413 onContactsPresenceUpdate(final Contact[] contacts)414 public void onContactsPresenceUpdate(final Contact[] contacts) { 415 // The client listens only to presence updates for now. Update 416 // the avatars first to ensure it can get the new avatar when 417 // presence updated. 418 // TODO: Don't update avatar now since none of the server supports it 419 // updateAvatarsContent(contacts); 420 updatePresenceContent(contacts); 421 422 final int N = mRemoteContactListeners.beginBroadcast(); 423 for (int i = 0; i < N; i++) { 424 IContactListListener listener = 425 mRemoteContactListeners.getBroadcastItem(i); 426 try { 427 listener.onContactsPresenceUpdate(contacts); 428 } catch (RemoteException e) { 429 // The RemoteCallbackList will take care of removing the 430 // dead listeners. 431 } 432 } 433 mRemoteContactListeners.finishBroadcast(); 434 } 435 onContactChange(final int type, final ContactList list, final Contact contact)436 public void onContactChange(final int type, final ContactList list, 437 final Contact contact) { 438 ContactListAdapter removed = null; 439 String notificationText = null; 440 441 switch (type) { 442 case LIST_LOADED: 443 case LIST_CREATED: 444 addContactListContent(list); 445 break; 446 447 case LIST_DELETED: 448 removed = removeContactListFromDataBase(list.getName()); 449 // handle case where a list is deleted before mAllContactsLoaded 450 if (!mAllContactsLoaded) { 451 // if a cached contact list is deleted before the actual contact list is 452 // downloaded from the server, we will have to remove the list again once 453 // once mAllContactsLoaded is true 454 if (!mValidatedContactLists.contains(list.getName())) { 455 mDelayedContactChanges.add(new StoredContactChange(type, list, contact)); 456 } 457 } 458 break; 459 460 case LIST_CONTACT_ADDED: 461 long listId = getContactListAdapter(list.getAddress()).getDataBaseId(); 462 String contactAddress = contact.getAddress().getFullName(); 463 if(isTemporary(contactAddress)){ 464 moveTemporaryContactToList(contactAddress, listId); 465 } else { 466 insertContactContent(contact, listId); 467 } 468 notificationText = mContext.getResources().getString( 469 R.string.add_contact_success, contact.getName()); 470 // handle case where a contact is added before mAllContactsLoaded 471 if (!mAllContactsLoaded) { 472 // if a contact is added to a cached contact list before the actual contact 473 // list is downloaded from the server, we will have to add the contact to 474 // the contact list once mAllContactsLoaded is true 475 if (!mValidatedContactLists.contains(list.getName())) { 476 mDelayedContactChanges.add(new StoredContactChange(type, list, contact)); 477 } 478 } 479 break; 480 481 case LIST_CONTACT_REMOVED: 482 deleteContactFromDataBase(contact, list); 483 // handle case where a contact is removed before mAllContactsLoaded 484 if (!mAllContactsLoaded) { 485 // if a contact is added to a cached contact list before the actual contact 486 // list is downloaded from the server, we will have to add the contact to 487 // the contact list once mAllContactsLoaded is true 488 if (!mValidatedContactLists.contains(list.getName())) { 489 mDelayedContactChanges.add(new StoredContactChange(type, list, contact)); 490 } 491 } 492 493 // Clear ChatSession if any. 494 String address = contact.getAddress().getFullName(); 495 closeChatSession(address); 496 497 notificationText = mContext.getResources().getString( 498 R.string.delete_contact_success, contact.getName()); 499 break; 500 501 case LIST_RENAMED: 502 updateListNameInDataBase(list); 503 // handle case where a list is renamed before mAllContactsLoaded 504 if (!mAllContactsLoaded) { 505 // if a contact list name is updated before the actual contact list is 506 // downloaded from the server, we will have to update the list name again 507 // once mAllContactsLoaded is true 508 if (!mValidatedContactLists.contains(list.getName())) { 509 mDelayedContactChanges.add(new StoredContactChange(type, list, contact)); 510 } 511 } 512 break; 513 514 case CONTACT_BLOCKED: 515 insertBlockedContactToDataBase(contact); 516 address = contact.getAddress().getFullName(); 517 updateContactType(address, Im.Contacts.TYPE_BLOCKED); 518 closeChatSession(address); 519 notificationText = mContext.getResources().getString( 520 R.string.block_contact_success, contact.getName()); 521 break; 522 523 case CONTACT_UNBLOCKED: 524 removeBlockedContactFromDataBase(contact); 525 notificationText = mContext.getResources().getString( 526 R.string.unblock_contact_success, contact.getName()); 527 // handle case where a contact is unblocked before mAllContactsLoaded 528 if (!mAllContactsLoaded) { 529 // if a contact list name is updated before the actual contact list is 530 // downloaded from the server, we will have to update the list name again 531 // once mAllContactsLoaded is true 532 if (!mValidatedBlockedContacts.contains(contact.getName())) { 533 mDelayedContactChanges.add(new StoredContactChange(type, list, contact)); 534 } 535 } 536 break; 537 538 default: 539 Log.e(TAG, "Unknown list update event!"); 540 break; 541 } 542 543 final ContactListAdapter listAdapter; 544 if (type == LIST_DELETED) { 545 listAdapter = removed; 546 } else { 547 listAdapter = (list == null) ? null 548 : getContactListAdapter(list.getAddress()); 549 } 550 final int N = mRemoteContactListeners.beginBroadcast(); 551 for (int i = 0; i < N; i++) { 552 IContactListListener listener = 553 mRemoteContactListeners.getBroadcastItem(i); 554 try { 555 listener.onContactChange(type, listAdapter, contact); 556 } catch (RemoteException e) { 557 // The RemoteCallbackList will take care of removing the 558 // dead listeners. 559 } 560 } 561 mRemoteContactListeners.finishBroadcast(); 562 563 if (mAllContactsLoaded && notificationText != null) { 564 mContext.showToast(notificationText, Toast.LENGTH_SHORT); 565 } 566 } 567 onContactError(final int errorType, final ImErrorInfo error, final String listName, final Contact contact)568 public void onContactError(final int errorType, final ImErrorInfo error, 569 final String listName, final Contact contact) { 570 final int N = mRemoteContactListeners.beginBroadcast(); 571 for (int i = 0; i < N; i++) { 572 IContactListListener listener = 573 mRemoteContactListeners.getBroadcastItem(i); 574 try { 575 listener.onContactError(errorType, error, listName, contact); 576 } catch (RemoteException e) { 577 // The RemoteCallbackList will take care of removing the 578 // dead listeners. 579 } 580 } 581 mRemoteContactListeners.finishBroadcast(); 582 } 583 handleDelayedContactChanges()584 public void handleDelayedContactChanges() { 585 for (StoredContactChange change : mDelayedContactChanges) { 586 onContactChange(change.mType, change.mList, change.mContact); 587 } 588 } 589 onAllContactListsLoaded()590 public void onAllContactListsLoaded() { 591 mAllContactsLoaded = true; 592 handleDelayedContactChanges(); 593 removeObsoleteContactsAndLists(); 594 final int N = mRemoteContactListeners.beginBroadcast(); 595 for (int i = 0; i < N; i++) { 596 IContactListListener listener = 597 mRemoteContactListeners.getBroadcastItem(i); 598 try { 599 listener.onAllContactListsLoaded(); 600 } catch (RemoteException e) { 601 // The RemoteCallbackList will take care of removing the 602 // dead listeners. 603 } 604 } 605 mRemoteContactListeners.finishBroadcast(); 606 } 607 } 608 609 final class SubscriptionRequestListenerAdapter 610 implements SubscriptionRequestListener { 611 onSubScriptionRequest(final Contact from)612 public void onSubScriptionRequest(final Contact from) { 613 String username = from.getAddress().getFullName(); 614 String nickname = from.getName(); 615 Uri uri = insertOrUpdateSubscription(username, nickname, 616 Im.Contacts.SUBSCRIPTION_TYPE_FROM, 617 Im.Contacts.SUBSCRIPTION_STATUS_SUBSCRIBE_PENDING); 618 mContext.getStatusBarNotifier().notifySubscriptionRequest(mProviderId, mAccountId, 619 ContentUris.parseId(uri), username, nickname); 620 final int N = mRemoteSubscriptionListeners.beginBroadcast(); 621 for (int i = 0; i < N; i++) { 622 ISubscriptionListener listener = 623 mRemoteSubscriptionListeners.getBroadcastItem(i); 624 try { 625 listener.onSubScriptionRequest(from); 626 } catch (RemoteException e) { 627 // The RemoteCallbackList will take care of removing the 628 // dead listeners. 629 } 630 } 631 mRemoteSubscriptionListeners.finishBroadcast(); 632 } 633 onSubscriptionApproved(final String contact)634 public void onSubscriptionApproved(final String contact) { 635 insertOrUpdateSubscription(contact, null, 636 Im.Contacts.SUBSCRIPTION_TYPE_NONE, 637 Im.Contacts.SUBSCRIPTION_STATUS_NONE); 638 639 final int N = mRemoteSubscriptionListeners.beginBroadcast(); 640 for (int i = 0; i < N; i++) { 641 ISubscriptionListener listener = 642 mRemoteSubscriptionListeners.getBroadcastItem(i); 643 try { 644 listener.onSubscriptionApproved(contact); 645 } catch (RemoteException e) { 646 // The RemoteCallbackList will take care of removing the 647 // dead listeners. 648 } 649 } 650 mRemoteSubscriptionListeners.finishBroadcast(); 651 } 652 onSubscriptionDeclined(final String contact)653 public void onSubscriptionDeclined(final String contact) { 654 insertOrUpdateSubscription(contact, null, 655 Im.Contacts.SUBSCRIPTION_TYPE_NONE, 656 Im.Contacts.SUBSCRIPTION_STATUS_NONE); 657 658 final int N = mRemoteSubscriptionListeners.beginBroadcast(); 659 for (int i = 0; i < N; i++) { 660 ISubscriptionListener listener = 661 mRemoteSubscriptionListeners.getBroadcastItem(i); 662 try { 663 listener.onSubscriptionDeclined(contact); 664 } catch (RemoteException e) { 665 // The RemoteCallbackList will take care of removing the 666 // dead listeners. 667 } 668 } 669 mRemoteSubscriptionListeners.finishBroadcast(); 670 } 671 onApproveSubScriptionError(final String contact, final ImErrorInfo error)672 public void onApproveSubScriptionError(final String contact, final ImErrorInfo error) { 673 String displayableAddress = getDisplayableAddress(contact); 674 String msg = mContext.getString(R.string.approve_subscription_error, displayableAddress); 675 mContext.showToast(msg, Toast.LENGTH_SHORT); 676 } 677 onDeclineSubScriptionError(final String contact, final ImErrorInfo error)678 public void onDeclineSubScriptionError(final String contact, final ImErrorInfo error) { 679 String displayableAddress = getDisplayableAddress(contact); 680 String msg = mContext.getString(R.string.decline_subscription_error, displayableAddress); 681 mContext.showToast(msg, Toast.LENGTH_SHORT); 682 } 683 } 684 getDisplayableAddress(String impsAddress)685 String getDisplayableAddress(String impsAddress) { 686 if (impsAddress.startsWith("wv:")) { 687 return impsAddress.substring(3); 688 } 689 return impsAddress; 690 } 691 insertBlockedContactToDataBase(Contact contact)692 void insertBlockedContactToDataBase(Contact contact) { 693 // Remove the blocked contact if it already exists, to avoid duplicates and 694 // handle the odd case where a blocked contact's nickname has changed 695 removeBlockedContactFromDataBase(contact); 696 697 Uri.Builder builder = Im.BlockedList.CONTENT_URI.buildUpon(); 698 ContentUris.appendId(builder, mProviderId); 699 ContentUris.appendId(builder, mAccountId); 700 Uri uri = builder.build(); 701 702 String username = contact.getAddress().getFullName(); 703 ContentValues values = new ContentValues(2); 704 values.put(Im.BlockedList.USERNAME, username); 705 values.put(Im.BlockedList.NICKNAME, contact.getName()); 706 707 mResolver.insert(uri, values); 708 709 mValidatedBlockedContacts.add(username); 710 } 711 removeBlockedContactFromDataBase(Contact contact)712 void removeBlockedContactFromDataBase(Contact contact) { 713 String address = contact.getAddress().getFullName(); 714 715 Uri.Builder builder = Im.BlockedList.CONTENT_URI.buildUpon(); 716 ContentUris.appendId(builder, mProviderId); 717 ContentUris.appendId(builder, mAccountId); 718 719 Uri uri = builder.build(); 720 mResolver.delete(uri, Im.BlockedList.USERNAME + "=?", new String[]{ address }); 721 722 int type = isTemporary(address) ? Im.Contacts.TYPE_TEMPORARY 723 : Im.Contacts.TYPE_NORMAL; 724 updateContactType(address, type); 725 } 726 moveTemporaryContactToList(String address, long listId)727 void moveTemporaryContactToList(String address, long listId) { 728 synchronized (mTemporaryContacts) { 729 mTemporaryContacts.remove(address); 730 } 731 ContentValues values = new ContentValues(2); 732 values.put(Im.Contacts.TYPE, Im.Contacts.TYPE_NORMAL); 733 values.put(Im.Contacts.CONTACTLIST, listId); 734 735 String selection = Im.Contacts.USERNAME + "=? AND " + Im.Contacts.TYPE + "=" 736 + Im.Contacts.TYPE_TEMPORARY; 737 String[] selectionArgs = { address }; 738 739 mResolver.update(mContactUrl, values, selection, selectionArgs); 740 } 741 updateContactType(String address, int type)742 void updateContactType(String address, int type) { 743 ContentValues values = new ContentValues(1); 744 values.put(Im.Contacts.TYPE, type); 745 updateContact(address, values); 746 } 747 748 /** 749 * Insert or update subscription request from user into the database. 750 * 751 * @param username 752 * @param nickname 753 * @param subscriptionType 754 * @param subscriptionStatus 755 */ insertOrUpdateSubscription(String username, String nickname, int subscriptionType, int subscriptionStatus)756 Uri insertOrUpdateSubscription(String username, String nickname, int subscriptionType, 757 int subscriptionStatus) { 758 Cursor cursor = mResolver.query(mContactUrl, new String[]{ Im.Contacts._ID }, 759 Im.Contacts.USERNAME + "=?", new String[]{username}, null); 760 if (cursor == null) { 761 Log.w(TAG, "query contact " + username + " failed"); 762 return null; 763 } 764 765 Uri uri; 766 if (cursor.moveToFirst()) { 767 ContentValues values = new ContentValues(2); 768 values.put(Im.Contacts.SUBSCRIPTION_TYPE, subscriptionType); 769 values.put(Im.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); 770 771 long contactId = cursor.getLong(0); 772 uri = ContentUris.withAppendedId(Im.Contacts.CONTENT_URI, contactId); 773 mResolver.update(uri, values, null, null); 774 } else { 775 ContentValues values = new ContentValues(6); 776 values.put(Im.Contacts.USERNAME, username); 777 values.put(Im.Contacts.NICKNAME, nickname); 778 values.put(Im.Contacts.TYPE, Im.Contacts.TYPE_NORMAL); 779 values.put(Im.Contacts.CONTACTLIST, FAKE_TEMPORARY_LIST_ID); 780 values.put(Im.Contacts.SUBSCRIPTION_TYPE, subscriptionType); 781 values.put(Im.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); 782 783 uri = mResolver.insert(mContactUrl, values); 784 } 785 cursor.close(); 786 return uri; 787 } 788 updateContact(String username, ContentValues values)789 void updateContact(String username, ContentValues values) { 790 String selection = Im.Contacts.USERNAME + "=?"; 791 String[] selectionArgs = { username }; 792 mResolver.update(mContactUrl, values, selection, selectionArgs); 793 } 794 updatePresenceContent(Contact[] contacts)795 void updatePresenceContent(Contact[] contacts) { 796 ArrayList<String> usernames = new ArrayList<String>(); 797 ArrayList<String> statusArray = new ArrayList<String>(); 798 ArrayList<String> customStatusArray = new ArrayList<String>(); 799 ArrayList<String> clientTypeArray = new ArrayList<String>(); 800 801 for(Contact c : contacts) { 802 String username = c.getAddress().getFullName(); 803 Presence p = c.getPresence(); 804 int status = convertPresenceStatus(p); 805 String customStatus = p.getStatusText(); 806 int clientType = translateClientType(p); 807 808 usernames.add(username); 809 statusArray.add(String.valueOf(status)); 810 customStatusArray.add(customStatus); 811 clientTypeArray.add(String.valueOf(clientType)); 812 } 813 814 ContentValues values = new ContentValues(); 815 values.put(Im.Contacts.ACCOUNT, mAccountId); 816 values.putStringArrayList(Im.Contacts.USERNAME, usernames); 817 values.putStringArrayList(Im.Presence.PRESENCE_STATUS, statusArray); 818 values.putStringArrayList(Im.Presence.PRESENCE_CUSTOM_STATUS, customStatusArray); 819 values.putStringArrayList(Im.Presence.CONTENT_TYPE, clientTypeArray); 820 821 mResolver.update(Im.Presence.BULK_CONTENT_URI, values, null, null); 822 } 823 updateAvatarsContent(Contact[] contacts)824 void updateAvatarsContent(Contact[] contacts) { 825 ArrayList<ContentValues> avatars = new ArrayList<ContentValues>(); 826 ArrayList<String> usernames = new ArrayList<String>(); 827 828 for (Contact contact : contacts) { 829 byte[] avatarData = contact.getPresence().getAvatarData(); 830 if (avatarData == null) { 831 continue; 832 } 833 834 String username = contact.getAddress().getFullName(); 835 836 ContentValues values = new ContentValues(2); 837 values.put(Im.Avatars.CONTACT, username); 838 values.put(Im.Avatars.DATA, avatarData); 839 avatars.add(values); 840 usernames.add(username); 841 } 842 if (avatars.size() > 0) { 843 // ImProvider will replace the avatar content if it already exist. 844 mResolver.bulkInsert(mAvatarUrl, avatars.toArray( 845 new ContentValues[avatars.size()])); 846 847 // notify avatar changed 848 Intent i = new Intent(ImServiceConstants.ACTION_AVATAR_CHANGED); 849 i.putExtra(ImServiceConstants.EXTRA_INTENT_FROM_ADDRESS, usernames); 850 i.putExtra(ImServiceConstants.EXTRA_INTENT_PROVIDER_ID, mProviderId); 851 i.putExtra(ImServiceConstants.EXTRA_INTENT_ACCOUNT_ID, mAccountId); 852 mContext.sendBroadcast(i); 853 } 854 } 855 removeContactListFromDataBase(String name)856 ContactListAdapter removeContactListFromDataBase(String name) { 857 ContactListAdapter listAdapter = getContactListAdapter(name); 858 if (listAdapter == null) { 859 return null; 860 } 861 long id = listAdapter.getDataBaseId(); 862 863 // delete contacts of this list first 864 mResolver.delete(mContactUrl, 865 Im.Contacts.CONTACTLIST + "=?", new String[]{Long.toString(id)}); 866 867 mResolver.delete(ContentUris.withAppendedId(Im.ContactList.CONTENT_URI, id), null, null); 868 synchronized (mContactLists) { 869 return mContactLists.remove(listAdapter.getAddress()); 870 } 871 } 872 addContactListContent(ContactList list)873 void addContactListContent(ContactList list) { 874 String selection = Im.ContactList.NAME + "=? AND " 875 + Im.ContactList.PROVIDER + "=? AND " 876 + Im.ContactList.ACCOUNT + "=?"; 877 String[] selectionArgs = { list.getName(), 878 Long.toString(mProviderId), 879 Long.toString(mAccountId) }; 880 Cursor cursor = mResolver.query(Im.ContactList.CONTENT_URI, 881 CONTACT_LIST_ID_PROJECTION, 882 selection, 883 selectionArgs, 884 null); // no sort order 885 long listId = 0; 886 Uri uri = null; 887 try { 888 if (cursor.moveToFirst()) { 889 listId = cursor.getLong(0); 890 uri = ContentUris.withAppendedId(Im.ContactList.CONTENT_URI, listId); 891 //Log.d(TAG,"Found and removing ContactList with name "+list.getName()); 892 } 893 } finally { 894 cursor.close(); 895 } 896 if (uri != null) { 897 // remove existing ContactList and Contacts of that list for replacement by the newly 898 // downloaded list 899 mResolver.delete(mContactUrl, Im.Contacts.CONTACTLIST + "=?", 900 new String[]{Long.toString(listId)}); 901 mResolver.delete(uri, selection, selectionArgs); 902 } 903 904 ContentValues contactListValues = new ContentValues(3); 905 contactListValues.put(Im.ContactList.NAME, list.getName()); 906 contactListValues.put(Im.ContactList.PROVIDER, mProviderId); 907 contactListValues.put(Im.ContactList.ACCOUNT, mAccountId); 908 909 //Log.d(TAG, "Adding ContactList name="+list.getName()); 910 mValidatedContactLists.add(list.getName()); 911 uri = mResolver.insert(Im.ContactList.CONTENT_URI, contactListValues); 912 listId = ContentUris.parseId(uri); 913 914 synchronized (mContactLists) { 915 mContactLists.put(list.getAddress(), 916 new ContactListAdapter(list, listId)); 917 } 918 919 Collection<Contact> contacts = list.getContacts(); 920 if (contacts == null || contacts.size() == 0) { 921 return; 922 } 923 924 Iterator<Contact> iter = contacts.iterator(); 925 while(iter.hasNext()) { 926 Contact c = iter.next(); 927 String address = c.getAddress().getFullName(); 928 if(isTemporary(address)) { 929 moveTemporaryContactToList(address, listId); 930 iter.remove(); 931 } 932 mValidatedContacts.add(address); 933 } 934 935 ArrayList<String> usernames = new ArrayList<String>(); 936 ArrayList<String> nicknames = new ArrayList<String>(); 937 ArrayList<String> contactTypeArray = new ArrayList<String>(); 938 for (Contact c : contacts) { 939 String username = c.getAddress().getFullName(); 940 String nickname = c.getName(); 941 int type = Im.Contacts.TYPE_NORMAL; 942 if(isTemporary(username)) { 943 type = Im.Contacts.TYPE_TEMPORARY; 944 } 945 if (isBlocked(username)) { 946 type = Im.Contacts.TYPE_BLOCKED; 947 } 948 949 usernames.add(username); 950 nicknames.add(nickname); 951 contactTypeArray.add(String.valueOf(type)); 952 } 953 ContentValues values = new ContentValues(6); 954 955 values.put(Im.Contacts.PROVIDER, mProviderId); 956 values.put(Im.Contacts.ACCOUNT, mAccountId); 957 values.put(Im.Contacts.CONTACTLIST, listId); 958 values.putStringArrayList(Im.Contacts.USERNAME, usernames); 959 values.putStringArrayList(Im.Contacts.NICKNAME, nicknames); 960 values.putStringArrayList(Im.Contacts.TYPE, contactTypeArray); 961 962 mResolver.insert(Im.Contacts.BULK_CONTENT_URI, values); 963 } 964 updateListNameInDataBase(ContactList list)965 void updateListNameInDataBase(ContactList list) { 966 ContactListAdapter listAdapter = getContactListAdapter(list.getAddress()); 967 968 Uri uri = ContentUris.withAppendedId(Im.ContactList.CONTENT_URI, listAdapter.getDataBaseId()); 969 ContentValues values = new ContentValues(1); 970 values.put(Im.ContactList.NAME, list.getName()); 971 972 mResolver.update(uri, values, null, null); 973 } 974 deleteContactFromDataBase(Contact contact, ContactList list)975 void deleteContactFromDataBase(Contact contact, ContactList list) { 976 String selection = Im.Contacts.USERNAME 977 + "=? AND " + Im.Contacts.CONTACTLIST + "=?"; 978 long listId = getContactListAdapter(list.getAddress()).getDataBaseId(); 979 String username = contact.getAddress().getFullName(); 980 String[] selectionArgs = {username, Long.toString(listId)}; 981 982 mResolver.delete(mContactUrl, selection, selectionArgs); 983 984 // clear the history message if the contact doesn't exist in any list 985 // anymore. 986 if(mAdaptee.getContact(contact.getAddress()) == null) { 987 clearHistoryMessages(username); 988 } 989 } 990 insertContactContent(Contact contact, long listId)991 Uri insertContactContent(Contact contact, long listId) { 992 ContentValues values = getContactContentValues(contact, listId); 993 994 Uri uri = mResolver.insert(mContactUrl, values); 995 996 ContentValues presenceValues = getPresenceValues(ContentUris.parseId(uri), 997 contact.getPresence()); 998 999 mResolver.insert(Im.Presence.CONTENT_URI, presenceValues); 1000 1001 return uri; 1002 } 1003 getContactContentValues(Contact contact, long listId)1004 private ContentValues getContactContentValues(Contact contact, long listId) { 1005 final String username = contact.getAddress().getFullName(); 1006 final String nickname = contact.getName(); 1007 int type = Im.Contacts.TYPE_NORMAL; 1008 if(isTemporary(username)) { 1009 type = Im.Contacts.TYPE_TEMPORARY; 1010 } 1011 if (isBlocked(username)) { 1012 type = Im.Contacts.TYPE_BLOCKED; 1013 } 1014 1015 ContentValues values = new ContentValues(4); 1016 values.put(Im.Contacts.USERNAME, username); 1017 values.put(Im.Contacts.NICKNAME, nickname); 1018 values.put(Im.Contacts.CONTACTLIST, listId); 1019 values.put(Im.Contacts.TYPE, type); 1020 return values; 1021 } 1022 clearHistoryMessages(String contact)1023 void clearHistoryMessages(String contact) { 1024 Uri uri = Im.Messages.getContentUriByContact(mProviderId, 1025 mAccountId, contact); 1026 mResolver.delete(uri, null, null); 1027 } 1028 getPresenceValues(long contactId, Presence p)1029 private ContentValues getPresenceValues(long contactId, Presence p) { 1030 ContentValues values = new ContentValues(3); 1031 values.put(Im.Presence.CONTACT_ID, contactId); 1032 values.put(Im.Contacts.PRESENCE_STATUS, convertPresenceStatus(p)); 1033 values.put(Im.Contacts.PRESENCE_CUSTOM_STATUS, p.getStatusText()); 1034 values.put(Im.Presence.CLIENT_TYPE, translateClientType(p)); 1035 return values; 1036 } 1037 translateClientType(Presence presence)1038 private int translateClientType(Presence presence) { 1039 int clientType = presence.getClientType(); 1040 switch (clientType) { 1041 case Presence.CLIENT_TYPE_MOBILE: 1042 return Im.Presence.CLIENT_TYPE_MOBILE; 1043 default: 1044 return Im.Presence.CLIENT_TYPE_DEFAULT; 1045 } 1046 } 1047 1048 /** 1049 * Converts the presence status to the value defined for ImProvider. 1050 * 1051 * @param presence The presence from the IM engine. 1052 * @return The status value defined in for ImProvider. 1053 */ convertPresenceStatus(Presence presence)1054 public static int convertPresenceStatus(Presence presence) { 1055 switch (presence.getStatus()) { 1056 case Presence.AVAILABLE: 1057 return Im.Presence.AVAILABLE; 1058 1059 case Presence.IDLE: 1060 return Im.Presence.IDLE; 1061 1062 case Presence.AWAY: 1063 return Im.Presence.AWAY; 1064 1065 case Presence.DO_NOT_DISTURB: 1066 return Im.Presence.DO_NOT_DISTURB; 1067 1068 case Presence.OFFLINE: 1069 return Im.Presence.OFFLINE; 1070 } 1071 1072 // impossible... 1073 Log.e(TAG, "Illegal presence status value " + presence.getStatus()); 1074 return Im.Presence.AVAILABLE; 1075 } 1076 clearOnLogout()1077 public void clearOnLogout() { 1078 clearValidatedContactsAndLists(); 1079 clearTemporaryContacts(); 1080 clearPresence(); 1081 } 1082 1083 /** 1084 * Clears the list of validated contacts and contact lists. 1085 * As contacts and contacts lists are added after login, contacts and contact lists are 1086 * stored as "validated contacts". After initial download of contacts is complete, any contacts 1087 * and contact lists that remain in the database, but are not in the validated list, are 1088 * obsolete and should be removed. This function resets that list for use upon login. 1089 */ clearValidatedContactsAndLists()1090 private void clearValidatedContactsAndLists() { 1091 // clear the list of validated contacts, contact lists, and blocked contacts 1092 mValidatedContacts.clear(); 1093 mValidatedContactLists.clear(); 1094 mValidatedBlockedContacts.clear(); 1095 } 1096 1097 /** 1098 * Clear the temporary contacts in the database. As contacts are persist between 1099 * IM sessions, the temporary contacts need to be cleared after logout. 1100 */ clearTemporaryContacts()1101 private void clearTemporaryContacts() { 1102 String selection = Im.Contacts.CONTACTLIST + "=" + FAKE_TEMPORARY_LIST_ID; 1103 mResolver.delete(mContactUrl, selection, null); 1104 } 1105 1106 /** 1107 * Clears the presence of the all contacts. As contacts are persist between 1108 * IM sessions, the presence need to be cleared after logout. 1109 */ clearPresence()1110 void clearPresence() { 1111 StringBuilder where = new StringBuilder(); 1112 where.append(Im.Presence.CONTACT_ID); 1113 where.append(" in (select _id from contacts where "); 1114 where.append(Im.Contacts.ACCOUNT); 1115 where.append("="); 1116 where.append(mAccountId); 1117 where.append(")"); 1118 mResolver.delete(Im.Presence.CONTENT_URI, where.toString(), null); 1119 } 1120 closeChatSession(String address)1121 void closeChatSession(String address) { 1122 ChatSessionManagerAdapter sessionManager = 1123 (ChatSessionManagerAdapter) mConn.getChatSessionManager(); 1124 ChatSessionAdapter session = 1125 (ChatSessionAdapter) sessionManager.getChatSession(address); 1126 if(session != null) { 1127 session.leave(); 1128 } 1129 } 1130 updateChatPresence(String address, String nickname, Presence p)1131 void updateChatPresence(String address, String nickname, Presence p) { 1132 ChatSessionManagerAdapter sessionManager = 1133 (ChatSessionManagerAdapter) mConn.getChatSessionManager(); 1134 // TODO: This only find single chat sessions, we need to go through all 1135 // active chat sessions and find if the contact is a participant of the 1136 // session. 1137 ChatSessionAdapter session = 1138 (ChatSessionAdapter) sessionManager.getChatSession(address); 1139 if(session != null) { 1140 session.insertPresenceUpdatesMsg(nickname, p); 1141 } 1142 } 1143 1144 1145 } 1146