Merge "Use CONTACT_ID field for the quick contact badge."
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d99577c..56bf4e5 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -64,6 +64,7 @@
         <!-- A list of recent calls -->
         <activity android:name=".activities.CallLogActivity"
             android:label="@string/recentCallsIconLabel"
+            android:theme="@style/DialtactsTheme"
         >
             <intent-filter>
                 <action android:name="com.android.phone.action.RECENT_CALLS" />
@@ -611,6 +612,16 @@
                     android:resource="@xml/social_widget_info" />
         </receiver>
 
+        <receiver android:name=".calllog.NewVoicemailReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.NEW_VOICEMAIL" />
+                <data
+                    android:scheme="content"
+                    android:host="com.android.voicemail"
+                />
+            </intent-filter>
+        </receiver>
+
         <activity
             android:name=".socialwidget.SocialWidgetConfigureActivity"
             android:theme="@android:style/Theme.Translucent.NoTitleBar" >
diff --git a/res/drawable-hdpi/ic_call_log_list_action_call.png b/res/drawable-hdpi/ic_call_log_list_action_call.png
new file mode 100644
index 0000000..f47ada7
--- /dev/null
+++ b/res/drawable-hdpi/ic_call_log_list_action_call.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_log_list_action_play.png b/res/drawable-hdpi/ic_call_log_list_action_play.png
new file mode 100644
index 0000000..db19cdd
--- /dev/null
+++ b/res/drawable-hdpi/ic_call_log_list_action_play.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_log_list_incoming_call.png b/res/drawable-hdpi/ic_call_log_list_incoming_call.png
old mode 100755
new mode 100644
index 15b5e44..c98cd39
--- a/res/drawable-hdpi/ic_call_log_list_incoming_call.png
+++ b/res/drawable-hdpi/ic_call_log_list_incoming_call.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_log_list_missed_call.png b/res/drawable-hdpi/ic_call_log_list_missed_call.png
old mode 100755
new mode 100644
index 8dcb279..c52efbc
--- a/res/drawable-hdpi/ic_call_log_list_missed_call.png
+++ b/res/drawable-hdpi/ic_call_log_list_missed_call.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_log_list_outgoing_call.png b/res/drawable-hdpi/ic_call_log_list_outgoing_call.png
old mode 100755
new mode 100644
index 160c707..4204953
--- a/res/drawable-hdpi/ic_call_log_list_outgoing_call.png
+++ b/res/drawable-hdpi/ic_call_log_list_outgoing_call.png
Binary files differ
diff --git a/res/drawable-hdpi/ic_call_log_list_voicemail.png b/res/drawable-hdpi/ic_call_log_list_voicemail.png
new file mode 100644
index 0000000..d2ee44f
--- /dev/null
+++ b/res/drawable-hdpi/ic_call_log_list_voicemail.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_log_list_action_call.png b/res/drawable-mdpi/ic_call_log_list_action_call.png
new file mode 100644
index 0000000..f47ada7
--- /dev/null
+++ b/res/drawable-mdpi/ic_call_log_list_action_call.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_log_list_action_play.png b/res/drawable-mdpi/ic_call_log_list_action_play.png
new file mode 100644
index 0000000..db19cdd
--- /dev/null
+++ b/res/drawable-mdpi/ic_call_log_list_action_play.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_log_list_incoming_call.png b/res/drawable-mdpi/ic_call_log_list_incoming_call.png
index c7eec52..c98cd39 100644
--- a/res/drawable-mdpi/ic_call_log_list_incoming_call.png
+++ b/res/drawable-mdpi/ic_call_log_list_incoming_call.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_log_list_missed_call.png b/res/drawable-mdpi/ic_call_log_list_missed_call.png
index 1907a62..c52efbc 100644
--- a/res/drawable-mdpi/ic_call_log_list_missed_call.png
+++ b/res/drawable-mdpi/ic_call_log_list_missed_call.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_log_list_outgoing_call.png b/res/drawable-mdpi/ic_call_log_list_outgoing_call.png
index 041d086..4204953 100644
--- a/res/drawable-mdpi/ic_call_log_list_outgoing_call.png
+++ b/res/drawable-mdpi/ic_call_log_list_outgoing_call.png
Binary files differ
diff --git a/res/drawable-mdpi/ic_call_log_list_voicemail.png b/res/drawable-mdpi/ic_call_log_list_voicemail.png
new file mode 100644
index 0000000..d2ee44f
--- /dev/null
+++ b/res/drawable-mdpi/ic_call_log_list_voicemail.png
Binary files differ
diff --git a/res/layout/call_detail.xml b/res/layout/call_detail.xml
index a47452c..83c8f1b 100644
--- a/res/layout/call_detail.xml
+++ b/res/layout/call_detail.xml
@@ -82,7 +82,7 @@
                 android:layout_height="?attr/call_detail_action_icon_size"
                 android:layout_alignParentRight="true"
                 android:gravity="center_vertical"
