OSDN Git Service

auto import from //depot/cupcake/@135843
[android-x86/packages-apps-IM.git] / src / com / android / im / service / ContactListManagerAdapter.java
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
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
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 = 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
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 = 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
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(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
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, 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
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                     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
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
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
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 = 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
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
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
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      */
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
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
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
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
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
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
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
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
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
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
1023     void clearHistoryMessages(String contact) {
1024         Uri uri = Im.Messages.getContentUriByContact(mProviderId,
1025             mAccountId, contact);
1026         mResolver.delete(uri, null, null);
1027     }
1028
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
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      */
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
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      */
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      */
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      */
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
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
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 }