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.util.Log; 37 import android.widget.Toast; 38 39 import com.android.im.IChatListener; 40 import com.android.im.IContactList; 41 import com.android.im.IContactListListener; 42 import com.android.im.IContactListManager; 43 import com.android.im.ISubscriptionListener; 44 import com.android.im.R; 45 import com.android.im.engine.Address; 46 import com.android.im.engine.Contact; 47 import com.android.im.engine.ContactList; 48 import com.android.im.engine.ContactListListener; 49 import com.android.im.engine.ContactListManager; 50 import com.android.im.engine.ImErrorInfo; 51 import com.android.im.engine.ImException; 52 import com.android.im.engine.Presence; 53 import com.android.im.engine.SubscriptionRequestListener; 54 import com.android.im.provider.Imps; 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 = { Imps.ContactList._ID }; 86 87 RemoteImService mContext; 88 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 = Imps.Avatars.CONTENT_URI_AVATARS_BY.buildUpon(); 110 ContentUris.appendId(builder, mProviderId); 111 ContentUris.appendId(builder, mAccountId); 112 113 mAvatarUrl = builder.build(); 114 115 builder = Imps.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon(); 116 ContentUris.appendId(builder, mProviderId); 117 ContentUris.appendId(builder, mAccountId); 118 119 mContactUrl = builder.build(); 120 } 121 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 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 142 public List getContactLists() { 143 synchronized (mContactLists) { 144 return new ArrayList<ContactListAdapter>(mContactLists.values()); 145 } 146 } 147 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 = Imps.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 179 public void approveSubscription(String address) { 180 mAdaptee.approveSubscriptionRequest(address); 181 } 182 183 public void declineSubscription(String address) { 184 mAdaptee.declineSubscriptionRequest(address); 185 } 186 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 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 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 217 public void registerContactListListener(IContactListListener listener) { 218 if (listener != null) { 219 mRemoteContactListeners.register(listener); 220 } 221 } 222 223 public void unregisterContactListListener(IContactListListener listener) { 224 if (listener != null) { 225 mRemoteContactListeners.unregister(listener); 226 } 227 } 228 229 public void registerSubscriptionListener(ISubscriptionListener listener) { 230 if (listener != null) { 231 mRemoteSubscriptionListeners.register(listener); 232 } 233 } 234 235 public void unregisterSubscriptionListener(ISubscriptionListener listener) { 236 if (listener != null) { 237 mRemoteSubscriptionListeners.unregister(listener); 238 } 239 } 240 241 public IContactList getContactList(String name) { 242 return getContactListAdapter(name); 243 } 244 245 public void loadContactLists() { 246 if(mAdaptee.getState() == ContactListManager.LISTS_NOT_LOADED){ 247 clearValidatedContactsAndLists(); 248 mAdaptee.loadContactListsAsync(); 249 } 250 } 251 252 public int getState() { 253 return mAdaptee.getState(); 254 } 255 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 267 public Contact createTemporaryContact(String address) { 268 Contact c = mAdaptee.createTemporaryContact(address); 269 insertTemporary(c); 270 return c; 271 } 272 273 public long queryOrInsertContact(Contact c) { 274 long result; 275 276 String username = c.getAddress().getFullName(); 277 String selection = Imps.Contacts.USERNAME + "=?"; 278 String[] selectionArgs = { username }; 279 String[] projection = {Imps.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 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 */ 314 public boolean isTemporary(String address) { 315 synchronized (mTemporaryContacts) { 316 return mTemporaryContacts.containsKey(address); 317 } 318 } 319 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 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 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 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 361 public String getSelection() { 362 return mSelection.toString(); 363 } 364 365 public String[] getSelectionArgs() { 366 return (String []) mSelectionArgs.toArray(new String[0]); 367 } 368 } 369 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(Imps.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(Imps.BlockedList.USERNAME, mValidatedBlockedContacts); 379 Uri.Builder builder = Imps.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(Imps.ContactList.NAME, mValidatedContactLists); 388 builder = Imps.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 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 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 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, Imps.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 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 584 public void handleDelayedContactChanges() { 585 for (StoredContactChange change : mDelayedContactChanges) { 586 onContactChange(change.mType, change.mList, change.mContact); 587 } 588 } 589 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 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 Imps.Contacts.SUBSCRIPTION_TYPE_FROM, 617 Imps.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 634 public void onSubscriptionApproved(final String contact) { 635 insertOrUpdateSubscription(contact, null, 636 Imps.Contacts.SUBSCRIPTION_TYPE_NONE, 637 Imps.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 653 public void onSubscriptionDeclined(final String contact) { 654 insertOrUpdateSubscription(contact, null, 655 Imps.Contacts.SUBSCRIPTION_TYPE_NONE, 656 Imps.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 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 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 685 String getDisplayableAddress(String impsAddress) { 686 if (impsAddress.startsWith("wv:")) { 687 return impsAddress.substring(3); 688 } 689 return impsAddress; 690 } 691 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 = Imps.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(Imps.BlockedList.USERNAME, username); 705 values.put(Imps.BlockedList.NICKNAME, contact.getName()); 706 707 mResolver.insert(uri, values); 708 709 mValidatedBlockedContacts.add(username); 710 } 711 712 void removeBlockedContactFromDataBase(Contact contact) { 713 String address = contact.getAddress().getFullName(); 714 715 Uri.Builder builder = Imps.BlockedList.CONTENT_URI.buildUpon(); 716 ContentUris.appendId(builder, mProviderId); 717 ContentUris.appendId(builder, mAccountId); 718 719 Uri uri = builder.build(); 720 mResolver.delete(uri, Imps.BlockedList.USERNAME + "=?", new String[]{ address }); 721 722 int type = isTemporary(address) ? Imps.Contacts.TYPE_TEMPORARY 723 : Imps.Contacts.TYPE_NORMAL; 724 updateContactType(address, type); 725 } 726 727 void moveTemporaryContactToList(String address, long listId) { 728 synchronized (mTemporaryContacts) { 729 mTemporaryContacts.remove(address); 730 } 731 ContentValues values = new ContentValues(2); 732 values.put(Imps.Contacts.TYPE, Imps.Contacts.TYPE_NORMAL); 733 values.put(Imps.Contacts.CONTACTLIST, listId); 734 735 String selection = Imps.Contacts.USERNAME + "=? AND " + Imps.Contacts.TYPE + "=" 736 + Imps.Contacts.TYPE_TEMPORARY; 737 String[] selectionArgs = { address }; 738 739 mResolver.update(mContactUrl, values, selection, selectionArgs); 740 } 741 742 void updateContactType(String address, int type) { 743 ContentValues values = new ContentValues(1); 744 values.put(Imps.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 */ 756 Uri insertOrUpdateSubscription(String username, String nickname, int subscriptionType, 757 int subscriptionStatus) { 758 Cursor cursor = mResolver.query(mContactUrl, new String[]{ Imps.Contacts._ID }, 759 Imps.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(Imps.Contacts.SUBSCRIPTION_TYPE, subscriptionType); 769 values.put(Imps.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); 770 771 long contactId = cursor.getLong(0); 772 uri = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, contactId); 773 mResolver.update(uri, values, null, null); 774 } else { 775 ContentValues values = new ContentValues(6); 776 values.put(Imps.Contacts.USERNAME, username); 777 values.put(Imps.Contacts.NICKNAME, nickname); 778 values.put(Imps.Contacts.TYPE, Imps.Contacts.TYPE_NORMAL); 779 values.put(Imps.Contacts.CONTACTLIST, FAKE_TEMPORARY_LIST_ID); 780 values.put(Imps.Contacts.SUBSCRIPTION_TYPE, subscriptionType); 781 values.put(Imps.Contacts.SUBSCRIPTION_STATUS, subscriptionStatus); 782 783 uri = mResolver.insert(mContactUrl, values); 784 } 785 cursor.close(); 786 return uri; 787 } 788 789 void updateContact(String username, ContentValues values) { 790 String selection = Imps.Contacts.USERNAME + "=?"; 791 String[] selectionArgs = { username }; 792 mResolver.update(mContactUrl, values, selection, selectionArgs); 793 } 794 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(Imps.Contacts.ACCOUNT, mAccountId); 816 values.putStringArrayList(Imps.Contacts.USERNAME, usernames); 817 values.putStringArrayList(Imps.Presence.PRESENCE_STATUS, statusArray); 818 values.putStringArrayList(Imps.Presence.PRESENCE_CUSTOM_STATUS, customStatusArray); 819 values.putStringArrayList(Imps.Presence.CONTENT_TYPE, clientTypeArray); 820 821 mResolver.update(Imps.Presence.BULK_CONTENT_URI, values, null, null); 822 } 823 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(Imps.Avatars.CONTACT, username); 838 values.put(Imps.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 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 Imps.Contacts.CONTACTLIST + "=?", new String[]{Long.toString(id)}); 866 867 mResolver.delete(ContentUris.withAppendedId(Imps.ContactList.CONTENT_URI, id), null, null); 868 synchronized (mContactLists) { 869 return mContactLists.remove(listAdapter.getAddress()); 870 } 871 } 872 873 void addContactListContent(ContactList list) { 874 String selection = Imps.ContactList.NAME + "=? AND " 875 + Imps.ContactList.PROVIDER + "=? AND " 876 + Imps.ContactList.ACCOUNT + "=?"; 877 String[] selectionArgs = { list.getName(), 878 Long.toString(mProviderId), 879 Long.toString(mAccountId) }; 880 Cursor cursor = mResolver.query(Imps.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(Imps.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, Imps.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(Imps.ContactList.NAME, list.getName()); 906 contactListValues.put(Imps.ContactList.PROVIDER, mProviderId); 907 contactListValues.put(Imps.ContactList.ACCOUNT, mAccountId); 908 909 //Log.d(TAG, "Adding ContactList name="+list.getName()); 910 mValidatedContactLists.add(list.getName()); 911 uri = mResolver.insert(Imps.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 = Imps.Contacts.TYPE_NORMAL; 942 if(isTemporary(username)) { 943 type = Imps.Contacts.TYPE_TEMPORARY; 944 } 945 if (isBlocked(username)) { 946 type = Imps.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(Imps.Contacts.PROVIDER, mProviderId); 956 values.put(Imps.Contacts.ACCOUNT, mAccountId); 957 values.put(Imps.Contacts.CONTACTLIST, listId); 958 values.putStringArrayList(Imps.Contacts.USERNAME, usernames); 959 values.putStringArrayList(Imps.Contacts.NICKNAME, nicknames); 960 values.putStringArrayList(Imps.Contacts.TYPE, contactTypeArray); 961 962 mResolver.insert(Imps.Contacts.BULK_CONTENT_URI, values); 963 } 964 965 void updateListNameInDataBase(ContactList list) { 966 ContactListAdapter listAdapter = getContactListAdapter(list.getAddress()); 967 968 Uri uri = ContentUris.withAppendedId(Imps.ContactList.CONTENT_URI, listAdapter.getDataBaseId()); 969 ContentValues values = new ContentValues(1); 970 values.put(Imps.ContactList.NAME, list.getName()); 971 972 mResolver.update(uri, values, null, null); 973 } 974 975 void deleteContactFromDataBase(Contact contact, ContactList list) { 976 String selection = Imps.Contacts.USERNAME 977 + "=? AND " + Imps.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 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(Imps.Presence.CONTENT_URI, presenceValues); 1000 1001 return uri; 1002 } 1003 1004 private ContentValues getContactContentValues(Contact contact, long listId) { 1005 final String username = contact.getAddress().getFullName(); 1006 final String nickname = contact.getName(); 1007 int type = Imps.Contacts.TYPE_NORMAL; 1008 if(isTemporary(username)) { 1009 type = Imps.Contacts.TYPE_TEMPORARY; 1010 } 1011 if (isBlocked(username)) { 1012 type = Imps.Contacts.TYPE_BLOCKED; 1013 } 1014 1015 ContentValues values = new ContentValues(4); 1016 values.put(Imps.Contacts.USERNAME, username); 1017 values.put(Imps.Contacts.NICKNAME, nickname); 1018 values.put(Imps.Contacts.CONTACTLIST, listId); 1019 values.put(Imps.Contacts.TYPE, type); 1020 return values; 1021 } 1022 1023 void clearHistoryMessages(String contact) { 1024 Uri uri = Imps.Messages.getContentUriByContact(mAccountId, contact); 1025 mResolver.delete(uri, null, null); 1026 } 1027 1028 private ContentValues getPresenceValues(long contactId, Presence p) { 1029 ContentValues values = new ContentValues(3); 1030 values.put(Imps.Presence.CONTACT_ID, contactId); 1031 values.put(Imps.Contacts.PRESENCE_STATUS, convertPresenceStatus(p)); 1032 values.put(Imps.Contacts.PRESENCE_CUSTOM_STATUS, p.getStatusText()); 1033 values.put(Imps.Presence.CLIENT_TYPE, translateClientType(p)); 1034 return values; 1035 } 1036 1037 private int translateClientType(Presence presence) { 1038 int clientType = presence.getClientType(); 1039 switch (clientType) { 1040 case Presence.CLIENT_TYPE_MOBILE: 1041 return Imps.Presence.CLIENT_TYPE_MOBILE; 1042 default: 1043 return Imps.Presence.CLIENT_TYPE_DEFAULT; 1044 } 1045 } 1046 1047 /** 1048 * Converts the presence status to the value defined for ImProvider. 1049 * 1050 * @param presence The presence from the IM engine. 1051 * @return The status value defined in for ImProvider. 1052 */ 1053 public static int convertPresenceStatus(Presence presence) { 1054 switch (presence.getStatus()) { 1055 case Presence.AVAILABLE: 1056 return Imps.Presence.AVAILABLE; 1057 1058 case Presence.IDLE: 1059 return Imps.Presence.IDLE; 1060 1061 case Presence.AWAY: 1062 return Imps.Presence.AWAY; 1063 1064 case Presence.DO_NOT_DISTURB: 1065 return Imps.Presence.DO_NOT_DISTURB; 1066 1067 case Presence.OFFLINE: 1068 return Imps.Presence.OFFLINE; 1069 } 1070 1071 // impossible... 1072 Log.e(TAG, "Illegal presence status value " + presence.getStatus()); 1073 return Imps.Presence.AVAILABLE; 1074 } 1075 1076 public void clearOnLogout() { 1077 clearValidatedContactsAndLists(); 1078 clearTemporaryContacts(); 1079 clearPresence(); 1080 } 1081 1082 /** 1083 * Clears the list of validated contacts and contact lists. 1084 * As contacts and contacts lists are added after login, contacts and contact lists are 1085 * stored as "validated contacts". After initial download of contacts is complete, any contacts 1086 * and contact lists that remain in the database, but are not in the validated list, are 1087 * obsolete and should be removed. This function resets that list for use upon login. 1088 */ 1089 private void clearValidatedContactsAndLists() { 1090 // clear the list of validated contacts, contact lists, and blocked contacts 1091 mValidatedContacts.clear(); 1092 mValidatedContactLists.clear(); 1093 mValidatedBlockedContacts.clear(); 1094 } 1095 1096 /** 1097 * Clear the temporary contacts in the database. As contacts are persist between 1098 * IM sessions, the temporary contacts need to be cleared after logout. 1099 */ 1100 private void clearTemporaryContacts() { 1101 String selection = Imps.Contacts.CONTACTLIST + "=" + FAKE_TEMPORARY_LIST_ID; 1102 mResolver.delete(mContactUrl, selection, null); 1103 } 1104 1105 /** 1106 * Clears the presence of the all contacts. As contacts are persist between 1107 * IM sessions, the presence need to be cleared after logout. 1108 */ 1109 void clearPresence() { 1110 StringBuilder where = new StringBuilder(); 1111 where.append(Imps.Presence.CONTACT_ID); 1112 where.append(" in (select _id from contacts where "); 1113 where.append(Imps.Contacts.ACCOUNT); 1114 where.append("="); 1115 where.append(mAccountId); 1116 where.append(")"); 1117 mResolver.delete(Imps.Presence.CONTENT_URI, where.toString(), null); 1118 } 1119 1120 void closeChatSession(String address) { 1121 ChatSessionManagerAdapter sessionManager = 1122 (ChatSessionManagerAdapter) mConn.getChatSessionManager(); 1123 ChatSessionAdapter session = 1124 (ChatSessionAdapter) sessionManager.getChatSession(address); 1125 if(session != null) { 1126 session.leave(); 1127 } 1128 } 1129 1130 void updateChatPresence(String address, String nickname, Presence p) { 1131 ChatSessionManagerAdapter sessionManager = 1132 (ChatSessionManagerAdapter) mConn.getChatSessionManager(); 1133 // TODO: This only find single chat sessions, we need to go through all 1134 // active chat sessions and find if the contact is a participant of the 1135 // session. 1136 ChatSessionAdapter session = 1137 (ChatSessionAdapter) sessionManager.getChatSession(address); 1138 if(session != null) { 1139 session.insertPresenceUpdatesMsg(nickname, p); 1140 } 1141 } 1142 1143 1144 } 1145