Merge changes I45978ea4,Ia85b1008,I9e68c561,I9255dd3c

* changes:
  BEGIN_PUBLIC Automated rollback of changelist 172683494
  BEGIN_PUBLIC Automated rollback of changelist 172956409
  Download and play voicemails from server when not locally available.
  Updated writing of PhoneLookup columns in annotated call log.
diff --git a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java
index f689571..513c8aa 100644
--- a/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java
+++ b/java/com/android/dialer/app/calllog/CallLogGroupBuilder.java
@@ -21,6 +21,7 @@
 import android.os.Build.VERSION_CODES;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
+import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.text.format.Time;
 import com.android.contacts.common.util.DateUtils;
@@ -193,7 +194,7 @@
     if (PhoneNumberHelper.isUriNumber(number1) || PhoneNumberHelper.isUriNumber(number2)) {
       return compareSipAddresses(number1, number2);
     } else {
-      return PhoneNumberHelper.compare(number1, number2);
+      return PhoneNumberUtils.compare(number1, number2);
     }
   }
 
diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
index c9c053c..7071ab5 100644
--- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
+++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
@@ -42,10 +42,9 @@
     String TIMESTAMP = "timestamp";
 
     /**
-     * Copied from {@link android.provider.CallLog.Calls#CACHED_NAME}.
-     *
-     * <p>This is exactly how it should appear to the user. If the user's locale or name display
-     * preferences change, this column should be rewritten.
+     * The name (which may be a person's name or business name, but not a number) formatted exactly
+     * as it should appear to the user. If the user's locale or name display preferences change,
+     * this column should be rewritten.
      *
      * <p>Type: TEXT
      */
@@ -61,28 +60,29 @@
     String NUMBER = "number";
 
     /**
-     * Copied from {@link android.provider.CallLog.Calls#CACHED_FORMATTED_NUMBER}.
+     * The number formatted as it should be displayed to the user. Note that it may not always be
+     * displayed, for example if the number has a corresponding person or business name.
      *
      * <p>Type: TEXT
      */
     String FORMATTED_NUMBER = "formatted_number";
 
     /**
-     * Copied from {@link android.provider.CallLog.Calls#CACHED_PHOTO_URI}.
+     * A photo URI for the contact to display in the call log list view.
      *
      * <p>TYPE: TEXT
      */
     String PHOTO_URI = "photo_uri";
 
     /**
-     * Copied from {@link android.provider.CallLog.Calls#CACHED_PHOTO_ID}.
+     * A photo ID (from the contacts provider) for the contact to display in the call log list view.
      *
      * <p>Type: INTEGER (long)
      */
     String PHOTO_ID = "photo_id";
 
     /**
-     * Copied from {@link android.provider.CallLog.Calls#CACHED_LOOKUP_URI}.
+     * The contacts provider lookup URI for the contact associated with the call.
      *
      * <p>TYPE: TEXT
      */
diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
index fa7d3be..93e8414 100644
--- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
@@ -32,6 +32,7 @@
 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
 import com.android.dialer.calllog.datasources.CallLogDataSource;
 import com.android.dialer.calllog.datasources.CallLogMutations;
+import com.android.dialer.calllog.datasources.util.RowCombiner;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
 import com.android.dialer.common.concurrent.Annotations.LightweightExecutor;
@@ -260,8 +261,13 @@
   @WorkerThread
   @Override
   public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) {
-    // TODO(zachh): Implementation.
-    return new ContentValues();
+    return new RowCombiner(individualRowsSortedByTimestampDesc)
+        .useMostRecentString(AnnotatedCallLog.NAME)
+        .useMostRecentString(AnnotatedCallLog.NUMBER_TYPE_LABEL)
+        .useMostRecentString(AnnotatedCallLog.PHOTO_URI)
+        .useMostRecentLong(AnnotatedCallLog.PHOTO_ID)
+        .useMostRecentString(AnnotatedCallLog.LOOKUP_URI)
+        .combine();
   }
 
   @MainThread
@@ -434,7 +440,7 @@
       PhoneLookupInfo phoneLookupInfo = existingInfo.get(id);
       // Existing info might be missing if data was cleared or for other reasons.
       if (phoneLookupInfo != null) {
-        contentValues.put(AnnotatedCallLog.NAME, selectName(phoneLookupInfo));
+        updateContentValues(contentValues, phoneLookupInfo);
       }
     }
   }
@@ -474,17 +480,17 @@
          * mutations from PhoneLookupHistory; in this case "John" would be copied during
          * populateInserts() and there wouldn't be further updates needed here.
          */
-        contentValuesToInsert.put(AnnotatedCallLog.NAME, selectName(phoneLookupInfo));
+        updateContentValues(contentValuesToInsert, phoneLookupInfo);
         continue;
       }
       ContentValues contentValuesToUpdate = mutations.getUpdates().get(id);
       if (contentValuesToUpdate != null) {
-        contentValuesToUpdate.put(AnnotatedCallLog.NAME, selectName(phoneLookupInfo));
+        updateContentValues(contentValuesToUpdate, phoneLookupInfo);
         continue;
       }
       // Else this row is not already scheduled for insert or update and we need to schedule it.
       ContentValues contentValues = new ContentValues();
-      contentValues.put(AnnotatedCallLog.NAME, selectName(phoneLookupInfo));
+      updateContentValues(contentValues, phoneLookupInfo);
       mutations.getUpdates().put(id, contentValues);
     }
   }
@@ -525,8 +531,17 @@
     return normalizedNumbersToDelete;
   }
 
