Show icon and label for a spam number in the new call log.

Bug: 73077158
Test: CallLogEntryTextTest, GlidePhotoManagerImplTest, PhoneLookupInfoConsolidatorTest
PiperOrigin-RevId: 185017362
Change-Id: I113472482da2213d17a847054272a22249edc578
diff --git a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
index 3f90e6a..0f00a5d 100644
--- a/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
+++ b/java/com/android/dialer/binary/aosp/AospDialerRootComponent.java
@@ -24,6 +24,7 @@
 import com.android.dialer.duo.stub.StubDuoModule;
 import com.android.dialer.enrichedcall.stub.StubEnrichedCallModule;
 import com.android.dialer.feedback.stub.StubFeedbackModule;
+import com.android.dialer.glidephotomanager.GlidePhotoManagerModule;
 import com.android.dialer.inject.ContextModule;
 import com.android.dialer.phonelookup.PhoneLookupModule;
 import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
@@ -50,6 +51,7 @@
     CommandLineModule.class,
     ContextModule.class,
     DialerExecutorModule.class,
+    GlidePhotoManagerModule.class,
     PhoneLookupModule.class,
     PhoneNumberGeoUtilModule.class,
     PreCallModule.class,
diff --git a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
index d8efd0a..3e7db9d 100644
--- a/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
+++ b/java/com/android/dialer/binary/basecomponent/BaseDialerRootComponent.java
@@ -25,6 +25,7 @@
 import com.android.dialer.duo.DuoComponent;
 import com.android.dialer.enrichedcall.EnrichedCallComponent;
 import com.android.dialer.feedback.FeedbackComponent;
+import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent;
 import com.android.dialer.main.MainComponent;
 import com.android.dialer.phonelookup.PhoneLookupComponent;
 import com.android.dialer.phonenumbergeoutil.PhoneNumberGeoUtilComponent;
@@ -55,6 +56,7 @@
         DuoComponent.HasComponent,
         EnrichedCallComponent.HasComponent,
         FeedbackComponent.HasComponent,
+        GlidePhotoManagerComponent.HasComponent,
         MainComponent.HasComponent,
         MapsComponent.HasComponent,
         NewBubbleComponent.HasComponent,
diff --git a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
index 4da0f92..d4520f33 100644
--- a/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
+++ b/java/com/android/dialer/binary/google/GoogleStubDialerRootComponent.java
@@ -24,6 +24,7 @@
 import com.android.dialer.duo.stub.StubDuoModule;
 import com.android.dialer.enrichedcall.stub.StubEnrichedCallModule;
 import com.android.dialer.feedback.stub.StubFeedbackModule;
+import com.android.dialer.glidephotomanager.GlidePhotoManagerModule;
 import com.android.dialer.inject.ContextModule;
 import com.android.dialer.phonelookup.PhoneLookupModule;
 import com.android.dialer.phonenumbergeoutil.impl.PhoneNumberGeoUtilModule;
@@ -54,6 +55,7 @@
     CommandLineModule.class,
     ContextModule.class,
     DialerExecutorModule.class,
+    GlidePhotoManagerModule.class,
     MapsModule.class,
     PhoneLookupModule.class, // TODO(zachh): Module which uses APDL?
     PhoneNumberGeoUtilModule.class,
diff --git a/java/com/android/dialer/calllog/database/contract/number_attributes.proto b/java/com/android/dialer/calllog/database/contract/number_attributes.proto
index b1a7566..594e676 100644
--- a/java/com/android/dialer/calllog/database/contract/number_attributes.proto
+++ b/java/com/android/dialer/calllog/database/contract/number_attributes.proto
@@ -22,6 +22,7 @@
 package com.android.dialer;
 
 // Information related to the phone number of the call.
+// Next ID: 12
 message NumberAttributes {
   // 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
@@ -59,6 +60,9 @@
   // display time.
   optional bool is_cp2_info_incomplete = 9;
 
-  // The number is blocked.
+  // Whether the number is blocked.
   optional bool is_blocked = 10;
+
+  // Whether the number is spam.
+  optional bool is_spam = 11;
 }
\ No newline at end of file
diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
index 8fa6b67..52570c0 100644
--- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
@@ -586,6 +586,7 @@
             .setIsBusiness(phoneLookupInfoConsolidator.isBusiness())
             .setIsVoicemail(phoneLookupInfoConsolidator.isVoicemail())
             .setIsBlocked(phoneLookupInfoConsolidator.isBlocked())
+            .setIsSpam(phoneLookupInfoConsolidator.isSpam())
             .setCanReportAsInvalidNumber(phoneLookupInfoConsolidator.canReportAsInvalidNumber())
             .setIsCp2InfoIncomplete(phoneLookupInfoConsolidator.isCp2LocalInfoIncomplete())
             .build()
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
index 05a3399..f7ba9ef 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java
@@ -25,6 +25,8 @@
 import android.view.ViewGroup;
 import com.android.dialer.calllogutils.CallLogDates;
 import com.android.dialer.common.Assert;
+import com.android.dialer.glidephotomanager.GlidePhotoManager;
+import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent;
 import com.android.dialer.time.Clock;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -53,6 +55,7 @@
 
   private final Clock clock;
   private final RealtimeRowProcessor realtimeRowProcessor;
+  private final GlidePhotoManager glidePhotoManager;
 
   private Cursor cursor;
 
@@ -69,6 +72,7 @@
     this.cursor = cursor;
     this.clock = clock;
     this.realtimeRowProcessor = CallLogUiComponent.get(context).realtimeRowProcessor();
+    this.glidePhotoManager = GlidePhotoManagerComponent.get(context).glidePhotoManager();
 
     setHeaderPositions();
   }
@@ -138,7 +142,8 @@
             LayoutInflater.from(viewGroup.getContext())
                 .inflate(R.layout.new_call_log_entry, viewGroup, false),
             clock,
-            realtimeRowProcessor);
+            realtimeRowProcessor,
+            glidePhotoManager);
       default:
         throw Assert.createUnsupportedOperationFailException("Unsupported view type: " + viewType);
     }
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
index ee114b5..cf01608 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
@@ -19,26 +19,22 @@
 import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.database.Cursor;
-import android.net.Uri;
 import android.provider.CallLog.Calls;
 import android.support.annotation.DrawableRes;
