Merge changes Ida554313,Ie7187b02,I7a7c23b4
am: ef7a7eb0f1

Change-Id: Ief66221a645aa90569c9f9cb5c7838d7122a501a
diff --git a/Android.mk b/Android.mk
index 62b6f81..e1a58ad 100644
--- a/Android.mk
+++ b/Android.mk
@@ -91,6 +91,7 @@
 	com.android.dialer.app \
 	com.android.dialer.app.manifests.activities \
 	com.android.dialer.app.voicemail.error \
+	com.android.dialer.assisteddialing.ui \
 	com.android.dialer.backup \
 	com.android.dialer.binary.aosp.testing \
 	com.android.dialer.binary.google \
@@ -102,9 +103,9 @@
 	com.android.dialer.calldetails \
 	com.android.dialer.calllog.database \
 	com.android.dialer.calllog.ui \
-        com.android.dialer.calllog.ui.menu \
+	com.android.dialer.calllog.ui.menu \
 	com.android.dialer.calllogutils \
-        com.android.dialer.clipboard \
+	com.android.dialer.clipboard \
 	com.android.dialer.common \
 	com.android.dialer.configprovider \
 	com.android.dialer.contactactions \
diff --git a/java/com/android/dialer/app/settings/DialerSettingsActivity.java b/java/com/android/dialer/app/settings/DialerSettingsActivity.java
index 10ef9ed..706f098 100644
--- a/java/com/android/dialer/app/settings/DialerSettingsActivity.java
+++ b/java/com/android/dialer/app/settings/DialerSettingsActivity.java
@@ -33,9 +33,12 @@
 import android.widget.Toast;
 import com.android.dialer.about.AboutPhoneFragment;
 import com.android.dialer.app.R;
+import com.android.dialer.assisteddialing.ConcreteCreator;
+import com.android.dialer.assisteddialing.ui.AssistedDialingSettingFragment;
 import com.android.dialer.blocking.FilteredNumberCompat;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.compat.telephony.TelephonyManagerCompat;
+import com.android.dialer.configprovider.ConfigProviderBindings;
 import com.android.dialer.proguard.UsedByReflection;
 import com.android.voicemail.VoicemailClient;
 import com.android.voicemail.VoicemailComponent;
@@ -135,6 +138,21 @@
       target.add(accessibilitySettingsHeader);
     }
 
+    boolean isAssistedDialingEnabled =
+        ConcreteCreator.isAssistedDialingEnabled(
+            ConfigProviderBindings.get(getApplicationContext()));
+    LogUtil.i(
+        "DialerSettingsActivity.onBuildHeaders",
+        "showing assisted dialing header: " + isAssistedDialingEnabled);
+    if (isAssistedDialingEnabled) {
+
+      Header assistedDialingSettingsHeader = new Header();
+      assistedDialingSettingsHeader.titleRes =
+          com.android.dialer.assisteddialing.ui.R.string.assisted_dialing_setting_title;
+      assistedDialingSettingsHeader.fragment = AssistedDialingSettingFragment.class.getName();
+      target.add(assistedDialingSettingsHeader);
+    }
+
     if (showAbout()) {
       Header aboutPhoneHeader = new Header();
       aboutPhoneHeader.titleRes = R.string.about_phone_label;
diff --git a/java/com/android/dialer/assisteddialing/ConcreteCreator.java b/java/com/android/dialer/assisteddialing/ConcreteCreator.java
index a8a9d2a..1790b8f 100644
--- a/java/com/android/dialer/assisteddialing/ConcreteCreator.java
+++ b/java/com/android/dialer/assisteddialing/ConcreteCreator.java
@@ -20,8 +20,10 @@
 import android.content.Context;
 import android.os.Build;
 import android.os.Build.VERSION_CODES;
+import android.preference.PreferenceManager;
 import android.support.annotation.NonNull;
 import android.telephony.TelephonyManager;
+import com.android.dialer.assisteddialing.ui.R;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.configprovider.ConfigProvider;
 import com.android.dialer.configprovider.ConfigProviderBindings;
@@ -63,8 +65,15 @@
       throw new NullPointerException("Provided context was null");
     }
 
-    if ((Build.VERSION.SDK_INT < BUILD_CODE_FLOOR || Build.VERSION.SDK_INT > BUILD_CODE_CEILING)
-        || !configProvider.getBoolean("assisted_dialing_enabled", false)) {
+    if (!isAssistedDialingEnabled(configProvider)) {
+      LogUtil.i("ConcreteCreator.createNewAssistedDialingMediator", "feature not enabled");
+      return new AssistedDialingMediatorStub();
+    }
+
+    if (!PreferenceManager.getDefaultSharedPreferences(context)
+        .getBoolean(context.getString(R.string.assisted_dialing_setting_toggle_key), false)) {
+      LogUtil.i("ConcreteCreator.createNewAssistedDialingMediator", "disabled by local setting");
+
       return new AssistedDialingMediatorStub();
     }
 
@@ -74,4 +83,16 @@
     return new AssistedDialingMediatorImpl(
         new LocationDetector(telephonyManager), new NumberTransformer(constraints));
   }
