Merge changes I2e091371,I4e0bc1c6,I24063ee4

* changes:
  Implement PhoneLookup for CP2 remote contacts
  Merge PhoneLookupDataSource results into a proto in annotated call log.
  Attempt to place call even call permission is missing
diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
index f90d657..da93ff8 100644
--- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
+++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
@@ -38,15 +38,10 @@
           + " ("
           + (AnnotatedCallLog._ID + " integer primary key, ")
           + (AnnotatedCallLog.TIMESTAMP + " integer, ")
-          + (AnnotatedCallLog.NAME + " text, ")
           + (AnnotatedCallLog.NUMBER + " blob, ")
           + (AnnotatedCallLog.FORMATTED_NUMBER + " text, ")
-          + (AnnotatedCallLog.PHOTO_URI + " text, ")
-          + (AnnotatedCallLog.PHOTO_ID + " integer, ")
-          + (AnnotatedCallLog.LOOKUP_URI + " text, ")
           + (AnnotatedCallLog.DURATION + " integer, ")
           + (AnnotatedCallLog.DATA_USAGE + " integer, ")
-          + (AnnotatedCallLog.NUMBER_TYPE_LABEL + " text, ")
           + (AnnotatedCallLog.IS_READ + " integer, ")
           + (AnnotatedCallLog.NEW + " integer, ")
           + (AnnotatedCallLog.GEOCODED_LOCATION + " text, ")
@@ -55,13 +50,10 @@
           + (AnnotatedCallLog.PHONE_ACCOUNT_LABEL + " text, ")
           + (AnnotatedCallLog.PHONE_ACCOUNT_COLOR + " integer, ")
           + (AnnotatedCallLog.FEATURES + " integer, ")
-          + (AnnotatedCallLog.IS_BUSINESS + " integer, ")
-          + (AnnotatedCallLog.IS_VOICEMAIL + " integer, ")
           + (AnnotatedCallLog.TRANSCRIPTION + " integer, ")
           + (AnnotatedCallLog.VOICEMAIL_URI + " text, ")
           + (AnnotatedCallLog.CALL_TYPE + " integer, ")
-          + (AnnotatedCallLog.CAN_REPORT_AS_INVALID_NUMBER + " integer, ")
-          + (AnnotatedCallLog.CP2_INFO_INCOMPLETE + " integer")
+          + (AnnotatedCallLog.NUMBER_ATTRIBUTES + " blob ")
           + ");";
 
   /** Deletes all but the first maxRows rows (by timestamp) to keep the table a manageable size. */
diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
index 9161d60..d382517 100644
--- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
+++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
@@ -42,15 +42,6 @@
     String TIMESTAMP = "timestamp";
 
     /**
-     * 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
-     */
-    String NAME = "name";
-
-    /**
      * The phone number called or number the call came from, encoded as a {@link
      * com.android.dialer.DialerPhoneNumber} proto. The number may be empty if it was an incoming
      * call and the number was unknown.
@@ -68,38 +59,6 @@
     String FORMATTED_NUMBER = "formatted_number";
 
     /**
-     * A photo URI for the contact to display in the call log list view.
-     *
-     * <p>TYPE: TEXT
-     */
-    String PHOTO_URI = "photo_uri";
-
-    /**
-     * 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";
-
-    /**
-     * The contacts provider lookup URI for the contact associated with the call.
-     *
-     * <p>TYPE: TEXT
-     */
-    String LOOKUP_URI = "lookup_uri";
-
-    // TODO(zachh): If we need to support photos other than local contacts', add a (blob?) column.
-
-    /**
-     * The number type as a string to be displayed to the user, for example "Home" or "Mobile".
-     *
-     * <p>This column should be updated for the appropriate language when the locale changes.
-     *
-     * <p>TYPE: TEXT
-     */
-    String NUMBER_TYPE_LABEL = "number_type_label";
-
-    /**
      * See {@link android.provider.CallLog.Calls#IS_READ}.
      *
      * <p>TYPE: INTEGER (boolean)
@@ -156,20 +115,13 @@
     String FEATURES = "features";
 
     /**
-     * True if a caller ID data source informed us that this is a business number. This is used to
-     * determine if a generic business avatar should be shown vs. a generic person avatar.
+     * Additional attributes about the number.
      *
-     * <p>TYPE: INTEGER (boolean)
-     */
-    String IS_BUSINESS = "is_business";
-
-    /**
-     * True if this was a call to voicemail. This is used to determine if the voicemail avatar
-     * should be displayed.
+     * <p>TYPE: BLOB
      *
-     * <p>TYPE: INTEGER (boolean)
+     * @see com.android.dialer.calllog.model.NumberAttributes
      */
-    String IS_VOICEMAIL = "is_voicemail";
+    String NUMBER_ATTRIBUTES = "number_attributes";
 
     /**
      * Copied from {@link android.provider.CallLog.Calls#TYPE}.
@@ -178,31 +130,12 @@
      */
     String CALL_TYPE = "call_type";
 
-    /**
-     * True if the number can be reported as invalid.
-     *
-     * <p>TYPE: INTEGER (boolean)
-     */
-    String CAN_REPORT_AS_INVALID_NUMBER = "can_report_as_invalid_number";
-
-    /**
-     * True if the CP2 information is incomplete and needs to be queried at display time.
-     *
-     * <p>TYPE: INTEGER (boolean)
-     */
-    String CP2_INFO_INCOMPLETE = "cp2_info_incomplete";
-
     String[] ALL_COMMON_COLUMNS =
         new String[] {
           _ID,
           TIMESTAMP,
-          NAME,
           NUMBER,
           FORMATTED_NUMBER,
-          PHOTO_URI,
-          PHOTO_ID,
-          LOOKUP_URI,
-          NUMBER_TYPE_LABEL,
           IS_READ,
           NEW,
           GEOCODED_LOCATION,
@@ -211,11 +144,8 @@
           PHONE_ACCOUNT_LABEL,
           PHONE_ACCOUNT_COLOR,
           FEATURES,
-          IS_BUSINESS,
-          IS_VOICEMAIL,
+          NUMBER_ATTRIBUTES,
           CALL_TYPE,
-          CAN_REPORT_AS_INVALID_NUMBER,
-          CP2_INFO_INCOMPLETE
         };
   }
 
diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
index 30461a4..b73c169 100644
--- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
@@ -28,6 +28,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.NumberAttributes;
 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
 import com.android.dialer.calllog.datasources.CallLogDataSource;
 import com.android.dialer.calllog.datasources.CallLogMutations;
@@ -286,13 +287,7 @@
   @Override
   public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) {
     return new RowCombiner(individualRowsSortedByTimestampDesc)
-        .useMostRecentString(AnnotatedCallLog.NAME)
-        .useMostRecentString(AnnotatedCallLog.NUMBER_TYPE_LABEL)
-        .useMostRecentString(AnnotatedCallLog.PHOTO_URI)
-        .useMostRecentLong(AnnotatedCallLog.PHOTO_ID)
-        .useMostRecentString(AnnotatedCallLog.LOOKUP_URI)
-        .useMostRecentInt(AnnotatedCallLog.CAN_REPORT_AS_INVALID_NUMBER)
-        .useMostRecentInt(AnnotatedCallLog.CP2_INFO_INCOMPLETE)
+        .useMostRecentBlob(AnnotatedCallLog.NUMBER_ATTRIBUTES)
         .combine();
   }
 
@@ -584,19 +579,20 @@
   }
 
   private 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));
-    contentValues.put(
-        AnnotatedCallLog.CAN_REPORT_AS_INVALID_NUMBER,
-        PhoneLookupSelector.canReportAsInvalidNumber(phoneLookupInfo));
-    contentValues.put(
-        AnnotatedCallLog.CP2_INFO_INCOMPLETE, phoneLookupInfo.getCp2LocalInfo().getIsIncomplete());
+        AnnotatedCallLog.NUMBER_ATTRIBUTES,
+        NumberAttributes.newBuilder()
+            .setName(phoneLookupSelector.selectName(phoneLookupInfo))
+            .setPhotoUri(phoneLookupSelector.selectPhotoUri(phoneLookupInfo))
+            .setPhotoId(phoneLookupSelector.selectPhotoId(phoneLookupInfo))
+            .setLookupUri(phoneLookupSelector.selectLookupUri(phoneLookupInfo))
+            .setNumberTypeLabel(phoneLookupSelector.selectNumberLabel(phoneLookupInfo))
+            .setIsBusiness(phoneLookupSelector.selectIsBusiness(phoneLookupInfo))
+            .setIsVoicemail(phoneLookupSelector.selectIsVoicemail(phoneLookupInfo))
+            .setCanReportAsInvalidNumber(
+                phoneLookupSelector.canReportAsInvalidNumber(phoneLookupInfo))
+            .setIsCp2InfoIncomplete(phoneLookupSelector.selectIsCp2InfoIncomplete(phoneLookupInfo))
+            .build()
+            .toByteArray());
   }
 }