-                android:src="@android:drawable/sym_action_call"
+                android:src="@drawable/ic_call_log_list_action_call"
                 android:scaleType="center"
             />
             <View
diff --git a/res/layout/call_log_contact_photo.xml b/res/layout/call_log_contact_photo.xml
index c47c23c..178c45b 100644
--- a/res/layout/call_log_contact_photo.xml
+++ b/res/layout/call_log_contact_photo.xml
@@ -2,9 +2,10 @@
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
     <QuickContactBadge
         android:id="@+id/contact_photo"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignParentTop="true"
+        android:layout_width="?attr/call_log_contact_photo_size"
+        android:layout_height="?attr/call_log_contact_photo_size"
+        android:layout_margin="?attr/call_log_contact_photo_margin"
         android:layout_alignParentLeft="true"
+        android:layout_gravity="center_vertical"
     />
 </merge>
diff --git a/res/layout/call_log_list_child_item.xml b/res/layout/call_log_list_child_item.xml
index d2c22a9..ba7e4ec 100644
--- a/res/layout/call_log_list_child_item.xml
+++ b/res/layout/call_log_list_child_item.xml
@@ -16,7 +16,7 @@
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:layout_height="?attr/call_log_list_item_height"
 >
 
     <include layout="@layout/call_log_action_call"/>
diff --git a/res/layout/call_log_list_group_item.xml b/res/layout/call_log_list_group_item.xml
index e7ab026..352d7ec 100644
--- a/res/layout/call_log_list_group_item.xml
+++ b/res/layout/call_log_list_group_item.xml
@@ -16,7 +16,7 @@
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:layout_height="?attr/call_log_list_item_height"
 >
 
     <include layout="@layout/call_log_contact_photo"/>
diff --git a/res/layout/call_log_list_item.xml b/res/layout/call_log_list_item.xml
index e07479d..7e82b40 100644
--- a/res/layout/call_log_list_item.xml
+++ b/res/layout/call_log_list_item.xml
@@ -16,7 +16,7 @@
 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="?android:attr/listPreferredItemHeight"
+    android:layout_height="?attr/call_log_list_item_height"
 >
 
     <include layout="@layout/call_log_contact_photo"/>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0064a8b..a2baaa2 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1021,7 +1021,7 @@
     <!-- Button caption that allows the user to clear the defaults  (like primary email) of one
     contact. [CHAR LIMIT=15] -->
     <string name="quickcontact_clear_defaults_button">Clear</string>
-    
+
     <!-- The menu item to open the list of accounts -->
     <string name="menu_accounts">Accounts</string>
 
@@ -1427,7 +1427,7 @@
         contact aggregation suggestion in Contact editor. [CHAR LIMIT=128]-->
     <string name="aggregation_suggestion_edit_dialog_title">Edit selected contacts</string>
 