+
+  /** Returns a boolean indicating whether or not the assisted dialing feature is enabled. */
+  public static boolean isAssistedDialingEnabled(@NonNull ConfigProvider configProvider) {
+    if (configProvider == null) {
+      LogUtil.i("ConcreteCreator.isAssistedDialingEnabled", "provided configProvider was null");
+      throw new NullPointerException("Provided configProvider was null");
+    }
+
+    return (Build.VERSION.SDK_INT >= BUILD_CODE_FLOOR
+            && Build.VERSION.SDK_INT <= BUILD_CODE_CEILING)
+        && configProvider.getBoolean("assisted_dialing_enabled", false);
+  }
 }
diff --git a/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml b/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml
new file mode 100644
index 0000000..6625dff
--- /dev/null
+++ b/java/com/android/dialer/assisteddialing/ui/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<!-- Copyright (C) 2017 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 xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.dialer.assisteddialing">
+
+  <uses-sdk
+      android:minSdkVersion="23"
+      android:targetSdkVersion="24"/>
+
+</manifest>
\ No newline at end of file
diff --git a/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java b/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java
new file mode 100644
index 0000000..8847448
--- /dev/null
+++ b/java/com/android/dialer/assisteddialing/ui/AssistedDialingSettingFragment.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2017 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.assisteddialing.ui;
+
+import android.os.Bundle;
+import android.preference.PreferenceFragment;
+
+/** The setting for Assisted Dialing */
+public class AssistedDialingSettingFragment extends PreferenceFragment {
+
+  @Override
+  public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    // Load the preferences from an XML resource
+    addPreferencesFromResource(R.xml.assisted_dialing_setting);
+  }
+}
diff --git a/java/com/android/dialer/assisteddialing/ui/res/values/string.xml b/java/com/android/dialer/assisteddialing/ui/res/values/string.xml
new file mode 100644
index 0000000..cd159bf
--- /dev/null
+++ b/java/com/android/dialer/assisteddialing/ui/res/values/string.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2017 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
+  -->
+
+<resources>
+  <!-- Label for a setting enabling assisted dialing [CHAR LIMIT=40]-->
+  <string name="assisted_dialing_setting_title">Assisted dialing</string>
+
+  <!-- Label for a setting enabling assisted dialing switch preference-->
+  <string name="assisted_dialing_setting_summary">Automatically correct the phone number prefix when traveling and calling international numbers</string>
+
+  <!-- Key for the assisted dialing setting toggle-->
+  <string name="assisted_dialing_setting_toggle_key" translatable="false">assisted_dialing_setting_toggle_key</string>
+</resources>
\ No newline at end of file
diff --git a/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml b/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml
new file mode 100644
index 0000000..8e3c62d
--- /dev/null
+++ b/java/com/android/dialer/assisteddialing/ui/res/xml/assisted_dialing_setting.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2017 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.
+  -->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreference
+        android:defaultValue="false"
+        android:key="@string/assisted_dialing_setting_toggle_key"
+        android:title="@string/assisted_dialing_setting_title"
+        android:summary="@string/assisted_dialing_setting_summary" />
+
+</PreferenceScreen>
\ No newline at end of file
diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
index a5f1425..589fe63 100644
--- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
+++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
@@ -60,6 +60,7 @@
           .toString();
 
   /** Deletes all but the first maxRows rows (by timestamp) to keep the table a manageable size. */
+  // TODO(zachh): Prevent voicemails from being garbage collected.
   private static final String CREATE_TRIGGER_SQL =
       "create trigger delete_old_rows after insert on "
           + AnnotatedCallLog.TABLE
@@ -79,12 +80,20 @@
           + AnnotatedCallLog.TABLE
           + " )); end;";
 