diff --git a/java/com/android/dialer/calllog/model/CoalescedRow.java b/java/com/android/dialer/calllog/model/CoalescedRow.java
index 2520d99..312c29c 100644
--- a/java/com/android/dialer/calllog/model/CoalescedRow.java
+++ b/java/com/android/dialer/calllog/model/CoalescedRow.java
@@ -20,6 +20,7 @@
 import android.support.annotation.Nullable;
 import com.android.dialer.CoalescedIds;
 import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.NumberAttributes;
 import com.google.auto.value.AutoValue;
 
 /** Data class containing the contents of a row from the CoalescedAnnotatedCallLog. */
@@ -31,16 +32,12 @@
         .setId(0)
         .setTimestamp(0)
         .setNumber(DialerPhoneNumber.getDefaultInstance())
-        .setPhotoId(0)
         .setIsRead(false)
         .setIsNew(false)
         .setPhoneAccountColor(0)
         .setFeatures(0)
-        .setIsBusiness(false)
-        .setIsVoicemail(false)
         .setCallType(0)
-        .setCanReportAsInvalidNumber(false)
-        .setCp2InfoIncomplete(false)
+        .setNumberAttributes(NumberAttributes.getDefaultInstance())
         .setCoalescedIds(CoalescedIds.getDefaultInstance());
   }
 
@@ -53,22 +50,8 @@
   public abstract DialerPhoneNumber number();
 
   @Nullable
-  public abstract String name();
-
-  @Nullable
   public abstract String formattedNumber();
 
-  @Nullable
-  public abstract String photoUri();
-
-  public abstract long photoId();
-
-  @Nullable
-  public abstract String lookupUri();
-
-  @Nullable
-  public abstract String numberTypeLabel();
-
   public abstract boolean isRead();
 
   public abstract boolean isNew();
@@ -90,15 +73,9 @@
 
   public abstract int features();
 
-  public abstract boolean isBusiness();
-
-  public abstract boolean isVoicemail();
-
   public abstract int callType();
 
-  public abstract boolean canReportAsInvalidNumber();
-
-  public abstract boolean cp2InfoIncomplete();
+  public abstract NumberAttributes numberAttributes();
 
   public abstract CoalescedIds coalescedIds();
 
@@ -112,18 +89,8 @@
 
     public abstract Builder setNumber(DialerPhoneNumber number);
 
-    public abstract Builder setName(@Nullable String name);
-
     public abstract Builder setFormattedNumber(@Nullable String formattedNumber);
 
-    public abstract Builder setPhotoUri(@Nullable String photoUri);
-
-    public abstract Builder setPhotoId(long photoId);
-
-    public abstract Builder setLookupUri(@Nullable String lookupUri);
-
-    public abstract Builder setNumberTypeLabel(@Nullable String numberTypeLabel);
-
     public abstract Builder setIsRead(boolean isRead);
 
     public abstract Builder setIsNew(boolean isNew);
@@ -141,15 +108,9 @@
 
     public abstract Builder setFeatures(int features);
 
-    public abstract Builder setIsBusiness(boolean isBusiness);
-
-    public abstract Builder setIsVoicemail(boolean isVoicemail);
-
     public abstract Builder setCallType(int callType);
 
-    public abstract Builder setCanReportAsInvalidNumber(boolean canReportAsInvalidNumber);
-
-    public abstract Builder setCp2InfoIncomplete(boolean cp2InfoIncomplete);
+    public abstract Builder setNumberAttributes(NumberAttributes numberAttributes);
 
     public abstract Builder setCoalescedIds(CoalescedIds coalescedIds);
 
diff --git a/java/com/android/dialer/calllog/model/number_attributes.proto b/java/com/android/dialer/calllog/model/number_attributes.proto
new file mode 100644
index 0000000..64f8f18
--- /dev/null
+++ b/java/com/android/dialer/calllog/model/number_attributes.proto
@@ -0,0 +1,61 @@
+// 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
+
+syntax = "proto2";
+
+option java_package = "com.android.dialer";
+option java_multiple_files = true;
+option optimize_for = LITE_RUNTIME;
+
+
+package com.android.dialer;
+
+// Information related to the phone number of the call.
+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
+  // name display preferences change, this field should be rewritten.
+  optional string name = 1;
+
+  // A photo URI for the contact to display in the call log list view.
+  optional string photo_uri = 2;
+
+  // A photo ID (from the contacts provider) for the contact to display in the
+  // call log list view.
+  optional int64 photo_id = 3;
+
+  // TODO(zachh): If we need to support photos other than local contacts', add a
+  // (blob?) column.
+
+  // The contacts provider lookup URI for the contact associated with the call.
+  optional string lookup_uri = 4;
+
+  // The number type as a string to be displayed to the user, for example "Home"
+  // or "Mobile". This column should be updated for the appropriate language
+  // when the locale changes.
+  optional string number_type_label = 5;
+
+  // The number is a call to a business from nearby places lookup.
+  optional bool is_business = 6;
+
+  // The number is a call to the voicemail inbox.
+  optional bool is_voicemail = 7;
+
+  // Can the number be reported as invalid through People API
+  optional bool can_report_as_invalid_number = 8;
+
+  // True if the CP2 information is incomplete and needs to be queried at
+  // display time.
+  optional bool is_cp2_info_incomplete = 9;
+}
\ No newline at end of file
diff --git a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
index 5c0ce28..d72544b 100644
--- a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
+++ b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java
@@ -21,6 +21,7 @@
 import android.support.v4.content.CursorLoader;
 import com.android.dialer.CoalescedIds;
 import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.NumberAttributes;
 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog;
 import com.android.dialer.calllog.model.CoalescedRow;
 import com.google.protobuf.InvalidProtocolBufferException;
@@ -31,27 +32,19 @@
   // Indexes for CoalescedAnnotatedCallLog.ALL_COLUMNS
   private static final int ID = 0;
   private static final int TIMESTAMP = 1;
-  private static final int NAME = 2;
-  private static final int NUMBER = 3;
-  private static final int FORMATTED_NUMBER = 4;
-  private static final int PHOTO_URI = 5;
-  private static final int PHOTO_ID = 6;
-  private static final int LOOKUP_URI = 7;
-  private static final int NUMBER_TYPE_LABEL = 8;
-  private static final int IS_READ = 9;
-  private static final int NEW = 10;
-  private static final int GEOCODED_LOCATION = 11;
-  private static final int PHONE_ACCOUNT_COMPONENT_NAME = 12;
-  private static final int PHONE_ACCOUNT_ID = 13;
-  private static final int PHONE_ACCOUNT_LABEL = 14;
-  private static final int PHONE_ACCOUNT_COLOR = 15;
-  private static final int FEATURES = 16;
-  private static final int IS_BUSINESS = 17;
-  private static final int IS_VOICEMAIL = 18;
-  private static final int CALL_TYPE = 19;
-  private static final int CAN_REPORT_AS_INVALID_NUMBER = 20;
-  private static final int CP2_INFO_INCOMPLETE = 21;
-  private static final int COALESCED_IDS = 22;
+  private static final int NUMBER = 2;
+  private static final int FORMATTED_NUMBER = 3;
+  private static final int IS_READ = 4;
+  private static final int NEW = 5;
+  private static final int GEOCODED_LOCATION = 6;
+  private static final int PHONE_ACCOUNT_COMPONENT_NAME = 7;
+  private static final int PHONE_ACCOUNT_ID = 8;
+  private static final int PHONE_ACCOUNT_LABEL = 9;
+  private static final int PHONE_ACCOUNT_COLOR = 10;
+  private static final int FEATURES = 11;
+  private static final int NUMBER_ATTRIBUTES = 12;
+  private static final int CALL_TYPE = 13;
+  private static final int COALESCED_IDS = 14;
 
   CoalescedAnnotatedCallLogCursorLoader(Context context) {
     // CoalescedAnnotatedCallLog requires that PROJECTION be ALL_COLUMNS and the following params be
@@ -81,16 +74,18 @@
       throw new IllegalStateException("Couldn't parse CoalescedIds bytes");
     }
 