-    <!-- The message in a confirmation dialog shown when the user selects a 
+    <!-- The message in a confirmation dialog shown when the user selects a
         contact aggregation suggestion in Contact editor. [CHAR LIMIT=512]-->
     <string name="aggregation_suggestion_edit_dialog_message">Switch to editing
         the selected contact? Information you entered so far will be copied.</string>
@@ -1542,7 +1542,7 @@
     </plurals>
 
     <!-- Confirmation message of the dialog that allows deletion of a contact group  [CHAR LIMIT=256] -->
-    <string name="delete_group_dialog_message">Are you sure you want to delete the group 
+    <string name="delete_group_dialog_message">Are you sure you want to delete the group
       \'<xliff:g id="group_label" example="Friends">%1$s</xliff:g>\'?
       (Contacts themselves will not be deleted.)
     </string>
@@ -1558,7 +1558,7 @@
 
     <!-- Toast shown when text is copied to the clipboard [CHAR LIMIT=64] -->
     <string name="toast_text_copied">Text copied</string>
-    
+
     <!-- Title of the alert dialog when the user hits the Cancel button in the editor [CHAR LIMIT=64] -->
     <string name="cancel_confirmation_dialog_title">Discard changes</string>
 
@@ -1585,4 +1585,7 @@
 
     <!-- The title of the activity that edits an existing group [CHAR LIMIT=NONE] -->
     <string name="editGroup_title_edit">Edit group</string>
+
+    <!-- Title of the notification of new voicemail. -->
+    <string name="notification_voicemail_title">New voicemail</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2abfaa2..e880673 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -36,6 +36,10 @@
         <item name="list_item_header_text_color">?color/section_header_text_color</item>
         <item name="list_item_header_text_size">14sp</item>
         <item name="contact_filter_popup_width">320dip</item>
+        <!-- CallLogActivity -->
+        <item name="call_log_contact_photo_size">50dip</item>
+        <item name="call_log_contact_photo_margin">5dip</item>
+        <item name="call_log_list_item_height">60dip</item>
     </style>
 
     <style name="CallDetailActivityTheme" parent="android:Theme.Holo.Light">
@@ -54,6 +58,7 @@
         <item name="call_detail_action_icon_size">50dip</item>
         <item name="call_detail_secondary_background_color">#FFFFFF</item>
     </style>
+
     <style name="ContactDetailActivityTheme" parent="android:Theme.Holo.Light">
         <item name="android:windowContentOverlay">@null</item>
     </style>
@@ -138,6 +143,12 @@
         <attr name="call_detail_secondary_background_color" format="color" />
     </declare-styleable>
 
+    <declare-styleable name="CallLogActivity">
+        <attr name="call_log_contact_photo_size" format="dimension" />
+        <attr name="call_log_contact_photo_margin" format="dimension" />
+        <attr name="call_log_list_item_height" format="dimension" />
+    </declare-styleable>
+
     <style name="PeopleTheme" parent="android:Theme.Holo.Light">
         <item name="list_item_height">?android:attr/listPreferredItemHeight</item>
         <item name="activated_background">@drawable/list_item_activated_background</item>
diff --git a/src/com/android/contacts/calllog/CallLogFragment.java b/src/com/android/contacts/calllog/CallLogFragment.java
index bfac0f3..8635716 100644
--- a/src/com/android/contacts/calllog/CallLogFragment.java
+++ b/src/com/android/contacts/calllog/CallLogFragment.java
@@ -250,10 +250,17 @@
                     R.drawable.ic_call_log_list_outgoing_call);
             Drawable drawableMissed = getResources().getDrawable(
                     R.drawable.ic_call_log_list_missed_call);