+  private static final String CREATE_INDEX_ON_CALL_TYPE_SQL =
+      "create index call_type_index on "
+          + AnnotatedCallLog.TABLE
+          + " ("
+          + AnnotatedCallLog.CALL_TYPE
+          + ");";
+
   @Override
   public void onCreate(SQLiteDatabase db) {
     LogUtil.enterBlock("AnnotatedCallLogDatabaseHelper.onCreate");
     long startTime = System.currentTimeMillis();
     db.execSQL(CREATE_TABLE_SQL);
     db.execSQL(String.format(Locale.US, CREATE_TRIGGER_SQL, maxRows, maxRows));
+    db.execSQL(CREATE_INDEX_ON_CALL_TYPE_SQL);
     // TODO(zachh): Consider logging impression.
     LogUtil.i(
         "AnnotatedCallLogDatabaseHelper.onCreate",
diff --git a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
index b680bd5..f501792 100644
--- a/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
+++ b/java/com/android/dialer/phonenumbercache/ContactInfoHelper.java
@@ -337,30 +337,30 @@
       return ContactInfo.EMPTY;
     }
 
-    Cursor phoneLookupCursor = null;
-    try {
-      String[] projection = PhoneQuery.getPhoneLookupProjection(uri);
-      phoneLookupCursor = mContext.getContentResolver().query(uri, projection, null, null, null);
-    } catch (NullPointerException e) {
-      LogUtil.e("ContactInfoHelper.lookupContactFromUri", "phone lookup", e);
-      // Trap NPE from pre-N CP2
-      return null;
-    }
-    if (phoneLookupCursor == null) {
-      LogUtil.d("ContactInfoHelper.lookupContactFromUri", "phoneLookupCursor is null");
-      return null;
-    }
+    try (Cursor phoneLookupCursor =
+        mContext
+            .getContentResolver()
+            .query(uri, PhoneQuery.getPhoneLookupProjection(uri), null, null, null)) {
+      if (phoneLookupCursor == null) {
+        LogUtil.d("ContactInfoHelper.lookupContactFromUri", "phoneLookupCursor is null");
+        return null;
+      }
 
-    try {
       if (!phoneLookupCursor.moveToFirst()) {
         return ContactInfo.EMPTY;
       }
-      String lookupKey = phoneLookupCursor.getString(PhoneQuery.LOOKUP_KEY);
-      ContactInfo contactInfo = createPhoneLookupContactInfo(phoneLookupCursor, lookupKey);
+
+      Cursor matchedCursor =
+          PhoneNumberHelper.getCursorMatchForContactLookupUri(
+              phoneLookupCursor, PhoneQuery.MATCHED_NUMBER, uri);
+      if (matchedCursor == null) {
+        return ContactInfo.EMPTY;
+      }
+
+      String lookupKey = matchedCursor.getString(PhoneQuery.LOOKUP_KEY);
+      ContactInfo contactInfo = createPhoneLookupContactInfo(matchedCursor, lookupKey);
       fillAdditionalContactInfo(mContext, contactInfo);
       return contactInfo;
-    } finally {
-      phoneLookupCursor.close();
     }
   }
 
diff --git a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
index cc9b730..84cbb69 100644
--- a/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
+++ b/java/com/android/dialer/phonenumberutil/PhoneNumberHelper.java
@@ -17,6 +17,8 @@
 package com.android.dialer.phonenumberutil;
 
 import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
 import android.os.Trace;
 import android.provider.CallLog;
 import android.support.annotation.Nullable;
@@ -24,6 +26,7 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
+import com.android.dialer.common.Assert;
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.compat.CompatUtils;
 import com.android.dialer.compat.telephony.TelephonyManagerCompat;
@@ -47,6 +50,78 @@
   }
 
   /**
+   * Find the cursor pointing to a number that matches the number in a contact lookup URI.
+   *
+   * <p>When determining whether two phone numbers are identical enough for caller ID purposes, the
+   * Contacts Provider uses {@link PhoneNumberUtils#compare(String, String)}, which ignores special
+   * dialable characters such as '#', '*', '+', etc. This makes it possible for the cursor returned
+   * by the Contacts Provider to have multiple rows even when the URI asks for a specific number.
+   *
+   * <p>For example, suppose the user has two contacts whose numbers are "#123" and "123",
+   * respectively. When the URI asks for number "123", both numbers will be returned. Therefore, the
+   * following strategy is employed to find a match.
+   *
+   * <p>If the cursor points to a global phone number (i.e., a number that can be accepted by {@link
+   * PhoneNumberUtils#isGlobalPhoneNumber(String)}) and the lookup number in the URI is a PARTIAL
+   * match, return the cursor.
+   *
+   * <p>If the cursor points to a number that is not a global phone number, return the cursor iff
+   * the lookup number in the URI is an EXACT match.
+   *
+   * <p>Return null in all other circumstances.
+   *
+   * @param cursor A cursor returned by the Contacts Provider.
+   * @param columnIndexForNumber The index of the column where phone numbers are stored. It is the
+   *     caller's responsibility to pass the correct column index.
+   * @param contactLookupUri A URI used to retrieve a contact via the Contacts Provider. It is the
+   *     caller's responsibility to ensure the URI is one that asks for a specific phone number.
+   * @return The cursor considered as a match by the description above or null if no such cursor can
+   *     be found.
+   */
+  public static Cursor getCursorMatchForContactLookupUri(
+      Cursor cursor, int columnIndexForNumber, Uri contactLookupUri) {
+    if (cursor == null || contactLookupUri == null) {
+      return null;
+    }
+
+    if (!cursor.moveToFirst()) {
+      return null;
+    }
+
+    Assert.checkArgument(
+        0 <= columnIndexForNumber && columnIndexForNumber < cursor.getColumnCount());
+
+    String lookupNumber = contactLookupUri.getLastPathSegment();
+    if (lookupNumber == null) {
+      return null;
+    }
+
+    boolean isMatchFound;
+    do {
+      // All undialable characters should be converted/removed before comparing the lookup number
+      // and the existing contact number.
+      String rawExistingContactNumber =
+          PhoneNumberUtils.stripSeparators(
+              PhoneNumberUtils.convertKeypadLettersToDigits(
+                  cursor.getString(columnIndexForNumber)));
+      String rawQueryNumber =
+          PhoneNumberUtils.stripSeparators(
+              PhoneNumberUtils.convertKeypadLettersToDigits(lookupNumber));
+
+      isMatchFound =
+          PhoneNumberUtils.isGlobalPhoneNumber(rawExistingContactNumber)
+              ? rawExistingContactNumber.contains(rawQueryNumber)
+              : rawExistingContactNumber.equals(rawQueryNumber);
+
+      if (isMatchFound) {
+        return cursor;
+      }
+    } while (cursor.moveToNext());
+
+    return null;
+  }
+
+  /**
    * Returns true if the given number is the number of the configured voicemail. To be able to
    * mock-out this, it is not a static method.
    */
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
new file mode 100644
index 0000000..71178ce
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 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.voicemail.listui;
+
+import android.database.Cursor;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.dialer.common.LogUtil;
+
+/** {@link RecyclerView.Adapter} for the new voicemail call log fragment. */
+final class NewVoicemailAdapter extends RecyclerView.Adapter<NewVoicemailViewHolder> {
+
+  private final Cursor cursor;
+
+  /** @param cursor whose projection is {@link VoicemailCursorLoader.VOICEMAIL_COLUMNS} */
+  NewVoicemailAdapter(Cursor cursor) {
+    this.cursor = cursor;
+  }
+
+  @Override
+  public NewVoicemailViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
+    LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
+    View view = inflater.inflate(R.layout.new_voicemail_call_log_entry, viewGroup, false);
+    NewVoicemailViewHolder newVoicemailViewHolder = new NewVoicemailViewHolder(view);
+    return newVoicemailViewHolder;
+  }
+
+  @Override
+  public void onBindViewHolder(NewVoicemailViewHolder viewHolder, int position) {
+    LogUtil.i("onBindViewHolder", "position" + position);
+    cursor.moveToPosition(position);
+    viewHolder.bind(cursor);
+  }
+
+  @Override
+  public int getItemCount() {
+    return cursor.getCount();
+  }
+}
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogAdapter.java b/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogAdapter.java
deleted file mode 100644
index b40c863..0000000
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogAdapter.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2017 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.voicemail.listui;
-
-import android.support.v7.widget.RecyclerView;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import com.android.dialer.common.LogUtil;
-import com.android.dialer.voicemail.datasources.VoicemailData;
-import java.util.List;
-
-/** {@link RecyclerView.Adapter} for the new voicemail call log fragment. */
-final class NewVoicemailCallLogAdapter extends RecyclerView.Adapter<NewVoicemailCallLogViewHolder> {
-
-  private final List<VoicemailData> voicemailData;
-
-  NewVoicemailCallLogAdapter(List<VoicemailData> dataSet) {
-    voicemailData = dataSet;
-  }
-
-  @Override
-  public NewVoicemailCallLogViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
-
-    LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
-    View v = inflater.inflate(R.layout.new_voicemail_call_log_entry, viewGroup, false);
-    NewVoicemailCallLogViewHolder newVoicemailCallLogViewHolder =
-        new NewVoicemailCallLogViewHolder(v);
-    return newVoicemailCallLogViewHolder;
-  }
-
-  @Override
-  public void onBindViewHolder(NewVoicemailCallLogViewHolder viewHolder, int position) {
-    LogUtil.i("onBindViewHolder", "position" + position);
-    viewHolder.bind(voicemailData.get(position));
-  }
-
-  @Override
-  public int getItemCount() {
-    return voicemailData.size();
-  }
-}
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogViewHolder.java
deleted file mode 100644
index 9ec7684..0000000
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailCallLogViewHolder.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2017 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.voicemail.listui;
-
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-import android.widget.TextView;
-import com.android.dialer.voicemail.datasources.VoicemailData;
-
-/** {@link RecyclerView.ViewHolder} for the new voicemail call log. */
-final class NewVoicemailCallLogViewHolder extends RecyclerView.ViewHolder {
-
-  private final TextView primaryTextView;
-  private final TextView secondaryTextView;
-  private final TextView transcriptionTextView;
-
-  NewVoicemailCallLogViewHolder(View view) {
-    super(view);
-    primaryTextView = (TextView) view.findViewById(R.id.primary_text);
-    secondaryTextView = (TextView) view.findViewById(R.id.secondary_text);
-    transcriptionTextView = (TextView) view.findViewById(R.id.transcription_text);
-  }
-
-  void bind(VoicemailData voicemailData) {
-    primaryTextView.setText(voicemailData.name());
-    secondaryTextView.setText(getVoicemailLocationDateAndDuration(voicemailData));
-    transcriptionTextView.setText(voicemailData.transcription());
-  }
-
-  private String getVoicemailLocationDateAndDuration(VoicemailData voicemailData) {
-    return voicemailData.location()
-        + " · "
-        + voicemailData.date()
-        + " · "
-        + voicemailData.duration();
-  }
-}
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
index 3629b75..1912e1e 100644
--- a/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailFragment.java
@@ -16,49 +16,50 @@
 
 package com.android.dialer.voicemail.listui;
 