-  private static String selectName(PhoneLookupInfo phoneLookupInfo) {
-    return PhoneLookupSelector.selectName(phoneLookupInfo);
+  private static void updateContentValues(
+      ContentValues contentValues, PhoneLookupInfo phoneLookupInfo) {
+    contentValues.put(AnnotatedCallLog.NAME, PhoneLookupSelector.selectName(phoneLookupInfo));
+    contentValues.put(
+        AnnotatedCallLog.PHOTO_URI, PhoneLookupSelector.selectPhotoUri(phoneLookupInfo));
+    contentValues.put(
+        AnnotatedCallLog.PHOTO_ID, PhoneLookupSelector.selectPhotoId(phoneLookupInfo));
+    contentValues.put(
+        AnnotatedCallLog.LOOKUP_URI, PhoneLookupSelector.selectLookupUri(phoneLookupInfo));
+    contentValues.put(
+        AnnotatedCallLog.NUMBER_TYPE_LABEL, PhoneLookupSelector.selectNumberLabel(phoneLookupInfo));
   }
 
   private static Uri numberUri(String number) {
diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
index 91db915..0ed1859 100644
--- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
@@ -27,7 +27,6 @@
 import android.os.Handler;
 import android.provider.CallLog;
 import android.provider.CallLog.Calls;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.support.annotation.ColorInt;
 import android.support.annotation.MainThread;
 import android.support.annotation.Nullable;
@@ -35,6 +34,7 @@
 import android.support.annotation.WorkerThread;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import com.android.dialer.DialerPhoneNumber;
@@ -174,23 +174,16 @@
 
   @Override
   public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) {
-    // TODO(zachh): Complete implementation.
-
     assertNoVoicemailsInRows(individualRowsSortedByTimestampDesc);
 
     return new RowCombiner(individualRowsSortedByTimestampDesc)
         .useMostRecentLong(AnnotatedCallLog.TIMESTAMP)
         .useMostRecentLong(AnnotatedCallLog.NEW)
-        .useMostRecentString(AnnotatedCallLog.NUMBER_TYPE_LABEL)
-        .useMostRecentString(AnnotatedCallLog.NAME)
         // Two different DialerPhoneNumbers could be combined if they are different but considered
         // to be an "exact match" by libphonenumber; in this case we arbitrarily select the most
         // recent one.
         .useMostRecentBlob(AnnotatedCallLog.NUMBER)
         .useMostRecentString(AnnotatedCallLog.FORMATTED_NUMBER)
-        .useMostRecentString(AnnotatedCallLog.PHOTO_URI)
-        .useMostRecentLong(AnnotatedCallLog.PHOTO_ID)
-        .useMostRecentString(AnnotatedCallLog.LOOKUP_URI)
         .useMostRecentString(AnnotatedCallLog.GEOCODED_LOCATION)
         .useSingleValueString(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME)
         .useSingleValueString(AnnotatedCallLog.PHONE_ACCOUNT_ID)
@@ -233,13 +226,6 @@
                   Calls.NUMBER,
                   Calls.TYPE,
                   Calls.COUNTRY_ISO,
-                  Calls.CACHED_NAME,
-                  Calls.CACHED_FORMATTED_NUMBER,
-                  Calls.CACHED_PHOTO_URI,
-                  Calls.CACHED_PHOTO_ID,
-                  Calls.CACHED_LOOKUP_URI,
-                  Calls.CACHED_NUMBER_TYPE,
-                  Calls.CACHED_NUMBER_LABEL,
                   Calls.DURATION,
                   Calls.DATA_USAGE,
                   Calls.TRANSCRIPTION,
@@ -272,14 +258,6 @@
         int numberColumn = cursor.getColumnIndexOrThrow(Calls.NUMBER);
         int typeColumn = cursor.getColumnIndexOrThrow(Calls.TYPE);
         int countryIsoColumn = cursor.getColumnIndexOrThrow(Calls.COUNTRY_ISO);
-        int cachedNameColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NAME);
-        int cachedFormattedNumberColumn =
-            cursor.getColumnIndexOrThrow(Calls.CACHED_FORMATTED_NUMBER);
-        int cachedPhotoUriColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_PHOTO_URI);
-        int cachedPhotoIdColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_PHOTO_ID);
-        int cachedLookupUriColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_LOOKUP_URI);
-        int cachedNumberTypeColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NUMBER_TYPE);
-        int cachedNumberLabelColumn = cursor.getColumnIndexOrThrow(Calls.CACHED_NUMBER_LABEL);
         int durationsColumn = cursor.getColumnIndexOrThrow(Calls.DURATION);
         int dataUsageColumn = cursor.getColumnIndexOrThrow(Calls.DATA_USAGE);
         int transcriptionColumn = cursor.getColumnIndexOrThrow(Calls.TRANSCRIPTION);
@@ -301,13 +279,6 @@
           String numberAsStr = cursor.getString(numberColumn);
           long type = cursor.getInt(typeColumn);
           String countryIso = cursor.getString(countryIsoColumn);
-          String cachedName = cursor.getString(cachedNameColumn);
-          String formattedNumber = cursor.getString(cachedFormattedNumberColumn);
-          String cachedPhotoUri = cursor.getString(cachedPhotoUriColumn);
-          long cachedPhotoId = cursor.getLong(cachedPhotoIdColumn);
-          String cachedLookupUri = cursor.getString(cachedLookupUriColumn);
-          int cachedNumberType = cursor.getInt(cachedNumberTypeColumn);
-          String cachedNumberLabel = cursor.getString(cachedNumberLabelColumn);
           int duration = cursor.getInt(durationsColumn);
           int dataUsage = cursor.getInt(dataUsageColumn);
           String transcription = cursor.getString(transcriptionColumn);
@@ -323,31 +294,19 @@
           contentValues.put(AnnotatedCallLog.TIMESTAMP, date);
 
           if (!TextUtils.isEmpty(numberAsStr)) {
-            byte[] numberAsProtoBytes =
-                dialerPhoneNumberUtil.parse(numberAsStr, countryIso).toByteArray();
+            DialerPhoneNumber dialerPhoneNumber =
+                dialerPhoneNumberUtil.parse(numberAsStr, countryIso);
+
+            contentValues.put(AnnotatedCallLog.NUMBER, dialerPhoneNumber.toByteArray());
+            contentValues.put(
+                AnnotatedCallLog.FORMATTED_NUMBER,
+                PhoneNumberUtils.formatNumber(numberAsStr, countryIso));
             // TODO(zachh): Need to handle post-dial digits; different on N and M.
-            contentValues.put(AnnotatedCallLog.NUMBER, numberAsProtoBytes);
           } else {
             contentValues.put(
                 AnnotatedCallLog.NUMBER, DialerPhoneNumber.getDefaultInstance().toByteArray());
           }
-
           contentValues.put(AnnotatedCallLog.CALL_TYPE, type);
-          contentValues.put(AnnotatedCallLog.NAME, cachedName);
-          // TODO(zachh): Format the number using DialerPhoneNumberUtil here.
-          contentValues.put(AnnotatedCallLog.FORMATTED_NUMBER, formattedNumber);
-          contentValues.put(AnnotatedCallLog.PHOTO_URI, cachedPhotoUri);
-          contentValues.put(AnnotatedCallLog.PHOTO_ID, cachedPhotoId);
-          contentValues.put(AnnotatedCallLog.LOOKUP_URI, cachedLookupUri);
-
-          // Phone.getTypeLabel returns "Custom" if given (0, null) which is not of any use. Just
-          // omit setting the label if there's no information for it.
-          if (cachedNumberType != 0 || cachedNumberLabel != null) {
-            contentValues.put(
-                AnnotatedCallLog.NUMBER_TYPE_LABEL,
-                Phone.getTypeLabel(appContext.getResources(), cachedNumberType, cachedNumberLabel)
-                    .toString());
-          }
           contentValues.put(AnnotatedCallLog.IS_READ, isRead);
           contentValues.put(AnnotatedCallLog.NEW, isNew);
           contentValues.put(AnnotatedCallLog.GEOCODED_LOCATION, geocodedLocation);
