diff options
Diffstat (limited to 'InCallUI/src/com/android/incallui/ContactInfoCache.java')
-rw-r--r-- | InCallUI/src/com/android/incallui/ContactInfoCache.java | 324 |
1 files changed, 163 insertions, 161 deletions
diff --git a/InCallUI/src/com/android/incallui/ContactInfoCache.java b/InCallUI/src/com/android/incallui/ContactInfoCache.java index e62900ea9..a80d91c82 100644 --- a/InCallUI/src/com/android/incallui/ContactInfoCache.java +++ b/InCallUI/src/com/android/incallui/ContactInfoCache.java @@ -16,10 +16,6 @@ package com.android.incallui; -import com.google.android.collect.Lists; -import com.google.android.collect.Maps; -import com.google.common.base.Preconditions; - import android.content.ContentUris; import android.content.Context; import android.graphics.Bitmap; @@ -31,7 +27,14 @@ import android.provider.ContactsContract.Contacts; import android.text.TextUtils; import com.android.services.telephony.common.Call; +import com.android.services.telephony.common.CallIdentification; +import com.android.services.telephony.common.MoreStrings; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,73 +45,94 @@ import java.util.Map; * queries. * This class always gets called from the UI thread so it does not need thread protection. */ -public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteListener, - ContactsAsyncHelper.OnImageLoadCompleteListener { +public class ContactInfoCache implements ContactsAsyncHelper.OnImageLoadCompleteListener { + private static final String TAG = ContactInfoCache.class.getSimpleName(); private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0; private final Context mContext; - private final Map<Integer, SearchEntry> mInfoMap = Maps.newHashMap(); + private final HashMap<Integer, ContactCacheEntry> mInfoMap = Maps.newHashMap(); + private final HashMap<Integer, List<ContactInfoCacheCallback>> mCallBacks = Maps.newHashMap(); - public ContactInfoCache(Context context) { + private static ContactInfoCache sCache = null; + + public static synchronized ContactInfoCache getInstance(Context mContext) { + if (sCache == null) { + sCache = new ContactInfoCache(mContext); + } + return sCache; + } + + private ContactInfoCache(Context context) { mContext = context; } + public ContactCacheEntry getInfo(int callId) { + return mInfoMap.get(callId); + } + + public static ContactCacheEntry buildCacheEntryFromCall(Context context, + CallIdentification identification, boolean isIncoming) { + final ContactCacheEntry entry = new ContactCacheEntry(); + + // TODO: get rid of caller info. + final CallerInfo info = CallerInfoUtils.buildCallerInfo(context, identification); + ContactInfoCache.populateCacheEntry(context, info, entry, + identification.getNumberPresentation(), isIncoming); + return entry; + } + /** * Requests contact data for the Call object passed in. * Returns the data through callback. If callback is null, no response is made, however the * query is still performed and cached. * - * @param call The call to look up. + * @param identification The call identification * @param callback The function to call back when the call is found. Can be null. */ - public void findInfo(Call call, ContactInfoCacheCallback callback) { + public void findInfo(final CallIdentification identification, final boolean isIncoming, + ContactInfoCacheCallback callback) { Preconditions.checkState(Looper.getMainLooper().getThread() == Thread.currentThread()); Preconditions.checkNotNull(callback); - Preconditions.checkNotNull(call); - - final SearchEntry entry; + final int callId = identification.getCallId(); // If the entry already exists, add callback - if (mInfoMap.containsKey(call.getCallId())) { - entry = mInfoMap.get(call.getCallId()); - - // If this entry is still pending, the callback will also get called when it returns. - if (!entry.finished) { - entry.addCallback(callback); - } + List<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId); + if (callBacks == null) { + + // New lookup + callBacks = Lists.newArrayList(); + callBacks.add(callback); + mCallBacks.put(callId, callBacks); + + /** + * Performs a query for caller information. + * Save any immediate data we get from the query. An asynchronous query may also be made + * for any data that we do not already have. Some queries, such as those for voicemail and + * emergency call information, will not perform an additional asynchronous query. + */ + CallerInfoUtils.getCallerInfoForCall(mContext, identification, + new CallerInfoAsyncQuery.OnQueryCompleteListener() { + @Override + public void onQueryComplete(int token, Object cookie, CallerInfo ci) { + int presentationMode = identification.getNumberPresentation(); + if (ci.contactExists || ci.isEmergencyNumber() || ci + .isVoiceMailNumber()) { + presentationMode = Call.PRESENTATION_ALLOWED; + } + + // This starts the photo load. + final ContactCacheEntry cacheEntry = buildEntry(mContext, + identification.getCallId(), ci, presentationMode, isIncoming, + ContactInfoCache.this); + + // Add the contact info to the cache. + mInfoMap.put(callId, cacheEntry); + sendNotification(identification.getCallId(), cacheEntry); + } + }); } else { - entry = new SearchEntry(call, callback); - mInfoMap.put(call.getCallId(), entry); - startQuery(entry); - } - - // Call back with the information we have - callback.onContactInfoComplete(entry.call.getCallId(), entry.info); - } - - /** - * Callback method for asynchronous caller information query. - */ - @Override - public void onQueryComplete(int token, Object cookie, CallerInfo ci) { - if (cookie instanceof Call) { - final Call call = (Call) cookie; - - if (!mInfoMap.containsKey(call.getCallId())) { - return; - } - - final SearchEntry entry = mInfoMap.get(call.getCallId()); - - int presentationMode = call.getNumberPresentation(); - if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) { - presentationMode = Call.PRESENTATION_ALLOWED; - } - - // start photo query - - updateCallerInfo(entry, ci, presentationMode); + callBacks.add(callback); } } @@ -124,30 +148,30 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis // TODO (klp): What is this, and why does it need the write_contacts permission? // CallerInfoUtils.sendViewNotificationAsync(mContext, mLoadingPersonUri); - final Call call = (Call) cookie; + final int callId = (Integer) cookie; - if (!mInfoMap.containsKey(call.getCallId())) { + if (!mInfoMap.containsKey(callId)) { Log.e(this, "Image Load received for empty search entry."); return; } - final SearchEntry entry = mInfoMap.get(call.getCallId()); + final ContactCacheEntry entry = mInfoMap.get(callId); Log.d(this, "setting photo for entry: ", entry); // TODO (klp): Handle conference calls if (photo != null) { Log.v(this, "direct drawable: ", photo); - entry.info.photo = photo; + entry.photo = photo; } else if (photoIcon != null) { Log.v(this, "photo icon: ", photoIcon); - entry.info.photo = new BitmapDrawable(mContext.getResources(), photoIcon); + entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon); } else { Log.v(this, "unknown photo"); - entry.info.photo = null; + entry.photo = null; } - sendNotification(entry); + sendNotification(callId, entry); } /** @@ -155,33 +179,63 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis */ public void clearCache() { mInfoMap.clear(); + mCallBacks.clear(); } - /** - * Performs a query for caller information. - * Save any immediate data we get from the query. An asynchronous query may also be made - * for any data that we do not already have. Some queries, such as those for voicemail and - * emergency call information, will not perform an additional asynchronous query. - */ - private void startQuery(SearchEntry entry) { - final CallerInfo ci = CallerInfoUtils.getCallerInfoForCall(mContext, entry.call, this); + private static ContactCacheEntry buildEntry(Context context, int callId, + CallerInfo info, int presentation, boolean isIncoming, + ContactsAsyncHelper.OnImageLoadCompleteListener imageLoadListener) { + // The actual strings we're going to display onscreen: + Drawable photo = null; + + final ContactCacheEntry cce = new ContactCacheEntry(); + populateCacheEntry(context, info, cce, presentation, isIncoming); + + // This will only be true for emergency numbers + if (info.photoResource != 0) { + photo = context.getResources().getDrawable(info.photoResource); + } else if (info.isCachedPhotoCurrent) { + if (info.cachedPhoto != null) { + photo = info.cachedPhoto; + } else { + photo = context.getResources().getDrawable(R.drawable.picture_unknown); + } + } else { + Uri personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, info.person_id); + Log.d(TAG, "- got personUri: '" + personUri + "', based on info.person_id: " + + info.person_id); - updateCallerInfo(entry, ci, entry.call.getNumberPresentation()); + if (personUri == null) { + Log.v(TAG, "personUri is null. Just use unknown picture."); + photo = context.getResources().getDrawable(R.drawable.picture_unknown); + } else { + Log.d(TAG, "startObtainPhotoAsync"); + // Load the image with a callback to update the image state. + // When the load is finished, onImageLoadComplete() will be called. + ContactsAsyncHelper.startObtainPhotoAsync(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, + context, personUri, imageLoadListener, callId); + + // If the image load is too slow, we show a default avatar icon afterward. + // If it is fast enough, this message will be canceled on onImageLoadComplete(). + // TODO (klp): Figure out if this handler is still needed. + // mHandler.removeMessages(MESSAGE_SHOW_UNKNOWN_PHOTO); + // mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_UNKNOWN_PHOTO, MESSAGE_DELAY); + } + } + + cce.photo = photo; + return cce; } - private void updateCallerInfo(SearchEntry entry, CallerInfo info, int presentation) { - // The actual strings we're going to display onscreen: + /** + * Populate a cache entry from a caller identification (which got converted into a caller info). + */ + public static void populateCacheEntry(Context context, CallerInfo info, ContactCacheEntry cce, + int presentation, boolean isIncoming) { + Preconditions.checkNotNull(info); String displayName; String displayNumber = null; String label = null; - Uri personUri = null; - Drawable photo = null; - - final Call call = entry.call; - - // Gather missing info unless the call is generic, in which case we wouldn't use - // the gathered information anyway. - if (info != null) { // It appears that there is a small change in behaviour with the // PhoneUtils' startGetCallerInfo whereby if we query with an @@ -219,21 +273,20 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis if (TextUtils.isEmpty(number)) { // No name *or* number! Display a generic "unknown" string // (or potentially some other default based on the presentation.) - displayName = getPresentationString(presentation); - Log.d(this, " ==> no name *or* number! displayName = " + displayName); + displayName = getPresentationString(context, presentation); + Log.d(TAG, " ==> no name *or* number! displayName = " + displayName); } else if (presentation != Call.PRESENTATION_ALLOWED) { // This case should never happen since the network should never send a phone # // AND a restricted presentation. However we leave it here in case of weird // network behavior - displayName = getPresentationString(presentation); - Log.d(this, " ==> presentation not allowed! displayName = " + displayName); + displayName = getPresentationString(context, presentation); + Log.d(TAG, " ==> presentation not allowed! displayName = " + displayName); } else if (!TextUtils.isEmpty(info.cnapName)) { // No name, but we do have a valid CNAP name, so use that. displayName = info.cnapName; info.name = info.cnapName; displayNumber = number; - Log.d(this, " ==> cnapName available: displayName '" - + displayName + "', displayNumber '" + displayNumber + "'"); + Log.d(TAG, " ==> cnapName available: displayName '" + displayName + "', displayNumber '" + displayNumber + "'"); } else { // No name; all we have is a number. This is the typical // case when an incoming call doesn't match any contact, @@ -245,16 +298,16 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis // ...and use the "number" slot for a geographical description // string if available (but only for incoming calls.) - if ((call != null) && (call.getState() == Call.State.INCOMING)) { + if (isIncoming) { // TODO (CallerInfoAsyncQuery cleanup): Fix the CallerInfo // query to only do the geoDescription lookup in the first // place for incoming calls. displayNumber = info.geoDescription; // may be null - Log.d(this, "Geodescrption: " + info.geoDescription); + Log.d(TAG, "Geodescrption: " + info.geoDescription); } - Log.d(this, " ==> no name; falling back to number: displayName '" - + displayName + "', displayNumber '" + displayNumber + "'"); + Log.d(TAG, + " ==> no name; falling back to number: displayName '" + displayName + "', displayNumber '" + displayNumber + "'"); } } else { // We do have a valid "name" in the CallerInfo. Display that @@ -263,79 +316,44 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis // This case should never happen since the network should never send a name // AND a restricted presentation. However we leave it here in case of weird // network behavior - displayName = getPresentationString(presentation); - Log.d(this, " ==> valid name, but presentation not allowed!" - + " displayName = " + displayName); + displayName = getPresentationString(context, presentation); + Log.d(TAG, + " ==> valid name, but presentation not allowed!" + " displayName = " + displayName); } else { displayName = info.name; displayNumber = number; label = info.phoneLabel; - Log.d(this, " ==> name is present in CallerInfo: displayName '" - + displayName + "', displayNumber '" + displayNumber + "'"); + Log.d(TAG, " ==> name is present in CallerInfo: displayName '" + displayName + + "', displayNumber '" + displayNumber + "'"); } } - personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, info.person_id); - Log.d(this, "- got personUri: '" + personUri - + "', based on info.person_id: " + info.person_id); - } else { - displayName = getPresentationString(presentation); - } - // This will only be true for emergency numbers - if (info.photoResource != 0) { - photo = mContext.getResources().getDrawable(info.photoResource); - } else if (info.isCachedPhotoCurrent) { - if (info.cachedPhoto != null) { - photo = info.cachedPhoto; - } else { - photo = mContext.getResources().getDrawable(R.drawable.picture_unknown); - } - } else { - if (personUri == null) { - Log.v(this, "personUri is null. Just use unknown picture."); - photo = mContext.getResources().getDrawable(R.drawable.picture_unknown); - } else { - Log.d(this, "startObtainPhotoAsync"); - // Load the image with a callback to update the image state. - // When the load is finished, onImageLoadComplete() will be called. - ContactsAsyncHelper.startObtainPhotoAsync(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE, - mContext, personUri, this, entry.call); - - // If the image load is too slow, we show a default avatar icon afterward. - // If it is fast enough, this message will be canceled on onImageLoadComplete(). - // TODO (klp): Figure out if this handler is still needed. - // mHandler.removeMessages(MESSAGE_SHOW_UNKNOWN_PHOTO); - // mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_UNKNOWN_PHOTO, MESSAGE_DELAY); - } - } - - final ContactCacheEntry cce = entry.info; cce.name = displayName; cce.number = displayNumber; cce.label = label; - cce.photo = photo; - - sendNotification(entry); } /** * Sends the updated information to call the callbacks for the entry. */ - private void sendNotification(SearchEntry entry) { - for (int i = 0; i < entry.callbacks.size(); i++) { - entry.callbacks.get(i).onContactInfoComplete(entry.call.getCallId(), entry.info); + private void sendNotification(int callId, ContactCacheEntry entry) { + List<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId); + if (callBacks != null) { + for (ContactInfoCacheCallback callBack : callBacks) { + callBack.onContactInfoComplete(callId, entry); + } } } /** * Gets name strings based on some special presentation modes. */ - private String getPresentationString(int presentation) { - String name = mContext.getString(R.string.unknown); + private static String getPresentationString(Context context, int presentation) { + String name = context.getString(R.string.unknown); if (presentation == Call.PRESENTATION_RESTRICTED) { - name = mContext.getString(R.string.private_num); + name = context.getString(R.string.private_num); } else if (presentation == Call.PRESENTATION_PAYPHONE) { - name = mContext.getString(R.string.payphone); + name = context.getString(R.string.payphone); } return name; } @@ -352,31 +370,15 @@ public class ContactInfoCache implements CallerInfoAsyncQuery.OnQueryCompleteLis public String number; public String label; public Drawable photo; - } - - private static class SearchEntry { - public Call call; - public boolean finished; - public final ContactCacheEntry info; - public final List<ContactInfoCacheCallback> callbacks = Lists.newArrayList(); - - public SearchEntry(Call call, ContactInfoCacheCallback callback) { - this.call = call; - - info = new ContactCacheEntry(); - finished = false; - callbacks.add(callback); - } - - public void addCallback(ContactInfoCacheCallback cb) { - if (!callbacks.contains(cb)) { - callbacks.add(cb); - } - } - public void finish() { - callbacks.clear(); - finished = true; + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("name", MoreStrings.toSafeString(name)) + .add("number", MoreStrings.toSafeString(number)) + .add("label", label) + .add("photo", photo) + .toString(); } } } |