From d2bd93fe3929403f84367f826f07cbadc0c6366b Mon Sep 17 00:00:00 2001 From: Santos Cordon Date: Mon, 12 Aug 2013 16:31:35 -0700 Subject: Add Simplifying layer between Contact Info search and CallCardProvider. I will be getting contact info on notifications for the next CL so this creates a simpler layer for interacting with the contact data async requester. CHANGES: - Moved the code which does the requesting from CallCardPresenter to ContactInfoCache. - ContactInfo Cache defines new listening interface and new simpler object for transmitting the return data: ContactInfoEntry. - Updated CallCardPresenter to use the new simpler interface. - Updated some logging entries. - Simplified Ui interface inside for CallCardPresenter. Change-Id: Ic802c4e53cdf17fcd37c70deb6da61a78b9d8993 --- .../com/android/incallui/CallCardPresenter.java | 358 +++++---------------- 1 file changed, 81 insertions(+), 277 deletions(-) (limited to 'InCallUI/src/com/android/incallui/CallCardPresenter.java') diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java index d4a39eaab..105e3482a 100644 --- a/InCallUI/src/com/android/incallui/CallCardPresenter.java +++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java @@ -16,15 +16,12 @@ package com.android.incallui; -import android.content.ContentUris; import android.content.Context; -import android.graphics.Bitmap; import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.provider.ContactsContract.Contacts; -import android.text.TextUtils; import com.android.incallui.AudioModeProvider.AudioModeListener; +import com.android.incallui.ContactInfoCache.ContactCacheEntry; +import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback; import com.android.incallui.InCallPresenter.InCallState; import com.android.incallui.InCallPresenter.InCallStateListener; @@ -35,27 +32,18 @@ import com.android.services.telephony.common.Call; * Presenter for the Call Card Fragment. * This class listens for changes to InCallState and passes it along to the fragment. */ -public class CallCardPresenter extends Presenter implements - InCallStateListener, CallerInfoAsyncQuery.OnQueryCompleteListener, - ContactsAsyncHelper.OnImageLoadCompleteListener, AudioModeListener { - - private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0; +public class CallCardPresenter extends Presenter + implements InCallStateListener, AudioModeListener, ContactInfoCacheCallback { private Context mContext; private AudioModeProvider mAudioModeProvider; + private ContactInfoCache mContactInfoCache; private Call mPrimary; - - /** - * Uri being used to load contact photo for mPhoto. Will be null when nothing is being loaded, - * or a photo is already loaded. - */ - private Uri mLoadingPersonUri; - - // Track the state for the photo. - private ContactsAsyncHelper.ImageTracker mPhotoTracker; + private Call mSecondary; + private ContactCacheEntry mPrimaryContactInfo; + private ContactCacheEntry mSecondaryContactInfo; public CallCardPresenter() { - mPhotoTracker = new ContactsAsyncHelper.ImageTracker(); } @Override @@ -75,10 +63,14 @@ public class CallCardPresenter extends Presenter i mAudioModeProvider.removeListener(this); } mPrimary = null; + mPrimaryContactInfo = null; + mSecondaryContactInfo = null; } public void setContext(Context context) { mContext = context; + mContactInfoCache = new ContactInfoCache(mContext); + startContactInfoSearch(); } @Override @@ -107,32 +99,23 @@ public class CallCardPresenter extends Presenter i Logger.d(this, "Primary call: " + primary); Logger.d(this, "Secondary call: " + secondary); + mPrimary = primary; + mSecondary = secondary; - if (primary != null) { - // Set primary call data - final CallerInfo primaryCallInfo = CallerInfoUtils.getCallerInfoForCall(mContext, - primary, null, this); - updateDisplayByCallerInfo(primary, primaryCallInfo, primary.getNumberPresentation(), - true); + // Query for contact data. This will call back on onContactInfoComplete at least once + // synchronously, and potentially a second time asynchronously if it needs to make + // a full query for the data. + // It is in that callback that we set the values into the Ui. + startContactInfoSearch(); + // Set the call state + if (mPrimary != null) { final boolean bluetoothOn = mAudioModeProvider != null && mAudioModeProvider.getAudioMode() == AudioMode.BLUETOOTH; - - ui.setNumber(primary.getNumber()); - ui.setCallState(primary.getState(), primary.getDisconnectCause(), bluetoothOn); + ui.setCallState(mPrimary.getState(), mPrimary.getDisconnectCause(), bluetoothOn); } else { - ui.setNumber(""); ui.setCallState(Call.State.INVALID, Call.DisconnectCause.UNKNOWN, false); } - - // Set secondary call data - if (secondary != null) { - ui.setSecondaryCallInfo(true, secondary.getNumber()); - } else { - ui.setSecondaryCallInfo(false, null); - } - - mPrimary = primary; } @Override @@ -148,6 +131,25 @@ public class CallCardPresenter extends Presenter i public void onSupportedAudioMode(int mask) { } + /** + * Starts a query for more contact data for the save primary and secondary calls. + */ + private void startContactInfoSearch() { + if (mPrimary != null && mContactInfoCache != null) { + mContactInfoCache.findInfo(mPrimary, this); + } else { + mPrimaryContactInfo = null; + updatePrimaryDisplayInfo(); + } + + if (mSecondary != null && mContactInfoCache != null) { + mContactInfoCache.findInfo(mSecondary, this); + } else { + mSecondaryContactInfo = null; + updateSecondaryDisplayInfo(); + } + } + /** * Get the highest priority call to display. * Goes through the calls and chooses which to return based on priority of which type of call @@ -183,251 +185,56 @@ public class CallCardPresenter extends Presenter i return retval; } - public interface CallCardUi extends Ui { - // TODO(klp): Consider passing in the Call object directly in these methods. - void setVisible(boolean on); - void setNumber(String number); - void setNumberLabel(String label); - void setName(String name); - void setName(String name, boolean isNumber); - void setImage(int resource); - void setImage(Drawable drawable); - void setImage(Bitmap bitmap); - void setSecondaryCallInfo(boolean show, String number); - void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn); - } - - @Override - public void onQueryComplete(int token, Object cookie, CallerInfo ci) { - if (cookie instanceof Call) { - final Call call = (Call) cookie; - if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) { - updateDisplayByCallerInfo(call, ci, Call.PRESENTATION_ALLOWED, true); - } else { - // If the contact doesn't exist, we can still use information from the - // returned caller info (geodescription, etc). - updateDisplayByCallerInfo(call, ci, call.getNumberPresentation(), true); - } - - // Todo (klp): updatePhotoForCallState(call); - } - } - /** - * Based on the given caller info, determine a suitable name, phone number and label - * to be passed to the CallCardUI. - * - * If the current call is a conference call, use - * updateDisplayForConference() instead. + * Callback received when Contact info data query completes. */ - private void updateDisplayByCallerInfo(Call call, CallerInfo info, int presentation, - boolean isPrimary) { - - // Inform the state machine that we are displaying a photo. - mPhotoTracker.setPhotoRequest(info); - mPhotoTracker.setPhotoState(ContactsAsyncHelper.ImageTracker.DISPLAY_IMAGE); - - // The actual strings we're going to display onscreen: - String displayName; - String displayNumber = null; - String label = null; - Uri personUri = null; - - // 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 - // empty number, we will get a valid CallerInfo object, but with - // fields that are all null, and the isTemporary boolean input - // parameter as true. - - // In the past, we would see a NULL callerinfo object, but this - // ends up causing null pointer exceptions elsewhere down the - // line in other cases, so we need to make this fix instead. It - // appears that this was the ONLY call to PhoneUtils - // .getCallerInfo() that relied on a NULL CallerInfo to indicate - // an unknown contact. - - // Currently, infi.phoneNumber may actually be a SIP address, and - // if so, it might sometimes include the "sip:" prefix. That - // prefix isn't really useful to the user, though, so strip it off - // if present. (For any other URI scheme, though, leave the - // prefix alone.) - // TODO: It would be cleaner for CallerInfo to explicitly support - // SIP addresses instead of overloading the "phoneNumber" field. - // Then we could remove this hack, and instead ask the CallerInfo - // for a "user visible" form of the SIP address. - String number = info.phoneNumber; - if ((number != null) && number.startsWith("sip:")) { - number = number.substring(4); - } - - if (TextUtils.isEmpty(info.name)) { - // No valid "name" in the CallerInfo, so fall back to - // something else. - // (Typically, we promote the phone number up to the "name" slot - // onscreen, and possibly display a descriptive string in the - // "number" slot.) - 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); - Logger.d(this, " ==> 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); - Logger.d(this, " ==> 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; - Logger.d(this, " ==> 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, - // or if you manually dial an outgoing number using the - // dialpad. - - // Promote the phone number up to the "name" slot: - displayName = number; - - // ...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)) { - // 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 - Logger.d(this, "Geodescrption: " + info.geoDescription); - } - - Logger.d(this, " ==> no name; falling back to number: displayName '" - + displayName + "', displayNumber '" + displayNumber + "'"); - } - } else { - // We do have a valid "name" in the CallerInfo. Display that - // in the "name" slot, and the phone number in the "number" slot. - if (presentation != Call.PRESENTATION_ALLOWED) { - // 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); - Logger.d(this, " ==> valid name, but presentation not allowed!" - + " displayName = " + displayName); - } else { - displayName = info.name; - displayNumber = number; - label = info.phoneLabel; - Logger.d(this, " ==> name is present in CallerInfo: displayName '" - + displayName + "', displayNumber '" + displayNumber + "'"); - } - } - personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, info.person_id); - Logger.d(this, "- got personUri: '" + personUri - + "', based on info.person_id: " + info.person_id); - } else { - displayName = getPresentationString(presentation); - } + @Override + public void onContactInfoComplete(int callId, ContactCacheEntry entry) { + Logger.d(this, "onContactInfoComplete: ", entry.name); + Logger.d(this, "onContactInfoComplete: ", entry.number); + Logger.d(this, "onContactInfoComplete: ", entry.label); + Logger.d(this, "onContactInfoComplete: ", entry.photo); - // TODO (klp): Update secondary user call info as well. - if (isPrimary) { - updateInfoUiForPrimary(displayName, displayNumber, label); + if (mPrimary != null && mPrimary.getCallId() == callId) { + mPrimaryContactInfo = entry; + updatePrimaryDisplayInfo(); } + if (mSecondary != null && mSecondary.getCallId() == callId) { + mSecondaryContactInfo = entry; + updateSecondaryDisplayInfo(); + } + + } - // If the photoResource is filled in for the CallerInfo, (like with the - // Emergency Number case), then we can just set the photo image without - // requesting for an image load. Please refer to CallerInfoAsyncQuery.java - // for cases where CallerInfo.photoResource may be set. We can also avoid - // the image load step if the image data is cached. + private void updatePrimaryDisplayInfo() { final CallCardUi ui = getUi(); - if (info == null) return; - - // This will only be true for emergency numbers - if (info.photoResource != 0) { - ui.setImage(info.photoResource); - } else if (info.isCachedPhotoCurrent) { - if (info.cachedPhoto != null) { - ui.setImage(info.cachedPhoto); - } else { - ui.setImage(R.drawable.picture_unknown); - } - } else { - if (personUri == null) { - Logger.v(this, "personUri is null. Just use unknown picture."); - ui.setImage(R.drawable.picture_unknown); - } else if (personUri.equals(mLoadingPersonUri)) { - Logger.v(this, "The requested Uri (" + personUri + ") is being loaded already." - + " Ignore the duplicate load request."); - } else { - // Remember which person's photo is being loaded right now so that we won't issue - // unnecessary load request multiple times, which will mess up animation around - // the contact photo. - mLoadingPersonUri = personUri; - - // 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, 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); - } + if (ui == null) { + return; } - // TODO (klp): Update other fields - photo, sip label, etc. - } - /** - * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface. - * make sure that the call state is reflected after the image is loaded. - */ - @Override - public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) { - // mHandler.removeMessages(MESSAGE_SHOW_UNKNOWN_PHOTO); - if (mLoadingPersonUri != null) { - // Start sending view notification after the current request being done. - // New image may possibly be available from the next phone calls. - // - // TODO: may be nice to update the image view again once the newer one - // is available on contacts database. - // TODO (klp): What is this, and why does it need the write_contacts permission? - // CallerInfoUtils.sendViewNotificationAsync(mContext, mLoadingPersonUri); + if (mPrimaryContactInfo != null) { + ui.setPrimary(mPrimaryContactInfo.number, mPrimaryContactInfo.name, + mPrimaryContactInfo.label, mPrimaryContactInfo.photo); } else { - // This should not happen while we need some verbose info if it happens.. - Logger.v(this, "Person Uri isn't available while Image is successfully loaded."); + // reset to nothing (like at end of call) + ui.setPrimary(null, null, null, null); } - mLoadingPersonUri = null; - - Call call = (Call) cookie; - // TODO (klp): Handle conference calls + } + private void updateSecondaryDisplayInfo() { final CallCardUi ui = getUi(); - if (photo != null) { - ui.setImage(photo); - } else if (photoIcon != null) { - ui.setImage(photoIcon); - } else { - ui.setImage(R.drawable.picture_unknown); + if (ui == null) { + return; } - } - /** - * Updates the info portion of the call card with passed in values for the primary user. - */ - private void updateInfoUiForPrimary(String displayName, String displayNumber, String label) { - final CallCardUi ui = getUi(); - ui.setName(displayName); - ui.setNumber(displayNumber); - ui.setNumberLabel(label); + if (mSecondaryContactInfo != null) { + ui.setSecondary(true, mSecondaryContactInfo.number, mSecondaryContactInfo.name, + mSecondaryContactInfo.label, mSecondaryContactInfo.photo); + } else { + // reset to nothing so that it starts off blank next time we use it. + ui.setSecondary(false, null, null, null, null); + } } public void setAudioModeProvider(AudioModeProvider audioModeProvider) { @@ -435,13 +242,10 @@ public class CallCardPresenter extends Presenter i mAudioModeProvider.addListener(this); } - public String getPresentationString(int presentation) { - String name = mContext.getString(R.string.unknown); - if (presentation == Call.PRESENTATION_RESTRICTED) { - name = mContext.getString(R.string.private_num); - } else if (presentation == Call.PRESENTATION_PAYPHONE) { - name = mContext.getString(R.string.payphone); - } - return name; + public interface CallCardUi extends Ui { + void setVisible(boolean on); + void setPrimary(String number, String name, String label, Drawable photo); + void setSecondary(boolean show, String number, String name, String label, Drawable photo); + void setCallState(int state, Call.DisconnectCause cause, boolean bluetoothOn); } } -- cgit v1.2.3