+import android.database.Cursor;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 import android.support.v4.app.Fragment;
+import android.support.v4.app.LoaderManager.LoaderCallbacks;
+import android.support.v4.content.Loader;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import com.android.dialer.common.LogUtil;
-import com.android.dialer.voicemail.datasources.VoicemailData;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Random;
 
 /** Fragment for Dialer Voicemail Tab. */
-public final class NewVoicemailFragment extends Fragment {
+public final class NewVoicemailFragment extends Fragment implements LoaderCallbacks<Cursor> {
+
+  private RecyclerView recyclerView;
+
   @Nullable
   @Override
   public View onCreateView(
       LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
     View view = inflater.inflate(R.layout.new_voicemail_call_log_fragment, container, false);
-    RecyclerView recyclerView =
-        (RecyclerView) view.findViewById(R.id.new_voicemail_call_log_recycler_view);
-    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
-
-    // TODO(uabdullah): To be removed once we hook up the UI to the voicemail backend
-    List<VoicemailData> voicemailData = new ArrayList<>();
-    Random rand = new Random();
-    for (int i = 0; i < 50; i++) {
-      VoicemailData mocked =
-          VoicemailData.builder()
-              .setName("Fatima Abdullah " + i)
-              .setLocation("San Francisco, CA")
-              .setDate("March " + (rand.nextInt(30) + 1))
-              .setDuration("00:" + (rand.nextInt(50) + 10))
-              .setTranscription(
-                  "This is a transcription text message that literally means nothing.")
-              .build();
-      voicemailData.add(mocked);
-    }
-
-    LogUtil.i("onCreateView", "size of input:" + voicemailData.size());
-    recyclerView.setAdapter(new NewVoicemailCallLogAdapter(voicemailData));
+    recyclerView = view.findViewById(R.id.new_voicemail_call_log_recycler_view);
+    getLoaderManager().restartLoader(0, null, this);
     return view;
   }
+
+  @Override
+  public Loader<Cursor> onCreateLoader(int id, Bundle args) {
+    LogUtil.enterBlock("NewVoicemailFragment.onCreateLoader");
+    return new VoicemailCursorLoader(getContext());
+  }
+
+  @Override
+  public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
+    LogUtil.i("NewVoicemailFragment.onCreateLoader", "cursor size is %d", data.getCount());
+    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
+    recyclerView.setAdapter(new NewVoicemailAdapter(data));
+  }
+
+  @Override
+  public void onLoaderReset(Loader<Cursor> loader) {
+    LogUtil.enterBlock("NewVoicemailFragment.onLoaderReset");
+    recyclerView.setAdapter(null);
+  }
 }
