summaryrefslogtreecommitdiff
path: root/InCallUI/src/com/android/incallui/ContactInfoCache.java
diff options
context:
space:
mode:
Diffstat (limited to 'InCallUI/src/com/android/incallui/ContactInfoCache.java')
-rw-r--r--InCallUI/src/com/android/incallui/ContactInfoCache.java324
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();
}
}
}