Use shared contact info cache to get data for the notification.

Move around where ContactInfoCache is created so that StatusBarNotifier
and CallCardPresenter will use the same shared instance.

Change-Id: I6e0a7c48aa7a35a0f1a52b399eb127d69f41a879
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index 6caf008..552a42a 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -95,12 +95,6 @@
     }
 
     @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        getPresenter().setContext(activity);
-    }
-
-    @Override
     public void setPrimary(String number, String name, String label, Drawable photo) {
         boolean nameIsNumber = false;
 
@@ -197,8 +191,7 @@
 
     private void setDrawableToImageView(ImageView view, Drawable photo) {
         if (photo == null) {
-            mPhoto.setVisibility(View.INVISIBLE);
-            return;
+            photo = view.getResources().getDrawable(R.drawable.picture_unknown);
         }
 
         final Drawable current = view.getDrawable();
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index 105e348..8a855c1 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -35,7 +35,6 @@
 public class CallCardPresenter extends Presenter<CallCardPresenter.CallCardUi>
         implements InCallStateListener, AudioModeListener, ContactInfoCacheCallback {
 
-    private Context mContext;
     private AudioModeProvider mAudioModeProvider;
     private ContactInfoCache mContactInfoCache;
     private Call mPrimary;
@@ -67,9 +66,8 @@
         mSecondaryContactInfo = null;
     }
 
-    public void setContext(Context context) {
-        mContext = context;
-        mContactInfoCache = new ContactInfoCache(mContext);
+    public void setContactInfoCache(ContactInfoCache cache) {
+        mContactInfoCache = cache;
         startContactInfoSearch();
     }
 
diff --git a/InCallUI/src/com/android/incallui/ContactInfoCache.java b/InCallUI/src/com/android/incallui/ContactInfoCache.java
index 6cbe060..c5a4cf5 100644
--- a/InCallUI/src/com/android/incallui/ContactInfoCache.java
+++ b/InCallUI/src/com/android/incallui/ContactInfoCache.java
@@ -144,13 +144,20 @@
             entry.info.photo = new BitmapDrawable(mContext.getResources(), photoIcon);
         } else {
             Logger.v(this, "unknown photo");
-            entry.info.photo = mContext.getResources().getDrawable(R.drawable.picture_unknown);
+            entry.info.photo = null;
         }
 
         sendNotification(entry);
     }
 
     /**
+     * Blows away the stored cache values.
+     */
+    public void clearCache() {
+        mInfoMap.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
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index c76f08a..e1a9796 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -287,6 +287,8 @@
                 mainPresenter.getAudioModeProvider());
         mCallCardFragment.getPresenter().setAudioModeProvider(
                 mainPresenter.getAudioModeProvider());
+        mCallCardFragment.getPresenter().setContactInfoCache(
+                mainPresenter.getContactInfoCache());
 
         mainPresenter.addListener(mCallButtonFragment.getPresenter());
         mainPresenter.addListener(mCallCardFragment.getPresenter());
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 25c2447..3ae4d3a 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -43,6 +43,7 @@
 
     private AudioModeProvider mAudioModeProvider;
     private StatusBarNotifier mStatusBarNotifier;
+    private ContactInfoCache mContactInfoCache;
     private Context mContext;
     private CallList mCallList;
     private InCallActivity mInCallActivity;
@@ -63,7 +64,9 @@
         mCallList = callList;
         mCallList.addListener(this);
 
-        mStatusBarNotifier = new StatusBarNotifier(context);
+        mContactInfoCache = new ContactInfoCache(context);
+
+        mStatusBarNotifier = new StatusBarNotifier(context, mContactInfoCache, mCallList);
         addListener(mStatusBarNotifier);
 
         mAudioModeProvider = audioModeProvider;
@@ -148,6 +151,10 @@
         return mAudioModeProvider;
     }
 
+    public ContactInfoCache getContactInfoCache() {
+        return mContactInfoCache;
+    }
+
     /**
      * Hangs up any active or outgoing calls.
      */
@@ -244,6 +251,10 @@
                 mInCallActivity = null;
 
                 temp.finish();
+
+                // blow away stale contact info so that we get fresh data on
+                // the next set of calls
+                mContactInfoCache.clearCache();
             }
         }
 
diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
index ff3c47b..e2dc42c 100644
--- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java
+++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
@@ -23,7 +23,13 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.text.TextUtils;
 
+import com.android.incallui.ContactInfoCache.ContactCacheEntry;
+import com.android.incallui.ContactInfoCache.ContactInfoCacheCallback;
 import com.android.incallui.InCallApp.NotificationBroadcastReceiver;
 import com.android.incallui.InCallPresenter.InCallState;
 import com.android.services.telephony.common.Call;
@@ -31,21 +37,29 @@
 /**
  * This class adds Notifications to the status bar for the in-call experience.
  */