-import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
-import android.text.TextUtils;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
 import com.android.dialer.calllog.model.CoalescedRow;
 import com.android.dialer.calllog.ui.menu.NewCallLogMenu;
-import com.android.dialer.calllogutils.CallLogContactTypes;
 import com.android.dialer.calllogutils.CallLogEntryText;
 import com.android.dialer.calllogutils.CallLogIntents;
+import com.android.dialer.calllogutils.NumberAttributesConverter;
 import com.android.dialer.common.concurrent.DialerExecutorComponent;
 import com.android.dialer.compat.AppCompatConstants;
 import com.android.dialer.compat.telephony.TelephonyManagerCompat;
-import com.android.dialer.contactphoto.ContactPhotoManager;
-import com.android.dialer.contactphoto.NumberAttributeConverter;
+import com.android.dialer.glidephotomanager.GlidePhotoManager;
 import com.android.dialer.oem.MotorolaUtils;
 import com.android.dialer.time.Clock;
 import com.google.common.util.concurrent.FutureCallback;
@@ -65,9 +61,15 @@
   private final RealtimeRowProcessor realtimeRowProcessor;
   private final ExecutorService uiExecutorService;
 
+  private final GlidePhotoManager glidePhotoManager;
+
   private int currentRowId;
 
-  NewCallLogViewHolder(View view, Clock clock, RealtimeRowProcessor realtimeRowProcessor) {
+  NewCallLogViewHolder(
+      View view,
+      Clock clock,
+      RealtimeRowProcessor realtimeRowProcessor,
+      GlidePhotoManager glidePhotoManager) {
     super(view);
     this.context = view.getContext();
     primaryTextView = view.findViewById(R.id.primary_text);
@@ -83,6 +85,7 @@
 
     this.clock = clock;
     this.realtimeRowProcessor = realtimeRowProcessor;
+    this.glidePhotoManager = glidePhotoManager;
     uiExecutorService = DialerExecutorComponent.get(context).uiExecutor();
   }
 
@@ -147,19 +150,11 @@
   }
 
   private void setPhoto(CoalescedRow row) {
-    ContactPhotoManager.getInstance(context)
-        .loadDialerThumbnailOrPhoto(
-            quickContactBadge,
-            parseUri(row.numberAttributes().getLookupUri()),
-            row.numberAttributes().getPhotoId(),
-            NumberAttributeConverter.getPhotoUri(context, row.numberAttributes()),
-            CallLogEntryText.buildPrimaryText(context, row).toString(),
-            CallLogContactTypes.getContactType(row));
-  }
-
-  @Nullable
-  private static Uri parseUri(@Nullable String uri) {
-    return TextUtils.isEmpty(uri) ? null : Uri.parse(uri);
+    glidePhotoManager.loadQuickContactBadge(
+        quickContactBadge,
+        NumberAttributesConverter.toPhotoInfoBuilder(row.numberAttributes())
+            .setFormattedNumber(row.formattedNumber())
+            .build());
   }
 
   private void setFeatureIcons(CoalescedRow row) {
@@ -254,7 +249,8 @@
   }
 
   private void setOnClickListenerForMenuButon(CoalescedRow row) {
-    menuButton.setOnClickListener(NewCallLogMenu.createOnClickListener(context, row));
+    menuButton.setOnClickListener(
+        NewCallLogMenu.createOnClickListener(context, row, glidePhotoManager));
   }
 
   private class RealtimeRowFutureCallback implements FutureCallback<CoalescedRow> {
diff --git a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
index 421c35f..5083a95 100644
--- a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
+++ b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
@@ -212,6 +212,7 @@
                 .setIsBusiness(phoneLookupInfoConsolidator.isBusiness())
                 .setIsVoicemail(phoneLookupInfoConsolidator.isVoicemail())
                 .setIsBlocked(phoneLookupInfoConsolidator.isBlocked())
+                .setIsSpam(phoneLookupInfoConsolidator.isSpam())
                 .setCanReportAsInvalidNumber(phoneLookupInfoConsolidator.canReportAsInvalidNumber())
                 .build())
         .build();
diff --git a/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
index 2ae823e..81c0513 100644
--- a/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
+++ b/java/com/android/dialer/calllog/ui/menu/NewCallLogMenu.java
@@ -20,14 +20,19 @@
 import android.view.View;
 import com.android.dialer.calllog.model.CoalescedRow;
 import com.android.dialer.contactactions.ContactActionBottomSheet;
+import com.android.dialer.glidephotomanager.GlidePhotoManager;
 
 /** Handles configuration of the bottom sheet menus for call log entries. */
 public final class NewCallLogMenu {
 
   /** Creates and returns the OnClickListener which opens the menu for the provided row. */
-  public static View.OnClickListener createOnClickListener(Context context, CoalescedRow row) {
+  public static View.OnClickListener createOnClickListener(
+      Context context, CoalescedRow row, GlidePhotoManager glidePhotoManager) {
     return (view) ->
         ContactActionBottomSheet.show(
-            context, PrimaryAction.fromRow(context, row), Modules.fromRow(context, row));
+            context,
+            PrimaryAction.fromRow(context, row),
+            Modules.fromRow(context, row),
+            glidePhotoManager);
   }
 }
diff --git a/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java b/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
index 2a43a3c..92a8453 100644
--- a/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
+++ b/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
@@ -19,12 +19,10 @@
 import android.content.Context;
 import android.provider.CallLog.Calls;
 import com.android.dialer.calllog.model.CoalescedRow;
-import com.android.dialer.calllogutils.CallLogContactTypes;
 import com.android.dialer.calllogutils.CallLogEntryText;
 import com.android.dialer.calllogutils.CallLogIntents;
+import com.android.dialer.calllogutils.NumberAttributesConverter;
 import com.android.dialer.contactactions.ContactPrimaryActionInfo;
