Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2008 Esmertec AG.
      3  * Copyright (C) 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.mms.ui;
     19 
     20 import com.android.mms.R;
     21 import com.google.android.mms.MmsException;
     22 
     23 import android.content.AsyncQueryHandler;
     24 import android.content.ContentResolver;
     25 import android.content.ContentUris;
     26 import android.content.Context;
     27 import android.database.Cursor;
     28 import android.graphics.Bitmap;
     29 import android.graphics.BitmapFactory;
     30 import android.graphics.drawable.BitmapDrawable;
     31 import android.graphics.drawable.Drawable;
     32 import android.net.Uri;
     33 import android.os.Handler;
     34 import android.provider.BaseColumns;
     35 import android.provider.ContactsContract.Contacts;
     36 import android.provider.ContactsContract.Data;
     37 import android.provider.ContactsContract.PhoneLookup;
     38 import android.provider.ContactsContract.RawContacts;
     39 import android.provider.ContactsContract.StatusUpdates;
     40 import android.provider.ContactsContract.CommonDataKinds.Email;
     41 import android.provider.ContactsContract.CommonDataKinds.Photo;
     42 import android.provider.Telephony.Mms;
     43 import android.provider.Telephony.MmsSms;
     44 import android.provider.Telephony.Sms;
     45 import android.provider.Telephony.MmsSms.PendingMessages;
     46 import android.provider.Telephony.Sms.Conversations;
     47 import android.text.TextUtils;
     48 import android.text.format.DateUtils;
     49 import android.util.Config;
     50 import android.util.Log;
     51 import android.view.LayoutInflater;
     52 import android.view.View;
     53 import android.view.ViewGroup;
     54 import android.widget.CursorAdapter;
     55 import android.widget.ListView;
     56 
     57 import java.util.HashMap;
     58 import java.util.HashSet;
     59 import java.util.LinkedHashMap;
     60 import java.util.Map;
     61 import java.util.regex.Pattern;
     62 
     63 /**
     64  * The back-end data adapter of a message list.
     65  */
     66 public class MessageListAdapter extends CursorAdapter {
     67     private static final String TAG = "MessageListAdapter";
     68     private static final boolean DEBUG = false;
     69     private static final boolean LOCAL_LOGV = Config.LOGV && DEBUG;
     70 
     71     static final String[] PROJECTION = new String[] {
     72         // TODO: should move this symbol into com.android.mms.telephony.Telephony.
     73         MmsSms.TYPE_DISCRIMINATOR_COLUMN,
     74         BaseColumns._ID,
     75         Conversations.THREAD_ID,
     76         // For SMS
     77         Sms.ADDRESS,
     78         Sms.BODY,
     79         Sms.DATE,
     80         Sms.READ,
     81         Sms.TYPE,
     82         Sms.STATUS,
     83         Sms.LOCKED,
     84         Sms.ERROR_CODE,
     85         // For MMS
     86         Mms.SUBJECT,
     87         Mms.SUBJECT_CHARSET,
     88         Mms.DATE,
     89         Mms.READ,
     90         Mms.MESSAGE_TYPE,
     91         Mms.MESSAGE_BOX,
     92         Mms.DELIVERY_REPORT,
     93         Mms.READ_REPORT,
     94         PendingMessages.ERROR_TYPE,
     95         Mms.LOCKED
     96     };
     97 
     98     // The indexes of the default columns which must be consistent
     99     // with above PROJECTION.
    100     static final int COLUMN_MSG_TYPE            = 0;
    101     static final int COLUMN_ID                  = 1;
    102     static final int COLUMN_THREAD_ID           = 2;
    103     static final int COLUMN_SMS_ADDRESS         = 3;
    104     static final int COLUMN_SMS_BODY            = 4;
    105     static final int COLUMN_SMS_DATE            = 5;
    106     static final int COLUMN_SMS_READ            = 6;
    107     static final int COLUMN_SMS_TYPE            = 7;
    108     static final int COLUMN_SMS_STATUS          = 8;
    109     static final int COLUMN_SMS_LOCKED          = 9;
    110     static final int COLUMN_SMS_ERROR_CODE      = 10;
    111     static final int COLUMN_MMS_SUBJECT         = 11;
    112     static final int COLUMN_MMS_SUBJECT_CHARSET = 12;
    113     static final int COLUMN_MMS_DATE            = 13;
    114     static final int COLUMN_MMS_READ            = 14;
    115     static final int COLUMN_MMS_MESSAGE_TYPE    = 15;
    116     static final int COLUMN_MMS_MESSAGE_BOX     = 16;
    117     static final int COLUMN_MMS_DELIVERY_REPORT = 17;
    118     static final int COLUMN_MMS_READ_REPORT     = 18;
    119     static final int COLUMN_MMS_ERROR_TYPE      = 19;
    120     static final int COLUMN_MMS_LOCKED          = 20;
    121 
    122     private static final int CACHE_SIZE         = 50;
    123 
    124     protected LayoutInflater mInflater;
    125     private final ListView mListView;
    126     private final LinkedHashMap<Long, MessageItem> mMessageItemCache;
    127     private final ColumnsMap mColumnsMap;
    128     private OnDataSetChangedListener mOnDataSetChangedListener;
    129     private Handler mMsgListItemHandler;
    130     private Pattern mHighlight;
    131     private Context mContext;
    132 
    133     private HashMap<String, HashSet<MessageListItem>> mAddressToMessageListItems
    134         = new HashMap<String, HashSet<MessageListItem>>();
    135 
    136     public MessageListAdapter(
    137             Context context, Cursor c, ListView listView,
    138             boolean useDefaultColumnsMap, Pattern highlight) {
    139         super(context, c, false /* auto-requery */);
    140         mContext = context;
    141         mHighlight = highlight;
    142 
    143         mInflater = (LayoutInflater) context.getSystemService(
    144                 Context.LAYOUT_INFLATER_SERVICE);
    145         mListView = listView;
    146         mMessageItemCache = new LinkedHashMap<Long, MessageItem>(
    147                     10, 1.0f, true) {
    148             @Override
    149             protected boolean removeEldestEntry(Map.Entry eldest) {
    150                 return size() > CACHE_SIZE;
    151             }
    152         };
    153 
    154         if (useDefaultColumnsMap) {
    155             mColumnsMap = new ColumnsMap();
    156         } else {
    157             mColumnsMap = new ColumnsMap(c);
    158         }
    159 
    160         mAvatarCache = new AvatarCache();
    161     }
    162 
    163     @Override
    164     public void bindView(View view, Context context, Cursor cursor) {
    165         if (view instanceof MessageListItem) {
    166             String type = cursor.getString(mColumnsMap.mColumnMsgType);
    167             long msgId = cursor.getLong(mColumnsMap.mColumnMsgId);
    168 
    169             MessageItem msgItem = getCachedMessageItem(type, msgId, cursor);
    170             if (msgItem != null) {
    171                 MessageListItem mli = (MessageListItem) view;
    172 
    173                 // Remove previous item from mapping
    174                 MessageItem oldMessageItem = mli.getMessageItem();
    175                 if (oldMessageItem != null) {
    176                     String oldAddress = oldMessageItem.mAddress;
    177                     if (oldAddress != null) {
    178                         HashSet<MessageListItem> set = mAddressToMessageListItems.get(oldAddress);
    179                         if (set != null) {
    180                             set.remove(mli);
    181                         }
    182                     }
    183                 }
    184 
    185                 mli.bind(mAvatarCache, msgItem);
    186                 mli.setMsgListItemHandler(mMsgListItemHandler);
    187 
    188                 // Add current item to mapping
    189 
    190                 String addr;
    191                 if (!Sms.isOutgoingFolder(msgItem.mBoxId)) {
    192                     addr = msgItem.mAddress;
    193                 } else {
    194                     addr = MessageUtils.getLocalNumber();
    195                 }
    196 
    197                 HashSet<MessageListItem> set = mAddressToMessageListItems.get(addr);
    198                 if (set == null) {
    199                     set = new HashSet<MessageListItem>();
    200                     mAddressToMessageListItems.put(addr, set);
    201                 }
    202                 set.add(mli);
    203             }
    204         }
    205     }
    206 
    207     public interface OnDataSetChangedListener {
    208         void onDataSetChanged(MessageListAdapter adapter);
    209         void onContentChanged(MessageListAdapter adapter);
    210     }
    211 
    212     public void setOnDataSetChangedListener(OnDataSetChangedListener l) {
    213         mOnDataSetChangedListener = l;
    214     }
    215 
    216     public void setMsgListItemHandler(Handler handler) {
    217         mMsgListItemHandler = handler;
    218     }
    219 
    220     public void notifyImageLoaded(String address) {
    221         HashSet<MessageListItem> set = mAddressToMessageListItems.get(address);
    222         if (set != null) {
    223             for (MessageListItem mli : set) {
    224                 mli.bind(mAvatarCache, mli.getMessageItem());
    225             }
    226         }
    227     }
    228 
    229     @Override
    230     public void notifyDataSetChanged() {
    231         super.notifyDataSetChanged();
    232         if (LOCAL_LOGV) {
    233             Log.v(TAG, "MessageListAdapter.notifyDataSetChanged().");
    234         }
    235 
    236         mListView.setSelection(mListView.getCount());
    237         mMessageItemCache.clear();
    238 
    239         if (mOnDataSetChangedListener != null) {
    240             mOnDataSetChangedListener.onDataSetChanged(this);
    241         }
    242     }
    243 
    244     @Override
    245     protected void onContentChanged() {
    246         if (getCursor() != null && !getCursor().isClosed()) {
    247             if (mOnDataSetChangedListener != null) {
    248                 mOnDataSetChangedListener.onContentChanged(this);
    249             }
    250         }
    251     }
    252 
    253     @Override
    254     public View newView(Context context, Cursor cursor, ViewGroup parent) {
    255         return mInflater.inflate(R.layout.message_list_item, parent, false);
    256     }
    257 
    258     public MessageItem getCachedMessageItem(String type, long msgId, Cursor c) {
    259         MessageItem item = mMessageItemCache.get(getKey(type, msgId));
    260         if (item == null && c != null && isCursorValid(c)) {
    261             try {
    262                 item = new MessageItem(mContext, type, c, mColumnsMap, mHighlight);
    263                 mMessageItemCache.put(getKey(item.mType, item.mMsgId), item);
    264             } catch (MmsException e) {
    265                 Log.e(TAG, e.getMessage());
    266             }
    267         }
    268         return item;
    269     }
    270 
    271     private boolean isCursorValid(Cursor cursor) {
    272         // Check whether the cursor is valid or not.
    273         if (cursor.isClosed() || cursor.isBeforeFirst() || cursor.isAfterLast()) {
    274             return false;
    275         }
    276         return true;
    277     }
    278 
    279     private static long getKey(String type, long id) {
    280         if (type.equals("mms")) {
    281             return -id;
    282         } else {
    283             return id;
    284         }
    285     }
    286 
    287     public static class ColumnsMap {
    288         public int mColumnMsgType;
    289         public int mColumnMsgId;
    290         public int mColumnSmsAddress;
    291         public int mColumnSmsBody;
    292         public int mColumnSmsDate;
    293         public int mColumnSmsRead;
    294         public int mColumnSmsType;
    295         public int mColumnSmsStatus;
    296         public int mColumnSmsLocked;
    297         public int mColumnSmsErrorCode;
    298         public int mColumnMmsSubject;
    299         public int mColumnMmsSubjectCharset;
    300         public int mColumnMmsDate;
    301         public int mColumnMmsRead;
    302         public int mColumnMmsMessageType;
    303         public int mColumnMmsMessageBox;
    304         public int mColumnMmsDeliveryReport;
    305         public int mColumnMmsReadReport;
    306         public int mColumnMmsErrorType;
    307         public int mColumnMmsLocked;
    308 
    309         public ColumnsMap() {
    310             mColumnMsgType            = COLUMN_MSG_TYPE;
    311             mColumnMsgId              = COLUMN_ID;
    312             mColumnSmsAddress         = COLUMN_SMS_ADDRESS;
    313             mColumnSmsBody            = COLUMN_SMS_BODY;
    314             mColumnSmsDate            = COLUMN_SMS_DATE;
    315             mColumnSmsType            = COLUMN_SMS_TYPE;
    316             mColumnSmsStatus          = COLUMN_SMS_STATUS;
    317             mColumnSmsLocked          = COLUMN_SMS_LOCKED;
    318             mColumnSmsErrorCode       = COLUMN_SMS_ERROR_CODE;
    319             mColumnMmsSubject         = COLUMN_MMS_SUBJECT;
    320             mColumnMmsSubjectCharset  = COLUMN_MMS_SUBJECT_CHARSET;
    321             mColumnMmsMessageType     = COLUMN_MMS_MESSAGE_TYPE;
    322             mColumnMmsMessageBox      = COLUMN_MMS_MESSAGE_BOX;
    323             mColumnMmsDeliveryReport  = COLUMN_MMS_DELIVERY_REPORT;
    324             mColumnMmsReadReport      = COLUMN_MMS_READ_REPORT;
    325             mColumnMmsErrorType       = COLUMN_MMS_ERROR_TYPE;
    326             mColumnMmsLocked          = COLUMN_MMS_LOCKED;
    327         }
    328 
    329         public ColumnsMap(Cursor cursor) {
    330             // Ignore all 'not found' exceptions since the custom columns
    331             // may be just a subset of the default columns.
    332             try {
    333                 mColumnMsgType = cursor.getColumnIndexOrThrow(
    334                         MmsSms.TYPE_DISCRIMINATOR_COLUMN);
    335             } catch (IllegalArgumentException e) {
    336                 Log.w("colsMap", e.getMessage());
    337             }
    338 
    339             try {
    340                 mColumnMsgId = cursor.getColumnIndexOrThrow(BaseColumns._ID);
    341             } catch (IllegalArgumentException e) {
    342                 Log.w("colsMap", e.getMessage());
    343             }
    344 
    345             try {
    346                 mColumnSmsAddress = cursor.getColumnIndexOrThrow(Sms.ADDRESS);
    347             } catch (IllegalArgumentException e) {
    348                 Log.w("colsMap", e.getMessage());
    349             }
    350 
    351             try {
    352                 mColumnSmsBody = cursor.getColumnIndexOrThrow(Sms.BODY);
    353             } catch (IllegalArgumentException e) {
    354                 Log.w("colsMap", e.getMessage());
    355             }
    356 
    357             try {
    358                 mColumnSmsDate = cursor.getColumnIndexOrThrow(Sms.DATE);
    359             } catch (IllegalArgumentException e) {
    360                 Log.w("colsMap", e.getMessage());
    361             }
    362 
    363             try {
    364                 mColumnSmsType = cursor.getColumnIndexOrThrow(Sms.TYPE);
    365             } catch (IllegalArgumentException e) {
    366                 Log.w("colsMap", e.getMessage());
    367             }
    368 
    369             try {
    370                 mColumnSmsStatus = cursor.getColumnIndexOrThrow(Sms.STATUS);
    371             } catch (IllegalArgumentException e) {
    372                 Log.w("colsMap", e.getMessage());
    373             }
    374 
    375             try {
    376                 mColumnSmsLocked = cursor.getColumnIndexOrThrow(Sms.LOCKED);
    377             } catch (IllegalArgumentException e) {
    378                 Log.w("colsMap", e.getMessage());
    379             }
    380 
    381             try {
    382                 mColumnSmsErrorCode = cursor.getColumnIndexOrThrow(Sms.ERROR_CODE);
    383             } catch (IllegalArgumentException e) {
    384                 Log.w("colsMap", e.getMessage());
    385             }
    386 
    387             try {
    388                 mColumnMmsSubject = cursor.getColumnIndexOrThrow(Mms.SUBJECT);
    389             } catch (IllegalArgumentException e) {
    390                 Log.w("colsMap", e.getMessage());
    391             }
    392 
    393             try {
    394                 mColumnMmsSubjectCharset = cursor.getColumnIndexOrThrow(Mms.SUBJECT_CHARSET);
    395             } catch (IllegalArgumentException e) {
    396                 Log.w("colsMap", e.getMessage());
    397             }
    398 
    399             try {
    400                 mColumnMmsMessageType = cursor.getColumnIndexOrThrow(Mms.MESSAGE_TYPE);
    401             } catch (IllegalArgumentException e) {
    402                 Log.w("colsMap", e.getMessage());
    403             }
    404 
    405             try {
    406                 mColumnMmsMessageBox = cursor.getColumnIndexOrThrow(Mms.MESSAGE_BOX);
    407             } catch (IllegalArgumentException e) {
    408                 Log.w("colsMap", e.getMessage());
    409             }
    410 
    411             try {
    412                 mColumnMmsDeliveryReport = cursor.getColumnIndexOrThrow(Mms.DELIVERY_REPORT);
    413             } catch (IllegalArgumentException e) {
    414                 Log.w("colsMap", e.getMessage());
    415             }
    416 
    417             try {
    418                 mColumnMmsReadReport = cursor.getColumnIndexOrThrow(Mms.READ_REPORT);
    419             } catch (IllegalArgumentException e) {
    420                 Log.w("colsMap", e.getMessage());
    421             }
    422 
    423             try {
    424                 mColumnMmsErrorType = cursor.getColumnIndexOrThrow(PendingMessages.ERROR_TYPE);
    425             } catch (IllegalArgumentException e) {
    426                 Log.w("colsMap", e.getMessage());
    427             }
    428 
    429             try {
    430                 mColumnMmsLocked = cursor.getColumnIndexOrThrow(Mms.LOCKED);
    431             } catch (IllegalArgumentException e) {
    432                 Log.w("colsMap", e.getMessage());
    433             }
    434         }
    435     }
    436 
    437     private AvatarCache mAvatarCache;
    438 
    439     /*
    440      * Track avatars for each of the members of in the group chat.
    441      */
    442     class AvatarCache {
    443         private static final int TOKEN_PHONE_LOOKUP = 101;
    444         private static final int TOKEN_EMAIL_LOOKUP = 102;
    445         private static final int TOKEN_CONTACT_INFO = 201;
    446         private static final int TOKEN_PHOTO_DATA = 301;
    447 
    448         //Projection used for the summary info in the header.
    449         private final String[] COLUMNS = new String[] {
    450                   Contacts._ID,
    451                   Contacts.PHOTO_ID,
    452                   // Other fields which we might want/need in the future (for example)
    453 //                Contacts.LOOKUP_KEY,
    454 //                Contacts.DISPLAY_NAME,
    455 //                Contacts.STARRED,
    456 //                Contacts.CONTACT_PRESENCE,
    457 //                Contacts.CONTACT_STATUS,
    458 //                Contacts.CONTACT_STATUS_TIMESTAMP,
    459 //                Contacts.CONTACT_STATUS_RES_PACKAGE,
    460 //                Contacts.CONTACT_STATUS_LABEL,
    461         };
    462         private final int PHOTO_ID = 1;
    463 
    464         private final String[] PHONE_LOOKUP_PROJECTION = new String[] {
    465             PhoneLookup._ID,
    466             PhoneLookup.LOOKUP_KEY,
    467         };
    468         private static final int PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
    469         private static final int PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
    470 
    471         private final String[] EMAIL_LOOKUP_PROJECTION = new String[] {
    472             RawContacts.CONTACT_ID,
    473             Contacts.LOOKUP_KEY,
    474         };
    475         private static final int EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX = 0;
    476         private static final int EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX = 1;
    477 
    478 
    479         /*
    480          * Map from mAddress to a blob of data which contains the contact id
    481          * and the avatar.
    482          */
    483         HashMap<String, ContactData> mImageCache = new HashMap<String, ContactData>();
    484 
    485         public class ContactData {
    486             private String mAddress;
    487             private long mContactId;
    488             private Uri mContactUri;
    489             private Drawable mPhoto;
    490 
    491             ContactData(String address) {
    492                 mAddress = address;
    493             }
    494 
    495             public Drawable getAvatar() {
    496                 return mPhoto;
    497             }
    498 
    499             public Uri getContactUri() {
    500                 return mContactUri;
    501             }
    502 
    503             private boolean startInitialQuery() {
    504                 if (Mms.isPhoneNumber(mAddress)) {
    505                     mQueryHandler.startQuery(
    506                             TOKEN_PHONE_LOOKUP,
    507                             this,
    508                             Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(mAddress)),
    509                             PHONE_LOOKUP_PROJECTION,
    510                             null,
    511                             null,
    512                             null);
    513                     return true;
    514                 } else if (Mms.isEmailAddress(mAddress)) {
    515                     mQueryHandler.startQuery(
    516                             TOKEN_EMAIL_LOOKUP,
    517                             this,
    518                             Uri.withAppendedPath(Email.CONTENT_LOOKUP_URI, Uri.encode(mAddress)),
    519                             EMAIL_LOOKUP_PROJECTION,
    520                             null,
    521                             null,
    522                             null);
    523                     return true;
    524                 } else {
    525                     return false;
    526                 }
    527             }
    528             /*
    529              * Once we have the photo data load it into a drawable.
    530              */
    531             private boolean onPhotoDataLoaded(Cursor c) {
    532                 if (c == null || !c.moveToFirst()) return false;
    533 
    534                 try {
    535                     byte[] photoData = c.getBlob(0);
    536                     Bitmap b = BitmapFactory.decodeByteArray(photoData, 0, photoData.length, null);
    537                     mPhoto = new BitmapDrawable(mContext.getResources(), b);
    538                     return true;
    539                 } catch (Exception ex) {
    540                     return false;
    541                 }
    542             }
    543 
    544             /*
    545              * Once we have the contact info loaded take the photo id and query
    546              * for the photo data.
    547              */
    548             private boolean onContactInfoLoaded(Cursor c) {
    549                 if (c == null || !c.moveToFirst()) return false;
    550 
    551                 long photoId = c.getLong(PHOTO_ID);
    552                 Uri contactUri  = ContentUris.withAppendedId(Data.CONTENT_URI, photoId);
    553                 mQueryHandler.startQuery(
    554                         TOKEN_PHOTO_DATA,
    555                         this,
    556                         contactUri,
    557                         new String[] { Photo.PHOTO },
    558                         null,
    559                         null,
    560                         null);
    561 
    562                 return true;
    563             }
    564 
    565             /*
    566              * Once we have the contact id loaded start the query for the
    567              * contact information (which will give us the photo id).
    568              */
    569             private boolean onContactIdLoaded(Cursor c, int contactIdColumn, int lookupKeyColumn) {
    570                 if (c == null || !c.moveToFirst()) return false;
    571 
    572                 mContactId = c.getLong(contactIdColumn);
    573                 String lookupKey = c.getString(lookupKeyColumn);
    574                 mContactUri = Contacts.getLookupUri(mContactId, lookupKey);
    575                 mQueryHandler.startQuery(
    576                         TOKEN_CONTACT_INFO,
    577                         this,
    578                         mContactUri,
    579                         COLUMNS,
    580                         null,
    581                         null,
    582                         null);
    583                 return true;
    584             }
    585 
    586             /*
    587              * If for whatever reason we can't get the photo load teh
    588              * default avatar.  NOTE that fasttrack tries to get fancy
    589              * with various random images (upside down, etc.) we're not
    590              * doing that here.
    591              */
    592             private void loadDefaultAvatar() {
    593                 if (mDefaultAvatarDrawable == null) {
    594                     Bitmap b = BitmapFactory.decodeResource(mContext.getResources(),
    595                             R.drawable.ic_contact_picture);
    596                     mDefaultAvatarDrawable = new BitmapDrawable(mContext.getResources(), b);
    597                 }
    598                 mPhoto = mDefaultAvatarDrawable;
    599             }
    600 
    601         };
    602 
    603         Drawable mDefaultAvatarDrawable = null;
    604         AsyncQueryHandler mQueryHandler = new AsyncQueryHandler(mContext.getContentResolver()) {
    605             @Override
    606             protected void onQueryComplete(int token, Object cookieObject, Cursor cursor) {
    607                 super.onQueryComplete(token, cookieObject, cursor);
    608 
    609                 ContactData cookie = (ContactData) cookieObject;
    610                 switch (token) {
    611                     case TOKEN_PHONE_LOOKUP: {
    612                         if (!cookie.onContactIdLoaded(
    613                                 cursor,
    614                                 PHONE_LOOKUP_CONTACT_ID_COLUMN_INDEX,
    615                                 PHONE_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX)) {
    616                             cookie.loadDefaultAvatar();
    617                         }
    618                         break;
    619                     }
    620                     case TOKEN_EMAIL_LOOKUP: {
    621                         if (!cookie.onContactIdLoaded(
    622                                 cursor,
    623                                 EMAIL_LOOKUP_CONTACT_ID_COLUMN_INDEX,
    624                                 EMAIL_LOOKUP_CONTACT_LOOKUP_KEY_COLUMN_INDEX)) {
    625                             cookie.loadDefaultAvatar();
    626                         }
    627                         break;
    628                     }
    629                     case TOKEN_CONTACT_INFO: {
    630                         if (!cookie.onContactInfoLoaded(cursor)) {
    631                             cookie.loadDefaultAvatar();
    632                         }
    633                         break;
    634                     }
    635                     case TOKEN_PHOTO_DATA: {
    636                         if (!cookie.onPhotoDataLoaded(cursor)) {
    637                             cookie.loadDefaultAvatar();
    638                         } else {
    639                             MessageListAdapter.this.notifyImageLoaded(cookie.mAddress);
    640                         }
    641                         break;
    642                     }
    643                     default:
    644                         break;
    645                 }
    646             }
    647         };
    648 
    649         public ContactData get(final String address) {
    650             if (mImageCache.containsKey(address)) {
    651                 return mImageCache.get(address);
    652             } else {
    653                 // Create the ContactData object and put it into the hashtable
    654                 // so that any subsequent requests for this same avatar do not kick
    655                 // off another query.
    656                 ContactData cookie = new ContactData(address);
    657                 mImageCache.put(address, cookie);
    658                 cookie.startInitialQuery();
    659                 cookie.loadDefaultAvatar();
    660                 return cookie;
    661             }
    662         }
    663 
    664         public AvatarCache() {
    665         }
    666     };
    667 
    668 
    669 }
    670