diff --git a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java b/java/com/android/dialer/phonelookup/PhoneLookupSelector.java
index a746ea4..af8b849 100644
--- a/java/com/android/dialer/phonelookup/PhoneLookupSelector.java
+++ b/java/com/android/dialer/phonelookup/PhoneLookupSelector.java
@@ -16,6 +16,8 @@
 package com.android.dialer.phonelookup;
 
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo;
 
 /**
  * Prioritizes information from a {@link PhoneLookupInfo}.
@@ -37,11 +39,80 @@
    */
   @NonNull
   public static String selectName(PhoneLookupInfo phoneLookupInfo) {
-    if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) {
-      // Arbitrarily select the first contact's name. In the future, it may make sense to join the
-      // names such as "Mom, Dad" in the case that multiple contacts share the same number.
-      return phoneLookupInfo.getCp2Info().getCp2ContactInfo(0).getName();
+    Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
+    if (firstLocalContact != null) {
+      String name = firstLocalContact.getName();
+      if (!name.isEmpty()) {
+        return firstLocalContact.getName();
+      }
     }
     return "";
   }
+
+  /** Select the photo URI associated with this number. */
+  @NonNull
+  public static String selectPhotoUri(PhoneLookupInfo phoneLookupInfo) {
+    Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
+    if (firstLocalContact != null) {
+      String photoUri = firstLocalContact.getPhotoUri();
+      if (!photoUri.isEmpty()) {
+        return photoUri;
+      }
+    }
+    return "";
+  }
+
+  /** Select the photo ID associated with this number, or 0 if there is none. */
+  public static long selectPhotoId(PhoneLookupInfo phoneLookupInfo) {
+    Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
+    if (firstLocalContact != null) {
+      long photoId = firstLocalContact.getPhotoId();
+      if (photoId > 0) {
+        return photoId;
+      }
+    }
+    return 0;
+  }
+
+  /** Select the lookup URI associated with this number. */
+  @NonNull
+  public static String selectLookupUri(PhoneLookupInfo phoneLookupInfo) {
+    Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
+    if (firstLocalContact != null) {
+      String lookupUri = firstLocalContact.getLookupUri();
+      if (!lookupUri.isEmpty()) {
+        return lookupUri;
+      }
+    }
+    return "";
+  }
+
+  /**
+   * A localized string representing the number type such as "Home" or "Mobile", or a custom value
+   * set by the user.
+   */
+  @NonNull
+  public static String selectNumberLabel(PhoneLookupInfo phoneLookupInfo) {
+    Cp2ContactInfo firstLocalContact = firstLocalContact(phoneLookupInfo);
+    if (firstLocalContact != null) {
+      String label = firstLocalContact.getLabel();
+      if (!label.isEmpty()) {
+        return label;
+      }
+    }
+    return "";
+  }
+
+  /**
+   * Arbitrarily select the first contact. In the future, it may make sense to display contact
+   * information from all contacts with the same number (for example show the name as "Mom, Dad" or
+   * show a synthesized photo containing photos of both "Mom" and "Dad").
+   */
+  @Nullable
+  private static Cp2ContactInfo firstLocalContact(PhoneLookupInfo phoneLookupInfo) {
+    if (phoneLookupInfo.getCp2Info().getCp2ContactInfoCount() > 0) {
+      return phoneLookupInfo.getCp2Info().getCp2ContactInfo(0);
+    }
+    return null;
+  }
 }
diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java
index 03e05b5..3829a8d 100644
--- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java
@@ -58,17 +58,21 @@
         Phone.DISPLAY_NAME_PRIMARY, // 0
         Phone.PHOTO_THUMBNAIL_URI, // 1
         Phone.PHOTO_ID, // 2
-        Phone.LABEL, // 3
-        Phone.NORMALIZED_NUMBER, // 4
-        Phone.CONTACT_ID, // 5
+        Phone.TYPE, // 3
+        Phone.LABEL, // 4
+        Phone.NORMALIZED_NUMBER, // 5
+        Phone.CONTACT_ID, // 6
+        Phone.LOOKUP_KEY // 7
       };
 
   private static final int CP2_INFO_NAME_INDEX = 0;
   private static final int CP2_INFO_PHOTO_URI_INDEX = 1;
   private static final int CP2_INFO_PHOTO_ID_INDEX = 2;
-  private static final int CP2_INFO_LABEL_INDEX = 3;
-  private static final int CP2_INFO_NUMBER_INDEX = 4;
-  private static final int CP2_INFO_CONTACT_ID_INDEX = 5;
+  private static final int CP2_INFO_TYPE_INDEX = 3;
+  private static final int CP2_INFO_LABEL_INDEX = 4;
+  private static final int CP2_INFO_NUMBER_INDEX = 5;
+  private static final int CP2_INFO_CONTACT_ID_INDEX = 6;
+  private static final int CP2_INFO_LOOKUP_KEY_INDEX = 7;
 
   private final Context appContext;
   private final SharedPreferences sharedPreferences;
@@ -89,6 +93,7 @@
   @Override
   public ListenableFuture<PhoneLookupInfo> lookup(@NonNull Call call) {
     // TODO(zachh): Implementation.
+    // TODO(zachh): Note: Should write empty Cp2Info even when no contact found.
     return backgroundExecutorService.submit(PhoneLookupInfo::getDefaultInstance);
   }
 
@@ -207,7 +212,7 @@
     // For each DialerPhoneNumber that was associated with a contact or added to a contact,
     // build a map of those DialerPhoneNumbers to a set Cp2ContactInfos, where each Cp2ContactInfo
     // represents a contact.
-    ImmutableMap<DialerPhoneNumber, Set<Cp2ContactInfo>> updatedContacts =
+    Map<DialerPhoneNumber, Set<Cp2ContactInfo>> updatedContacts =
         buildMapForUpdatedOrAddedContacts(existingInfoMap, lastModified, deletedPhoneNumbers);
 
     // Start build a new map of updated info. This will replace existing info.
@@ -216,23 +221,26 @@
 
     // For each DialerPhoneNumber in existing info...
     for (Entry<DialerPhoneNumber, PhoneLookupInfo> entry : existingInfoMap.entrySet()) {
+      DialerPhoneNumber dialerPhoneNumber = entry.getKey();
+      PhoneLookupInfo existingInfo = entry.getValue();
+
       // Build off the existing info
-      PhoneLookupInfo.Builder infoBuilder = PhoneLookupInfo.newBuilder(entry.getValue());
+      PhoneLookupInfo.Builder infoBuilder = PhoneLookupInfo.newBuilder(existingInfo);
 
       // If the contact was updated, replace the Cp2ContactInfo list
-      if (updatedContacts.containsKey(entry.getKey())) {
+      if (updatedContacts.containsKey(dialerPhoneNumber)) {
         infoBuilder.setCp2Info(
-            Cp2Info.newBuilder().addAllCp2ContactInfo(updatedContacts.get(entry.getKey())));
+            Cp2Info.newBuilder().addAllCp2ContactInfo(updatedContacts.get(dialerPhoneNumber)));
 
         // If it was deleted and not added to a new contact, replace the Cp2ContactInfo list with
         // the default instance of Cp2ContactInfo
-      } else if (deletedPhoneNumbers.contains(entry.getKey())) {
+      } else if (deletedPhoneNumbers.contains(dialerPhoneNumber)) {
         infoBuilder.setCp2Info(
             Cp2Info.newBuilder().addCp2ContactInfo(Cp2ContactInfo.getDefaultInstance()));
       }
 
       // If the DialerPhoneNumber didn't change, add the unchanged existing info.
-      newInfoMapBuilder.put(entry.getKey(), infoBuilder.build());
+      newInfoMapBuilder.put(dialerPhoneNumber, infoBuilder.build());
     }
     return newInfoMapBuilder.build();
   }