-public class StatusBarNotifier implements InCallPresenter.InCallStateListener {
+public class StatusBarNotifier implements InCallPresenter.InCallStateListener,
+        ContactInfoCacheCallback {
     // notification types
     private static final int IN_CALL_NOTIFICATION = 1;
 
     private final Context mContext;
+    private final ContactInfoCache mContactInfoCache;
+    private final CallList mCallList;
     private final NotificationManager mNotificationManager;
     private boolean mIsShowingNotification = false;
     private InCallState mInCallState = InCallState.HIDDEN;
     private int mSavedIcon = 0;
     private int mSavedContent = 0;
+    private Bitmap mSavedLargeIcon;
+    private String mSavedContentTitle;
 
-    public StatusBarNotifier(Context context) {
+    public StatusBarNotifier(Context context, ContactInfoCache contactInfoCache,
+            CallList callList) {
         Preconditions.checkNotNull(context);
 
         mContext = context;
+        mContactInfoCache = contactInfoCache;
+        mCallList = callList;
         mNotificationManager =
                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
     }
@@ -59,6 +73,14 @@
     }
 
     /**
+     * Called after the Contact Info query has finished.
+     */
+    @Override
+    public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
+        updateNotification(mInCallState, mCallList);
+    }
+
+    /**
      * Updates the phone app's status bar notification based on the
      * current telephony state, or cancels the notification if the phone
      * is totally idle.
@@ -134,8 +156,8 @@
      *   Watch out: This should be set to true *only* when directly
      *   handling a new incoming call for the first time.
      */
-    private void updateInCallNotification(boolean allowFullScreenIntent, InCallState state,
-            CallList callList) {
+    private void updateInCallNotification(final boolean allowFullScreenIntent,
+            final InCallState state, CallList callList) {
         Logger.d(this, "updateInCallNotification(allowFullScreenIntent = "
                      + allowFullScreenIntent + ")...");
 
@@ -144,30 +166,42 @@
             return;
         }
 
-        buildAndSendNotification(state, callList, allowFullScreenIntent);
+        final Call call = getCallToShow(callList);
+        if (call == null) {
+            Logger.wtf(this, "No call for the notification!");
+        }
+
+        // we make a call to the contact info cache to query for supplemental data to what the
+        // call provides.  This includes the contact name and photo.
+        // This callback will always get called immediately and synchronously with whatever data
+        // it has available, and may make a subsequent call later (same thread) if it had to
+        // call into the contacts provider for more data.
+        mContactInfoCache.findInfo(call, new ContactInfoCacheCallback() {
+            @Override
+            public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
+                buildAndSendNotification(state, call, entry, allowFullScreenIntent);
+            }
+        });
 
     }
 
     /**
      * Sets up the main Ui for the notification
      */
-    private void buildAndSendNotification(InCallState state, CallList callList,
-            boolean allowFullScreenIntent) {
-
-        final Call call = getCallToShow(callList);
-        if (call == null) {
-            Logger.wtf(this, "No call for the notification!");
-        }
+    private void buildAndSendNotification(InCallState state, Call call,
+            ContactCacheEntry contactInfo, boolean allowFullScreenIntent) {
 
         final int iconResId = getIconToDisplay(call);
+        final Bitmap largeIcon = getLargeIconToDisplay(contactInfo);
         final int contentResId = getContentString(call);
+        final String contentTitle = getContentTitle(contactInfo);
 
         // If we checked and found that nothing is different, dont issue another notification.
-        if (!checkForChangeAndSaveData(iconResId, contentResId, state, allowFullScreenIntent)) {
+        if (!checkForChangeAndSaveData(iconResId, contentResId, largeIcon, contentTitle, state,
+                allowFullScreenIntent)) {
             return;
         }
 
-
         /*
          * Nothing more to check...build and send it.
          */
@@ -185,6 +219,8 @@
         // set the content
         builder.setContentText(mContext.getString(contentResId));
         builder.setSmallIcon(iconResId);
+        builder.setContentTitle(contentTitle);
+        builder.setLargeIcon(largeIcon);
 
         // Add special Content for calls that are ongoing
         if (InCallState.INCALL == state || InCallState.OUTGOING == state) {
@@ -205,10 +241,20 @@
      * are already displaying. If the data is exactly the same, we return false so that
      * we do not issue a new notification for the exact same data.
      */
-    private boolean checkForChangeAndSaveData(int icon, int content, InCallState state,
-            boolean showFullScreenIntent) {
+    private boolean checkForChangeAndSaveData(int icon, int content, Bitmap largeIcon,
+            String contentTitle, InCallState state, boolean showFullScreenIntent) {
+
+        // The two are different:
+        // if new title is not null, it should be different from saved version OR
+        // if new title is null, the saved version should not be null
+        final boolean contentTitleChanged =
+                (contentTitle != null && !contentTitle.equals(mSavedContentTitle)) ||
+                (contentTitle == null && mSavedContentTitle != null);
+
+        // any change means we are definitely updating
         boolean retval = (mSavedIcon != icon) || (mSavedContent != content) ||
-                (mInCallState == state);
+                (mInCallState != state) || (mSavedLargeIcon != largeIcon) ||
+                contentTitleChanged;
 
         // A full screen intent means that we have been asked to interrupt an activity,
         // so we definitely want to show it.
@@ -226,6 +272,8 @@
         mSavedIcon = icon;
         mSavedContent = content;
         mInCallState = state;
+        mSavedLargeIcon = largeIcon;
+        mSavedContentTitle = contentTitle;
 
         if (retval) {
             Logger.d(this, "Data changed.  Showing notification");
@@ -235,6 +283,28 @@
     }
 
     /**
+     * Returns the main string to use in the notification.
+     */
+    private String getContentTitle(ContactCacheEntry contactInfo) {
+        if (TextUtils.isEmpty(contactInfo.name)) {
+            return contactInfo.number;
+        }
+
+        return contactInfo.name;
+    }
+
+    /**
+     * Gets a large icon from the contact info object to display in the notification.
+     */
+    private Bitmap getLargeIconToDisplay(ContactCacheEntry contactInfo) {
+        if (contactInfo.photo != null && (contactInfo.photo instanceof BitmapDrawable)) {
+            return ((BitmapDrawable) contactInfo.photo).getBitmap();
+        }
+
+        return null;
+    }
+
+    /**
      * Returns the appropriate icon res Id to display based on the call for which
      * we want to display information.
      */