FileDocCategorySizeDatePackage
ContactInfoCache.javaAPI DocAndroid 1.5 API20034Wed May 06 22:42:46 BST 2009com.android.mms.util

ContactInfoCache

public class ContactInfoCache extends Object
This class caches query results of contact database and provides convenient methods to return contact display name, etc. TODO: To improve performance, we should make contacts query by ourselves instead of doing it one by one calling the CallerInfo API. In the long term, the contacts database could have a caching layer to ease the work for all apps.

Fields Summary
private static final String
TAG
private static final boolean
LOCAL_DEBUG
private static final long
REBUILD_DELAY
private static final String
SEPARATOR
private static final String
CALLER_ID_SELECTION
private static final android.net.Uri
PHONES_WITH_PRESENCE_URI
private static final String[]
CALLER_ID_PROJECTION
private static final int
PHONE_NUMBER_COLUMN
private static final int
PHONE_LABEL_COLUMN
private static final int
CONTACT_NAME_COLUMN
private static final int
CONTACT_ID_COLUMN
private static final int
CONTACT_PRESENCE_COLUMN
private static final String
CONTACT_METHOD_SELECTION
private static final android.net.Uri
CONTACT_METHOD_WITH_PRESENCE_URI
private static final String[]
CONTACT_METHOD_PROJECTION
private static final int
CONTACT_METHOD_NAME_COLUMN
private static final int
CONTACT_METHOD_STATUS_COLUMN
private static final int
CONTACT_METHOD_ID_COLUMN
private static ContactInfoCache
sInstance
private final android.content.Context
mContext
private String[]
mContactInfoSelectionArgs
private final HashMap
mCache
private Thread
mCacheRebuilder
private Object
mCacheRebuildLock
private boolean
mPhoneCacheInvalidated
private boolean
mEmailCacheInvalidated
Constructors Summary
private ContactInfoCache(android.content.Context context)

        mContext = context;

        ContentResolver resolver = context.getContentResolver();
        resolver.registerContentObserver(Contacts.Phones.CONTENT_URI, true,
                new ContentObserver(new Handler()) {
                    @Override
                    public void onChange(boolean selfUpdate) {
                        synchronized (mCacheRebuildLock) {
                            mPhoneCacheInvalidated = true;
                            startCacheRebuilder();
                        }
                    }
                });
        resolver.registerContentObserver(Contacts.ContactMethods.CONTENT_EMAIL_URI, true,
                new ContentObserver(new Handler()) {
                    @Override
                    public void onChange(boolean selfUpdate) {
                        synchronized (mCacheRebuildLock) {
                            mEmailCacheInvalidated = true;
                            startCacheRebuilder();
                        }
                    }
                });
    