-import com.android.dialer.contactactions.ContactPrimaryActionInfo.PhotoInfo;
-import com.android.dialer.contactphoto.NumberAttributeConverter;
 
 /** Configures the primary action row (top row) for the bottom sheet. */
 final class PrimaryAction {
@@ -34,13 +32,9 @@
     return ContactPrimaryActionInfo.builder()
         .setNumber(row.number())
         .setPhotoInfo(
-            PhotoInfo.builder()
-                .setPhotoId(row.numberAttributes().getPhotoId())
-                .setPhotoUri(NumberAttributeConverter.getPhotoUri(context, row.numberAttributes()))
-                .setLookupUri(row.numberAttributes().getLookupUri())
+            NumberAttributesConverter.toPhotoInfoBuilder(row.numberAttributes())
+                .setFormattedNumber(row.formattedNumber())
                 .setIsVideo((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO)
-                .setContactType(CallLogContactTypes.getContactType(row))
-                .setDisplayName(primaryText.toString())
                 .build())
         .setPrimaryText(primaryText)
         .setSecondaryText(CallLogEntryText.buildSecondaryTextForBottomSheet(context, row))
diff --git a/java/com/android/dialer/calllogutils/CallLogEntryText.java b/java/com/android/dialer/calllogutils/CallLogEntryText.java
index 49f5e42..737b1d3 100644
--- a/java/com/android/dialer/calllogutils/CallLogEntryText.java
+++ b/java/com/android/dialer/calllogutils/CallLogEntryText.java
@@ -65,7 +65,13 @@
   /**
    * The secondary text to show in the main call log entry list.
    *
-   * <p>Rules: (Blocked • )?(Duo video, )?$Label|$Location • Date
+   * <p>Rules:
+   *
+   * <ul>
+   *   <li>For numbers that are not spam or blocked: (Duo video, )?$Label|$Location • Date
+   *   <li>For blocked non-spam numbers: Blocked • (Duo video, )?$Label|$Location • Date
+   *   <li>For spam numbers: Spam • (Duo video, )?$Label • Date
+   * </ul>
    *
    * <p>Examples:
    *
@@ -74,6 +80,10 @@
    *   <li>Duo Video • 10 min ago
    *   <li>Mobile • 11:45 PM
    *   <li>Mobile • Sun
+   *   <li>Blocked • Duo Video, Mobile • Now
+   *   <li>Blocked • Brooklyn, NJ • 10 min ago
+   *   <li>Spam • Mobile • Now
+   *   <li>Spam • Now
    *   <li>Brooklyn, NJ • Jan 15
    * </ul>
    *
@@ -82,7 +92,11 @@
   public static CharSequence buildSecondaryTextForEntries(
       Context context, Clock clock, CoalescedRow row) {
     List<CharSequence> components = new ArrayList<>();
-    if (row.numberAttributes().getIsBlocked()) {
+
+    // If a number is both spam and blocked, only show "Spam".
+    if (row.numberAttributes().getIsSpam()) {
+      components.add(context.getText(R.string.new_call_log_secondary_spam));
+    } else if (row.numberAttributes().getIsBlocked()) {
       components.add(context.getText(R.string.new_call_log_secondary_blocked));
     }
 
@@ -102,7 +116,13 @@
    */
   public static CharSequence buildSecondaryTextForBottomSheet(Context context, CoalescedRow row) {
     /*
-     * Rules: (Blocked • )(Duo video, )?$Label|$Location [• NumberIfNoName]?
+     * Rules:
+     *   For numbers that are not spam or blocked:
+     *     (Duo video, )?$Label|$Location [• NumberIfNoName]?
+     *   For blocked non-spam numbers:
+     *     Blocked • (Duo video, )?$Label|$Location [• NumberIfNoName]?
+     *   For spam numbers:
+     *     Spam • (Duo video, )?$Label [• NumberIfNoName]?
      *
      * The number is shown at the end if there is no name for the entry. (It is shown in primary
      * text otherwise.)
@@ -112,11 +132,17 @@
      *   Duo Video • 555-1234
      *   Mobile • 555-1234
      *   Blocked • Mobile • 555-1234
+     *   Blocked • Brooklyn, NJ • 555-1234
+     *   Spam • Mobile • 555-1234
      *   Mobile • 555-1234
      *   Brooklyn, NJ
      */
     List<CharSequence> components = new ArrayList<>();
-    if (row.numberAttributes().getIsBlocked()) {
+
+    // If a number is both spam and blocked, only show "Spam".
+    if (row.numberAttributes().getIsSpam()) {
+      components.add(context.getText(R.string.new_call_log_secondary_spam));
+    } else if (row.numberAttributes().getIsBlocked()) {
       components.add(context.getText(R.string.new_call_log_secondary_blocked));
     }
 
@@ -162,7 +188,8 @@
         secondaryText.append(", ");
       }
       secondaryText.append(numberTypeLabel);
-    } else { // If there's a number type label, don't show the location.
+    } else if (!row.numberAttributes().getIsSpam()) {
+      // Don't show the location if there's a number type label or the number is spam.
       String location = row.geocodedLocation();
       if (!TextUtils.isEmpty(location)) {
         if (secondaryText.length() > 0) {
diff --git a/java/com/android/dialer/calllogutils/NumberAttributesConverter.java b/java/com/android/dialer/calllogutils/NumberAttributesConverter.java
new file mode 100644
index 0000000..bed1edd
--- /dev/null
+++ b/java/com/android/dialer/calllogutils/NumberAttributesConverter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 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.dialer.calllogutils;
+
+import com.android.dialer.NumberAttributes;
+import com.android.dialer.glidephotomanager.PhotoInfo;
+
+/** Converts {@link NumberAttributes} to {@link PhotoInfo} */
+public final class NumberAttributesConverter {
+
+  /** Converts to {@link PhotoInfo.Builder} */
+  public static PhotoInfo.Builder toPhotoInfoBuilder(NumberAttributes numberAttributes) {
+    return PhotoInfo.builder()
+        .setName(numberAttributes.getName())
+        .setPhotoUri(numberAttributes.getPhotoUri())
+        .setPhotoId(numberAttributes.getPhotoId())
+        .setLookupUri(numberAttributes.getLookupUri())
+        .setIsBusiness(numberAttributes.getIsBusiness())
+        .setIsSpam(numberAttributes.getIsSpam())
+        .setIsVoicemail(numberAttributes.getIsVoicemail())
+        .setIsBlocked(numberAttributes.getIsBlocked());
+  }
+}
diff --git a/java/com/android/dialer/calllogutils/res/values/strings.xml b/java/com/android/dialer/calllogutils/res/values/strings.xml
index 4622e50..f536ca6 100644
--- a/java/com/android/dialer/calllogutils/res/values/strings.xml
+++ b/java/com/android/dialer/calllogutils/res/values/strings.xml
@@ -139,4 +139,7 @@
 
   <!-- String used to display calls from blocked numbers in the call log.   [CHAR LIMIT=30] -->
   <string name="new_call_log_secondary_blocked">Blocked</string>
+
+  <!-- String used to display calls from spam numbers in the call log.   [CHAR LIMIT=30] -->
+  <string name="new_call_log_secondary_spam">Spam</string>
 </resources>
\ No newline at end of file
diff --git a/java/com/android/dialer/contactactions/ContactActionBottomSheet.java b/java/com/android/dialer/contactactions/ContactActionBottomSheet.java
index 27e3187..98a5dd1 100644
--- a/java/com/android/dialer/contactactions/ContactActionBottomSheet.java
+++ b/java/com/android/dialer/contactactions/ContactActionBottomSheet.java
@@ -17,7 +17,6 @@
 package com.android.dialer.contactactions;
 
 import android.content.Context;
-import android.net.Uri;
 import android.os.Bundle;
 import android.support.design.widget.BottomSheetDialog;
 import android.text.TextUtils;
@@ -29,8 +28,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 import com.android.dialer.common.Assert;
-import com.android.dialer.contactactions.ContactPrimaryActionInfo.PhotoInfo;
-import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.glidephotomanager.GlidePhotoManager;
 import java.util.List;
 
 /**
@@ -44,23 +42,27 @@
 
   private final List<ContactActionModule> modules;
   private final ContactPrimaryActionInfo contactPrimaryActionInfo;
+  private final GlidePhotoManager glidePhotoManager;
 
   private ContactActionBottomSheet(
       Context context,
       ContactPrimaryActionInfo contactPrimaryActionInfo,
-      List<ContactActionModule> modules) {
+      List<ContactActionModule> modules,
+      GlidePhotoManager glidePhotoManager) {
     super(context);
     this.modules = modules;
     this.contactPrimaryActionInfo = contactPrimaryActionInfo;
+    this.glidePhotoManager = glidePhotoManager;
     setContentView(LayoutInflater.from(context).inflate(R.layout.sheet_layout, null));
   }
 
   public static ContactActionBottomSheet show(
       Context context,
       ContactPrimaryActionInfo contactPrimaryActionInfo,
-      List<ContactActionModule> modules) {
+      List<ContactActionModule> modules,
+      GlidePhotoManager glidePhotoManager) {
     ContactActionBottomSheet sheet =
-        new ContactActionBottomSheet(context, contactPrimaryActionInfo, modules);
+        new ContactActionBottomSheet(context, contactPrimaryActionInfo, modules, glidePhotoManager);
     sheet.show();
     return sheet;
   }
@@ -85,15 +87,8 @@
     View contactView = inflater.inflate(R.layout.contact_layout, container, false);
 
     // TODO(zachh): The contact image should be badged with a video icon if it is for a video call.
-    PhotoInfo photoInfo = contactPrimaryActionInfo.photoInfo();
-    ContactPhotoManager.getInstance(getContext())
-        .loadDialerThumbnailOrPhoto(
-            contactView.findViewById(R.id.quick_contact_photo),
-            !TextUtils.isEmpty(photoInfo.lookupUri()) ? Uri.parse(photoInfo.lookupUri()) : null,
-            photoInfo.photoId(),
-            photoInfo.photoUri(),
-            photoInfo.displayName(),
-            photoInfo.contactType());
+    glidePhotoManager.loadQuickContactBadge(
+        contactView.findViewById(R.id.quick_contact_photo), contactPrimaryActionInfo.photoInfo());
 
     TextView primaryTextView = contactView.findViewById(R.id.primary_text);
     TextView secondaryTextView = contactView.findViewById(R.id.secondary_text);
diff --git a/java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java b/java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java
index f19fd28..5017d83 100644
--- a/java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java
+++ b/java/com/android/dialer/contactactions/ContactPrimaryActionInfo.java
@@ -16,11 +16,10 @@
 package com.android.dialer.contactactions;
 
 import android.content.Intent;
-import android.net.Uri;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import com.android.dialer.DialerPhoneNumber;
-import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.glidephotomanager.PhotoInfo;
 import com.google.auto.value.AutoValue;
 
 /**
@@ -35,50 +34,6 @@
   @Nullable
   public abstract DialerPhoneNumber number();
 
-  /** Information used to construct the photo for the contact. */
-  @AutoValue
-  public abstract static class PhotoInfo {
-    public abstract long photoId();
-
-    @Nullable
-    public abstract Uri photoUri();
-
-    @Nullable
-    public abstract String lookupUri();
-
-    /** Badges the photo with a video icon if true. */
-    public abstract boolean isVideo();
-
-    @LetterTileDrawable.ContactType
-    public abstract int contactType();
-
-    /** Used to generate letter tile if there is no photo. */
-    @Nullable
-    public abstract String displayName();
-
-    /** Builder for {@link PhotoInfo}. */
-    @AutoValue.Builder
-    public abstract static class Builder {
-      public abstract Builder setPhotoId(long photoId);
-
-      public abstract Builder setPhotoUri(@Nullable Uri photoUri);
-
-      public abstract Builder setLookupUri(@Nullable String lookupUri);
-
-      public abstract Builder setIsVideo(boolean isVideo);
-
-      public abstract Builder setContactType(@LetterTileDrawable.ContactType int contactType);
-
-      public abstract Builder setDisplayName(@Nullable String displayName);
-
-      public abstract PhotoInfo build();
-    }
-
-    public static Builder builder() {
-      return new AutoValue_ContactPrimaryActionInfo_PhotoInfo.Builder();
-    }
-  }
-
   @NonNull
   public abstract PhotoInfo photoInfo();
 
diff --git a/java/com/android/dialer/contactphoto/NumberAttributeConverter.java b/java/com/android/dialer/contactphoto/NumberAttributeConverter.java
deleted file mode 100644
index d7bf9bd..0000000
--- a/java/com/android/dialer/contactphoto/NumberAttributeConverter.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2018 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.dialer.contactphoto;
-
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.support.annotation.DrawableRes;
-import android.support.annotation.Nullable;
-import android.text.TextUtils;
-import com.android.dialer.NumberAttributes;
-
-/**
- * Convert photo information in {@link NumberAttributes} to an URI suitable for {@link
- * ContactPhotoManager}.
- *
- * <p>This class is temporary. The new photo manager should take NumberAttributes directly.
- */
-public final class NumberAttributeConverter {
-
-  /**
-   * Computes the photo URI from NumberAttributes.
-   *
-   * <p>The photo URI is shown in the quick contact badge in the main call log list or in the top
-   * item of the bottom sheet menu.
-   */
-  @Nullable
-  public static Uri getPhotoUri(Context context, NumberAttributes numberAttributes) {
-    if (numberAttributes.getIsBlocked()) {
-      return getResourceUri(context.getResources(), R.drawable.ic_block_grey_48dp);
-    } else {
-      return parseUri(numberAttributes.getPhotoUri());
-    }
-  }
-
-  @Nullable
-  private static Uri parseUri(@Nullable String uri) {
-    return TextUtils.isEmpty(uri) ? null : Uri.parse(uri);
-  }
-
-  private static Uri getResourceUri(Resources resources, @DrawableRes int drawable) {
-    return Uri.parse(
-        ContentResolver.SCHEME_ANDROID_RESOURCE
-            + "://"
-            + resources.getResourcePackageName(drawable)
-            + "/"
-            + resources.getResourceTypeName(drawable)
-            + "/"
-            + resources.getResourceEntryName(drawable));
-  }
-}
diff --git a/java/com/android/dialer/glide/DialerGlideModule.java b/java/com/android/dialer/glide/DialerGlideModule.java
new file mode 100644
index 0000000..c74c7cf
--- /dev/null
+++ b/java/com/android/dialer/glide/DialerGlideModule.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2018 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.dialer.glide;
+
+import com.bumptech.glide.annotation.GlideModule;
+import com.bumptech.glide.module.AppGlideModule;
+
+/**
+ * Generates {@link GlideApp}. This class is required for glide annotation processor to generate
+ * generated API, which most documentations are based on.
+ */
+@GlideModule
+public class DialerGlideModule extends AppGlideModule {}
diff --git a/java/com/android/dialer/glidephotomanager/GlidePhotoManager.java b/java/com/android/dialer/glidephotomanager/GlidePhotoManager.java
new file mode 100644
index 0000000..519a394
--- /dev/null
+++ b/java/com/android/dialer/glidephotomanager/GlidePhotoManager.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2018 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.dialer.glidephotomanager;
+
+import android.support.annotation.MainThread;
+import android.widget.QuickContactBadge;
+
+/** Class to load photo for call/contacts */
+public interface GlidePhotoManager {
+
+  /**
+   * Load {@code photoInfo} into the {@code badge}. The loading is performed in the background and a
+   * placeholder will be used appropriately. {@code badge} must be already attached to an
+   * activity/fragment, and the load will be automatically canceled if the lifecycle of the activity
+   * ends.
+   */
+  @MainThread
+  void loadQuickContactBadge(QuickContactBadge badge, PhotoInfo photoInfo);
+}
diff --git a/java/com/android/dialer/glidephotomanager/GlidePhotoManagerComponent.java b/java/com/android/dialer/glidephotomanager/GlidePhotoManagerComponent.java
new file mode 100644
index 0000000..b4699be
--- /dev/null
+++ b/java/com/android/dialer/glidephotomanager/GlidePhotoManagerComponent.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 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.dialer.glidephotomanager;
+
+import android.content.Context;
+import com.android.dialer.inject.HasRootComponent;
+import dagger.Subcomponent;
+
+/** Entry point for {@link GlidePhotoManager} */
+@Subcomponent
+public abstract class GlidePhotoManagerComponent {
+
+  public abstract GlidePhotoManager glidePhotoManager();
+
+  public static GlidePhotoManagerComponent get(Context context) {
+    return ((HasComponent) ((HasRootComponent) context.getApplicationContext()).component())
+        .glidePhotoManagerComponent();
+  }
+
+  /** Used to refer to the root application component. */
+  public interface HasComponent {
+    GlidePhotoManagerComponent glidePhotoManagerComponent();
+  }
+}
diff --git a/java/com/android/dialer/glidephotomanager/GlidePhotoManagerModule.java b/java/com/android/dialer/glidephotomanager/GlidePhotoManagerModule.java
new file mode 100644
index 0000000..79629d6
--- /dev/null
+++ b/java/com/android/dialer/glidephotomanager/GlidePhotoManagerModule.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2018 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.dialer.glidephotomanager;
+
+import com.android.dialer.glidephotomanager.impl.GlidePhotoManagerImpl;
+import dagger.Binds;
+import dagger.Module;
+import javax.inject.Singleton;
+
+/** Module for {@link GlidePhotoManagerComponent} */
+@Module
+public abstract class GlidePhotoManagerModule {
+  @Binds
+  @Singleton
+  public abstract GlidePhotoManager bindGlidePhotoManager(GlidePhotoManagerImpl glidePhotoManager);
+}
diff --git a/java/com/android/dialer/glidephotomanager/PhotoInfo.java b/java/com/android/dialer/glidephotomanager/PhotoInfo.java
new file mode 100644
index 0000000..e4dd871
--- /dev/null
+++ b/java/com/android/dialer/glidephotomanager/PhotoInfo.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2018 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.dialer.glidephotomanager;
+
+import android.support.annotation.Nullable;
+import com.google.auto.value.AutoValue;
+
+/** The number information used to create the photo.. */
+@AutoValue
+public abstract class PhotoInfo {
+
+  /** The display name of the number */
+  @Nullable
+  public abstract String name();
+
+  /** The number displayed to the user. */
+  @Nullable
+  public abstract String formattedNumber();
+
+  /** The URI to the photo */
+  @Nullable
+  public abstract String photoUri();
+
+  /** Value of {@link android.provider.ContactsContract.CommonDataKinds.Photo#_ID} */
+  public abstract long photoId();
+
+  /** The contacts provider lookup URI for the contact associated with the number */
+  @Nullable
+  public abstract String lookupUri();
+
+  /** Should a business icon be displayed */
+  public abstract boolean isBusiness();
+
+  /** Should a voicemail icon be displayed */
+  public abstract boolean isVoicemail();
+
+  /** Should a blocked icon be displayed */
+  public abstract boolean isBlocked();
+
+  /** Should a spam icon be displayed */
+  public abstract boolean isSpam();
+
+  /**
+   * Should the photo be badged as video call.
+   *
+   * <p>Defaults to false.
+   */
+  public abstract boolean isVideo();
+
+  /**
+   * Should the result be circularized.
+   *
+   * <p>Defaults to true.
+   */
+  public abstract boolean isCircular();
+
+  /** Builder for {@link PhotoInfo} */
+  @AutoValue.Builder
+  public abstract static class Builder {
+
+    public abstract Builder setName(@Nullable String name);
+
+    public abstract Builder setFormattedNumber(@Nullable String formattedNumber);
+
+    public abstract Builder setPhotoUri(@Nullable String uri);
+
+    public abstract Builder setPhotoId(long id);
+
+    public abstract Builder setLookupUri(@Nullable String uri);
+
+    public abstract Builder setIsBusiness(boolean isBusiness);
+
+    public abstract Builder setIsVoicemail(boolean isVoicemail);
+
+    public abstract Builder setIsBlocked(boolean isBlocked);
+
+    public abstract Builder setIsSpam(boolean isSpam);
+
+    public abstract Builder setIsVideo(boolean isVideo);
+
+    public abstract Builder setIsCircular(boolean isCircular);
+
+    public abstract PhotoInfo build();
+  }
+
+  public static PhotoInfo.Builder builder() {
+    return new AutoValue_PhotoInfo.Builder()
+        .setPhotoId(0)
+        .setIsBusiness(false)
+        .setIsVoicemail(false)
+        .setIsBlocked(false)
+        .setIsSpam(false)
+        .setIsCircular(true)
+        .setIsVideo(false);
+  }
+}
diff --git a/java/com/android/dialer/glidephotomanager/impl/AndroidManifest.xml b/java/com/android/dialer/glidephotomanager/impl/AndroidManifest.xml
new file mode 100644
index 0000000..065c103
--- /dev/null
+++ b/java/com/android/dialer/glidephotomanager/impl/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+<manifest
+    package="com.android.dialer.glidephotomanager.impl">
+</manifest>
\ No newline at end of file
diff --git a/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java b/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java
new file mode 100644
index 0000000..20d379c
--- /dev/null
+++ b/java/com/android/dialer/glidephotomanager/impl/GlidePhotoManagerImpl.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2018 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.dialer.glidephotomanager.impl;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.ContactsContract.Data;
+import android.support.annotation.MainThread;
+import android.support.annotation.Nullable;
+import android.telecom.TelecomManager;
+import android.text.TextUtils;
+import android.widget.QuickContactBadge;
+import com.android.dialer.common.Assert;
+import com.android.dialer.glide.GlideApp;
+import com.android.dialer.glide.GlideRequest;
+import com.android.dialer.glide.GlideRequests;
+import com.android.dialer.glidephotomanager.GlidePhotoManager;
+import com.android.dialer.glidephotomanager.PhotoInfo;
+import com.android.dialer.inject.ApplicationContext;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import javax.inject.Inject;
+
+/** Implementation of {@link GlidePhotoManager} */
+public class GlidePhotoManagerImpl implements GlidePhotoManager {
+  private final Context appContext;
+
+  @Inject
+  public GlidePhotoManagerImpl(@ApplicationContext Context appContext) {
+    this.appContext = appContext;
+  }
+
+  @MainThread
+  @Override
+  public void loadQuickContactBadge(QuickContactBadge badge, PhotoInfo photoInfo) {
+    Assert.isMainThread();
+    badge.assignContactUri(parseUri(photoInfo.lookupUri()));
+    badge.setOverlay(null);
+    LetterTileDrawable defaultDrawable = getDefaultDrawable(photoInfo);
+    GlideRequest<Drawable> request =
+        buildRequest(GlideApp.with(badge), photoInfo)
+            .placeholder(defaultDrawable) // when the photo is still loading.
+            .fallback(defaultDrawable); // when there's nothing to load.
+
+    if (photoInfo.isCircular()) {
+      request.circleCrop();
+    }
+
+    request.into(badge);
+  }
+
+  private GlideRequest<Drawable> buildRequest(GlideRequests requestManager, PhotoInfo photoInfo) {
+    // The spam status takes precedence over whether the number is blocked.
+    if (photoInfo.isSpam()) {
+      return requestManager.load(R.drawable.ic_report_red_48dp);
+    }
+    if (photoInfo.isBlocked()) {
+      return requestManager.load(R.drawable.ic_block_grey_48dp);
+    }
+    if (!TextUtils.isEmpty(photoInfo.photoUri())) {
+      return requestManager.load(parseUri(photoInfo.photoUri()));
+    }
+    if (photoInfo.photoId() != 0) {
+      return requestManager.load(ContentUris.withAppendedId(Data.CONTENT_URI, photoInfo.photoId()));
+    }
+    // load null to indicate fallback should be used.
+    return requestManager.load((Object) null);
+  }
+
+  /**
+   * Generate the default drawable when photos are not available. Used when the photo is loading or
+   * no photo is available.
+   */
+  private LetterTileDrawable getDefaultDrawable(PhotoInfo photoInfo) {
+    LetterTileDrawable letterTileDrawable = new LetterTileDrawable(appContext.getResources());
+    String displayName;
+    String identifier;
+    if (TextUtils.isEmpty(photoInfo.lookupUri())) {
+      // Use generic avatar instead of letter for non-contacts.
+      displayName = null;
+      identifier =
+          TextUtils.isEmpty(photoInfo.name()) ? photoInfo.formattedNumber() : photoInfo.name();
+    } else {
+      displayName = photoInfo.name();
+      identifier = photoInfo.lookupUri();
+    }
+    letterTileDrawable.setCanonicalDialerLetterTileDetails(
+        displayName,
+        identifier,
+        LetterTileDrawable.SHAPE_CIRCLE,
+        LetterTileDrawable.getContactTypeFromPrimitives(
+            photoInfo.isVoicemail(),
+            false, // TODO(twyen):implement
+            photoInfo.isBusiness(),
+            TelecomManager.PRESENTATION_ALLOWED, // TODO(twyen):implement
+            false)); // TODO(twyen):implement
+    return letterTileDrawable;
+  }
+
+  @Nullable
+  private static Uri parseUri(@Nullable String uri) {
+    return TextUtils.isEmpty(uri) ? null : Uri.parse(uri);
+  }
+}
diff --git a/java/com/android/dialer/contactphoto/res/drawable-xxxhdpi/ic_block_black_48dp.png b/java/com/android/dialer/glidephotomanager/impl/res/drawable-xxxhdpi/ic_block_black_48dp.png
similarity index 100%
rename from java/com/android/dialer/contactphoto/res/drawable-xxxhdpi/ic_block_black_48dp.png
rename to java/com/android/dialer/glidephotomanager/impl/res/drawable-xxxhdpi/ic_block_black_48dp.png
Binary files differ
diff --git a/java/com/android/dialer/contactphoto/res/drawable/ic_block_grey_48dp.xml b/java/com/android/dialer/glidephotomanager/impl/res/drawable/ic_block_grey_48dp.xml
similarity index 100%
rename from java/com/android/dialer/contactphoto/res/drawable/ic_block_grey_48dp.xml
rename to java/com/android/dialer/glidephotomanager/impl/res/drawable/ic_block_grey_48dp.xml
diff --git a/java/com/android/dialer/glidephotomanager/impl/res/drawable/ic_report_red_48dp.xml b/java/com/android/dialer/glidephotomanager/impl/res/drawable/ic_report_red_48dp.xml
new file mode 100644
index 0000000..8a1ddcd
--- /dev/null
+++ b/java/com/android/dialer/glidephotomanager/impl/res/drawable/ic_report_red_48dp.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2018 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
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+
+  <item>
+    <shape android:shape="oval">
+      <solid android:color="#A52714"/>
+      <size
+          android:height="48dp"
+          android:width="48dp"/>
+    </shape>
+  </item>
+
+  <item
+      android:drawable="@drawable/quantum_ic_report_vd_theme_24"
+      android:gravity="center"
+      android:height="36dp"
+      android:width="36dp"/>
+
+</layer-list>
\ No newline at end of file
diff --git a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
index 0373cfe..874bdbc 100644
--- a/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
+++ b/java/com/android/dialer/phonelookup/consolidator/PhoneLookupInfoConsolidator.java
@@ -228,6 +228,14 @@
   }
 
   /**
+   * The {@link PhoneLookupInfo} passed to the constructor is associated with a number. This method
+   * returns whether the number is spam.
+   */
+  public boolean isSpam() {
+    return phoneLookupInfo.getSpamInfo().getIsSpam();
+  }
+
+  /**
    * Returns true if the {@link PhoneLookupInfo} passed to the constructor has incomplete CP2 local
    * info.
    */
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
index 318f797..05a3bc9 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
@@ -42,6 +42,7 @@
 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
 import com.android.dialer.common.concurrent.DialerExecutorComponent;
 import com.android.dialer.common.concurrent.ThreadUtil;
+import com.android.dialer.glidephotomanager.GlidePhotoManager;
 import com.android.dialer.time.Clock;
 import com.android.dialer.voicemail.listui.NewVoicemailViewHolder.NewVoicemailViewHolderListener;
 import com.android.dialer.voicemail.listui.error.VoicemailErrorMessage;
@@ -75,6 +76,7 @@
   private Cursor cursor;
   private Cursor voicemailStatusCursor;
   private final Clock clock;
+  private final GlidePhotoManager glidePhotoManager;
 
   /** {@link Integer#MAX_VALUE} when the "Today" header should not be displayed. */
   private int todayHeaderPosition = Integer.MAX_VALUE;
@@ -117,11 +119,16 @@
       new NewVoicemailMediaPlayer(new MediaPlayer());
 
   /** @param cursor whose projection is {@link VoicemailCursorLoader#VOICEMAIL_COLUMNS} */
-  NewVoicemailAdapter(Cursor cursor, Clock clock, FragmentManager fragmentManager) {
+  NewVoicemailAdapter(
+      Cursor cursor,
+      Clock clock,
+      FragmentManager fragmentManager,
+      GlidePhotoManager glidePhotoManager) {
     LogUtil.enterBlock("NewVoicemailAdapter");
     this.cursor = cursor;
     this.clock = clock;
     this.fragmentManager = fragmentManager;
+    this.glidePhotoManager = glidePhotoManager;
     initializeMediaPlayerListeners();
     updateHeaderPositions();
   }
@@ -223,7 +230,7 @@
       case NewVoicemailAdapter.RowType.VOICEMAIL_ENTRY:
         view = inflater.inflate(R.layout.new_voicemail_entry, viewGroup, false);
         NewVoicemailViewHolder newVoicemailViewHolder =
-            new NewVoicemailViewHolder(view, clock, this);
+            new NewVoicemailViewHolder(view, clock, this, glidePhotoManager);
         newVoicemailViewHolderSet.add(newVoicemailViewHolder);
         return newVoicemailViewHolder;
       default:
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
index 8812bcc..45a5443 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
@@ -37,6 +37,7 @@
 import com.android.dialer.common.concurrent.UiListener;
 import com.android.dialer.database.CallLogQueryHandler;
 import com.android.dialer.database.CallLogQueryHandler.Listener;
+import com.android.dialer.glidephotomanager.GlidePhotoManagerComponent;
 import com.google.common.util.concurrent.ListenableFuture;
 
 // TODO(uabdullah): Register content observer for VoicemailContract.Status.CONTENT_URI in onStart
@@ -175,7 +176,10 @@
       // TODO(uabdullah): Replace getActivity().getFragmentManager() with getChildFragment()
       recyclerView.setAdapter(
           new NewVoicemailAdapter(
-              data, System::currentTimeMillis, getActivity().getFragmentManager()));
+              data,
+              System::currentTimeMillis,
+              getActivity().getFragmentManager(),
+              GlidePhotoManagerComponent.get(getContext()).glidePhotoManager()));
     } 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
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
index b0c07df..5328dd3 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
@@ -27,7 +27,6 @@
 import android.net.Uri;
 import android.provider.VoicemailContract.Voicemails;
 import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
 import android.support.annotation.WorkerThread;
 import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
@@ -38,15 +37,14 @@
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
+import com.android.dialer.calllogutils.NumberAttributesConverter;
 import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener;
 import com.android.dialer.common.concurrent.DialerExecutor.Worker;
 import com.android.dialer.common.concurrent.DialerExecutorComponent;
 import com.android.dialer.compat.android.provider.VoicemailCompat;
-import com.android.dialer.contactphoto.ContactPhotoManager;
-import com.android.dialer.contactphoto.NumberAttributeConverter;
-import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.glidephotomanager.GlidePhotoManager;
 import com.android.dialer.time.Clock;
 import com.android.dialer.voicemail.listui.menu.NewVoicemailMenu;
 import com.android.dialer.voicemail.model.VoicemailEntry;
@@ -69,9 +67,13 @@
   private VoicemailEntry voicemailEntryOfViewHolder;
   @NonNull private Uri viewHolderVoicemailUri;
   private final NewVoicemailViewHolderListener voicemailViewHolderListener;
+  private final GlidePhotoManager glidePhotoManager;
 
   NewVoicemailViewHolder(
-      View view, Clock clock, NewVoicemailViewHolderListener newVoicemailViewHolderListener) {
+      View view,
+      Clock clock,
+      NewVoicemailViewHolderListener newVoicemailViewHolderListener,
+      GlidePhotoManager glidePhotoManager) {
     super(view);
     LogUtil.enterBlock("NewVoicemailViewHolder");
     this.context = view.getContext();
@@ -84,6 +86,7 @@
     menuButton = view.findViewById(R.id.menu_button);
     this.clock = clock;
     voicemailViewHolderListener = newVoicemailViewHolderListener;
+    this.glidePhotoManager = glidePhotoManager;
 
     viewHolderId = -1;
     isViewHolderExpanded = false;
@@ -144,7 +147,8 @@
 
     itemView.setOnClickListener(this);
     menuButton.setOnClickListener(
-        NewVoicemailMenu.createOnClickListener(context, voicemailEntryOfViewHolder));
+        NewVoicemailMenu.createOnClickListener(
+            context, voicemailEntryOfViewHolder, glidePhotoManager));
 
     setPhoto(voicemailEntryOfViewHolder);
 
@@ -206,19 +210,11 @@
   }
 
   private void setPhoto(VoicemailEntry voicemailEntry) {
-    ContactPhotoManager.getInstance(context)
-        .loadDialerThumbnailOrPhoto(
-            quickContactBadge,
-            parseUri(voicemailEntry.numberAttributes().getLookupUri()),
-            voicemailEntry.numberAttributes().getPhotoId(),
-            NumberAttributeConverter.getPhotoUri(context, voicemailEntry.numberAttributes()),
-            VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntry),
-            LetterTileDrawable.TYPE_DEFAULT);
-  }
-
-  @Nullable
-  private static Uri parseUri(@Nullable String string) {
-    return TextUtils.isEmpty(string) ? null : Uri.parse(string);
+    glidePhotoManager.loadQuickContactBadge(
+        quickContactBadge,
+        NumberAttributesConverter.toPhotoInfoBuilder(voicemailEntry.numberAttributes())
+            .setFormattedNumber(voicemailEntry.formattedNumber())
+            .build());
   }
 
   void collapseViewHolder() {
diff --git a/java/com/android/dialer/voicemail/listui/menu/NewVoicemailMenu.java b/java/com/android/dialer/voicemail/listui/menu/NewVoicemailMenu.java
index 9af8de6..fbd7fe8 100644
--- a/java/com/android/dialer/voicemail/listui/menu/NewVoicemailMenu.java
+++ b/java/com/android/dialer/voicemail/listui/menu/NewVoicemailMenu.java
@@ -19,6 +19,7 @@
 import android.content.Context;
 import android.view.View;
 import com.android.dialer.contactactions.ContactActionBottomSheet;
+import com.android.dialer.glidephotomanager.GlidePhotoManager;
 import com.android.dialer.voicemail.model.VoicemailEntry;
 
 /** Handles configuration of the bottom sheet menus for voicemail entries. */
@@ -26,11 +27,12 @@
 
   /** Creates and returns the OnClickListener which opens the menu for the provided row. */
   public static View.OnClickListener createOnClickListener(
-      Context context, VoicemailEntry voicemailEntry) {
+      Context context, VoicemailEntry voicemailEntry, GlidePhotoManager glidePhotoManager) {
     return (view) ->
         ContactActionBottomSheet.show(
             context,
             PrimaryAction.fromVoicemailEntry(context, voicemailEntry),
-            Modules.fromVoicemailEntry(context, voicemailEntry));
+            Modules.fromVoicemailEntry(context, voicemailEntry),
+            glidePhotoManager);
   }
 }