+    NumberAttributes numberAttributes;
+    try {
+      numberAttributes = NumberAttributes.parseFrom(cursor.getBlob(NUMBER_ATTRIBUTES));
+    } catch (InvalidProtocolBufferException e) {
+      throw new IllegalStateException("Couldn't parse NumberAttributes bytes");
+    }
+
     return CoalescedRow.builder()
         .setId(cursor.getInt(ID))
         .setTimestamp(cursor.getLong(TIMESTAMP))
-        .setName(cursor.getString(NAME))
         .setNumber(number)
         .setFormattedNumber(cursor.getString(FORMATTED_NUMBER))
-        .setPhotoUri(cursor.getString(PHOTO_URI))
-        .setPhotoId(cursor.getLong(PHOTO_ID))
-        .setLookupUri(cursor.getString(LOOKUP_URI))
-        .setNumberTypeLabel(cursor.getString(NUMBER_TYPE_LABEL))
         .setIsRead(cursor.getInt(IS_READ) == 1)
         .setIsNew(cursor.getInt(NEW) == 1)
         .setGeocodedLocation(cursor.getString(GEOCODED_LOCATION))
@@ -99,11 +94,8 @@
         .setPhoneAccountLabel(cursor.getString(PHONE_ACCOUNT_LABEL))
         .setPhoneAccountColor(cursor.getInt(PHONE_ACCOUNT_COLOR))
         .setFeatures(cursor.getInt(FEATURES))
-        .setIsBusiness(cursor.getInt(IS_BUSINESS) == 1)
-        .setIsVoicemail(cursor.getInt(IS_VOICEMAIL) == 1)
         .setCallType(cursor.getInt(CALL_TYPE))
-        .setCanReportAsInvalidNumber(cursor.getInt(CAN_REPORT_AS_INVALID_NUMBER) == 1)
-        .setCp2InfoIncomplete(cursor.getInt(CP2_INFO_INCOMPLETE) == 1)
+        .setNumberAttributes(numberAttributes)
         .setCoalescedIds(coalescedIds)
         .build();
   }
diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
index 7482efd..5b526b4 100644
--- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
+++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java
@@ -20,6 +20,7 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.CallLog.Calls;
+import android.support.annotation.Nullable;
 import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
 import android.view.View;
@@ -134,13 +135,18 @@
     ContactPhotoManager.getInstance(context)
         .loadDialerThumbnailOrPhoto(
             quickContactBadge,
-            TextUtils.isEmpty(row.lookupUri()) ? null : Uri.parse(row.lookupUri()),
-            row.photoId(),
-            TextUtils.isEmpty(row.photoUri()) ? null : Uri.parse(row.photoUri()),
+            parseUri(row.numberAttributes().getLookupUri()),
+            row.numberAttributes().getPhotoId(),
+            parseUri(row.numberAttributes().getPhotoUri()),
             CallLogEntryText.buildPrimaryText(context, row).toString(),
             CallLogContactTypes.getContactType(row));
   }
 
+  @Nullable
+  private static Uri parseUri(@Nullable String uri) {
+    return TextUtils.isEmpty(uri) ? null : Uri.parse(uri);
+  }
+
   private void setPrimaryCallTypes(CoalescedRow row) {
     primaryCallTypeIconsView.setShowHd(
         (row.features() & Calls.FEATURES_HD_CALL) == Calls.FEATURES_HD_CALL);
diff --git a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
index 74b7def..2cfe0b4 100644
--- a/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
+++ b/java/com/android/dialer/calllog/ui/RealtimeRowProcessor.java
@@ -19,6 +19,7 @@
 import android.support.annotation.MainThread;
 import android.util.ArrayMap;
 import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.NumberAttributes;
 import com.android.dialer.calllog.model.CoalescedRow;
 import com.android.dialer.common.concurrent.Annotations.Ui;
 import com.android.dialer.phonelookup.PhoneLookupInfo;
@@ -68,7 +69,7 @@
   @MainThread
   ListenableFuture<Optional<CoalescedRow>> applyRealtimeProcessing(final CoalescedRow row) {
     // Cp2LocalPhoneLookup can not always efficiently process all rows.
-    if (!row.cp2InfoIncomplete()) {
+    if (!row.numberAttributes().getIsCp2InfoIncomplete()) {
       return Futures.immediateFuture(Optional.absent());
     }
 
@@ -97,11 +98,18 @@
     PhoneLookupInfo phoneLookupInfo = PhoneLookupInfo.newBuilder().setCp2LocalInfo(cp2Info).build();
     // It is safe to overwrite any existing data because CP2 always has highest priority.
     return row.toBuilder()
-        .setName(phoneLookupSelector.selectName(phoneLookupInfo))
-        .setPhotoUri(phoneLookupSelector.selectPhotoUri(phoneLookupInfo))
-        .setPhotoId(phoneLookupSelector.selectPhotoId(phoneLookupInfo))
-        .setLookupUri(phoneLookupSelector.selectLookupUri(phoneLookupInfo))
-        .setNumberTypeLabel(phoneLookupSelector.selectNumberLabel(phoneLookupInfo))
+        .setNumberAttributes(
+            NumberAttributes.newBuilder()
+                .setName(phoneLookupSelector.selectName(phoneLookupInfo))
+                .setPhotoUri(phoneLookupSelector.selectPhotoUri(phoneLookupInfo))
+                .setPhotoId(phoneLookupSelector.selectPhotoId(phoneLookupInfo))
+                .setLookupUri(phoneLookupSelector.selectLookupUri(phoneLookupInfo))
+                .setNumberTypeLabel(phoneLookupSelector.selectNumberLabel(phoneLookupInfo))
+                .setIsBusiness(phoneLookupSelector.selectIsBusiness(phoneLookupInfo))
+                .setIsVoicemail(phoneLookupSelector.selectIsVoicemail(phoneLookupInfo))
+                .setCanReportAsInvalidNumber(
+                    phoneLookupSelector.canReportAsInvalidNumber(phoneLookupInfo))
+                .build())
         .build();
   }
 }
diff --git a/java/com/android/dialer/calllog/ui/menu/Modules.java b/java/com/android/dialer/calllog/ui/menu/Modules.java
index 3d667fc..92dd411 100644
--- a/java/com/android/dialer/calllog/ui/menu/Modules.java
+++ b/java/com/android/dialer/calllog/ui/menu/Modules.java
@@ -45,7 +45,11 @@
 
     maybeAddModuleForVideoOrAudioCall(context, modules, row);
     SharedModules.maybeAddModuleForAddingToContacts(
-        context, modules, row.number(), row.name(), row.lookupUri());
+        context,
+        modules,
+        row.number(),
+        row.numberAttributes().getName(),
+        row.numberAttributes().getLookupUri());
 
     String originalNumber = row.number().getRawInput().getNumber();
     SharedModules.maybeAddModuleForSendingTextMessage(context, modules, originalNumber);
