2 * Copyright (C) 2007-2008 Esmertec AG.
3 * Copyright (C) 2007-2008 The Android Open Source Project
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
9 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 package com.android.im.service;
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;
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;
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;
56 public class ContactListManagerAdapter extends IContactListManager.Stub {
57 static final String TAG = RemoteImService.TAG;
59 ImConnectionAdapter mConn;
60 ContentResolver mResolver;
62 private ContactListManager mAdaptee;
63 private ContactListListenerAdapter mContactListListenerAdapter;
64 private SubscriptionRequestListenerAdapter mSubscriptionListenerAdapter;
66 final RemoteCallbackList<IContactListListener> mRemoteContactListeners
67 = new RemoteCallbackList<IContactListListener>();
68 final RemoteCallbackList<ISubscriptionListener> mRemoteSubscriptionListeners
69 = new RemoteCallbackList<ISubscriptionListener>();
71 HashMap<Address, ContactListAdapter> mContactLists;
72 HashMap<String, Contact> mTemporaryContacts;
74 HashSet<String> mValidatedContactLists;
75 HashSet<String> mValidatedContacts;
76 HashSet<String> mValidatedBlockedContacts;
78 private long mAccountId;
79 private long mProviderId;
81 private Uri mAvatarUrl;
82 private Uri mContactUrl;
84 static final long FAKE_TEMPORARY_LIST_ID = -1;
85 static final String[] CONTACT_LIST_ID_PROJECTION = { Im.ContactList._ID };
87 RemoteImService mContext;
89 public ContactListManagerAdapter(ImConnectionAdapter conn) {
90 mAdaptee = conn.getAdaptee().getContactListManager();
92 mContext = conn.getContext();
93 mResolver = mContext.getContentResolver();
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>();
103 mAdaptee.addContactListListener(mContactListListenerAdapter);
104 mAdaptee.setSubscriptionRequestListener(mSubscriptionListenerAdapter);
106 mAccountId = mConn.getAccountId();
107 mProviderId = mConn.getProviderId();
109 Uri.Builder builder = Im.Avatars.CONTENT_URI_AVATARS_BY.buildUpon();
110 ContentUris.appendId(builder, mProviderId);
111 ContentUris.appendId(builder, mAccountId);
113 mAvatarUrl = builder.build();
115 builder = Im.Contacts.CONTENT_URI_CONTACTS_BY.buildUpon();
116 ContentUris.appendId(builder, mProviderId);
117 ContentUris.appendId(builder, mAccountId);
119 mContactUrl = builder.build();
122 public int createContactList(String name, List<Contact> contacts) {
124 mAdaptee.createContactListAsync(name, contacts);
125 } catch (ImException e) {
126 return e.getImError().getCode();
129 return ImErrorInfo.NO_ERROR;
132 public int deleteContactList(String name) {
134 mAdaptee.deleteContactListAsync(name);
135 } catch (ImException e) {
136 return e.getImError().getCode();
139 return ImErrorInfo.NO_ERROR;
142 public List getContactLists() {
143 synchronized (mContactLists) {
144 return new ArrayList<ContactListAdapter>(mContactLists.values());
148 public int removeContact(String address) {
149 if(isTemporary(address)) {
150 // For temporary contact, just close the session and delete him in
152 closeChatSession(address);
154 String selection = Im.Contacts.USERNAME + "=?";
155 String[] selectionArgs = { address };
156 mResolver.delete(mContactUrl, selection, selectionArgs);
157 synchronized (mTemporaryContacts) {
158 mTemporaryContacts.remove(address);
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
169 if (ImErrorInfo.NO_ERROR != resCode) {
176 return ImErrorInfo.NO_ERROR;
179 public void approveSubscription(String address) {
180 mAdaptee.approveSubscriptionRequest(address);
183 public void declineSubscription(String address) {
184 mAdaptee.declineSubscriptionRequest(address);
187 public int blockContact(String address) {
189 mAdaptee.blockContactAsync(address);
190 } catch (ImException e) {
191 return e.getImError().getCode();
194 return ImErrorInfo.NO_ERROR;
197 public int unBlockContact(String address) {
199 mAdaptee.unblockContactAsync(address);
200 } catch (ImException e) {
201 Log.e(TAG, e.getMessage());
202 return e.getImError().getCode();
205 return ImErrorInfo.NO_ERROR;
208 public boolean isBlocked(String address) {
210 return mAdaptee.isBlocked(address);
211 } catch (ImException e) {
212 Log.e(TAG, e.getMessage());
217 public void registerContactListListener(IContactListListener listener) {
218 if (listener != null) {
219 mRemoteContactListeners.register(listener);
223 public void unregisterContactListListener(IContactListListener listener) {
224 if (listener != null) {
225 mRemoteContactListeners.unregister(listener);
229 public void registerSubscriptionListener(ISubscriptionListener listener) {
230 if (listener != null) {
231 mRemoteSubscriptionListeners.register(listener);
235 public void unregisterSubscriptionListener(ISubscriptionListener listener) {
236 if (listener != null) {
237 mRemoteSubscriptionListeners.unregister(listener);
241 public IContactList getContactList(String name) {
242 return getContactListAdapter(name);
245 public void loadContactLists() {
246 if(mAdaptee.getState() == ContactListManager.LISTS_NOT_LOADED){
247 clearValidatedContactsAndLists();
248 mAdaptee.loadContactListsAsync();
252 public int getState() {
253 return mAdaptee.getState();
256 public Contact getContactByAddress(String address) {
257 Contact c = mAdaptee.getContact(address);
259 synchronized (mTemporaryContacts) {
260 return mTemporaryContacts.get(address);
267 public Contact createTemporaryContact(String address) {
268 Contact c = mAdaptee.createTemporaryContact(address);
273 public long queryOrInsertContact(Contact c) {
276 String username = c.getAddress().getFullName();
277 String selection = Im.Contacts.USERNAME + "=?";
278 String[] selectionArgs = { username };
279 String[] projection = {Im.Contacts._ID};
281 Cursor cursor = mResolver.query(mContactUrl, projection, selection,
282 selectionArgs, null);
284 if(cursor != null && cursor.moveToFirst()) {
285 result = cursor.getLong(0);
287 result = insertTemporary(c);
296 private long insertTemporary(Contact c) {
297 synchronized (mTemporaryContacts) {
298 mTemporaryContacts.put(c.getAddress().getFullName(), c);
300 Uri uri = insertContactContent(c, FAKE_TEMPORARY_LIST_ID);
301 return ContentUris.parseId(uri);
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.
310 * the address of the contact.
311 * @return <code>true</code> if it's a temporary contact;
312 * <code>false</code> otherwise.
314 public boolean isTemporary(String address) {
315 synchronized (mTemporaryContacts) {
316 return mTemporaryContacts.containsKey(address);
320 ContactListAdapter getContactListAdapter(String name) {
321 synchronized (mContactLists) {
322 for (ContactListAdapter list : mContactLists.values()) {
323 if (name.equals(list.getName())) {
332 ContactListAdapter getContactListAdapter(Address address) {
333 synchronized (mContactLists) {
334 return mContactLists.get(address);
338 private class Exclusion {
339 private StringBuilder mSelection;
340 private List mSelectionArgs;
341 private String mExclusionColumn;
343 Exclusion(String exclusionColumn, Collection<String> items) {
344 mSelection = new StringBuilder();
345 mSelectionArgs = new ArrayList();
346 mExclusionColumn = exclusionColumn;
347 for (String s : items) {
352 public void add(String exclusionItem) {
353 if (mSelection.length()==0) {
354 mSelection.append(mExclusionColumn + "!=?");
356 mSelection.append(" AND " + mExclusionColumn + "!=?");
358 mSelectionArgs.add(exclusionItem);
361 public String getSelection() {
362 return mSelection.toString();
365 public String[] getSelectionArgs() {
366 return (String []) mSelectionArgs.toArray(new String[0]);
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());
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());
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());
396 final class ContactListListenerAdapter implements ContactListListener {
397 private boolean mAllContactsLoaded;
399 // class to hold contact changes made before mAllContactsLoaded
400 private class StoredContactChange {
405 StoredContactChange(int type, ContactList list, Contact contact) {
411 private Vector<StoredContactChange> mDelayedContactChanges =
412 new Vector<StoredContactChange>();
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
418 // TODO: Don't update avatar now since none of the server supports it
419 // updateAvatarsContent(contacts);
420 updatePresenceContent(contacts);
422 final int N = mRemoteContactListeners.beginBroadcast();
423 for (int i = 0; i < N; i++) {
424 IContactListListener listener =
425 mRemoteContactListeners.getBroadcastItem(i);
427 listener.onContactsPresenceUpdate(contacts);
428 } catch (RemoteException e) {
429 // The RemoteCallbackList will take care of removing the
433 mRemoteContactListeners.finishBroadcast();
436 public void onContactChange(final int type, final ContactList list,
437 final Contact contact) {
438 ContactListAdapter removed = null;
439 String notificationText = null;
444 addContactListContent(list);
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));
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);
466 insertContactContent(contact, listId);
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));
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));
493 // Clear ChatSession if any.
494 String address = contact.getAddress().getFullName();
495 closeChatSession(address);
497 notificationText = mContext.getResources().getString(
498 R.string.delete_contact_success, contact.getName());
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));
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());
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));
539 Log.e(TAG, "Unknown list update event!");
543 final ContactListAdapter listAdapter;
544 if (type == LIST_DELETED) {
545 listAdapter = removed;
547 listAdapter = (list == null) ? null
548 : getContactListAdapter(list.getAddress());
550 final int N = mRemoteContactListeners.beginBroadcast();
551 for (int i = 0; i < N; i++) {
552 IContactListListener listener =
553 mRemoteContactListeners.getBroadcastItem(i);
555 listener.onContactChange(type, listAdapter, contact);
556 } catch (RemoteException e) {
557 // The RemoteCallbackList will take care of removing the
561 mRemoteContactListeners.finishBroadcast();
563 if (mAllContactsLoaded && notificationText != null) {
564 mContext.showToast(notificationText, Toast.LENGTH_SHORT);
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);
575 listener.onContactError(errorType, error, listName, contact);
576 } catch (RemoteException e) {
577 // The RemoteCallbackList will take care of removing the
581 mRemoteContactListeners.finishBroadcast();
584 public void handleDelayedContactChanges() {
585 for (StoredContactChange change : mDelayedContactChanges) {
586 onContactChange(change.mType, change.mList, change.mContact);
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);
599 listener.onAllContactListsLoaded();
600 } catch (RemoteException e) {
601 // The RemoteCallbackList will take care of removing the
605 mRemoteContactListeners.finishBroadcast();
609 final class SubscriptionRequestListenerAdapter
610 implements SubscriptionRequestListener {
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);
625 listener.onSubScriptionRequest(from);
626 } catch (RemoteException e) {
627 // The RemoteCallbackList will take care of removing the
631 mRemoteSubscriptionListeners.finishBroadcast();
634 public void onSubscriptionApproved(final String contact) {
635 insertOrUpdateSubscription(contact, null,
636 Im.Contacts.SUBSCRIPTION_TYPE_NONE,
637 Im.Contacts.SUBSCRIPTION_STATUS_NONE);
639 final int N = mRemoteSubscriptionListeners.beginBroadcast();
640 for (int i = 0; i < N; i++) {
641 ISubscriptionListener listener =
642 mRemoteSubscriptionListeners.getBroadcastItem(i);
644 listener.onSubscriptionApproved(contact);
645 } catch (RemoteException e) {
646 // The RemoteCallbackList will take care of removing the
650 mRemoteSubscriptionListeners.finishBroadcast();
653 public void onSubscriptionDeclined(final String contact) {
654 insertOrUpdateSubscription(contact, null,
655 Im.Contacts.SUBSCRIPTION_TYPE_NONE,
656 Im.Contacts.SUBSCRIPTION_STATUS_NONE);
658 final int N = mRemoteSubscriptionListeners.beginBroadcast();
659 for (int i = 0; i < N; i++) {
660 ISubscriptionListener listener =
661 mRemoteSubscriptionListeners.getBroadcastItem(i);
663 listener.onSubscriptionDeclined(contact);
664 } catch (RemoteException e) {
665 // The RemoteCallbackList will take care of removing the
669 mRemoteSubscriptionListeners.finishBroadcast();
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);
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);
685 String getDisplayableAddress(String impsAddress) {
686 if (impsAddress.startsWith("wv:")) {
687 return impsAddress.substring(3);
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);
697 Uri.Builder builder = Im.BlockedList.CONTENT_URI.buildUpon();
698 ContentUris.appendId(builder, mProviderId);
699 ContentUris.appendId(builder, mAccountId);
700 Uri uri = builder.build();
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());
707 mResolver.insert(uri, values);
709 mValidatedBlockedContacts.add(username);
712 void removeBlockedContactFromDataBase(Contact contact) {
713 String address = contact.getAddress().getFullName();
715 Uri.Builder builder = Im.BlockedList.CONTENT_URI.buildUpon();
716 ContentUris.appendId(builder, mProviderId);
717 ContentUris.appendId(builder, mAccountId);
719 Uri uri = builder.build();
720 mResolver.delete(uri, Im.BlockedList.USERNAME + "=?", new String[]{ address });
722 int type = isTemporary(address) ? Im.Contacts.TYPE_TEMPORARY
723 : Im.Contacts.TYPE_NORMAL;
724 updateContactType(address, type);
727 void moveTemporaryContactToList(String address, long listId) {
728 synchronized (mTemporaryContacts) {
729 mTemporaryContacts.remove(address);
731 ContentValues values = new ContentValues(2);
732 values.put(Im.Contacts.TYPE, Im.Contacts.TYPE_NORMAL);
733 values.put(Im.Contacts.CONTACTLIST, listId);
735 String selection = Im.Contacts.USERNAME + "=? AND " + Im.Contacts.TYPE + "="
736 + Im.Contacts.TYPE_TEMPORARY;
737 String[] selectionArgs = { address };
739 mResolver.update(mContactUrl, values, selection, selectionArgs);
742 void updateContactType(String address, int type) {
743 ContentValues values = new ContentValues(1);
744 values.put(Im.Contacts.TYPE, type);
745 updateContact(address, values);
749 * Insert or update subscription request from user into the database.
753 * @param subscriptionType
754 * @param 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");
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);
771 long contactId = cursor.getLong(0);
772 uri = ContentUris.withAppendedId(Im.Contacts.CONTENT_URI, contactId);
773 mResolver.update(uri, values, null, null);
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);
783 uri = mResolver.insert(mContactUrl, 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);
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>();
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);
808 usernames.add(username);
809 statusArray.add(String.valueOf(status));
810 customStatusArray.add(customStatus);
811 clientTypeArray.add(String.valueOf(clientType));
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);
821 mResolver.update(Im.Presence.BULK_CONTENT_URI, values, null, null);
824 void updateAvatarsContent(Contact[] contacts) {
825 ArrayList<ContentValues> avatars = new ArrayList<ContentValues>();
826 ArrayList<String> usernames = new ArrayList<String>();
828 for (Contact contact : contacts) {
829 byte[] avatarData = contact.getPresence().getAvatarData();
830 if (avatarData == null) {
834 String username = contact.getAddress().getFullName();
836 ContentValues values = new ContentValues(2);
837 values.put(Im.Avatars.CONTACT, username);
838 values.put(Im.Avatars.DATA, avatarData);
840 usernames.add(username);
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()]));
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);
856 ContactListAdapter removeContactListFromDataBase(String name) {
857 ContactListAdapter listAdapter = getContactListAdapter(name);
858 if (listAdapter == null) {
861 long id = listAdapter.getDataBaseId();
863 // delete contacts of this list first
864 mResolver.delete(mContactUrl,
865 Im.Contacts.CONTACTLIST + "=?", new String[]{Long.toString(id)});
867 mResolver.delete(ContentUris.withAppendedId(Im.ContactList.CONTENT_URI, id), null, null);
868 synchronized (mContactLists) {
869 return mContactLists.remove(listAdapter.getAddress());
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,
884 null); // no sort order
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());
897 // remove existing ContactList and Contacts of that list for replacement by the newly
899 mResolver.delete(mContactUrl, Im.Contacts.CONTACTLIST + "=?",
900 new String[]{Long.toString(listId)});
901 mResolver.delete(uri, selection, selectionArgs);
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);
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);
914 synchronized (mContactLists) {
915 mContactLists.put(list.getAddress(),
916 new ContactListAdapter(list, listId));
919 Collection<Contact> contacts = list.getContacts();
920 if (contacts == null || contacts.size() == 0) {
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);
932 mValidatedContacts.add(address);
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;
945 if (isBlocked(username)) {
946 type = Im.Contacts.TYPE_BLOCKED;
949 usernames.add(username);
950 nicknames.add(nickname);
951 contactTypeArray.add(String.valueOf(type));
953 ContentValues values = new ContentValues(6);
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);
962 mResolver.insert(Im.Contacts.BULK_CONTENT_URI, values);
965 void updateListNameInDataBase(ContactList list) {
966 ContactListAdapter listAdapter = getContactListAdapter(list.getAddress());
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());
972 mResolver.update(uri, values, null, null);
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)};
982 mResolver.delete(mContactUrl, selection, selectionArgs);
984 // clear the history message if the contact doesn't exist in any list
986 if(mAdaptee.getContact(contact.getAddress()) == null) {
987 clearHistoryMessages(username);
991 Uri insertContactContent(Contact contact, long listId) {
992 ContentValues values = getContactContentValues(contact, listId);
994 Uri uri = mResolver.insert(mContactUrl, values);
996 ContentValues presenceValues = getPresenceValues(ContentUris.parseId(uri),
997 contact.getPresence());
999 mResolver.insert(Im.Presence.CONTENT_URI, presenceValues);
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;
1011 if (isBlocked(username)) {
1012 type = Im.Contacts.TYPE_BLOCKED;
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);
1023 void clearHistoryMessages(String contact) {
1024 Uri uri = Im.Messages.getContentUriByContact(mProviderId,
1025 mAccountId, contact);
1026 mResolver.delete(uri, null, null);
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));
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;
1044 return Im.Presence.CLIENT_TYPE_DEFAULT;
1049 * Converts the presence status to the value defined for ImProvider.
1051 * @param presence The presence from the IM engine.
1052 * @return The status value defined in for ImProvider.
1054 public static int convertPresenceStatus(Presence presence) {
1055 switch (presence.getStatus()) {
1056 case Presence.AVAILABLE:
1057 return Im.Presence.AVAILABLE;
1060 return Im.Presence.IDLE;
1063 return Im.Presence.AWAY;
1065 case Presence.DO_NOT_DISTURB:
1066 return Im.Presence.DO_NOT_DISTURB;
1068 case Presence.OFFLINE:
1069 return Im.Presence.OFFLINE;
1073 Log.e(TAG, "Illegal presence status value " + presence.getStatus());
1074 return Im.Presence.AVAILABLE;
1077 public void clearOnLogout() {
1078 clearValidatedContactsAndLists();
1079 clearTemporaryContacts();
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.
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();
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.
1101 private void clearTemporaryContacts() {
1102 String selection = Im.Contacts.CONTACTLIST + "=" + FAKE_TEMPORARY_LIST_ID;
1103 mResolver.delete(mContactUrl, selection, null);
1107 * Clears the presence of the all contacts. As contacts are persist between
1108 * IM sessions, the presence need to be cleared after logout.
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);
1116 where.append(mAccountId);
1118 mResolver.delete(Im.Presence.CONTENT_URI, where.toString(), null);
1121 void closeChatSession(String address) {
1122 ChatSessionManagerAdapter sessionManager =
1123 (ChatSessionManagerAdapter) mConn.getChatSessionManager();
1124 ChatSessionAdapter session =
1125 (ChatSessionAdapter) sessionManager.getChatSession(address);
1126 if(session != null) {
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
1137 ChatSessionAdapter session =
1138 (ChatSessionAdapter) sessionManager.getChatSession(address);
1139 if(session != null) {
1140 session.insertPresenceUpdatesMsg(nickname, p);