+            Drawable drawableVoicemail = getResources().getDrawable(
+                    R.drawable.ic_call_log_list_voicemail);
+            Drawable drawableCall = getResources().getDrawable(
+                    R.drawable.ic_call_log_list_action_call);
+            Drawable drawablePlay = getResources().getDrawable(
+                    R.drawable.ic_call_log_list_action_play);
 
             mContactPhotoManager = ContactPhotoManager.getInstance(getActivity());
             mCallLogViewsHelper = new CallLogListItemHelper(getResources(), mVoiceMailNumber,
-                    drawableIncoming, drawableOutgoing, drawableMissed);
+                    drawableIncoming, drawableOutgoing, drawableMissed, drawableVoicemail,
+                    drawableCall, drawablePlay);
         }
 
         /**
@@ -629,7 +636,7 @@
             views.numberView = (TextView) view.findViewById(R.id.number);
             views.dateView = (TextView) view.findViewById(R.id.date);
             views.iconView = (ImageView) view.findViewById(R.id.call_type_icon);
-            views.callView = view.findViewById(R.id.call_icon);
+            views.callView = (ImageView) view.findViewById(R.id.call_icon);
             if (views.callView != null) {
                 views.callView.setOnClickListener(this);
             }
diff --git a/src/com/android/contacts/calllog/CallLogListItemHelper.java b/src/com/android/contacts/calllog/CallLogListItemHelper.java
index 7697872..56399c0 100644
--- a/src/com/android/contacts/calllog/CallLogListItemHelper.java
+++ b/src/com/android/contacts/calllog/CallLogListItemHelper.java
@@ -43,6 +43,12 @@
     private final Drawable mDrawableOutgoing;
     /** Icon for missed calls. */
     private final Drawable mDrawableMissed;
+    /** Icon for voicemails. */
+    private final Drawable mDrawableVoicemail;
+    /** Icon for the call action. */
+    private final Drawable mDrawableCall;
+    /** Icon for the play action. */
+    private final Drawable mDrawablePlay;
 
     /**
      * Creates a new helper instance.
@@ -54,12 +60,16 @@
      * @param drawableMissed the icon drawn besides a missed call entry
      */
     public CallLogListItemHelper(Resources resources, String voicemailNumber,
-            Drawable drawableIncoming, Drawable drawableOutgoing, Drawable drawableMissed) {
+            Drawable drawableIncoming, Drawable drawableOutgoing, Drawable drawableMissed,
+            Drawable drawableVoicemail, Drawable drawableCall, Drawable drawablePlay) {
         mResources = resources;
         mVoiceMailNumber = voicemailNumber;
         mDrawableIncoming = drawableIncoming;
         mDrawableOutgoing = drawableOutgoing;
         mDrawableMissed = drawableMissed;
+        mDrawableVoicemail = drawableVoicemail;
+        mDrawableCall = drawableCall;
+        mDrawablePlay = drawablePlay;
     }
 
     /**
@@ -178,20 +188,48 @@
      */
     public void setCallType(final CallLogListItemViews views, int type) {
         if (views.iconView != null) {
-            // Set the icon
+            // Set the call type icon.
+            Drawable drawable = null;
             switch (type) {
                 case Calls.INCOMING_TYPE:
-                    views.iconView.setImageDrawable(mDrawableIncoming);
+                    drawable = mDrawableIncoming;
                     break;
 
                 case Calls.OUTGOING_TYPE:
-                    views.iconView.setImageDrawable(mDrawableOutgoing);
+                    drawable = mDrawableOutgoing;
                     break;
 
                 case Calls.MISSED_TYPE:
-                    views.iconView.setImageDrawable(mDrawableMissed);
+                    drawable = mDrawableMissed;
                     break;
+
+                case Calls.VOICEMAIL_TYPE:
+                    drawable = mDrawableVoicemail;
+                    break;
+
+                default:
+                    throw new IllegalArgumentException("invalid call type: " + type);
             }
+            views.iconView.setImageDrawable(drawable);
+        }
+        if (views.callView != null) {
+            // Set the action icon.
+            Drawable drawable = null;
+            switch (type) {
+                case Calls.INCOMING_TYPE:
+                case Calls.OUTGOING_TYPE:
+                case Calls.MISSED_TYPE:
+                    drawable = mDrawableCall;
+                    break;
+
+                case Calls.VOICEMAIL_TYPE:
+                    drawable = mDrawablePlay;
+                    break;
+
+                default:
+                    throw new IllegalArgumentException("invalid call type: " + type);
+            }
+            views.callView.setImageDrawable(drawable);
         }
     }
 }