@@ -260,7 +268,7 @@
    * @return Map of {@link DialerPhoneNumber} to {@link PhoneLookupInfo} with updated {@link
    *     Cp2ContactInfo}.
    */
-  private ImmutableMap<DialerPhoneNumber, Set<Cp2ContactInfo>> buildMapForUpdatedOrAddedContacts(
+  private Map<DialerPhoneNumber, Set<Cp2ContactInfo>> buildMapForUpdatedOrAddedContacts(
       ImmutableMap<DialerPhoneNumber, PhoneLookupInfo> existingInfoMap,
       long lastModified,
       Set<DialerPhoneNumber> deletedPhoneNumbers) {
@@ -270,16 +278,24 @@
 
     Set<Long> contactIds = new ArraySet<>();
     for (Entry<DialerPhoneNumber, PhoneLookupInfo> entry : existingInfoMap.entrySet()) {
+      DialerPhoneNumber dialerPhoneNumber = entry.getKey();
+      PhoneLookupInfo existingInfo = entry.getValue();
+
       // If the number was deleted, we need to check if it was added to a new contact.
-      if (deletedPhoneNumbers.contains(entry.getKey())) {
-        updatedNumbers.add(entry.getKey());
+      if (deletedPhoneNumbers.contains(dialerPhoneNumber)) {
+        updatedNumbers.add(dialerPhoneNumber);
         continue;
       }
 
+      // Note: Methods in this class must always set at least one Cp2Info, setting it to
+      // getDefaultInstance() if there is no information for the contact.
+      Assert.checkState(
+          existingInfo.getCp2Info().getCp2ContactInfoCount() > 0, "existing info has no cp2 infos");
+
       // For each Cp2ContactInfo for each existing DialerPhoneNumber...
       // Store the contact id if it exist, else automatically add the DialerPhoneNumber to our
       // set of DialerPhoneNumbers we want to update.
-      for (Cp2ContactInfo cp2ContactInfo : entry.getValue().getCp2Info().getCp2ContactInfoList()) {
+      for (Cp2ContactInfo cp2ContactInfo : existingInfo.getCp2Info().getCp2ContactInfoList()) {
         if (Objects.equals(cp2ContactInfo, Cp2ContactInfo.getDefaultInstance())) {
           // If the number doesn't have any Cp2ContactInfo set to it, for various reasons, we need
           // to look up the number to check if any exists.
@@ -287,7 +303,7 @@
           //  - An existing contact that wasn't in the call log is now in the call log.
           //  - A number was in the call log before but has now been added to a contact.
           //  - A number is in the call log, but isn't associated with any contact.
-          updatedNumbers.add(entry.getKey());
+          updatedNumbers.add(dialerPhoneNumber);
         } else {
           contactIds.add(cp2ContactInfo.getContactId());
         }
@@ -322,7 +338,7 @@
         // Map each dialer phone number to it's new cp2 info
         Set<DialerPhoneNumber> phoneNumbers =
             getDialerPhoneNumbers(updatedNumbers, cursor.getString(CP2_INFO_NUMBER_INDEX));
-        Cp2ContactInfo info = buildCp2ContactInfoFromUpdatedContactsCursor(cursor);
+        Cp2ContactInfo info = buildCp2ContactInfoFromUpdatedContactsCursor(appContext, cursor);
         for (DialerPhoneNumber phoneNumber : phoneNumbers) {
           if (map.containsKey(phoneNumber)) {
             map.get(phoneNumber).add(info);
@@ -334,7 +350,7 @@
         }
       }
     }
-    return ImmutableMap.copyOf(map);
+    return map;
   }
 
   /**
@@ -358,10 +374,15 @@
    * @param cursor with projection {@link #CP2_INFO_PROJECTION}.
    * @return new {@link Cp2ContactInfo} based on current row of {@code cursor}.
    */
-  private static Cp2ContactInfo buildCp2ContactInfoFromUpdatedContactsCursor(Cursor cursor) {
+  private static Cp2ContactInfo buildCp2ContactInfoFromUpdatedContactsCursor(
+      Context appContext, Cursor cursor) {
     String displayName = cursor.getString(CP2_INFO_NAME_INDEX);
     String photoUri = cursor.getString(CP2_INFO_PHOTO_URI_INDEX);
+    int photoId = cursor.getInt(CP2_INFO_PHOTO_ID_INDEX);
+    int type = cursor.getInt(CP2_INFO_TYPE_INDEX);
     String label = cursor.getString(CP2_INFO_LABEL_INDEX);
+    int contactId = cursor.getInt(CP2_INFO_CONTACT_ID_INDEX);
+    String lookupKey = cursor.getString(CP2_INFO_LOOKUP_KEY_INDEX);
 
     Cp2ContactInfo.Builder infoBuilder = Cp2ContactInfo.newBuilder();
     if (!TextUtils.isEmpty(displayName)) {
@@ -370,11 +391,19 @@
     if (!TextUtils.isEmpty(photoUri)) {
       infoBuilder.setPhotoUri(photoUri);
     }
-    if (!TextUtils.isEmpty(label)) {
-      infoBuilder.setLabel(label);
+    if (photoId > 0) {
+      infoBuilder.setPhotoId(photoId);
     }
-    infoBuilder.setPhotoId(cursor.getLong(CP2_INFO_PHOTO_ID_INDEX));
-    infoBuilder.setContactId(cursor.getLong(CP2_INFO_CONTACT_ID_INDEX));
+
+    // Phone.getTypeLabel returns "Custom" if given (0, null) which is not of any use. Just
+    // omit setting the label if there's no information for it.
+    if (type != 0 || !TextUtils.isEmpty(label)) {
+      infoBuilder.setLabel(Phone.getTypeLabel(appContext.getResources(), type, label).toString());
+    }
+    infoBuilder.setContactId(contactId);
+    if (!TextUtils.isEmpty(lookupKey)) {
+      infoBuilder.setLookupUri(Contacts.getLookupUri(contactId, lookupKey).toString());
+    }
     return infoBuilder.build();
   }
 
diff --git a/java/com/android/dialer/phonelookup/phone_lookup_info.proto b/java/com/android/dialer/phonelookup/phone_lookup_info.proto
index 93dd01e..36596c1 100644
--- a/java/com/android/dialer/phonelookup/phone_lookup_info.proto
+++ b/java/com/android/dialer/phonelookup/phone_lookup_info.proto
@@ -34,9 +34,13 @@
 
       // android.provider.ContactsContract.CommonDataKinds.Phone.CONTACT_ID
       optional fixed64 contact_id = 5;
+
+      // android.provider.ContactsContract.CONTENT_LOOKUP_URI
+      optional string lookup_uri = 6;
     }
     // Repeated because one phone number can be associated with multiple CP2
-    // contacts.
+    // contacts. If no contact is found for a number, this will contain exactly
+    // one element which is the default Cp2ContactInfo instance.
     repeated Cp2ContactInfo cp2_contact_info = 1;
   }
   optional Cp2Info cp2_info = 1;
diff --git a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
index cdc06de..e32ace5 100644
--- a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
+++ b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
@@ -29,7 +29,6 @@
 import android.text.BidiFormatter;
 import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
-import android.util.SparseIntArray;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.compat.CompatUtils;
@@ -38,7 +37,6 @@
 import com.android.dialer.telecom.TelecomUtil;
 import java.util.Arrays;
 import java.util.HashSet;
-import java.util.Objects;
 import java.util.Set;
 
 public class PhoneNumberHelper {
@@ -47,71 +45,6 @@
   private static final Set<String> LEGACY_UNKNOWN_NUMBERS =
       new HashSet<>(Arrays.asList("-1", "-2", "-3"));
 
-  /** The phone keypad letter mapping (see ITU E.161 or ISO/IEC 9995-8.) */
-  private static final SparseIntArray KEYPAD_MAP = new SparseIntArray();
-
-  static {
-    KEYPAD_MAP.put('a', '2');
-    KEYPAD_MAP.put('b', '2');
-    KEYPAD_MAP.put('c', '2');
-    KEYPAD_MAP.put('A', '2');
-    KEYPAD_MAP.put('B', '2');
-    KEYPAD_MAP.put('C', '2');
-
-    KEYPAD_MAP.put('d', '3');
-    KEYPAD_MAP.put('e', '3');
-    KEYPAD_MAP.put('f', '3');
-    KEYPAD_MAP.put('D', '3');
-    KEYPAD_MAP.put('E', '3');
-    KEYPAD_MAP.put('F', '3');
-
-    KEYPAD_MAP.put('g', '4');
-    KEYPAD_MAP.put('h', '4');
-    KEYPAD_MAP.put('i', '4');
-    KEYPAD_MAP.put('G', '4');
-    KEYPAD_MAP.put('H', '4');
-    KEYPAD_MAP.put('I', '4');
-
-    KEYPAD_MAP.put('j', '5');
-    KEYPAD_MAP.put('k', '5');
-    KEYPAD_MAP.put('l', '5');
-    KEYPAD_MAP.put('J', '5');
-    KEYPAD_MAP.put('K', '5');
-    KEYPAD_MAP.put('L', '5');
-
-    KEYPAD_MAP.put('m', '6');
-    KEYPAD_MAP.put('n', '6');
-    KEYPAD_MAP.put('o', '6');
-    KEYPAD_MAP.put('M', '6');
-    KEYPAD_MAP.put('N', '6');
-    KEYPAD_MAP.put('O', '6');
-
-    KEYPAD_MAP.put('p', '7');
-    KEYPAD_MAP.put('q', '7');
-    KEYPAD_MAP.put('r', '7');
-    KEYPAD_MAP.put('s', '7');
-    KEYPAD_MAP.put('P', '7');
-    KEYPAD_MAP.put('Q', '7');
-    KEYPAD_MAP.put('R', '7');
-    KEYPAD_MAP.put('S', '7');
-
-    KEYPAD_MAP.put('t', '8');
-    KEYPAD_MAP.put('u', '8');
-    KEYPAD_MAP.put('v', '8');
-    KEYPAD_MAP.put('T', '8');
-    KEYPAD_MAP.put('U', '8');
-    KEYPAD_MAP.put('V', '8');
-
-    KEYPAD_MAP.put('w', '9');
-    KEYPAD_MAP.put('x', '9');
-    KEYPAD_MAP.put('y', '9');
-    KEYPAD_MAP.put('z', '9');
-    KEYPAD_MAP.put('W', '9');
-    KEYPAD_MAP.put('X', '9');
-    KEYPAD_MAP.put('Y', '9');
-    KEYPAD_MAP.put('Z', '9');
-  }
-
   /** Returns true if it is possible to place a call to the given number. */
   public static boolean canPlaceCallsTo(CharSequence number, int presentation) {
     return presentation == CallLog.Calls.PRESENTATION_ALLOWED
@@ -120,76 +53,7 @@
   }
 
   /**
-   * Compare two phone numbers, return true if they're identical enough for caller ID purposes. This
-   * is an enhanced version of {@link PhoneNumberUtils#compare(String, String)}.
-   *
-   * <p>The two phone numbers are considered "identical enough" if
-   *
-   * <ul>
-   *   <li>their corresponding raw numbers are both global phone numbers (i.e., they can be accepted
-   *       by {@link PhoneNumberUtils#isGlobalPhoneNumber(String)}) and {@link
-   *       PhoneNumberUtils#compare(String, String)} deems them as "identical enough"; OR
-   *   <li>neither of the raw numbers is a global phone number and they are identical.
-   * </ul>
-   *
-   * See {@link #convertAndStrip(String)} for how a raw number is obtained.
-   */
-  public static boolean compare(@Nullable String number1, @Nullable String number2) {
-    if (number1 == null || number2 == null) {
-      return Objects.equals(number1, number2);
-    }
-
-    String rawNumber1 = convertAndStrip(number1);
-    String rawNumber2 = convertAndStrip(number2);
-
-    boolean isGlobalPhoneNumber1 = PhoneNumberUtils.isGlobalPhoneNumber(rawNumber1);
-    boolean isGlobalPhoneNumber2 = PhoneNumberUtils.isGlobalPhoneNumber(rawNumber2);
-
-    if (isGlobalPhoneNumber1 && isGlobalPhoneNumber2) {
-      return PhoneNumberUtils.compare(rawNumber1, rawNumber2);
-    }
-    if (!isGlobalPhoneNumber1 && !isGlobalPhoneNumber2) {
-      return rawNumber1.equals(rawNumber2);
-    }
-    return false;
-  }
-
-  /**
-   * Translating any alphabetic letters ([A-Za-z]) in the given phone number into the equivalent
-   * numeric digits and then removing all separators. The caller should ensure the number passed to
-   * this method is not null.
-   */
-  private static String convertAndStrip(@NonNull String number) {
-    int len = number.length();
-    if (len == 0) {
-      return number;
-    }
-
-    StringBuilder ret = new StringBuilder(len);
-    for (int i = 0; i < len; i++) {
-      char c = number.charAt(i);
-
-      // If the char isn't in KEYPAD_MAP, leave it alone for now.
-      c = (char) KEYPAD_MAP.get(c, c);
-
-      // Append the char to the result if it's a digit or non-separator.
-      int digit = Character.digit(c, 10);
-      if (digit != -1) {
-        ret.append(digit);
-      } else if (PhoneNumberUtils.isNonSeparator(c)) {
-        ret.append(c);
-      }
-    }
-
-    return ret.toString();
-  }
-
-  /**
-   * Find the cursor pointing to the row in which a number is identical enough to the number in a
-   * contact lookup URI.
-   *
-   * <p>See the description of {@link PhoneNumberHelper#compare(String, String)} for the definition
-   * of "identical enough".
+   * Find the cursor pointing to a number that matches the number in a contact lookup URI.
    *
    * <p>When determining whether two phone numbers are identical enough for caller ID purposes, the
    * Contacts Provider uses {@link PhoneNumberUtils#compare(String, String)}, which ignores special
@@ -197,17 +61,25 @@
    * by the Contacts Provider to have multiple rows even when the URI asks for a specific number.
    *
    * <p>For example, suppose the user has two contacts whose numbers are "#123" and "123",
-   * respectively. When the URI asks for number "123", both numbers will be returned. Therefore,
-   * {@link PhoneNumberHelper#compare(String, String)}, which is an enhanced version of {@link
-   * PhoneNumberUtils#compare(String, String)}, is employed to find a match.
+   * respectively. When the URI asks for number "123", both numbers will be returned. Therefore, the
+   * following strategy is employed to find a match.
+   *
+   * <p>If the cursor points to a global phone number (i.e., a number that can be accepted by {@link
+   * PhoneNumberUtils#isGlobalPhoneNumber(String)}) and the lookup number in the URI is a PARTIAL
+   * match, return the cursor.
+   *
+   * <p>If the cursor points to a number that is not a global phone number, return the cursor iff
+   * the lookup number in the URI is an EXACT match.
+   *
+   * <p>Return null in all other circumstances.
    *
    * @param cursor A cursor returned by the Contacts Provider.
    * @param columnIndexForNumber The index of the column where phone numbers are stored. It is the
    *     caller's responsibility to pass the correct column index.
    * @param contactLookupUri A URI used to retrieve a contact via the Contacts Provider. It is the
    *     caller's responsibility to ensure the URI is one that asks for a specific phone number.
-   * @return The cursor pointing to the row in which the number is considered a match by the
-   *     description above or null if no such cursor can be found.
+   * @return The cursor considered as a match by the description above or null if no such cursor can
+   *     be found.
    */
   public static Cursor getCursorMatchForContactLookupUri(
       Cursor cursor, int columnIndexForNumber, Uri contactLookupUri) {
@@ -227,10 +99,22 @@
       return null;
     }
 
+    boolean isMatchFound;
     do {
-      String existingContactNumber = cursor.getString(columnIndexForNumber);
+      // All undialable characters should be converted/removed before comparing the lookup number
+      // and the existing contact number.
+      String rawExistingContactNumber =
+          PhoneNumberUtils.stripSeparators(
+              PhoneNumberUtils.convertKeypadLettersToDigits(
+                  cursor.getString(columnIndexForNumber)));
+      String rawQueryNumber =
+          PhoneNumberUtils.stripSeparators(
+              PhoneNumberUtils.convertKeypadLettersToDigits(lookupNumber));
 
-      boolean isMatchFound = compare(existingContactNumber, lookupNumber);
+      isMatchFound =
+          PhoneNumberUtils.isGlobalPhoneNumber(rawExistingContactNumber)
+              ? rawExistingContactNumber.contains(rawQueryNumber)
+              : rawExistingContactNumber.equals(rawQueryNumber);
 
       if (isMatchFound) {
         return cursor;
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
index 955c7da..671a39a 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
@@ -56,7 +56,7 @@
     int VOICEMAIL_ENTRY = 2;
   }
 
-  private final Cursor cursor;
+  private Cursor cursor;
   private final Clock clock;
 
   /** {@link Integer#MAX_VALUE} when the "Today" header should not be displayed. */
@@ -129,6 +129,11 @@
     mediaPlayer.setOnErrorListener(onErrorListener);
   }
 
+  public void updateCursor(Cursor updatedCursor) {
+    this.cursor = updatedCursor;
+    notifyDataSetChanged();
+  }
+
   @Override
   public ViewHolder onCreateViewHolder(ViewGroup viewGroup, @RowType int viewType) {
     LogUtil.enterBlock("NewVoicemailAdapter.onCreateViewHolder");
@@ -714,7 +719,7 @@
       // returned when currentlyExpandedViewHolderId = -1 (viewholder was collapsed)
       LogUtil.i(
           "NewVoicemailAdapter.getCurrentlyExpandedViewHolder",
-          "no view holder found in newVoicemailViewHolderArrayMap size:%d for %d",
+          "no view holder found in hashmap size:%d for %d",
           newVoicemailViewHolderArrayMap.size(),
           currentlyExpandedViewHolderId);
       // TODO(uabdullah): a bug Remove logging, temporarily here for debugging.
@@ -749,4 +754,34 @@
     }
     return RowType.VOICEMAIL_ENTRY;
   }
+
+  /**
+   * This will be called once the voicemail that was attempted to be played (and was not locally
+   * available) was downloaded from the server. However it is possible that by the time the download
+   * was completed, the view holder was collapsed. In that case we shouldn't play the voicemail.
+   */
+  public void checkAndPlayVoicemail() {
+    LogUtil.i(
+        "NewVoicemailAdapter.checkAndPlayVoicemail",
+        "expandedViewHolder:%d, inViewHolderSet:%b, MPRequestToDownload:%s",
+        currentlyExpandedViewHolderId,
+        isCurrentlyExpandedViewHolderInViewHolderSet(),
+        String.valueOf(mediaPlayer.getVoicemailRequestedToDownload()));
+
+    NewVoicemailViewHolder currentlyExpandedViewHolder = getCurrentlyExpandedViewHolder();
+    if (currentlyExpandedViewHolderId != -1
+        && isCurrentlyExpandedViewHolderInViewHolderSet()
+        && currentlyExpandedViewHolder != null
+        // Used to differentiate underlying table changes from voicemail downloads and other changes
+        // (e.g delete)
+        && mediaPlayer.getVoicemailRequestedToDownload() != null
+        && (mediaPlayer
+            .getVoicemailRequestedToDownload()
+            .equals(currentlyExpandedViewHolder.getViewHolderVoicemailUri()))) {
+      currentlyExpandedViewHolder.clickPlayButtonOfViewHoldersMediaPlayerView(
+          currentlyExpandedViewHolder);
+    } else {
+      LogUtil.i("NewVoicemailAdapter.checkAndPlayVoicemail", "not playing downloaded voicemail");
+    }
+  }
 }
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
index 9a89dbe..82e704d 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
@@ -38,6 +38,7 @@
   @Override
   public View onCreateView(
       LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+    LogUtil.enterBlock("NewVoicemailFragment.onCreateView");
     View view = inflater.inflate(R.layout.new_voicemail_call_log_fragment, container, false);
     recyclerView = view.findViewById(R.id.new_voicemail_call_log_recycler_view);
     getLoaderManager().restartLoader(0, null, this);
@@ -52,11 +53,24 @@
 
   @Override
   public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
-    LogUtil.i("NewVoicemailFragment.onCreateLoader", "cursor size is %d", data.getCount());
-    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-    recyclerView.setAdapter(
-        new NewVoicemailAdapter(
-            data, System::currentTimeMillis, getActivity().getFragmentManager()));
+    LogUtil.i("NewVoicemailFragment.onLoadFinished", "cursor size is %d", data.getCount());
+    if (recyclerView.getAdapter() == null) {
+      recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+      recyclerView.setAdapter(
+          new NewVoicemailAdapter(
+              data, System::currentTimeMillis, getActivity().getFragmentManager()));
+    } else {
+      // This would only be called in cases such as when voicemail has been fetched from the server
+      // or a changed occurred in the annotated table changed (e.g deletes). To check if the change
+      // was due to a voicemail download,
+      // NewVoicemailAdapter.mediaPlayer.getVoicemailRequestedToDownload() is called.
+      LogUtil.i(
+          "NewVoicemailFragment.onLoadFinished",
+          "adapter: %s was not null, checking and playing the voicemail if conditions met",
+          recyclerView.getAdapter());
+      ((NewVoicemailAdapter) recyclerView.getAdapter()).updateCursor(data);
+      ((NewVoicemailAdapter) recyclerView.getAdapter()).checkAndPlayVoicemail();
+    }
   }
 
   @Override
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
index 2d59b24..48062a8 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayer.java
@@ -23,6 +23,7 @@
 import android.media.MediaPlayer.OnPreparedListener;
 import android.net.Uri;
 import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import java.io.IOException;
@@ -38,6 +39,7 @@
   private OnPreparedListener newVoicemailMediaPlayerOnPreparedListener;
   private OnCompletionListener newVoicemailMediaPlayerOnCompletionListener;
   private Uri pausedUri;
+  @Nullable private Uri voicemailRequestedToDownload;
 
   public NewVoicemailMediaPlayer(@NonNull MediaPlayer player) {
     mediaPlayer = Assert.isNotNull(player);
@@ -94,6 +96,7 @@
     mediaPlayer.start();
     voicemailLastPlayedOrPlayingUri = startPlayingVoicemailUri;
     pausedUri = null;
+    voicemailRequestedToDownload = null;
   }
 
   public void reset() {
@@ -102,6 +105,7 @@
     voicemailLastPlayedOrPlayingUri = null;
     voicemailUriLastPreparedOrPreparingToPlay = null;
     pausedUri = null;
+    voicemailRequestedToDownload = null;
   }
 
   public void pauseMediaPlayer(Uri voicemailUri) {
@@ -134,6 +138,11 @@
     newVoicemailMediaPlayerOnCompletionListener = onCompletionListener;
   }
 
+  public void setVoicemailRequestedToDownload(@NonNull Uri uri) {
+    Assert.isNotNull(uri, "cannot download a null voicemail");
+    voicemailRequestedToDownload = uri;
+  }
+
   /**
    * Note: In some cases it's possible mediaPlayer.isPlaying() can return true, but
    * mediaPlayer.getCurrentPosition() can be greater than mediaPlayer.getDuration(), after which
@@ -182,6 +191,17 @@
     return mediaPlayer.getDuration();
   }
 
+  /**
+   * A null v/s non-value is important for the {@link NewVoicemailAdapter} to differentiate between
+   * a underlying table change due to a voicemail being downloaded or something else (e.g delete).
+   *
+   * @return if there was a Uri that was requested to be downloaded from the server, null otherwise.
+   */
+  @Nullable
+  public Uri getVoicemailRequestedToDownload() {
+    return voicemailRequestedToDownload;
+  }
+
   public boolean isPaused() {
     return pausedUri != null;
   }
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
index 77dd9cc..3f2de7d 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailMediaPlayerView.java
@@ -17,12 +17,15 @@
 package com.android.dialer.voicemail.listui;
 
 import android.app.FragmentManager;
+import android.content.ContentValues;
 import android.content.Context;
+import android.content.Intent;
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Voicemails;
 import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
+import android.support.annotation.Nullable;
 import android.support.v4.util.Pair;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
@@ -45,7 +48,7 @@
 /**
  * The view of the media player that is visible when a {@link NewVoicemailViewHolder} is expanded.
  */
-public class NewVoicemailMediaPlayerView extends LinearLayout {
+public final class NewVoicemailMediaPlayerView extends LinearLayout {
 
   private ImageButton playButton;
   private ImageButton pauseButton;
@@ -55,6 +58,7 @@
   private TextView currentSeekBarPosition;
   private SeekBar seekBarView;
   private TextView totalDurationView;
+  private TextView voicemailLoadingStatusView;
   private Uri voicemailUri;
   private FragmentManager fragmentManager;
   private NewVoicemailViewHolder newVoicemailViewHolder;
@@ -86,6 +90,7 @@
     phoneButton = findViewById(R.id.phoneButton);
     deleteButton = findViewById(R.id.deleteButton);
     totalDurationView = findViewById(R.id.playback_seek_total_duration);
+    voicemailLoadingStatusView = findViewById(R.id.playback_state_text);
   }
 
   private void setupListenersForMediaPlayerButtons() {
@@ -100,6 +105,7 @@
   public void reset() {
     LogUtil.i("NewVoicemailMediaPlayer.reset", "the uri for this is " + voicemailUri);
     voicemailUri = null;
+    voicemailLoadingStatusView.setVisibility(GONE);
   }
 
   /**
@@ -261,6 +267,16 @@
         }
       };
 
+  /**
+   * Attempts to imitate clicking the play button. This is useful for when we the user attempted to
+   * play a voicemail, but the media player didn't start playing till the voicemail was downloaded
+   * from the server. However once we have the voicemail downloaded, we want to start playing, so as
+   * to make it seem like that this is a continuation of the users initial play button click.
+   */
+  public final void clickPlayButton() {
+    playButtonListener.onClick(null);
+  }
+
   private final View.OnClickListener playButtonListener =
       new View.OnClickListener() {
         @Override
@@ -268,7 +284,8 @@
           LogUtil.i(
               "NewVoicemailMediaPlayer.playButtonListener",
               "play button for voicemailUri: %s",
-              voicemailUri.toString());
+              String.valueOf(voicemailUri));
+
           if (mediaPlayer.getLastPausedVoicemailUri() != null
               && mediaPlayer
                   .getLastPausedVoicemailUri()
@@ -350,12 +367,85 @@
                 + getContext());
       }
     } else {
-      // TODO(a bug): Add logic for downloading voicemail content from the server.
       LogUtil.i(
           "NewVoicemailMediaPlayer.prepareVoicemailForMediaPlayer", "need to download content");
+      // Important to set since it allows the adapter to differentiate when to start playing the
+      // voicemail, after it's downloaded.
+      mediaPlayer.setVoicemailRequestedToDownload(uri);
+      voicemailLoadingStatusView.setVisibility(VISIBLE);
+      sendIntentToDownloadVoicemail(uri);
     }
   }
 
+  private void sendIntentToDownloadVoicemail(Uri uri) {
+    LogUtil.i("NewVoicemailMediaPlayer.sendIntentToDownloadVoicemail", "uri:%s", uri.toString());
+    // Send voicemail fetch request.
+    Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, uri);
+
+    Worker<Pair<Context, Uri>, Pair<String, Uri>> getVoicemailSourcePackage =
+        this::queryVoicemailSourcePackage;
+    SuccessListener<Pair<String, Uri>> checkVoicemailHasSourcePackageCallBack = this::sendIntent;
+
+    DialerExecutorComponent.get(getContext())
+        .dialerExecutorFactory()
+        .createUiTaskBuilder(fragmentManager, "lookup_voicemail_pkg", getVoicemailSourcePackage)
+        .onSuccess(checkVoicemailHasSourcePackageCallBack)
+        .build()
+        .executeSerial(new Pair<>(getContext(), voicemailUri));
+  }
+
+  private void sendIntent(Pair<String, Uri> booleanUriPair) {
+    String sourcePackage = booleanUriPair.first;
+    Uri uri = booleanUriPair.second;
+    LogUtil.i(
+        "NewVoicemailMediaPlayer.sendIntent",
+        "srcPkg:%s, uri:%%s",
+        sourcePackage,
+        String.valueOf(uri));
+    Intent intent = new Intent(VoicemailContract.ACTION_FETCH_VOICEMAIL, uri);
+    intent.setPackage(sourcePackage);
+    voicemailLoadingStatusView.setVisibility(VISIBLE);
+    getContext().sendBroadcast(intent);
+  }
+
+  @Nullable
+  private Pair<String, Uri> queryVoicemailSourcePackage(Pair<Context, Uri> contextUriPair) {
+    LogUtil.enterBlock("NewVoicemailMediaPlayer.queryVoicemailSourcePackage");
+    Context context = contextUriPair.first;
+    Uri uri = contextUriPair.second;
+    String sourcePackage;
+    try (Cursor cursor =
+        context
+            .getContentResolver()
+            .query(uri, new String[] {Voicemails.SOURCE_PACKAGE}, null, null, null)) {
+
+      if (!hasContent(cursor)) {
+        LogUtil.e(
+            "NewVoicemailMediaPlayer.queryVoicemailSourcePackage",
+            "uri: %s does not return a SOURCE_PACKAGE",
+            uri.toString());
+        sourcePackage = null;
+      } else {
+        sourcePackage = cursor.getString(0);
+        LogUtil.i(
+            "NewVoicemailMediaPlayer.queryVoicemailSourcePackage",
+            "uri: %s has a SOURCE_PACKAGE: %s",
+            uri.toString(),
+            sourcePackage);
+      }
+      LogUtil.i(
+          "NewVoicemailMediaPlayer.queryVoicemailSourcePackage",
+          "uri: %s has a SOURCE_PACKAGE: %s",
+          uri.toString(),
+          sourcePackage);
+    }
+    return new Pair<>(sourcePackage, uri);
+  }
+
+  private boolean hasContent(Cursor cursor) {
+    return cursor != null && cursor.moveToFirst();
+  }
+
   private final View.OnClickListener speakerButtonListener =
       new View.OnClickListener() {
         @Override
@@ -386,6 +476,21 @@
               "NewVoicemailMediaPlayer.deleteButtonListener",
               "delete voicemailUri %s",
               voicemailUri.toString());
+          // TODO(uabdullah): This will be removed in cl/177404259. It only sets the has_content to
+          // 0, to allow the annotated call log to change, and refresh the fragment. This is used to
+          // demo and test the downloading of voicemails from the server.
+          ContentValues contentValues = new ContentValues();
+          contentValues.put("has_content", 0);
+
+          try {
+            getContext().getContentResolver().update(voicemailUri, contentValues, "type = 4", null);
+          } catch (Exception e) {
+            LogUtil.i(
+                "NewVoicemailMediaPlayer.deleteButtonListener",
+                "update has content of voicemailUri %s caused an error: %s",
+                voicemailUri.toString(),
+                e.toString());
+          }
         }
       };
 
