Home | History | Annotate | Download | only in service
      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