Methods Summary
public voiddump()

        synchronized (mCache) {
            Log.i(TAG, "ContactInfoCache.dump");

            for (String name : mCache.keySet()) {
                CacheEntry entry = mCache.get(name);
                if (entry != null) {
                    Log.i(TAG, "key=" + name + ", cacheEntry={" + entry.toString() + '}");
                } else {
                    Log.i(TAG, "key=" + name + ", cacheEntry={null}");
                }
            }
        }        
    
private java.lang.StringgetCallerId(android.content.Context context, java.lang.String number)
A cached version of CallerInfo.getCallerId().

        ContactInfoCache.CacheEntry entry = getContactInfo(context, number);
        if (entry != null && !TextUtils.isEmpty(entry.name)) {
            return entry.name;
        }
        return number;
    
public com.android.mms.util.ContactInfoCache$CacheEntrygetContactInfo(android.content.Context context, java.lang.String numberOrEmail)
Returns the caller info in CacheEntry.

        if (Mms.isEmailAddress(numberOrEmail)) {
            return getContactInfoForEmailAddress(context, numberOrEmail, true /* allow query */);
        } else {
            return getContactInfoForPhoneNumber(context, numberOrEmail, true /* allow query */);
        }
    
public com.android.mms.util.ContactInfoCache$CacheEntrygetContactInfoForEmailAddress(android.content.Context context, java.lang.String email, boolean allowQuery)
Returns the contact info for a given email address

param
context the context.
param
email the email address.
param
allowQuery allow making (potentially blocking) content provider queries if true.
return
a CacheEntry if the contact is found.

        synchronized (mCache) {
            if (mCache.containsKey(email)) {
                CacheEntry entry = mCache.get(email);
                if (!allowQuery || !entry.isStale()) {
                    return entry;
                }
            } else if (!allowQuery) {
                return null;
            }

            CacheEntry entry = queryEmailDisplayName(context, email);
            mCache.put(email, entry);

            return entry;
        }
    
public com.android.mms.util.ContactInfoCache$CacheEntrygetContactInfoForPhoneNumber(android.content.Context context, java.lang.String number, boolean allowQuery)
Returns the caller info in a CacheEntry. If 'noQuery' is set to true, then this method only checks in the cache and makes no content provider query.

param
context the Context.
param
number the phone number for the contact.
param
allowQuery allow (potentially blocking) query the content provider if true.
return
the CacheEntry containing the contact info.

        // TODO: numbers like "6501234567" and "+16501234567" are equivalent.
        // we should convert them into a uniform format so that we don't cache
        // them twice.
        number = Recipient.filterPhoneNumber(number);
        synchronized (mCache) {
            if (mCache.containsKey(number)) {
                CacheEntry entry = mCache.get(number);
                if (LOCAL_DEBUG) {
                    log("getContactInfo: number=" + number + ", name=" + entry.name +
                            ", presence=" + entry.presenceResId);
                }
                if (!allowQuery || !entry.isStale()) {
                    return entry;
                }
            } else if (!allowQuery) {
                return null;
            }

            CacheEntry entry = queryContactInfoByNumber(context, number);
            mCache.put(number, entry);

            return entry;
        }
    
public java.lang.StringgetContactName(android.content.Context context, java.lang.String address)
Get the display names of contacts. Contacts can be either email address or phone number.

param
context the context to use
param
address the addresses to lookup, separated by ";"
return
a nicely formatted version of the contact names to display

        if (TextUtils.isEmpty(address)) {
            return "";
        }

        StringBuilder result = new StringBuilder();
        for (String value : address.split(SEPARATOR)) {
            if (value.length() > 0) {
                result.append(SEPARATOR);
                if (MessageUtils.isLocalNumber(value)) {
                    result.append(context.getString(com.android.internal.R.string.me));
                } else if (Mms.isEmailAddress(value)) {
                    result.append(getDisplayName(context, value));
                } else {
                    result.append(getCallerId(context, value));
                }
            }
        }

        if (result.length() > 0) {
            // Skip the first ";"
            return result.substring(1);
        }

        return "";
    
public java.lang.StringgetDisplayName(android.content.Context context, java.lang.String email)
Get the display name of an email address. If the address already contains the name, parse and return it. Otherwise, query the contact database. Cache query results for repeated queries.

        Matcher match = Mms.NAME_ADDR_EMAIL_PATTERN.matcher(email);
        if (match.matches()) {
            // email has display name
            return getEmailDisplayName(match.group(1));
        }

        CacheEntry entry = getContactInfoForEmailAddress(context, email, true /* allow query */);
        if (entry != null && entry.name != null) {
            return entry.name;
        }

        return email;
    
private static java.lang.StringgetEmailDisplayName(java.lang.String displayString)

        Matcher match = Mms.QUOTED_STRING_PATTERN.matcher(displayString);
        if (match.matches()) {
            return match.group(1);
        }

        return displayString;
    
public static com.android.mms.util.ContactInfoCachegetInstance()
Get the global instance.

        return sInstance;
    
private intgetPresenceIconResourceId(int presence)

        if (presence != Contacts.People.OFFLINE) {
            return Contacts.Presence.getPresenceIconResourceId(presence);
        }

        return 0;
    
private voidgetRebuildList(java.util.List phones, java.util.List emails)
Get the list of phone/email candidates for the cache rebuilding. This is a snapshot of the keys in the cache.

        synchronized (mCache) {
            for (String name : mCache.keySet()) {
                if (Mms.isEmailAddress(name)) {
                    if (emails != null) {
                        emails.add(name);
                    }
                } else {
                    if (phones != null) {
                        phones.add(name);
                    }
                }
            }
        } 
    
public static voidinit(android.content.Context context)
Initialize the global instance. Should call only once.

        sInstance = new ContactInfoCache(context);
    
public voidinvalidateCache()
invalidates the cache entries by marking CacheEntry.isStale to true.

        synchronized (mCache) {
            for (Map.Entry<String, CacheEntry> e: mCache.entrySet()) {
                CacheEntry entry = e.getValue();                
                entry.isStale = true;
            }
        }
    
public voidinvalidateContact(java.lang.String emailOrNumber)
invalidates a single cache entry. Can pass in an email or number.

        synchronized (mCache) {
            CacheEntry entry = mCache.get(emailOrNumber);
            if (entry != null) {
                entry.isStale = true;
            }
        }
    
private voidlog(java.lang.String msg)

        Log.d(TAG, "[ContactInfoCache] " + msg);
    
private com.android.mms.util.ContactInfoCache$CacheEntryqueryContactInfoByNumber(android.content.Context context, java.lang.String number)
Queries the caller id info with the phone number.

return
a CacheEntry containing the caller id info corresponding to the number.

        CacheEntry entry = new CacheEntry();
        entry.phoneNumber = number;

        //if (LOCAL_DEBUG) log("queryContactInfoByNumber: number=" + number);

        mContactInfoSelectionArgs[0] = number;

        Cursor cursor = context.getContentResolver().query(
                PHONES_WITH_PRESENCE_URI,
                CALLER_ID_PROJECTION,
                CALLER_ID_SELECTION,
                mContactInfoSelectionArgs,
                null);

        try {
            if (cursor.moveToFirst()) {
                entry.phoneLabel = cursor.getString(PHONE_LABEL_COLUMN);
                entry.name = cursor.getString(CONTACT_NAME_COLUMN);
                entry.person_id = cursor.getLong(CONTACT_ID_COLUMN);
                entry.presenceResId = getPresenceIconResourceId(
                        cursor.getInt(CONTACT_PRESENCE_COLUMN));
                if (LOCAL_DEBUG) {
                    log("queryContactInfoByNumber: name=" + entry.name + ", number=" + number +
                            ", presence=" + entry.presenceResId);
                }
            }
        } finally {
            cursor.close();
        }

        return entry;
    
private com.android.mms.util.ContactInfoCache$CacheEntryqueryEmailDisplayName(android.content.Context context, java.lang.String email)
Query the contact email table to get the name of an email address.

        CacheEntry entry = new CacheEntry();

        mContactInfoSelectionArgs[0] = email;
        Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
                CONTACT_METHOD_WITH_PRESENCE_URI,
                CONTACT_METHOD_PROJECTION,
                CONTACT_METHOD_SELECTION,
                mContactInfoSelectionArgs,
                null);

        if (cursor != null) {
            try {
                while (cursor.moveToNext()) {
                    entry.presenceResId = getPresenceIconResourceId(
                            cursor.getInt(CONTACT_METHOD_STATUS_COLUMN));
                    entry.person_id = cursor.getLong(CONTACT_METHOD_ID_COLUMN);

                    String name = cursor.getString(CONTACT_METHOD_NAME_COLUMN);
                    if (!TextUtils.isEmpty(name)) {
                        entry.name = name;
                        if (LOCAL_DEBUG) {
                            log("queryEmailDisplayName: name=" + entry.name + ", email=" + email +
                                    ", presence=" + entry.presenceResId);
                        }
                        break;
                    }
                }
            } finally {
                cursor.close();
            }
        }

        return entry;
    