diff --git a/src/com/android/contacts/calllog/CallLogListItemViews.java b/src/com/android/contacts/calllog/CallLogListItemViews.java
index dde7cf6..7264c96 100644
--- a/src/com/android/contacts/calllog/CallLogListItemViews.java
+++ b/src/com/android/contacts/calllog/CallLogListItemViews.java
@@ -16,7 +16,6 @@
 
 package com.android.contacts.calllog;
 
-import android.view.View;
 import android.widget.ImageView;
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
@@ -40,7 +39,7 @@
     /** The icon indicating the type of call. */
     public ImageView iconView;
     /** The icon used to place a call to the contact. Only present for non-group entries. */
-    public View callView;
+    public ImageView callView;
     /** The icon used to expand and collapse an entry. Only present for group entries. */
     public ImageView groupIndicator;
     /**
diff --git a/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
new file mode 100644
index 0000000..b2ee5ce
--- /dev/null
+++ b/src/com/android/contacts/calllog/DefaultVoicemailNotifier.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.contacts.calllog;
+
+import com.android.contacts.R;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.VoicemailContract;
+
+/**
+ * Implementation of {@link VoicemailNotifier} that shows a notification in the status bar.
+ */
+public class DefaultVoicemailNotifier implements VoicemailNotifier {
+    /** The tag used to identify notifications from this class. */
+    private static final String NOTIFICATION_TAG = "DefaultVoicemailNotifier";
+    /** The identifier of the notification of new voicemails. */
+    private static final int NOTIFICATION_ID = 1;
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    private final VoicemailNumberQuery mVoicemailNumberQuery;
+    private final NameLookupQuery mNameLookupQuery;
+
+    public DefaultVoicemailNotifier(Context context, NotificationManager notificationManager,
+            VoicemailNumberQuery voicemailNumberQuery, NameLookupQuery nameLookupQuery) {
+        mContext = context;
+        mNotificationManager = notificationManager;
+        mVoicemailNumberQuery = voicemailNumberQuery;
+        mNameLookupQuery = nameLookupQuery;
+    }
+
+    @Override
+    public void notifyNewVoicemail(Uri uri) {
+        // Lookup the number that left the voicemail.
+        String number = mVoicemailNumberQuery.query(uri);
+        // Lookup the name of the contact associated with this number.
+        String name = mNameLookupQuery.query(number);
+        // Show the name of the contact if available, falling back to using the number if not.
+        String displayName = name == null ? number : name;
+        Notification notification = new Notification.Builder(mContext)
+                .setSmallIcon(android.R.drawable.stat_notify_voicemail)
+                .setContentTitle(mContext.getString(R.string.notification_voicemail_title))
+                .setContentText(displayName)
+                .setDefaults(Notification.DEFAULT_ALL)
+                .setAutoCancel(true)
+                .getNotification();
+
+        // Open the voicemail when clicking on the notification.
+        notification.contentIntent =
+                PendingIntent.getActivity(mContext, 0, new Intent(Intent.ACTION_VIEW, uri), 0);
+
+        mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, notification);
+    }
+
+    @Override
+    public void clearNewVoicemailNotification() {
+        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
+    }
+
+    /** Allows determining the number associated with a given voicemail. */
+    public interface VoicemailNumberQuery {
+        /**
+         * Returns the number associated with a voicemail URI, or null if the URI does not actually
+         * correspond to a voicemail.
+         *
+         * @throws IllegalArgumentException if the given {@code uri} is not a voicemail URI.
+         */
+        public String query(Uri uri);
+    }
+
+    /** Create a new instance of {@link VoicemailNumberQuery}. */
+    public static VoicemailNumberQuery createVoicemailNumberQuery(ContentResolver contentResolver) {
+        return new DefaultVoicemailNumberQuery(contentResolver);
+    }
+
+    /**
+     * Default implementation of {@link VoicemailNumberQuery} that looks up the number in the
+     * voicemail content provider.
+     */
+    private static final class DefaultVoicemailNumberQuery implements VoicemailNumberQuery {
+        private static final String[] PROJECTION = { VoicemailContract.Voicemails.NUMBER };
+        private static final int NUMBER_COLUMN_INDEX = 0;
+
+        private final ContentResolver mContentResolver;
+
+        private DefaultVoicemailNumberQuery(ContentResolver contentResolver) {
+            mContentResolver = contentResolver;
+        }
+
+        @Override
+        public String query(Uri uri) {
+            validateVoicemailUri(uri);
+            Cursor cursor = null;
+            try {
+                cursor = mContentResolver.query(uri, PROJECTION, null, null, null);
+                if (cursor.getCount() != 1) return null;
+                if (!cursor.moveToFirst()) return null;
+                return cursor.getString(NUMBER_COLUMN_INDEX);
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+
+        /**
+         * Makes sure that the given URI is a valid voicemail URI.
+         *
+         * @throws IllegalArgumentException if the URI is not valid
+         */
+        private void validateVoicemailUri(Uri uri) {
+            // Cannot be null.
+            if (uri == null) throw new IllegalArgumentException("invalid voicemail URI");
+            // Must have the right schema.
+            if (!VoicemailContract.Voicemails.CONTENT_URI.getScheme().equals(uri.getScheme())) {
+                throw new IllegalArgumentException("invalid voicemail URI");
+            }
+            // Must have the right authority.
+            if (!VoicemailContract.AUTHORITY.equals(uri.getAuthority())) {
+                throw new IllegalArgumentException("invalid voicemail URI");
+            }
+            // Must have a valid path.
+            if (uri.getPath() == null) {
+                throw new IllegalArgumentException("invalid voicemail URI");
+            }
+            // Must be a path within the voicemails table.
+            if (!uri.getPath().startsWith(VoicemailContract.Voicemails.CONTENT_URI.getPath())) {
+                throw new IllegalArgumentException("invalid voicemail URI");
+            }
+        }
+    }
+
+    /** Allows determining the name associated with a given phone number. */
+    public interface NameLookupQuery {
+        /**
+         * Returns the name associated with the given number in the contacts database, or null if
+         * the number does not correspond to any of the contacts.
+         * <p>
+         * If there are multiple contacts with the same phone number, it will return the name of one
+         * of the matching contacts.
+         */
+        public String query(String number);
+    }
+
+    /** Create a new instance of {@link NameLookupQuery}. */
+    public static NameLookupQuery createNameLookupQuery(ContentResolver contentResolver) {
+        return new DefaultNameLookupQuery(contentResolver);
+    }
+
+    private static final class DefaultNameLookupQuery implements NameLookupQuery {
+        private static final String[] PROJECTION = { PhoneLookup.DISPLAY_NAME };
+        private static final int DISPLAY_NAME_COLUMN_INDEX = 0;
+
+        private final ContentResolver mContentResolver;
+
+        private DefaultNameLookupQuery(ContentResolver contentResolver) {
+            mContentResolver = contentResolver;
+        }
+
+        @Override
+        public String query(String number) {
+            Cursor cursor = null;
+            try {
+                cursor = mContentResolver.query(
+                        Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)),
+                        PROJECTION, null, null, null);
+                if (!cursor.moveToFirst()) return null;
+                return cursor.getString(DISPLAY_NAME_COLUMN_INDEX);
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/contacts/calllog/NewVoicemailReceiver.java b/src/com/android/contacts/calllog/NewVoicemailReceiver.java
new file mode 100644
index 0000000..0b9f2fa
--- /dev/null
+++ b/src/com/android/contacts/calllog/NewVoicemailReceiver.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.contacts.calllog;
+
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * Receiver for new voicemail notifications.
+ * <p>
+ * Delegates to a {@link VoicemailNotifier}.
+ */
+public class NewVoicemailReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        getVoicemailNotifier(context).notifyNewVoicemail(intent.getData());
+    }
+
+    private VoicemailNotifier getVoicemailNotifier(Context context) {
+        NotificationManager notificationManager =
+                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+        return new DefaultVoicemailNotifier(context, notificationManager,
+                DefaultVoicemailNotifier.createVoicemailNumberQuery(context.getContentResolver()),
+                DefaultVoicemailNotifier.createNameLookupQuery(context.getContentResolver()));
+    }
+}
diff --git a/src/com/android/contacts/calllog/VoicemailNotifier.java b/src/com/android/contacts/calllog/VoicemailNotifier.java
new file mode 100644
index 0000000..ba82f21
--- /dev/null
+++ b/src/com/android/contacts/calllog/VoicemailNotifier.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.contacts.calllog;
+
+import android.net.Uri;
+
+/**
+ * Handles notifications for voicemails.
+ */
+public interface VoicemailNotifier {
+      /**
+       * Notifies the user of a new voicemail.
+       *
+       * @param newVoicemailUri URI of the new voicemail record just inserted
+       * @throws IllegalArgumentException if the URI does not correspond to a voicemail
+       */
+      public void notifyNewVoicemail(Uri newVoicemailUri);
+
+      /** Clears the new voicemail notification. */
+      public void clearNewVoicemailNotification();
+}
diff --git a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
index 958ece5..a33b710 100644
--- a/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
+++ b/tests/src/com/android/contacts/calllog/CallLogListItemHelperTest.java
@@ -45,6 +45,12 @@
     private static final Drawable TEST_OUTGOING_DRAWABLE = new ColorDrawable(Color.BLUE);
     /** A drawable to be used for missed calls. */
     private static final Drawable TEST_MISSED_DRAWABLE = new ColorDrawable(Color.RED);
+    /** A drawable to be used for voicemails. */
+    private static final Drawable TEST_VOICEMAIL_DRAWABLE = new ColorDrawable(Color.RED);
+    /** A drawable to be used for the call action. */
+    private static final Drawable TEST_CALL_DRAWABLE = new ColorDrawable(Color.RED);
+    /** A drawable to be used for the play action. */
+    private static final Drawable TEST_PLAY_DRAWABLE = new ColorDrawable(Color.RED);
 
     /** The object under test. */
     private CallLogListItemHelper mHelper;
@@ -57,12 +63,13 @@
         super.setUp();
         Context context = getContext();
         mHelper = new CallLogListItemHelper(context.getResources(), TEST_VOICEMAIL_NUMBER,
-                TEST_INCOMING_DRAWABLE, TEST_OUTGOING_DRAWABLE, TEST_MISSED_DRAWABLE);
+                TEST_INCOMING_DRAWABLE, TEST_OUTGOING_DRAWABLE, TEST_MISSED_DRAWABLE,
+                TEST_VOICEMAIL_DRAWABLE, TEST_CALL_DRAWABLE, TEST_PLAY_DRAWABLE);
         mViews = new CallLogListItemViews();
         // Only set the views that are needed in the tests.
         mViews.iconView = new ImageView(context);
         mViews.dateView = new TextView(context);
-        mViews.callView = new View(context);
+        mViews.callView = new ImageView(context);
         mViews.line1View = new TextView(context);
         mViews.labelView = new TextView(context);
         mViews.numberView = new TextView(context);