diff --git a/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
new file mode 100644
index 0000000..daa24c8
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/NewVoicemailViewHolder.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 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.voicemail.listui;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.QuickContactBadge;
+import android.widget.TextView;
+import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.lettertile.LetterTileDrawable;
+import com.android.dialer.voicemail.model.VoicemailEntry;
+
+/** {@link RecyclerView.ViewHolder} for the new voicemail tab. */
+final class NewVoicemailViewHolder extends RecyclerView.ViewHolder {
+
+  private final Context context;
+  private final TextView primaryTextView;
+  private final QuickContactBadge quickContactBadge;
+
+  NewVoicemailViewHolder(View view) {
+    super(view);
+    this.context = view.getContext();
+    primaryTextView = (TextView) view.findViewById(R.id.primary_text);
+    quickContactBadge = view.findViewById(R.id.quick_contact_photo);
+  }
+
+  void bind(Cursor cursor) {
+    VoicemailEntry voicemailEntry = VoicemailCursorLoader.toVoicemailEntry(cursor);
+    primaryTextView.setText(VoicemailEntryText.buildPrimaryVoicemailText(context, voicemailEntry));
+    setPhoto(voicemailEntry);
+  }
+
+  // TODO(uabdullah): Consider/Implement TYPE (e.g Spam, TYPE_VOICEMAIL)
+  private void setPhoto(VoicemailEntry voicemailEntry) {
+    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(),
+            LetterTileDrawable.TYPE_DEFAULT);
+  }
+}
diff --git a/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java
new file mode 100644
index 0000000..5a41765
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/VoicemailCursorLoader.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2017 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.voicemail.listui;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.provider.CallLog.Calls;
+import android.support.v4.content.CursorLoader;
+import com.android.dialer.DialerPhoneNumber;
+import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
+import com.android.dialer.voicemail.model.VoicemailEntry;
+import com.google.protobuf.InvalidProtocolBufferException;
+
+/** CursorLoader for the annotated call log (voicemails only). */
+final class VoicemailCursorLoader extends CursorLoader {
+
+  // When adding columns be sure to update {@link #VoicemailCursorLoader.toVoicemailEntry}.
+  public static final String[] VOICEMAIL_COLUMNS =
+      new String[] {
+        AnnotatedCallLog._ID,
+        AnnotatedCallLog.TIMESTAMP,
+        AnnotatedCallLog.NAME,
+        AnnotatedCallLog.NUMBER,
+        AnnotatedCallLog.FORMATTED_NUMBER,
+        AnnotatedCallLog.PHOTO_URI,
+        AnnotatedCallLog.PHOTO_ID,
+        AnnotatedCallLog.LOOKUP_URI,
+        AnnotatedCallLog.GEOCODED_LOCATION,
+        AnnotatedCallLog.CALL_TYPE
+      };
+
+  // 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 GEOCODED_LOCATION = 8;
+  private static final int CALL_TYPE = 9;
+
+  // TODO(zachh): Optimize indexes
+  VoicemailCursorLoader(Context context) {
+    super(
+        context,
+        AnnotatedCallLog.CONTENT_URI,
+        VOICEMAIL_COLUMNS,
+        AnnotatedCallLog.CALL_TYPE + " = ?",
+        new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)},
+        AnnotatedCallLog.TIMESTAMP + " DESC");
+  }
+
+  /** Creates a new {@link VoicemailEntry} from the provided cursor using the current position. */
+  static VoicemailEntry toVoicemailEntry(Cursor cursor) {
+    DialerPhoneNumber number;
+    try {
+      number = DialerPhoneNumber.parseFrom(cursor.getBlob(NUMBER));
+    } catch (InvalidProtocolBufferException e) {
+      throw new IllegalStateException("Couldn't parse DialerPhoneNumber 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))
+        .setGeocodedLocation(cursor.getString(GEOCODED_LOCATION))
+        .setCallType(cursor.getInt(CALL_TYPE))
+        .build();
+  }
+}
diff --git a/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java b/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java
new file mode 100644
index 0000000..cf2fef2
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/VoicemailEntryText.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 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.voicemail.listui;
+
+import android.content.Context;
+import android.text.TextUtils;
+import com.android.dialer.voicemail.model.VoicemailEntry;
+
+/**
+ * Computes the primary text for voicemail entries.
+ *
+ * <p>These text values are shown in the voicemail tab.
+ */
+public class VoicemailEntryText {
+
+  public static String buildPrimaryVoicemailText(Context context, VoicemailEntry data) {
+    StringBuilder primaryText = new StringBuilder();
+    if (!TextUtils.isEmpty(data.name())) {
+      primaryText.append(data.name());
+    } else if (!TextUtils.isEmpty(data.formattedNumber())) {
+      primaryText.append(data.formattedNumber());
+    } else {
+      // TODO(uabdullah): Handle CallLog.Calls.PRESENTATION_*, including Verizon restricted numbers.
+      primaryText.append(context.getText(R.string.voicemail_entry_unknown));
+    }
+    return primaryText.toString();
+  }
+}
diff --git a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_call_log_entry.xml b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_call_log_entry.xml
index 01c0ee1..82ccf88 100644
--- a/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_call_log_entry.xml
+++ b/java/com/android/dialer/voicemail/listui/res/layout/new_voicemail_call_log_entry.xml
@@ -31,7 +31,6 @@
       android:layout_height="@dimen/call_log_entry_photo_size"
       android:layout_centerVertical="true"
       android:padding="@dimen/call_log_entry_photo_padding"
-      android:background="@color/dialer_secondary_color"
       android:focusable="true"/>
 
   <LinearLayout
@@ -42,6 +41,7 @@
       android:layout_toStartOf="@+id/menu_button"
       android:orientation="vertical">
 
+    <!--TODO(uabdullah): Resolve text clipping on top of primary text -->
     <TextView
         android:id="@+id/primary_text"
         style="@style/PrimaryText"
diff --git a/java/com/android/dialer/voicemail/listui/res/values/strings.xml b/java/com/android/dialer/voicemail/listui/res/values/strings.xml
new file mode 100644
index 0000000..39c368a
--- /dev/null
+++ b/java/com/android/dialer/voicemail/listui/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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
+ -->
+<resources>
+  <!-- String used to display voicemails from unknown numbers in the voicemail tab.  [CHAR LIMIT=30] -->
+  <string name="voicemail_entry_unknown">Unknown</string>
+</resources>
\ No newline at end of file
diff --git a/java/com/android/dialer/voicemail/model/VoicemailEntry.java b/java/com/android/dialer/voicemail/model/VoicemailEntry.java
new file mode 100644
index 0000000..00e1757
--- /dev/null
+++ b/java/com/android/dialer/voicemail/model/VoicemailEntry.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 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.voicemail.model;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import com.android.dialer.DialerPhoneNumber;
+import com.google.auto.value.AutoValue;
+
+/** Data class containing the contents of a voicemail entry from the AnnotatedCallLog. */
+@AutoValue
+public abstract class VoicemailEntry {
+
+  public static Builder builder() {
+    return new AutoValue_VoicemailEntry.Builder()
+        .setId(0)
+        .setTimestamp(0)
+        .setNumber(DialerPhoneNumber.getDefaultInstance())
+        .setPhotoId(0)
+        .setCallType(0);
+  }
+
+  public abstract int id();
+
+  public abstract long timestamp();
+
+  @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 int callType();
+
+  /** Builder for {@link VoicemailEntry}. */
+  @AutoValue.Builder
+  public abstract static class Builder {
+
+    public abstract Builder setId(int id);
+
+    public abstract Builder setTimestamp(long timestamp);
+
+    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 setGeocodedLocation(@Nullable String geocodedLocation);
+
+    public abstract Builder setCallType(int callType);
+
+    public abstract VoicemailEntry build();
+  }
+}
diff --git a/java/com/android/incallui/CallerInfo.java b/java/com/android/incallui/CallerInfo.java
index cc1a60a..809ed59 100644
--- a/java/com/android/incallui/CallerInfo.java
+++ b/java/com/android/incallui/CallerInfo.java
@@ -192,141 +192,145 @@
    *
    * @param context the context used to retrieve string constants
    * @param contactRef the URI to attach to this CallerInfo object
-   * @param cursor the first object in the cursor is used to build the CallerInfo object.
+   * @param cursor the first matching object in the cursor is used to build the CallerInfo object.
    * @return the CallerInfo which contains the caller id for the given number. The returned
    *     CallerInfo is null if no number is supplied.
    */
   public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
     CallerInfo info = new CallerInfo();
-    info.photoResource = 0;
-    info.phoneLabel = null;
-    info.numberType = 0;
-    info.numberLabel = null;
     info.cachedPhoto = null;
-    info.isCachedPhotoCurrent = false;
     info.contactExists = false;
+    info.contactRefUri = contactRef;
+    info.isCachedPhotoCurrent = false;
+    info.name = null;
+    info.needUpdate = false;
+    info.numberLabel = null;
+    info.numberType = 0;
+    info.phoneLabel = null;
+    info.photoResource = 0;
     info.userType = ContactsUtils.USER_TYPE_CURRENT;
 
     Log.v(TAG, "getCallerInfo() based on cursor...");
 
-    if (cursor != null) {
-      if (cursor.moveToFirst()) {
-        // TODO: photo_id is always available but not taken
-        // care of here. Maybe we should store it in the
-        // CallerInfo object as well.
-
-        long contactId = 0L;
-        int columnIndex;
-
-        // Look for the name
-        columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
-        if (columnIndex != -1) {
-          info.name = cursor.getString(columnIndex);
-        }
-
-        // Look for the number
-        columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
-        if (columnIndex != -1) {
-          info.phoneNumber = cursor.getString(columnIndex);
-        }
-
-        // Look for the normalized number
-        columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
-        if (columnIndex != -1) {
-          info.normalizedNumber = cursor.getString(columnIndex);
-        }
-
-        // Look for the label/type combo
-        columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
-        if (columnIndex != -1) {
-          int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
-          if (typeColumnIndex != -1) {
-            info.numberType = cursor.getInt(typeColumnIndex);
-            info.numberLabel = cursor.getString(columnIndex);
-            info.phoneLabel =
-                Phone.getTypeLabel(context.getResources(), info.numberType, info.numberLabel)
-                    .toString();
-          }
-        }
-
-        // cache the lookup key for later use to create lookup URIs
-        columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
-        if (columnIndex != -1) {
-          info.lookupKeyOrNull = cursor.getString(columnIndex);
-        }
-
-        // Look for the person_id.
-        columnIndex = getColumnIndexForPersonId(contactRef, cursor);
-        if (columnIndex != -1) {
-          contactId = cursor.getLong(columnIndex);
-          // QuickContacts in M doesn't support enterprise contact id
-          if (contactId != 0
-              && (VERSION.SDK_INT >= VERSION_CODES.N
-                  || !Contacts.isEnterpriseContactId(contactId))) {
-            info.contactIdOrZero = contactId;
-            Log.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);
-          }
-        } else {
-          // No valid columnIndex, so we can't look up person_id.
-          Log.v(TAG, "Couldn't find contactId column for " + contactRef);
-          // Watch out: this means that anything that depends on
-          // person_id will be broken (like contact photo lookups in
-          // the in-call UI, for example.)
-        }
-
-        // Display photo URI.
-        columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
-        if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
-          info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
-        } else {
-          info.contactDisplayPhotoUri = null;
-        }
-
-        // look for the custom ringtone, create from the string stored
-        // in the database.
-        columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
-        if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
-          if (TextUtils.isEmpty(cursor.getString(columnIndex))) {
-            // make it consistent with frameworks/base/.../CallerInfo.java
-            info.contactRingtoneUri = Uri.EMPTY;
-          } else {
-            info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
-          }
-        } else {
-          info.contactRingtoneUri = null;
-        }
-
-        // look for the send to voicemail flag, set it to true only
-        // under certain circumstances.
-        columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
-        info.shouldSendToVoicemail = (columnIndex != -1) && ((cursor.getInt(columnIndex)) == 1);
-        info.contactExists = true;
-
-        // Determine userType by directoryId and contactId
-        final String directory =
-            contactRef == null
-                ? null
-                : contactRef.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
-        Long directoryId = null;
-        if (directory != null) {
-          try {
-            directoryId = Long.parseLong(directory);
-          } catch (NumberFormatException e) {
-            // do nothing
-          }
-        }
-        info.userType = ContactsUtils.determineUserType(directoryId, contactId);
-
-        info.nameAlternative =
-            ContactInfoHelper.lookUpDisplayNameAlternative(
-                context, info.lookupKeyOrNull, info.userType, directoryId);
-      }
-      cursor.close();
+    if (cursor == null || !cursor.moveToFirst()) {
+      return info;
     }
 