private voidrebuildCache()
The actual work of rebuilding the cache, i.e. syncing our cache with the contacts database.

        List<String> phones;
        List<String> emails;

        for (;;) {
            // simulate the Nagle's algorithm:
            // delay for a while to prevent from getting too busy, when, say,
            // there is a big contacts sync going on
            try {
                Thread.sleep(REBUILD_DELAY);
            } catch (InterruptedException ie) {
            }

            phones = null;
            emails = null;
            synchronized (mCacheRebuildLock) {
                // if nothing changed during our sync, stop this thread
                // otherwise, just keep working on it.
                if (!(mPhoneCacheInvalidated || mEmailCacheInvalidated)) {
                    mCacheRebuilder = null;
                    return;
                }
                if (mPhoneCacheInvalidated) {
                    phones = new ArrayList<String>();
                    mPhoneCacheInvalidated = false;
                }
                if (mEmailCacheInvalidated) {
                    emails = new ArrayList<String>();
                    mEmailCacheInvalidated = false;
                }
            }
            // retrieve the list of phone/email candidates for syncing
            // which is a snapshot of the keys in the cache
            getRebuildList(phones, emails);
            // now sync
            if (phones != null) {
                if (LOCAL_DEBUG) log("rebuild cache for phone numbers...");
                for (String phone : phones) {
                    synchronized (mCache) {
                        CacheEntry entry = queryContactInfoByNumber(mContext, phone);
                        mCache.put(phone, entry);
                    }
                }
            }
            if (emails != null) {
                if (LOCAL_DEBUG) log("rebuild cache for emails...");
                for (String email : emails) {
                    synchronized (mCache) {
                        CacheEntry entry = queryEmailDisplayName(mContext, email);
                        mCache.put(email, entry);
                    }
                }
            }
        }
    
private voidstartCacheRebuilder()
Start the background cache rebuilding thread if there is not one yet.

        if (mCacheRebuilder == null) {
            mCacheRebuilder = new Thread(new Runnable() {
                    public void run() {
                        rebuildCache();
                    }
            });
            mCacheRebuilder.start();
        }