diff --git a/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java b/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java
index ffc53e7..91f505c 100644
--- a/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java
+++ b/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java
@@ -18,10 +18,8 @@
 
 import android.content.Context;
 import android.text.TextUtils;
+import com.android.dialer.calllogutils.NumberAttributesConverter;
 import com.android.dialer.contactactions.ContactPrimaryActionInfo;
-import com.android.dialer.contactactions.ContactPrimaryActionInfo.PhotoInfo;
-import com.android.dialer.contactphoto.NumberAttributeConverter;
-import com.android.dialer.lettertile.LetterTileDrawable;
 import com.android.dialer.voicemail.model.VoicemailEntry;
 
 /** Configures the primary action row (top row) for theottom sheet for the Voicemail Tab */
@@ -38,15 +36,8 @@
     return ContactPrimaryActionInfo.builder()
         .setNumber(voicemailEntry.number())
         .setPhotoInfo(
-            PhotoInfo.builder()
-                .setPhotoId(voicemailEntry.numberAttributes().getPhotoId())
-                .setPhotoUri(
-                    NumberAttributeConverter.getPhotoUri(
-                        context, voicemailEntry.numberAttributes()))
-                .setIsVideo(false)
-                .setContactType(
-                    LetterTileDrawable.TYPE_DEFAULT) // TODO(uabdullah): Use proper type.
-                .setDisplayName(voicemailEntry.numberAttributes().getName())
+            NumberAttributesConverter.toPhotoInfoBuilder(voicemailEntry.numberAttributes())
+                .setFormattedNumber(voicemailEntry.formattedNumber())
                 .build())
         .setPrimaryText(buildPrimaryVoicemailText(context, voicemailEntry))
         .setSecondaryText(buildSecondaryVoicemailText(voicemailEntry))
diff --git a/packages.mk b/packages.mk
index 3a47e26..bc98ef5 100644
--- a/packages.mk
+++ b/packages.mk
@@ -33,6 +33,7 @@
 	com.android.dialer.dialpadview \
 	com.android.dialer.enrichedcall.simulator \
 	com.android.dialer.feedback \
+	com.android.dialer.glidephotomanager.impl \
 	com.android.dialer.interactions \
 	com.android.dialer.lettertile \
 	com.android.dialer.location \