@@ -401,6 +506,7 @@
 
     playButton.setVisibility(GONE);
     pauseButton.setVisibility(VISIBLE);
+    voicemailLoadingStatusView.setVisibility(GONE);
 
     Assert.checkArgument(
         mp.equals(mediaPlayer), "there should only be one instance of a media player");
@@ -510,9 +616,4 @@
     }
     return String.format(Locale.US, "%02d:%02d", minutes, seconds);
   }
-
-  @VisibleForTesting(otherwise = VisibleForTesting.NONE)
-  void setFragmentManager(FragmentManager fragmentManager) {
-    this.fragmentManager = fragmentManager;
-  }
 }
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
index d5b17a1..0725465 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
@@ -187,6 +187,8 @@
         String.valueOf(viewHolderVoicemailUri));
     transcriptionTextView.setMaxLines(1);
     isViewHolderExpanded = false;
+
+    mediaPlayerView.reset();
     mediaPlayerView.setVisibility(GONE);
   }
 
@@ -333,6 +335,23 @@
     return viewHolderVoicemailUri;
   }
 
+  public void clickPlayButtonOfViewHoldersMediaPlayerView(
+      NewVoicemailViewHolder expandedViewHolder) {
+    LogUtil.i(
+        "NewVoicemailViewHolder.clickPlayButtonOfViewHoldersMediaPlayerView",
+        "expandedViewHolderID:%d",
+        expandedViewHolder.getViewHolderId());
+
+    Assert.checkArgument(
+        mediaPlayerView.getVoicemailUri().equals(expandedViewHolder.getViewHolderVoicemailUri()));
+    Assert.checkArgument(
+        expandedViewHolder.getViewHolderVoicemailUri().equals(getViewHolderVoicemailUri()));
+    Assert.checkArgument(
+        mediaPlayerView.getVisibility() == View.VISIBLE,
+        "the media player must be visible for viewholder id:%d, before we attempt to play");
+    mediaPlayerView.clickPlayButton();
+  }
+
   interface NewVoicemailViewHolderListener {
     void expandViewHolderFirstTimeAndCollapseAllOtherVisibleViewHolders(
         NewVoicemailViewHolder expandedViewHolder,
diff --git a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml
index 32726a9..3efcea5 100644
--- a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml
+++ b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_media_player_layout.xml
@@ -22,7 +22,6 @@
     android:paddingTop="@dimen/voicemail_media_player_padding_top"
     android:orientation="vertical">
 
-  <!-- TODO(uabdullah): Make visibility gone (once implement fetching from vm server) -->
   <TextView
       android:id="@+id/playback_state_text"
       android:layout_width="match_parent"