@@ -98,8 +102,8 @@
 
   private static void addModuleForAccessingCallDetails(
       Context context, List<ContactActionModule> modules, CoalescedRow row) {
-    boolean canReportAsInvalidNumber = row.canReportAsInvalidNumber();
-    boolean canSupportAssistedDialing = !TextUtils.isEmpty(row.lookupUri());
+    boolean canReportAsInvalidNumber = row.numberAttributes().getCanReportAsInvalidNumber();
+    boolean canSupportAssistedDialing = !TextUtils.isEmpty(row.numberAttributes().getLookupUri());
 
     modules.add(
         new IntentModule(
@@ -122,21 +126,21 @@
         DialerContact.newBuilder()
             .setNumber(originalNumber)
             .setContactType(LetterTileDrawable.TYPE_DEFAULT) // TODO(zachh): Use proper type.
-            .setPhotoId(row.photoId());
+            .setPhotoId(row.numberAttributes().getPhotoId());
 
-    if (!TextUtils.isEmpty(row.name())) {
-      dialerContactBuilder.setNameOrNumber(row.name());
+    if (!TextUtils.isEmpty(row.numberAttributes().getName())) {
+      dialerContactBuilder.setNameOrNumber(row.numberAttributes().getName());
     } else if (!TextUtils.isEmpty(originalNumber)) {
       dialerContactBuilder.setNameOrNumber(originalNumber);
     }
-    if (row.numberTypeLabel() != null) {
-      dialerContactBuilder.setNumberLabel(row.numberTypeLabel());
+    if (row.numberAttributes().hasNumberTypeLabel()) {
+      dialerContactBuilder.setNumberLabel(row.numberAttributes().getNumberTypeLabel());
     }
-    if (row.photoUri() != null) {
-      dialerContactBuilder.setPhotoUri(row.photoUri());
+    if (row.numberAttributes().hasPhotoUri()) {
+      dialerContactBuilder.setPhotoUri(row.numberAttributes().getPhotoUri());
     }
-    if (row.lookupUri() != null) {
-      dialerContactBuilder.setContactUri(row.lookupUri());
+    if (row.numberAttributes().hasLookupUri()) {
+      dialerContactBuilder.setContactUri(row.numberAttributes().getLookupUri());
     }
     if (row.formattedNumber() != null) {
       dialerContactBuilder.setDisplayNumber(row.formattedNumber());
diff --git a/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java b/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
index faedc8f..c7126e9 100644
--- a/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
+++ b/java/com/android/dialer/calllog/ui/menu/PrimaryAction.java
@@ -34,9 +34,9 @@
         .setNumber(row.number())
         .setPhotoInfo(
             PhotoInfo.builder()
-                .setPhotoId(row.photoId())
-                .setPhotoUri(row.photoUri())
-                .setLookupUri(row.lookupUri())
+                .setPhotoId(row.numberAttributes().getPhotoId())
+                .setPhotoUri(row.numberAttributes().getPhotoUri())
+                .setLookupUri(row.numberAttributes().getLookupUri())
                 .setIsVideo((row.features() & Calls.FEATURES_VIDEO) == Calls.FEATURES_VIDEO)
                 .setContactType(CallLogContactTypes.getContactType(row))
                 .setDisplayName(primaryText.toString())
diff --git a/java/com/android/dialer/calllogutils/CallLogEntryText.java b/java/com/android/dialer/calllogutils/CallLogEntryText.java
index 873f9eb..1df4458 100644
--- a/java/com/android/dialer/calllogutils/CallLogEntryText.java
+++ b/java/com/android/dialer/calllogutils/CallLogEntryText.java
@@ -38,8 +38,8 @@
    */
   public static CharSequence buildPrimaryText(Context context, CoalescedRow row) {
     StringBuilder primaryText = new StringBuilder();
-    if (!TextUtils.isEmpty(row.name())) {
-      primaryText.append(row.name());
+    if (!TextUtils.isEmpty(row.numberAttributes().getName())) {
+      primaryText.append(row.numberAttributes().getName());
     } else if (!TextUtils.isEmpty(row.formattedNumber())) {
       primaryText.append(row.formattedNumber());
     } else {
@@ -98,7 +98,7 @@
      */
     StringBuilder secondaryText = secondaryTextPrefix(context, row);
 
-    if (TextUtils.isEmpty(row.name())) {
+    if (TextUtils.isEmpty(row.numberAttributes().getName())) {
       // If the name is empty the number is shown as the primary text and there's nothing to add.
       return secondaryText.toString();
     }
@@ -128,7 +128,7 @@
       // TODO(zachh): Add "Duo" prefix?
       secondaryText.append(context.getText(R.string.new_call_log_video));
     }
-    String numberTypeLabel = row.numberTypeLabel();
+    String numberTypeLabel = row.numberAttributes().getNumberTypeLabel();
     if (!TextUtils.isEmpty(numberTypeLabel)) {
       if (secondaryText.length() > 0) {
         secondaryText.append(", ");
diff --git a/java/com/android/dialer/phonelookup/PhoneLookupModule.java b/java/com/android/dialer/phonelookup/PhoneLookupModule.java
index 5e215ba..e93ca0f 100644
--- a/java/com/android/dialer/phonelookup/PhoneLookupModule.java
+++ b/java/com/android/dialer/phonelookup/PhoneLookupModule.java
@@ -19,6 +19,7 @@
 import com.android.dialer.phonelookup.blockednumber.DialerBlockedNumberPhoneLookup;
 import com.android.dialer.phonelookup.composite.CompositePhoneLookup;
 import com.android.dialer.phonelookup.cp2.Cp2LocalPhoneLookup;
+import com.android.dialer.phonelookup.cp2.Cp2RemotePhoneLookup;
 import com.google.common.collect.ImmutableList;
 import dagger.Module;
 import dagger.Provides;
@@ -31,8 +32,10 @@
   @SuppressWarnings({"unchecked", "rawtype"})
   static ImmutableList<PhoneLookup> providePhoneLookupList(
       Cp2LocalPhoneLookup cp2LocalPhoneLookup,
+      Cp2RemotePhoneLookup cp2RemotePhoneLookup,
       DialerBlockedNumberPhoneLookup dialerBlockedNumberPhoneLookup) {
-    return ImmutableList.of(cp2LocalPhoneLookup, dialerBlockedNumberPhoneLookup);
+    return ImmutableList.of(
+        cp2LocalPhoneLookup, cp2RemotePhoneLookup, dialerBlockedNumberPhoneLookup);
   }
 
   @Provides
diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java
index 727977f..8879fee 100644
--- a/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java
+++ b/java/com/android/dialer/phonelookup/cp2/Cp2LocalPhoneLookup.java
@@ -66,45 +66,7 @@
 public final class Cp2LocalPhoneLookup implements PhoneLookup<Cp2Info> {
 
   private static final String PREF_LAST_TIMESTAMP_PROCESSED =
-      "cp2PhoneLookupLastTimestampProcessed";
-
-  /** Projection for performing batch lookups based on E164 numbers using the PHONE table. */
-  private static final String[] PHONE_PROJECTION =
-      new String[] {
-        Phone.DISPLAY_NAME_PRIMARY, // 0
-        Phone.PHOTO_THUMBNAIL_URI, // 1
-        Phone.PHOTO_ID, // 2
-        Phone.TYPE, // 3
-        Phone.LABEL, // 4
-        Phone.NORMALIZED_NUMBER, // 5
-        Phone.CONTACT_ID, // 6
-        Phone.LOOKUP_KEY // 7
-      };
-
-  /**
-   * Projection for performing individual lookups of non-E164 numbers using the PHONE_LOOKUP table.
-   */
-  private static final String[] PHONE_LOOKUP_PROJECTION =
-      new String[] {
-        ContactsContract.PhoneLookup.DISPLAY_NAME_PRIMARY, // 0
-        ContactsContract.PhoneLookup.PHOTO_THUMBNAIL_URI, // 1
-        ContactsContract.PhoneLookup.PHOTO_ID, // 2
-        ContactsContract.PhoneLookup.TYPE, // 3
-        ContactsContract.PhoneLookup.LABEL, // 4
-        ContactsContract.PhoneLookup.NORMALIZED_NUMBER, // 5
-        ContactsContract.PhoneLookup.CONTACT_ID, // 6
-        ContactsContract.PhoneLookup.LOOKUP_KEY // 7
-      };
-
-  // The following indexes should match both PHONE_PROJECTION and PHONE_LOOKUP_PROJECTION above.
-  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_TYPE_INDEX = 3;
-  private static final int CP2_INFO_LABEL_INDEX = 4;
-  private static final int CP2_INFO_NORMALIZED_NUMBER_INDEX = 5;
-  private static final int CP2_INFO_CONTACT_ID_INDEX = 6;
-  private static final int CP2_INFO_LOOKUP_KEY_INDEX = 7;
+      "cp2LocalPhoneLookupLastTimestampProcessed";
 
   // We cannot efficiently process invalid numbers because batch queries cannot be constructed which
   // accomplish the necessary loose matching. We'll attempt to process a limited number of them,
@@ -146,14 +108,15 @@
     // ensure consistency when the batch methods are used to update data.
     try (Cursor cursor =
         e164.isPresent()
-            ? queryPhoneTableBasedOnE164(PHONE_PROJECTION, ImmutableSet.of(e164.get()))
-            : queryPhoneLookup(PHONE_LOOKUP_PROJECTION, rawNumber)) {
+            ? queryPhoneTableBasedOnE164(
+                Cp2Projections.getProjectionForPhoneTable(), ImmutableSet.of(e164.get()))
+            : queryPhoneLookup(Cp2Projections.getProjectionForPhoneLookupTable(), rawNumber)) {
       if (cursor == null) {
         LogUtil.w("Cp2LocalPhoneLookup.lookupInternal", "null cursor");
         return Cp2Info.getDefaultInstance();
       }
       while (cursor.moveToNext()) {
-        cp2ContactInfos.add(buildCp2ContactInfoFromPhoneCursor(appContext, cursor));
+        cp2ContactInfos.add(Cp2Projections.buildCp2ContactInfoFromCursor(appContext, cursor));
       }
     }
     return Cp2Info.newBuilder().addAllCp2ContactInfo(cp2ContactInfos).build();
@@ -174,13 +137,14 @@
             return Cp2Info.getDefaultInstance();
           }
           Set<Cp2ContactInfo> cp2ContactInfos = new ArraySet<>();
-          try (Cursor cursor = queryPhoneLookup(PHONE_LOOKUP_PROJECTION, rawNumber)) {
+          try (Cursor cursor =
+              queryPhoneLookup(Cp2Projections.getProjectionForPhoneLookupTable(), rawNumber)) {
             if (cursor == null) {
               LogUtil.w("Cp2LocalPhoneLookup.lookup", "null cursor");
               return Cp2Info.getDefaultInstance();
             }
             while (cursor.moveToNext()) {
-              cp2ContactInfos.add(buildCp2ContactInfoFromPhoneCursor(appContext, cursor));
+              cp2ContactInfos.add(Cp2Projections.buildCp2ContactInfoFromCursor(appContext, cursor));
             }
           }
           return Cp2Info.newBuilder().addAllCp2ContactInfo(cp2ContactInfos).build();
@@ -767,18 +731,21 @@
           if (e164Numbers.isEmpty()) {
             return cp2ContactInfosByNumber;
           }
-          try (Cursor cursor = queryPhoneTableBasedOnE164(PHONE_PROJECTION, e164Numbers)) {
+          try (Cursor cursor =
+              queryPhoneTableBasedOnE164(
+                  Cp2Projections.getProjectionForPhoneTable(), e164Numbers)) {
             if (cursor == null) {
               LogUtil.w("Cp2LocalPhoneLookup.batchQueryForValidNumbers", "null cursor");
             } else {
               while (cursor.moveToNext()) {
-                String e164Number = cursor.getString(CP2_INFO_NORMALIZED_NUMBER_INDEX);
+                String e164Number = Cp2Projections.getNormalizedNumberFromCursor(cursor);
                 Set<Cp2ContactInfo> cp2ContactInfos = cp2ContactInfosByNumber.get(e164Number);
                 if (cp2ContactInfos == null) {
                   cp2ContactInfos = new ArraySet<>();
                   cp2ContactInfosByNumber.put(e164Number, cp2ContactInfos);
                 }
-                cp2ContactInfos.add(buildCp2ContactInfoFromPhoneCursor(appContext, cursor));
+                cp2ContactInfos.add(
+                    Cp2Projections.buildCp2ContactInfoFromCursor(appContext, cursor));
               }
             }
           }
@@ -794,12 +761,14 @@
           if (invalidNumber.isEmpty()) {
             return cp2ContactInfos;
           }
-          try (Cursor cursor = queryPhoneLookup(PHONE_LOOKUP_PROJECTION, invalidNumber)) {
+          try (Cursor cursor =
+              queryPhoneLookup(Cp2Projections.getProjectionForPhoneLookupTable(), invalidNumber)) {
             if (cursor == null) {
               LogUtil.w("Cp2LocalPhoneLookup.individualQueryForInvalidNumber", "null cursor");
             } else {
               while (cursor.moveToNext()) {
-                cp2ContactInfos.add(buildCp2ContactInfoFromPhoneCursor(appContext, cursor));
+                cp2ContactInfos.add(
+                    Cp2Projections.buildCp2ContactInfoFromCursor(appContext, cursor));
               }
             }
           }
@@ -843,43 +812,6 @@
     return appContext.getContentResolver().query(uri, projection, null, null, null);
   }
 
-  /**
-   * @param cursor with projection {@link #PHONE_PROJECTION}.
-   * @return new {@link Cp2ContactInfo} based on current row of {@code cursor}.
-   */
-  private static Cp2ContactInfo buildCp2ContactInfoFromPhoneCursor(
-      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)) {
-      infoBuilder.setName(displayName);
-    }
-    if (!TextUtils.isEmpty(photoUri)) {
-      infoBuilder.setPhotoUri(photoUri);
-    }
-    if (photoId > 0) {
-      infoBuilder.setPhotoId(photoId);
-    }
-
-    // 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();
-  }
-
   /** Returns set of DialerPhoneNumbers that were associated with now deleted contacts. */
   private ListenableFuture<Set<DialerPhoneNumber>> getDeletedPhoneNumbers(
       ImmutableMap<DialerPhoneNumber, Cp2Info> existingInfoMap, long lastModified) {
diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2Projections.java b/java/com/android/dialer/phonelookup/cp2/Cp2Projections.java
new file mode 100644
index 0000000..e392999
--- /dev/null
+++ b/java/com/android/dialer/phonelookup/cp2/Cp2Projections.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.phonelookup.cp2;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.text.TextUtils;
+import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info.Cp2ContactInfo;
+
+/**
+ * A class providing projection-related functionality for {@link
+ * com.android.dialer.phonelookup.PhoneLookup} implementations for ContactsProvider2 (CP2).
+ */
+final class Cp2Projections {
+
+  // Projection for performing lookups using the PHONE table
+  private static final String[] PHONE_PROJECTION =
+      new String[] {
+        Phone.DISPLAY_NAME_PRIMARY, // 0
+        Phone.PHOTO_THUMBNAIL_URI, // 1
+        Phone.PHOTO_ID, // 2
+        Phone.TYPE, // 3
+        Phone.LABEL, // 4
+        Phone.NORMALIZED_NUMBER, // 5
+        Phone.CONTACT_ID, // 6
+        Phone.LOOKUP_KEY // 7
+      };
+
+  // Projection for performing lookups using the PHONE_LOOKUP table
+  private static final String[] PHONE_LOOKUP_PROJECTION =
+      new String[] {
+        PhoneLookup.DISPLAY_NAME_PRIMARY, // 0
+        PhoneLookup.PHOTO_THUMBNAIL_URI, // 1
+        PhoneLookup.PHOTO_ID, // 2
+        PhoneLookup.TYPE, // 3
+        PhoneLookup.LABEL, // 4
+        PhoneLookup.NORMALIZED_NUMBER, // 5
+        PhoneLookup.CONTACT_ID, // 6
+        PhoneLookup.LOOKUP_KEY // 7
+      };
+
+  // The following indexes should match both PHONE_PROJECTION and PHONE_LOOKUP_PROJECTION above.
+  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_TYPE_INDEX = 3;
+  private static final int CP2_INFO_LABEL_INDEX = 4;
+  private static final int CP2_INFO_NORMALIZED_NUMBER_INDEX = 5;
+  private static final int CP2_INFO_CONTACT_ID_INDEX = 6;
+  private static final int CP2_INFO_LOOKUP_KEY_INDEX = 7;
+
+  private Cp2Projections() {}
+
+  static String[] getProjectionForPhoneTable() {
+    return PHONE_PROJECTION;
+  }
+
+  static String[] getProjectionForPhoneLookupTable() {
+    return PHONE_LOOKUP_PROJECTION;
+  }
+
+  /**
+   * Builds a {@link Cp2ContactInfo} based on the current row of {@code cursor}, of which the
+   * projection is either {@link #PHONE_PROJECTION} or {@link #PHONE_LOOKUP_PROJECTION}.
+   */
+  static Cp2ContactInfo buildCp2ContactInfoFromCursor(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)) {
+      infoBuilder.setName(displayName);
+    }
+    if (!TextUtils.isEmpty(photoUri)) {
+      infoBuilder.setPhotoUri(photoUri);
+    }
+    if (photoId > 0) {
+      infoBuilder.setPhotoId(photoId);
+    }
+
+    // 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();
+  }
+
+  /** Returns the normalized number in the current row of {@code cursor}. */
+  static String getNormalizedNumberFromCursor(Cursor cursor) {
+    return cursor.getString(CP2_INFO_NORMALIZED_NUMBER_INDEX);
+  }
+}
diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java
new file mode 100644
index 0000000..6a46829
--- /dev/null
+++ b/java/com/android/dialer/phonelookup/cp2/Cp2RemotePhoneLookup.java
@@ -0,0 +1,248 @@
+/*
+ * 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.phonelookup.cp2;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.Directory;
+import android.support.annotation.VisibleForTesting;
+import android.telecom.Call;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.common.LogUtil;
+import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
+import com.android.dialer.common.concurrent.Annotations.LightweightExecutor;
+import com.android.dialer.inject.ApplicationContext;
+import com.android.dialer.phonelookup.PhoneLookup;
+import com.android.dialer.phonelookup.PhoneLookupInfo;
+import com.android.dialer.phonelookup.PhoneLookupInfo.Cp2Info;
+import com.android.dialer.phonenumberutil.PhoneNumberHelper;
+import com.android.dialer.telecom.TelecomCallUtil;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.ListeningExecutorService;
+import java.util.ArrayList;
+import java.util.List;
+import javax.inject.Inject;
+
+/** PhoneLookup implementation for remote contacts. */
+public final class Cp2RemotePhoneLookup implements PhoneLookup<Cp2Info> {
+
+  private final Context appContext;
+  private final ListeningExecutorService backgroundExecutorService;
+  private final ListeningExecutorService lightweightExecutorService;
+
+  @Inject
+  Cp2RemotePhoneLookup(
+      @ApplicationContext Context appContext,
+      @BackgroundExecutor ListeningExecutorService backgroundExecutorService,
+      @LightweightExecutor ListeningExecutorService lightweightExecutorService) {
+    this.appContext = appContext;
+    this.backgroundExecutorService = backgroundExecutorService;
+    this.lightweightExecutorService = lightweightExecutorService;
+  }
+
+  @Override
+  public ListenableFuture<Cp2Info> lookup(Call call) {
+    String number = TelecomCallUtil.getNumber(call);
+    if (number == null) {
+      return Futures.immediateFuture(Cp2Info.getDefaultInstance());
+    }
+
+    return Futures.transformAsync(
+        queryCp2ForRemoteDirectoryIds(),
+        remoteDirectoryIds -> queryCp2ForRemoteContact(number, remoteDirectoryIds),
+        lightweightExecutorService);
+  }
+
+  private ListenableFuture<List<Long>> queryCp2ForRemoteDirectoryIds() {
+    return backgroundExecutorService.submit(
+        () -> {
+          List<Long> remoteDirectoryIds = new ArrayList<>();
+          try (Cursor cursor =
+              appContext
+                  .getContentResolver()
+                  .query(
+                      getContentUriForDirectoryIds(),
+                      /* projection = */ new String[] {ContactsContract.Directory._ID},
+                      /* selection = */ null,
+                      /* selectionArgs = */ null,
+                      /* sortOrder = */ ContactsContract.Directory._ID)) {
+            if (cursor == null) {
+              LogUtil.e("Cp2RemotePhoneLookup.queryCp2ForDirectoryIds", "null cursor");
+              return remoteDirectoryIds;
+            }
+
+            if (!cursor.moveToFirst()) {
+              LogUtil.i("Cp2RemotePhoneLookup.queryCp2ForDirectoryIds", "empty cursor");
+              return remoteDirectoryIds;
+            }
+
+            int idColumnIndex = cursor.getColumnIndexOrThrow(ContactsContract.Directory._ID);
+            do {
+              long directoryId = cursor.getLong(idColumnIndex);
+
+              // Note that IDs of non-remote directories will be included in the result, such as
+              // android.provider.ContactsContract.Directory.DEFAULT (the default directory that
+              // represents locally stored contacts).
+              if (isRemoteDirectory(directoryId)) {
+                remoteDirectoryIds.add(cursor.getLong(idColumnIndex));
+              }
+            } while (cursor.moveToNext());
+            return remoteDirectoryIds;
+          }
+        });
+  }
+
+  private ListenableFuture<Cp2Info> queryCp2ForRemoteContact(
+      String number, List<Long> remoteDirectoryIds) {
+    if (remoteDirectoryIds.isEmpty()) {
+      return Futures.immediateFuture(Cp2Info.getDefaultInstance());
+    }
+
+    List<ListenableFuture<Cp2Info>> cp2InfoFutures = new ArrayList<>();
+    for (long remoteDirectoryId : remoteDirectoryIds) {
+      cp2InfoFutures.add(queryCp2ForRemoteContact(number, remoteDirectoryId));
+    }
+
+    return Futures.transform(
+        Futures.allAsList(cp2InfoFutures),
+        cp2InfoList -> {
+          Cp2Info.Builder cp2InfoBuilder = Cp2Info.newBuilder();
+          for (Cp2Info cp2Info : cp2InfoList) {
+            cp2InfoBuilder.addAllCp2ContactInfo(cp2Info.getCp2ContactInfoList());
+          }
+          return cp2InfoBuilder.build();
+        },
+        lightweightExecutorService);
+  }
+
+  private ListenableFuture<Cp2Info> queryCp2ForRemoteContact(
+      String number, long remoteDirectoryId) {
+    return backgroundExecutorService.submit(
+        () -> {
+          Cp2Info.Builder cp2InfoBuilder = Cp2Info.newBuilder();
+          try (Cursor cursor =
+              appContext
+                  .getContentResolver()
+                  .query(
+                      getContentUriForContacts(number, remoteDirectoryId),
+                      Cp2Projections.getProjectionForPhoneLookupTable(),
+                      /* selection = */ null,
+                      /* selectionArgs = */ null,
+                      /* sortOrder = */ null)) {
+            if (cursor == null) {
+              LogUtil.e(
+                  "Cp2RemotePhoneLookup.queryCp2ForRemoteContact",
+                  "null cursor returned when querying directory %d",
+                  remoteDirectoryId);
+              return cp2InfoBuilder.build();
+            }
+
+            if (!cursor.moveToFirst()) {
+              LogUtil.i(
+                  "Cp2RemotePhoneLookup.queryCp2ForRemoteContact",
+                  "empty cursor returned when querying directory %d",
+                  remoteDirectoryId);
+              return cp2InfoBuilder.build();
+            }
+
+            do {
+              cp2InfoBuilder.addCp2ContactInfo(
+                  Cp2Projections.buildCp2ContactInfoFromCursor(appContext, cursor));
+            } while (cursor.moveToNext());
+          }
+
+          return cp2InfoBuilder.build();
+        });
+  }
+
+  @VisibleForTesting
+  static Uri getContentUriForDirectoryIds() {
+    return VERSION.SDK_INT >= VERSION_CODES.N
+        ? ContactsContract.Directory.ENTERPRISE_CONTENT_URI
+        : ContactsContract.Directory.CONTENT_URI;
+  }
+
+  @VisibleForTesting
+  static Uri getContentUriForContacts(String number, long directoryId) {
+    Uri baseUri =
+        VERSION.SDK_INT >= VERSION_CODES.N
+            ? ContactsContract.PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI
+            : ContactsContract.PhoneLookup.CONTENT_FILTER_URI;
+
+    Uri.Builder builder =
+        baseUri
+            .buildUpon()
+            .appendPath(number)
+            .appendQueryParameter(
+                ContactsContract.PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS,
+                String.valueOf(PhoneNumberHelper.isUriNumber(number)))
+            .appendQueryParameter(
+                ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(directoryId));
+
+    return builder.build();
+  }
+
+  private static boolean isRemoteDirectory(long directoryId) {
+    return VERSION.SDK_INT >= VERSION_CODES.N
+        ? Directory.isRemoteDirectoryId(directoryId)
+        : (directoryId != Directory.DEFAULT
+            && directoryId != Directory.LOCAL_INVISIBLE
+            // Directory.ENTERPRISE_DEFAULT is the default work profile directory for locally stored
+            // contacts
+            && directoryId != Directory.ENTERPRISE_DEFAULT
+            && directoryId != Directory.ENTERPRISE_LOCAL_INVISIBLE);
+  }
+
+  @Override
+  public ListenableFuture<Boolean> isDirty(ImmutableSet<DialerPhoneNumber> phoneNumbers) {
+    return Futures.immediateFuture(false);
+  }
+
+  @Override
+  public ListenableFuture<ImmutableMap<DialerPhoneNumber, Cp2Info>> getMostRecentInfo(
+      ImmutableMap<DialerPhoneNumber, Cp2Info> existingInfoMap) {
+    return Futures.immediateFuture(existingInfoMap);
+  }
+
+  @Override
+  public void setSubMessage(PhoneLookupInfo.Builder destination, Cp2Info subMessage) {
+    destination.setCp2RemoteInfo(subMessage);
+  }
+
+  @Override
+  public Cp2Info getSubMessage(PhoneLookupInfo phoneLookupInfo) {
+    return phoneLookupInfo.getCp2RemoteInfo();
+  }
+
+  @Override
+  public ListenableFuture<Void> onSuccessfulBulkUpdate() {
+    return Futures.immediateFuture(null);
+  }
+
+  @Override
+  public void registerContentObservers(
+      Context appContext, ContentObserverCallbacks contentObserverCallbacks) {
+    // No content observer needed for remote contacts
+  }
+}
diff --git a/java/com/android/dialer/phonelookup/phone_lookup_info.proto b/java/com/android/dialer/phonelookup/phone_lookup_info.proto
index f1497bd..b5e73cc 100644
--- a/java/com/android/dialer/phonelookup/phone_lookup_info.proto
+++ b/java/com/android/dialer/phonelookup/phone_lookup_info.proto
@@ -13,29 +13,51 @@
 // to an implementation of PhoneLookup. For example, field "cp2_local_info"
 // corresponds to class Cp2LocalPhoneLookup, and class Cp2LocalPhoneLookup
 // alone is responsible for populating it.
+// Next ID: 7
 message PhoneLookupInfo {
-  // Information about a PhoneNumber retrieved from CP2. Cp2LocalPhoneLookup is
-  // responsible for populating the data in this message.
+  // Information about a PhoneNumber retrieved from CP2.
   message Cp2Info {
-    // Information about a single local contact.
+    // Information about a single contact, which can be a local contact or a
+    // remote one.
     message Cp2ContactInfo {
-      // android.provider.ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY
+      // For a local contact:
+      //   android.provider.ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME_PRIMARY
+      // For a remote contact:
+      //   android.provider.ContactsContract.PhoneLookup.DISPLAY_NAME_PRIMARY
       optional string name = 1;
 
-      // android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_THUMBNAIL_URI
+      // For a local contact:
+      //   android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_THUMBNAIL_URI
+      // For a remote contact:
+      //   android.provider.ContactsContract.PhoneLookup.PHOTO_THUMBNAIL_URI
       optional string photo_uri = 2;
 
-      // android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_ID
+      // For a local contact:
+      //   android.provider.ContactsContract.CommonDataKinds.Phone.PHOTO_ID
+      // For a remote contact:
+      //   android.provider.ContactsContract.PhoneLookup.PHOTO_ID
       optional fixed64 photo_id = 3;
 
-      // android.provider.ContactsContract.CommonDataKinds.Phone.LABEL
-      // "Home", "Mobile", ect.
+      // For a local contact:
+      //   android.provider.ContactsContract.CommonDataKinds.Phone.LABEL
+      // For a remote contact:
+      //   android.provider.ContactsContract.PhoneLookup.LABEL
+      //
+      // The value can be "Home", "Mobile", ect.
       optional string label = 4;
 
-      // android.provider.ContactsContract.CommonDataKinds.Phone.CONTACT_ID
+      // For a local contact:
+      //   android.provider.ContactsContract.CommonDataKinds.Phone.CONTACT_ID
+      // For a remote contact:
+      //   android.provider.ContactsContract.PhoneLookup.CONTACT_ID
       optional fixed64 contact_id = 5;
 
-      // android.provider.ContactsContract.CONTENT_LOOKUP_URI
+      // For a local contact:
+      //   constructed based on
+      //   android.provider.ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY
+      // For a remote contact:
+      //   constructed based on
+      //   android.provider.ContactsContract.PhoneLookup.LOOKUP_KEY
       optional string lookup_uri = 6;
     }
     // Repeated because one phone number can be associated with multiple CP2
@@ -50,8 +72,15 @@
     // log needs to query for the CP2 information at render time.
     optional bool is_incomplete = 2;
   }
+
+  // Information about a local contact retrieved via CP2.
+  // Cp2LocalPhoneLookup is responsible for populating this field.
   optional Cp2Info cp2_local_info = 1;
 
+  // Information about a remote contact retrieved via CP2.
+  // Cp2RemotePhoneLookup is responsible for populating this field.
+  optional Cp2Info cp2_remote_info = 6;
+
   // Message for APDL, a lookup for the proprietary Google dialer.
   message ApdlInfo {
     optional bool is_spam = 1;
diff --git a/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java b/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java
index 6b217e9..8d08291 100644
--- a/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java
+++ b/java/com/android/dialer/phonelookup/selector/PhoneLookupSelector.java
@@ -127,6 +127,20 @@
     return "";
   }
 
+  public boolean selectIsBusiness(PhoneLookupInfo phoneLookupInfo) {
+    return phoneLookupInfo.hasPeopleApiInfo()
+        && phoneLookupInfo.getPeopleApiInfo().getInfoType() == InfoType.NEARBY_BUSINESS;
+  }
+
+  public boolean selectIsVoicemail(PhoneLookupInfo unused) {
+    // TODO(twyen): implement
+    return false;
+  }
+
+  public boolean selectIsCp2InfoIncomplete(PhoneLookupInfo phoneLookupInfo) {
+    return phoneLookupInfo.getCp2LocalInfo().getIsIncomplete();
+  }
+
   /**
    * Returns true if the number associated with the given {@link PhoneLookupInfo} can be reported as
    * invalid.
@@ -134,7 +148,7 @@
    * <p>As we currently report invalid numbers via the People API, only numbers from the People API
    * can be reported as invalid.
    */
-  public static boolean canReportAsInvalidNumber(PhoneLookupInfo phoneLookupInfo) {
+  public boolean canReportAsInvalidNumber(PhoneLookupInfo phoneLookupInfo) {
     // The presence of Cp2ContactInfo means the number associated with the given PhoneLookupInfo
     // matches that of a Cp2 (local) contact, and PeopleApiInfo will not be used to display
     // information like name, photo, etc. We should not allow the user to report the number in this
diff --git a/java/com/android/dialer/precall/impl/PermissionCheckAction.java b/java/com/android/dialer/precall/impl/PermissionCheckAction.java
index b57b37a..85e9ceb 100644
--- a/java/com/android/dialer/precall/impl/PermissionCheckAction.java
+++ b/java/com/android/dialer/precall/impl/PermissionCheckAction.java
@@ -21,14 +21,14 @@
 import com.android.dialer.callintent.CallIntentBuilder;
 import com.android.dialer.precall.PreCallAction;
 import com.android.dialer.precall.PreCallCoordinator;
-import com.android.dialer.util.PermissionsUtil;
+import com.android.dialer.telecom.TelecomUtil;
 
 /** Aborts call and show a toast if phone permissions are missing. */
 public class PermissionCheckAction implements PreCallAction {
 
   @Override
   public boolean requiresUi(Context context, CallIntentBuilder builder) {
-    return !PermissionsUtil.hasPhonePermissions(context);
+    return !TelecomUtil.hasCallPhonePermission(context);
   }
 
   @Override
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
index 9058158..46e2995 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
@@ -27,6 +27,7 @@
 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;
@@ -205,13 +206,18 @@
     ContactPhotoManager.getInstance(context)
         .loadDialerThumbnailOrPhoto(
             quickContactBadge,
-            voicemailEntry.lookupUri() == null ? null : Uri.parse(voicemailEntry.lookupUri()),
-            voicemailEntry.photoId(),
-            voicemailEntry.photoUri() == null ? null : Uri.parse(voicemailEntry.photoUri()),
-            voicemailEntry.name(),
+            parseUri(voicemailEntry.numberAttributes().getLookupUri()),
+            voicemailEntry.numberAttributes().getPhotoId(),
+            parseUri(voicemailEntry.numberAttributes().getPhotoUri()),
+            VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntry),
             LetterTileDrawable.TYPE_DEFAULT);
   }
 
+  @Nullable
+  private static Uri parseUri(@Nullable String string) {
+    return TextUtils.isEmpty(string) ? null : Uri.parse(string);
+  }
+
   void collapseViewHolder() {
     LogUtil.i(
         "NewVoicemailViewHolder.collapseViewHolder",
diff --git a/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java
index 55d36b3..7e03818 100644
--- a/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java
+++ b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java
@@ -21,6 +21,7 @@
 import android.provider.CallLog.Calls;
 import android.support.v4.content.CursorLoader;
 import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.NumberAttributes;
 import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
 import com.android.dialer.voicemail.model.VoicemailEntry;
 import com.google.protobuf.InvalidProtocolBufferException;
@@ -33,35 +34,29 @@
       new String[] {
         AnnotatedCallLog._ID,
         AnnotatedCallLog.TIMESTAMP,
-        AnnotatedCallLog.NAME,
         AnnotatedCallLog.NUMBER,
         AnnotatedCallLog.FORMATTED_NUMBER,
-        AnnotatedCallLog.PHOTO_URI,
-        AnnotatedCallLog.PHOTO_ID,
-        AnnotatedCallLog.LOOKUP_URI,
         AnnotatedCallLog.DURATION,
         AnnotatedCallLog.GEOCODED_LOCATION,
         AnnotatedCallLog.CALL_TYPE,
         AnnotatedCallLog.TRANSCRIPTION,
         AnnotatedCallLog.VOICEMAIL_URI,
-        AnnotatedCallLog.IS_READ
+        AnnotatedCallLog.IS_READ,
+        AnnotatedCallLog.NUMBER_ATTRIBUTES,
       };
 
   // Indexes for VOICEMAIL_COLUMNS
   private static final int ID = 0;
   private static final int TIMESTAMP = 1;
-  private static final int NAME = 2;
-  private static final int NUMBER = 3;
-  private static final int FORMATTED_NUMBER = 4;
-  private static final int PHOTO_URI = 5;
-  private static final int PHOTO_ID = 6;
-  private static final int LOOKUP_URI = 7;
-  private static final int DURATION = 8;
-  private static final int GEOCODED_LOCATION = 9;
-  private static final int CALL_TYPE = 10;
-  private static final int TRANSCRIPTION = 11;
-  private static final int VOICEMAIL_URI = 12;
-  private static final int IS_READ = 13;
+  private static final int NUMBER = 2;
+  private static final int FORMATTED_NUMBER = 3;
+  private static final int DURATION = 4;
+  private static final int GEOCODED_LOCATION = 5;
+  private static final int CALL_TYPE = 6;
+  private static final int TRANSCRIPTION = 7;
+  private static final int VOICEMAIL_URI = 8;
+  private static final int IS_READ = 9;
+  private static final int NUMBER_ATTRIBUTES = 10;
 
   // TODO(zachh): Optimize indexes
   VoicemailCursorLoader(Context context) {
@@ -82,22 +77,25 @@
     } catch (InvalidProtocolBufferException e) {
       throw new IllegalStateException("Couldn't parse DialerPhoneNumber bytes");
     }
+    NumberAttributes numberAttributes;
+    try {
+      numberAttributes = NumberAttributes.parseFrom(cursor.getBlob(NUMBER_ATTRIBUTES));
+    } catch (InvalidProtocolBufferException e) {
+      throw new IllegalStateException("Couldn't parse NumberAttributes bytes");
+    }
 
     return VoicemailEntry.builder()
         .setId(cursor.getInt(ID))
         .setTimestamp(cursor.getLong(TIMESTAMP))
-        .setName(cursor.getString(NAME))
         .setNumber(number)
         .setFormattedNumber(cursor.getString(FORMATTED_NUMBER))
-        .setPhotoUri(cursor.getString(PHOTO_URI))
-        .setPhotoId(cursor.getLong(PHOTO_ID))
-        .setLookupUri(cursor.getString(LOOKUP_URI))
         .setDuration(cursor.getLong(DURATION))
         .setTranscription(cursor.getString(TRANSCRIPTION))
         .setVoicemailUri(cursor.getString(VOICEMAIL_URI))
         .setGeocodedLocation(cursor.getString(GEOCODED_LOCATION))
         .setCallType(cursor.getInt(CALL_TYPE))
         .setIsRead(cursor.getInt(IS_READ))
+        .setNumberAttributes(numberAttributes)
         .build();
   }
 
diff --git a/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java b/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java
index d73d1f0..4aaf2d1 100644
--- a/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java
+++ b/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java
@@ -33,8 +33,8 @@
 
   public static String buildPrimaryVoicemailText(Context context, VoicemailEntry data) {
     StringBuilder primaryText = new StringBuilder();
-    if (!TextUtils.isEmpty(data.name())) {
-      primaryText.append(data.name());
+    if (!TextUtils.isEmpty(data.numberAttributes().getName())) {
+      primaryText.append(data.numberAttributes().getName());
     } else if (!TextUtils.isEmpty(data.formattedNumber())) {
       primaryText.append(data.formattedNumber());
     } else {
diff --git a/java/com/android/dialer/voicemail/listui/menu/Modules.java b/java/com/android/dialer/voicemail/listui/menu/Modules.java
index bb98d76..bd79932 100644
--- a/java/com/android/dialer/voicemail/listui/menu/Modules.java
+++ b/java/com/android/dialer/voicemail/listui/menu/Modules.java
@@ -40,8 +40,8 @@
         context,
         modules,
         voicemailEntry.number(),
-        voicemailEntry.name(),
-        voicemailEntry.lookupUri());
+        voicemailEntry.numberAttributes().getName(),
+        voicemailEntry.numberAttributes().getLookupUri());
 
     String originalNumber = voicemailEntry.number().getRawInput().getNumber();
     SharedModules.maybeAddModuleForSendingTextMessage(context, modules, originalNumber);
diff --git a/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java b/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java
index 7f4ac80..7b8adfe 100644
--- a/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java
+++ b/java/com/android/dialer/voicemail/listui/menu/PrimaryAction.java
@@ -38,12 +38,12 @@
         .setNumber(voicemailEntry.number())
         .setPhotoInfo(
             PhotoInfo.builder()
-                .setPhotoId(voicemailEntry.photoId())
-                .setPhotoUri(voicemailEntry.photoUri())
+                .setPhotoId(voicemailEntry.numberAttributes().getPhotoId())
+                .setPhotoUri(voicemailEntry.numberAttributes().getPhotoUri())
                 .setIsVideo(false)
                 .setContactType(
                     LetterTileDrawable.TYPE_DEFAULT) // TODO(uabdullah): Use proper type.
-                .setDisplayName(voicemailEntry.name())
+                .setDisplayName(voicemailEntry.numberAttributes().getName())
                 .build())
         .setPrimaryText(buildPrimaryVoicemailText(context, voicemailEntry))
         .setSecondaryText(buildSecondaryVoicemailText(voicemailEntry))
@@ -56,8 +56,8 @@
 
   public static String buildPrimaryVoicemailText(Context context, VoicemailEntry data) {
     StringBuilder primaryText = new StringBuilder();
-    if (!TextUtils.isEmpty(data.name())) {
-      primaryText.append(data.name());
+    if (!TextUtils.isEmpty(data.numberAttributes().getName())) {
+      primaryText.append(data.numberAttributes().getName());
     } else if (!TextUtils.isEmpty(data.formattedNumber())) {
       primaryText.append(data.formattedNumber());
     } else {
diff --git a/java/com/android/dialer/voicemail/model/VoicemailEntry.java b/java/com/android/dialer/voicemail/model/VoicemailEntry.java
index 702f52d..f17a23e 100644
--- a/java/com/android/dialer/voicemail/model/VoicemailEntry.java
+++ b/java/com/android/dialer/voicemail/model/VoicemailEntry.java
@@ -19,6 +19,7 @@
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.NumberAttributes;
 import com.google.auto.value.AutoValue;
 
 /** Data class containing the contents of a voicemail entry from the AnnotatedCallLog. */
@@ -30,7 +31,7 @@
         .setId(0)
         .setTimestamp(0)
         .setNumber(DialerPhoneNumber.getDefaultInstance())
-        .setPhotoId(0)
+        .setNumberAttributes(NumberAttributes.getDefaultInstance())
         .setDuration(0)
         .setCallType(0)
         .setIsRead(0);
@@ -43,21 +44,11 @@
   @NonNull
   public abstract DialerPhoneNumber number();
 
-  @Nullable
-  public abstract String name();
 
   @Nullable
   public abstract String formattedNumber();
 
   @Nullable
-  public abstract String photoUri();
-
-  public abstract long photoId();
-
-  @Nullable
-  public abstract String lookupUri();
-
-  @Nullable
   public abstract String geocodedLocation();
 
   public abstract long duration();
@@ -72,6 +63,8 @@
 
   public abstract int isRead();
 
+  public abstract NumberAttributes numberAttributes();
+
   /** Builder for {@link VoicemailEntry}. */
   @AutoValue.Builder
   public abstract static class Builder {
@@ -82,16 +75,8 @@
 
     public abstract Builder setNumber(@NonNull DialerPhoneNumber number);
 
-    public abstract Builder setName(@Nullable String name);
-
     public abstract Builder setFormattedNumber(@Nullable String formattedNumber);
 
-    public abstract Builder setPhotoUri(@Nullable String photoUri);
-
-    public abstract Builder setPhotoId(long photoId);
-
-    public abstract Builder setLookupUri(@Nullable String lookupUri);
-
     public abstract Builder setDuration(long duration);
 
     public abstract Builder setTranscription(@Nullable String transcription);
@@ -104,6 +89,8 @@
 
     public abstract Builder setIsRead(int isRead);
 
+    public abstract Builder setNumberAttributes(NumberAttributes numberAttributes);
+
     public abstract VoicemailEntry build();
   }
 }