-    info.needUpdate = false;
-    info.name = normalize(info.name);
-    info.contactRefUri = contactRef;
+    // TODO: photo_id is always available but not taken
+    // care of here. Maybe we should store it in the
+    // CallerInfo object as well.
+
+    long contactId = 0L;
+    int columnIndex;
+
+    // If the cursor has the phone number column, find the one that matches the lookup number in the
+    // URI.
+    columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
+    if (columnIndex != -1 && contactRef != null) {
+      cursor = PhoneNumberHelper.getCursorMatchForContactLookupUri(cursor, columnIndex, contactRef);
+      if (cursor != null) {
+        info.phoneNumber = cursor.getString(columnIndex);
+      } else {
+        return info;
+      }
+    }
+
+    // Look for the name
+    columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
+    if (columnIndex != -1) {
+      info.name = normalize(cursor.getString(columnIndex));
+    }
+
+    // Look for the normalized number
+    columnIndex = cursor.getColumnIndex(PhoneLookup.NORMALIZED_NUMBER);
+    if (columnIndex != -1) {
+      info.normalizedNumber = cursor.getString(columnIndex);
+    }
+
+    // Look for the label/type combo
+    columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
+    if (columnIndex != -1) {
+      int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
+      if (typeColumnIndex != -1) {
+        info.numberType = cursor.getInt(typeColumnIndex);
+        info.numberLabel = cursor.getString(columnIndex);
+        info.phoneLabel =
+            Phone.getTypeLabel(context.getResources(), info.numberType, info.numberLabel)
+                .toString();
+      }
+    }
+
+    // cache the lookup key for later use to create lookup URIs
+    columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY);
+    if (columnIndex != -1) {
+      info.lookupKeyOrNull = cursor.getString(columnIndex);
+    }
+
+    // Look for the person_id.
+    columnIndex = getColumnIndexForPersonId(contactRef, cursor);
+    if (columnIndex != -1) {
+      contactId = cursor.getLong(columnIndex);
+      // QuickContacts in M doesn't support enterprise contact id
+      if (contactId != 0
+          && (VERSION.SDK_INT >= VERSION_CODES.N || !Contacts.isEnterpriseContactId(contactId))) {
+        info.contactIdOrZero = contactId;
+        Log.v(TAG, "==> got info.contactIdOrZero: " + info.contactIdOrZero);
+      }
+    } else {
+      // No valid columnIndex, so we can't look up person_id.
+      Log.v(TAG, "Couldn't find contactId column for " + contactRef);
+      // Watch out: this means that anything that depends on
+      // person_id will be broken (like contact photo lookups in
+      // the in-call UI, for example.)
+    }
+
+    // Display photo URI.
+    columnIndex = cursor.getColumnIndex(PhoneLookup.PHOTO_URI);
+    if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
+      info.contactDisplayPhotoUri = Uri.parse(cursor.getString(columnIndex));
+    } else {
+      info.contactDisplayPhotoUri = null;
+    }
+
+    // look for the custom ringtone, create from the string stored
+    // in the database.
+    columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
+    if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
+      if (TextUtils.isEmpty(cursor.getString(columnIndex))) {
+        // make it consistent with frameworks/base/.../CallerInfo.java
+        info.contactRingtoneUri = Uri.EMPTY;
+      } else {
+        info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
+      }
+    } else {
+      info.contactRingtoneUri = null;
+    }
+
+    // look for the send to voicemail flag, set it to true only
+    // under certain circumstances.
+    columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
+    info.shouldSendToVoicemail = (columnIndex != -1) && ((cursor.getInt(columnIndex)) == 1);
+    info.contactExists = true;
+
+    // Determine userType by directoryId and contactId
+    final String directory =
+        contactRef == null
+            ? null
+            : contactRef.getQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY);
+    Long directoryId = null;
+    if (directory != null) {
+      try {
+        directoryId = Long.parseLong(directory);
+      } catch (NumberFormatException e) {
+        // do nothing
+      }
+    }
+    info.userType = ContactsUtils.determineUserType(directoryId, contactId);
+
+    info.nameAlternative =
+        ContactInfoHelper.lookUpDisplayNameAlternative(
+            context, info.lookupKeyOrNull, info.userType, directoryId);
+    cursor.close();
 
     return info;
   }