Merge changes I4b5a494b,I4b6f7ca8

* changes:
  Added logging to aid debugging of issue where dialer shows blank screen.
  NewSearchFragment contact photos now properly open quick contact cards.
diff --git a/java/com/android/dialer/app/DialtactsActivity.java b/java/com/android/dialer/app/DialtactsActivity.java
index 01bdfc2..3e6b50c 100644
--- a/java/com/android/dialer/app/DialtactsActivity.java
+++ b/java/com/android/dialer/app/DialtactsActivity.java
@@ -516,7 +516,7 @@
 
   @Override
   protected void onResume() {
-    LogUtil.d("DialtactsActivity.onResume", "");
+    LogUtil.enterBlock("DialtactsActivity.onResume");
     Trace.beginSection(TAG + " onResume");
     super.onResume();
 
@@ -529,11 +529,14 @@
 
     mStateSaved = false;
     if (mFirstLaunch) {
+      LogUtil.i("DialtactsActivity.onResume", "mFirstLaunch true, displaying fragment");
       displayFragment(getIntent());
     } else if (!phoneIsInUse() && mInCallDialpadUp) {
+      LogUtil.i("DialtactsActivity.onResume", "phone not in use, hiding dialpad fragment");
       hideDialpadFragment(false, true);
       mInCallDialpadUp = false;
     } else if (mShowDialpadOnResume) {
+      LogUtil.i("DialtactsActivity.onResume", "showing dialpad on resume");
       showDialpadFragment(false);
       mShowDialpadOnResume = false;
     } else {
@@ -662,10 +665,11 @@
 
   @Override
   public void onAttachFragment(final Fragment fragment) {
-    LogUtil.d("DialtactsActivity.onAttachFragment", "fragment: %s", fragment);
+    LogUtil.i("DialtactsActivity.onAttachFragment", "fragment: %s", fragment);
     if (fragment instanceof DialpadFragment) {
       mDialpadFragment = (DialpadFragment) fragment;
       if (!mIsDialpadShown && !mShowDialpadOnResume) {
+        LogUtil.i("DialtactsActivity.onAttachFragment", "hiding dialpad fragment");
         final FragmentTransaction transaction = getFragmentManager().beginTransaction();
         transaction.hide(mDialpadFragment);
         transaction.commit();
@@ -830,8 +834,13 @@
    * @see #onDialpadShown
    */
   private void showDialpadFragment(boolean animate) {
-    LogUtil.d("DialtactActivity.showDialpadFragment", "animate: %b", animate);
-    if (mIsDialpadShown || mStateSaved) {
+    LogUtil.i("DialtactActivity.showDialpadFragment", "animate: %b", animate);
+    if (mIsDialpadShown) {
+      LogUtil.i("DialtactsActivity.showDialpadFragment", "dialpad already shown");
+      return;
+    }
+    if (mStateSaved) {
+      LogUtil.i("DialtactsActivity.showDialpadFragment", "state already saved");
       return;
     }
     mIsDialpadShown = true;
@@ -875,7 +884,7 @@
   /** Callback from child DialpadFragment when the dialpad is shown. */
   @Override
   public void onDialpadShown() {
-    LogUtil.d("DialtactsActivity.onDialpadShown", "");
+    LogUtil.enterBlock("DialtactsActivity.onDialpadShown");
     Assert.isNotNull(mDialpadFragment);
     if (mDialpadFragment.getAnimate()) {
       Assert.isNotNull(mDialpadFragment.getView()).startAnimation(mSlideIn);
@@ -894,6 +903,7 @@
    */
   @Override
   public void hideDialpadFragment(boolean animate, boolean clearDialpad) {
+    LogUtil.enterBlock("DialtactsActivity.hideDialpadFragment");
     if (mDialpadFragment == null || mDialpadFragment.getView() == null) {
       return;
     }
@@ -1081,11 +1091,17 @@
       return;
     }
 
-    final boolean showDialpadChooser =
+    boolean showDialpadChooser =
         !ACTION_SHOW_TAB.equals(intent.getAction())
             && phoneIsInUse()
             && !DialpadFragment.isAddCallMode(intent);
-    if (showDialpadChooser || (intent.getData() != null && isDialIntent(intent))) {
+    boolean isDialIntent = intent.getData() != null && isDialIntent(intent);
+    if (showDialpadChooser || isDialIntent) {
+      LogUtil.i(
+          "DialtactsActivity.displayFragment",
+          "showing dialpad fragment (showDialpadChooser: %b, isDialIntent: %b)",
+          showDialpadChooser,
+          isDialIntent);
       showDialpadFragment(false);
       mDialpadFragment.setStartedFromNewIntent(true);
       if (showDialpadChooser && !mDialpadFragment.isVisible()) {
@@ -1109,6 +1125,7 @@
 
   @Override
   public void onNewIntent(Intent newIntent) {
+    LogUtil.enterBlock("DialtactsActivity.onNewIntent");
     setIntent(newIntent);
     mFirstLaunch = true;
 
@@ -1135,17 +1152,19 @@
 
   /** Shows the search fragment */
   private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) {
+    LogUtil.i("DialtactsActivity.enterSearchUi", "smart dial: %b", smartDialSearch);
     if (mStateSaved || getFragmentManager().isDestroyed()) {
       // Weird race condition where fragment is doing work after the activity is destroyed
       // due to talkback being on (b/10209937). Just return since we can't do any
       // constructive here.
+      LogUtil.i(
+          "DialtactsActivity.enterSearchUi",
+          "not entering search UI (mStateSaved: %b, isDestroyed: %b)",
+          mStateSaved,
+          getFragmentManager().isDestroyed());
       return;
     }
 
-    if (DEBUG) {
-      LogUtil.v("DialtactsActivity.enterSearchUi", "smart dial " + smartDialSearch);
-    }
-
     final FragmentTransaction transaction = getFragmentManager().beginTransaction();
     if (mInDialpadSearch && mSmartDialSearchFragment != null) {
       transaction.remove(mSmartDialSearchFragment);
@@ -1232,6 +1251,8 @@
 
   /** Hides the search fragment */
   private void exitSearchUi() {
+    LogUtil.enterBlock("DialtactsActivity.exitSearchUi");
+
     // See related bug in enterSearchUI();
     if (getFragmentManager().isDestroyed() || mStateSaved) {
       return;
diff --git a/java/com/android/dialer/app/calllog/CallLogFragment.java b/java/com/android/dialer/app/calllog/CallLogFragment.java
index 6d4aea9..441cb4a 100644
--- a/java/com/android/dialer/app/calllog/CallLogFragment.java
+++ b/java/com/android/dialer/app/calllog/CallLogFragment.java
@@ -201,7 +201,7 @@
 
   @Override
   public void onCreate(Bundle state) {
-    LogUtil.d("CallLogFragment.onCreate", toString());
+    LogUtil.enterBlock("CallLogFragment.onCreate");
     super.onCreate(state);
     mRefreshDataRequired = true;
     if (state != null) {
@@ -362,6 +362,7 @@
 
   @Override
   public void onActivityCreated(Bundle savedInstanceState) {
+    LogUtil.enterBlock("CallLogFragment.onActivityCreated");
     super.onActivityCreated(savedInstanceState);
     setupData();
     updateSelectAllState(savedInstanceState);
@@ -384,7 +385,7 @@
 
   @Override
   public void onResume() {
-    LogUtil.d("CallLogFragment.onResume", toString());
+    LogUtil.enterBlock("CallLogFragment.onResume");
     super.onResume();
     final boolean hasReadCallLogPermission =
         PermissionsUtil.hasPermission(getActivity(), READ_CALL_LOG);
@@ -411,7 +412,7 @@
 
   @Override
   public void onPause() {
-    LogUtil.d("CallLogFragment.onPause", toString());
+    LogUtil.enterBlock("CallLogFragment.onPause");
     cancelDisplayUpdate();
     mAdapter.onPause();
     super.onPause();
@@ -419,6 +420,7 @@
 
   @Override
   public void onStart() {
+    LogUtil.enterBlock("CallLogFragment.onStart");
     super.onStart();
     CequintCallerIdManager cequintCallerIdManager = null;
     if (CequintCallerIdManager.isCequintCallerIdEnabled(getContext())) {
@@ -429,6 +431,7 @@
 
   @Override
   public void onStop() {
+    LogUtil.enterBlock("CallLogFragment.onStop");
     super.onStop();
     mAdapter.onStop();
     mContactInfoCache.stop();
@@ -436,7 +439,7 @@
 
   @Override
   public void onDestroy() {
-    LogUtil.d("CallLogFragment.onDestroy", toString());
+    LogUtil.enterBlock("CallLogFragment.onDestroy");
     mAdapter.changeCursor(null);
 
     getActivity().getContentResolver().unregisterContentObserver(mCallLogObserver);
@@ -552,6 +555,7 @@
           "Requesting permissions: " + Arrays.toString(deniedPermissions));
       FragmentCompat.requestPermissions(this, deniedPermissions, PHONE_PERMISSIONS_REQUEST_CODE);
     } else if (!mIsCallLogActivity) {
+      LogUtil.i("CallLogFragment.onEmptyViewActionButtonClicked", "showing dialpad");
       // Show dialpad if we are not in the call log activity.
       ((HostInterface) activity).showDialpad();
     }
diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java
index c15014f..e22250c 100644
--- a/java/com/android/dialer/dialpadview/DialpadFragment.java
+++ b/java/com/android/dialer/dialpadview/DialpadFragment.java
@@ -317,6 +317,7 @@
   @Override
   public void onCreate(Bundle state) {
     Trace.beginSection(TAG + " onCreate");
+    LogUtil.enterBlock("DialpadFragment.onCreate");
     super.onCreate(state);
 
     mFirstLaunch = state == null;
@@ -352,6 +353,7 @@
   @Override
   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedState) {
     Trace.beginSection(TAG + " onCreateView");
+    LogUtil.enterBlock("DialpadFragment.onCreateView");
     Trace.beginSection(TAG + " inflate view");
     View fragmentView = inflater.inflate(R.layout.dialpad_fragment, container, false);
     Trace.endSection();
@@ -393,6 +395,7 @@
             (v, event) -> {
               if (isDigitsEmpty()) {
                 if (getActivity() != null) {
+                  LogUtil.i("DialpadFragment.onCreateView", "dialpad spacer touched");
                   return ((HostInterface) getActivity()).onDialpadSpacerTouchWithEmptyQuery();
                 }
                 return true;
@@ -486,12 +489,16 @@
    * screen to enter "Add Call" mode, this method will show correct UI for the mode.
    */
   private void configureScreenFromIntent(Activity parent) {
+    LogUtil.enterBlock("DialpadFragment.configureScreenFromIntent");
+
     // If we were not invoked with a DIAL intent
     if (!Intent.ACTION_DIAL.equals(parent.getIntent().getAction())) {
       setStartedFromNewIntent(false);
       return;
     }
 
+    LogUtil.i("DialpadFragment.configureScreenFromIntent", "dial intent");
+
     // See if we were invoked with a DIAL intent. If we were, fill in the appropriate
     // digits in the dialer field.
     Intent intent = parent.getIntent();
@@ -521,6 +528,7 @@
       if (!(mStartedFromNewIntent && digitsFilled)) {
 
         final String action = intent.getAction();
+        LogUtil.i("DialpadFragment.configureScreenFromIntent", "action: %s", action);
         if (Intent.ACTION_DIAL.equals(action)
             || Intent.ACTION_VIEW.equals(action)
             || Intent.ACTION_MAIN.equals(action)) {
@@ -532,6 +540,10 @@
         }
       }
     }
+    LogUtil.i(
+        "DialpadFragment.configureScreenFromIntent",
+        "needToShowDialpadChooser? %b",
+        needToShowDialpadChooser);
     showDialpadChooser(needToShowDialpadChooser);
     setStartedFromNewIntent(false);
   }
@@ -595,7 +607,7 @@
 
   @Override
   public void onStart() {
-    LogUtil.d("DialpadFragment.onStart", "first launch: %b", mFirstLaunch);
+    LogUtil.i("DialpadFragment.onStart", "first launch: %b", mFirstLaunch);
     Trace.beginSection(TAG + " onStart");
     super.onStart();
     // if the mToneGenerator creation fails, just continue without it.  It is
@@ -622,7 +634,7 @@
 
   @Override
   public void onResume() {
-    LogUtil.d("DialpadFragment.onResume", "");
+    LogUtil.enterBlock("DialpadFragment.onResume");
     Trace.beginSection(TAG + " onResume");
     super.onResume();
 
@@ -662,6 +674,7 @@
     stopWatch.lap("fdin");
 
     if (!isPhoneInUse()) {
+      LogUtil.i("DialpadFragment.onResume", "phone not in use");
       // A sanity-check: the "dialpad chooser" UI should not be visible if the phone is idle.
       showDialpadChooser(false);
     }
@@ -711,6 +724,7 @@
 
   @Override
   public void onStop() {
+    LogUtil.enterBlock("DialpadFragment.onStop");
     super.onStop();
 
     synchronized (mToneGeneratorLock) {
@@ -991,6 +1005,7 @@
   }
 
   private void hideAndClearDialpad(boolean animate) {
+    LogUtil.enterBlock("DialpadFragment.hideAndClearDialpad");
     FragmentUtils.getParentUnsafe(this, DialpadListener.class).hideDialpadFragment(animate, true);
   }
 
@@ -1194,8 +1209,10 @@
     } else {
       LogUtil.i("DialpadFragment.showDialpadChooser", "Displaying normal Dialer UI.");
       if (mDialpadView != null) {
+        LogUtil.i("DialpadFragment.showDialpadChooser", "mDialpadView not null");
         mDialpadView.setVisibility(View.VISIBLE);
       } else {
+        LogUtil.i("DialpadFragment.showDialpadChooser", "mDialpadView null");
         mDigits.setVisibility(View.VISIBLE);
       }
 
@@ -1681,6 +1698,7 @@
         // one of the choices, which would be confusing.  (But at
         // least that's better than leaving the dialpad chooser
         // onscreen, but useless...)
+        LogUtil.i("CallStateReceiver.onReceive", "hiding dialpad chooser, state: %s", state);
         showDialpadChooser(false);
       }
     }
diff --git a/java/com/android/dialer/searchfragment/common/SearchCursor.java b/java/com/android/dialer/searchfragment/common/SearchCursor.java
index 368ee09..7ad19aa 100644
--- a/java/com/android/dialer/searchfragment/common/SearchCursor.java
+++ b/java/com/android/dialer/searchfragment/common/SearchCursor.java
@@ -35,4 +35,11 @@
    * @return true if the data set has changed.
    */
   boolean updateQuery(@NonNull String query);
+
+  /**
+   * Returns an ID unique to the directory this cursor reads from. Generally this value will be
+   * related to {@link android.provider.ContactsContract.Directory} but could differ depending on
+   * the implementation.
+   */
+  long getDirectoryId();
 }
diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java
index 18c9ecd..508ca7f 100644
--- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java
+++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursor.java
@@ -20,6 +20,7 @@
 import android.database.Cursor;
 import android.database.MatrixCursor;
 import android.database.MergeCursor;
+import android.provider.ContactsContract.Directory;
 import android.support.annotation.Nullable;
 import com.android.dialer.searchfragment.common.SearchCursor;
 
@@ -32,7 +33,7 @@
 
   private final ContactFilterCursor contactFilterCursor;
 
-  public static SearchContactsCursor newInstnace(
+  static SearchContactsCursor newInstance(
       Context context, ContactFilterCursor contactFilterCursor) {
     MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION);
     headerCursor.addRow(new String[] {context.getString(R.string.all_contacts)});
@@ -56,6 +57,11 @@
   }
 
   @Override
+  public long getDirectoryId() {
+    return Directory.DEFAULT;
+  }
+
+  @Override
   public int getCount() {
     // If we don't have any contents, we don't want to show the header
     int count = contactFilterCursor.getCount();
diff --git a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java
index 84fd64a..b7fc9b5 100644
--- a/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java
+++ b/java/com/android/dialer/searchfragment/cp2/SearchContactsCursorLoader.java
@@ -47,6 +47,6 @@
     // Filtering logic
     ContactFilterCursor contactFilterCursor = new ContactFilterCursor(cursor, query);
     // Header logic
-    return SearchContactsCursor.newInstnace(getContext(), contactFilterCursor);
+    return SearchContactsCursor.newInstance(getContext(), contactFilterCursor);
   }
 }
diff --git a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
index 7fee969..910e454 100644
--- a/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
+++ b/java/com/android/dialer/searchfragment/list/NewSearchFragment.java
@@ -120,7 +120,6 @@
 
   private void initLoaders() {
     getLoaderManager().initLoader(CONTACTS_LOADER_ID, null, this);
-    loadNearbyPlacesCursor();
     loadRemoteDirectoriesCursor();
   }
 
@@ -129,7 +128,14 @@
     if (id == CONTACTS_LOADER_ID) {
       return new SearchContactsCursorLoader(getContext(), query);
     } else if (id == NEARBY_PLACES_LOADER_ID) {
-      return new NearbyPlacesCursorLoader(getContext(), query);
+      // Directories represent contact data sources on the device, but since nearby places aren't
+      // stored on the device, they don't have a directory ID. We pass the list of all existing IDs
+      // so that we can find one that doesn't collide.
+      List<Integer> directoryIds = new ArrayList<>();
+      for (Directory directory : directories) {
+        directoryIds.add(directory.getId());
+      }
+      return new NearbyPlacesCursorLoader(getContext(), query, directoryIds);
     } else if (id == REMOTE_DIRECTORIES_LOADER_ID) {
       return new RemoteDirectoriesCursorLoader(getContext());
     } else if (id == REMOTE_CONTACTS_LOADER_ID) {
@@ -162,6 +168,7 @@
       while (cursor.moveToNext()) {
         directories.add(RemoteDirectoriesCursorLoader.readDirectory(cursor));
       }
+      loadNearbyPlacesCursor();
       loadRemoteContactsCursors();
 
     } else {
@@ -212,18 +219,6 @@
     ThreadUtil.getUiThreadHandler().removeCallbacks(capabilitiesUpdatedRunnable);
   }
 
-  private void loadNearbyPlacesCursor() {
-    // Cancel existing load if one exists.
-    ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable);
-
-    // If nearby places is not enabled, do not try to load them.
-    if (!PhoneDirectoryExtenderAccessor.get(getContext()).isEnabled(getContext())) {
-      return;
-    }
-    ThreadUtil.getUiThreadHandler()
-        .postDelayed(loadNearbyPlacesRunnable, NETWORK_SEARCH_DELAY_MILLIS);
-  }
-
   @Override
   public void onRequestPermissionsResult(
       int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
@@ -250,12 +245,14 @@
     }
   }
 
+  // Loads remote directories.
   private void loadRemoteDirectoriesCursor() {
     if (!remoteDirectoriesDisabledForTesting) {
       getLoaderManager().initLoader(REMOTE_DIRECTORIES_LOADER_ID, null, this);
     }
   }
 
+  // Should not be called before remote directories have finished loading.
   private void loadRemoteContactsCursors() {
     if (remoteDirectoriesDisabledForTesting) {
       return;
@@ -267,6 +264,19 @@
         .postDelayed(loadRemoteContactsRunnable, NETWORK_SEARCH_DELAY_MILLIS);
   }
 
+  // Should not be called before remote directories (not contacts) have finished loading.
+  private void loadNearbyPlacesCursor() {
+    // Cancel existing load if one exists.
+    ThreadUtil.getUiThreadHandler().removeCallbacks(loadNearbyPlacesRunnable);
+
+    // If nearby places is not enabled, do not try to load them.
+    if (!PhoneDirectoryExtenderAccessor.get(getContext()).isEnabled(getContext())) {
+      return;
+    }
+    ThreadUtil.getUiThreadHandler()
+        .postDelayed(loadNearbyPlacesRunnable, NETWORK_SEARCH_DELAY_MILLIS);
+  }
+
   @Override
   public void onResume() {
     super.onResume();
diff --git a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java
index 575582e..fa07826 100644
--- a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java
+++ b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlaceViewHolder.java
@@ -17,13 +17,14 @@
 package com.android.dialer.searchfragment.nearbyplaces;
 
 import android.content.Context;
-import android.database.Cursor;
 import android.net.Uri;
 import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
 import android.support.v7.widget.RecyclerView;
 import android.view.View;
 import android.widget.QuickContactBadge;
 import android.widget.TextView;
+import com.android.contacts.common.util.Constants;
 import com.android.dialer.callintent.CallInitiationType;
 import com.android.dialer.callintent.CallIntentBuilder;
 import com.android.dialer.contactphoto.ContactPhotoManager;
@@ -31,6 +32,7 @@
 import com.android.dialer.searchfragment.common.Projections;
 import com.android.dialer.searchfragment.common.QueryBoldingUtil;
 import com.android.dialer.searchfragment.common.R;
+import com.android.dialer.searchfragment.common.SearchCursor;
 import com.android.dialer.telecom.TelecomUtil;
 
 /** ViewHolder for a nearby place row. */
@@ -57,14 +59,13 @@
    * Binds the ViewHolder with a cursor from {@link NearbyPlacesCursorLoader} with the data found at
    * the cursors set position.
    */
-  public void bind(Cursor cursor, String query) {
+  public void bind(SearchCursor cursor, String query) {
     number = cursor.getString(Projections.PHONE_NUMBER);
     String name = cursor.getString(Projections.PHONE_DISPLAY_NAME);
     String address = cursor.getString(Projections.PHONE_LABEL);
 
     placeName.setText(QueryBoldingUtil.getNameWithQueryBolded(query, name));
     placeAddress.setText(QueryBoldingUtil.getNameWithQueryBolded(query, address));
-
     String photoUri = cursor.getString(Projections.PHONE_PHOTO_URI);
     ContactPhotoManager.getInstance(context)
         .loadDialerThumbnailOrPhoto(
@@ -73,13 +74,21 @@
             cursor.getLong(Projections.PHONE_PHOTO_ID),
             photoUri == null ? null : Uri.parse(photoUri),
             name,
-            LetterTileDrawable.TYPE_DEFAULT);
+            LetterTileDrawable.TYPE_BUSINESS);
   }
 
-  private static Uri getContactUri(Cursor cursor) {
-    long contactId = cursor.getLong(Projections.PHONE_ID);
-    String lookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY);
-    return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+  private static Uri getContactUri(SearchCursor cursor) {
+    // Since the lookup key for Nearby Places is actually a JSON representation of the information,
+    // we need to pass it in as an encoded fragment in our contact uri.
+    // It includes information like display name, photo uri, phone number, ect.
+    String businessInfoJson = cursor.getString(Projections.PHONE_LOOKUP_KEY);
+    return Contacts.CONTENT_LOOKUP_URI
+        .buildUpon()
+        .appendPath(Constants.LOOKUP_URI_ENCODED)
+        .appendQueryParameter(
+            ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(cursor.getDirectoryId()))
+        .encodedFragment(businessInfoJson)
+        .build();
   }
 
   @Override
diff --git a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursor.java b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursor.java
index a4142a4..3be59b6 100644
--- a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursor.java
+++ b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursor.java
@@ -27,16 +27,23 @@
 final class NearbyPlacesCursor extends MergeCursor implements SearchCursor {
 
   private final Cursor nearbyPlacesCursor;
+  private final long directoryId;
 
-  public static NearbyPlacesCursor newInstnace(Context context, Cursor nearbyPlacesCursor) {
+  /**
+   * @param directoryId unique directory id that doesn't collide with other remote/local
+   *     directories. directoryIds are needed to load the correct quick contact card.
+   */
+  static NearbyPlacesCursor newInstance(
+      Context context, Cursor nearbyPlacesCursor, long directoryId) {
     MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION);
     headerCursor.addRow(new String[] {context.getString(R.string.nearby_places)});
-    return new NearbyPlacesCursor(new Cursor[] {headerCursor, nearbyPlacesCursor});
+    return new NearbyPlacesCursor(new Cursor[] {headerCursor, nearbyPlacesCursor}, directoryId);
   }
 
-  private NearbyPlacesCursor(Cursor[] cursors) {
+  private NearbyPlacesCursor(Cursor[] cursors, long directoryId) {
     super(cursors);
     nearbyPlacesCursor = cursors[1];
+    this.directoryId = directoryId;
   }
 
   @Override
@@ -61,4 +68,9 @@
     int count = nearbyPlacesCursor.getCount();
     return count == 0 ? 0 : count + 1;
   }
+
+  @Override
+  public long getDirectoryId() {
+    return directoryId;
+  }
 }
diff --git a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursorLoader.java b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursorLoader.java
index 6807a6e..c8bb36a 100644
--- a/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursorLoader.java
+++ b/java/com/android/dialer/searchfragment/nearbyplaces/NearbyPlacesCursorLoader.java
@@ -21,21 +21,37 @@
 import android.database.Cursor;
 import android.net.Uri;
 import android.provider.ContactsContract;
+import android.support.annotation.NonNull;
 import com.android.contacts.common.extensions.PhoneDirectoryExtenderAccessor;
+import com.android.dialer.common.LogUtil;
 import com.android.dialer.searchfragment.common.Projections;
+import java.util.List;
 
 /** Cursor loader for nearby places search results. */
 public final class NearbyPlacesCursorLoader extends CursorLoader {
 
   private static final String MAX_RESULTS = "3";
+  private static final long INVALID_DIRECTORY_ID = Long.MAX_VALUE;
+  private final long directoryId;
 
-  public NearbyPlacesCursorLoader(Context context, String query) {
+  /**
+   * @param directoryIds List of directoryIds associated with all directories on device. Required in
+   *     order to find a directory ID for the nearby places cursor that doesn't collide with
+   *     existing directories.
+   */
+  public NearbyPlacesCursorLoader(
+      Context context, String query, @NonNull List<Integer> directoryIds) {
     super(context, getContentUri(context, query), Projections.PHONE_PROJECTION, null, null, null);
+    this.directoryId = getDirectoryId(directoryIds);
   }
 
   @Override
   public Cursor loadInBackground() {
-    return NearbyPlacesCursor.newInstnace(getContext(), super.loadInBackground());
+    if (directoryId == INVALID_DIRECTORY_ID) {
+      LogUtil.i("NearbyPlacesCursorLoader.loadInBackground", "directory id not set.");
+      return null;
+    }
+    return NearbyPlacesCursor.newInstance(getContext(), super.loadInBackground(), directoryId);
   }
 
   private static Uri getContentUri(Context context, String query) {
@@ -46,4 +62,22 @@
         .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY, MAX_RESULTS)
         .build();
   }
+
+  private static long getDirectoryId(List<Integer> directoryIds) {
+    if (directoryIds.isEmpty()) {
+      return INVALID_DIRECTORY_ID;
+    }
+
+    // The Directory.LOCAL_INVISIBLE might not be a directory we use, but we can't reuse it's
+    // "special" ID.
+    long maxId = ContactsContract.Directory.LOCAL_INVISIBLE;
+    for (int i = 0, n = directoryIds.size(); i < n; i++) {
+      long id = directoryIds.get(i);
+      if (id > maxId) {
+        maxId = id;
+      }
+    }
+    // Add one so that the nearby places ID doesn't collide with extended directory IDs.
+    return maxId + 1;
+  }
 }
diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java
index 5fb12d3..df3eacc 100644
--- a/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java
+++ b/java/com/android/dialer/searchfragment/remote/RemoteContactViewHolder.java
@@ -22,6 +22,7 @@
 import android.net.Uri;
 import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Contacts;
 import android.support.v7.widget.RecyclerView;
 import android.text.TextUtils;
 import android.view.View;
@@ -119,10 +120,14 @@
     return (String) Phone.getTypeLabel(resources, numberType, numberLabel);
   }
 
-  private static Uri getContactUri(Cursor cursor) {
+  private static Uri getContactUri(SearchCursor cursor) {
     long contactId = cursor.getLong(Projections.PHONE_ID);
     String lookupKey = cursor.getString(Projections.PHONE_LOOKUP_KEY);
-    return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+    return Contacts.getLookupUri(contactId, lookupKey)
+        .buildUpon()
+        .appendQueryParameter(
+            ContactsContract.DIRECTORY_PARAM_KEY, String.valueOf(cursor.getDirectoryId()))
+        .build();
   }
 
   @Override
diff --git a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java
index d7c4f38..e6f3c26 100644
--- a/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java
+++ b/java/com/android/dialer/searchfragment/remote/RemoteContactsCursor.java
@@ -26,6 +26,7 @@
 import com.android.dialer.searchfragment.common.SearchCursor;
 import com.android.dialer.searchfragment.remote.RemoteDirectoriesCursorLoader.Directory;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -41,6 +42,16 @@
 public final class RemoteContactsCursor extends MergeCursor implements SearchCursor {
 
   /**
+   * {@link SearchCursor#HEADER_PROJECTION} with {@link #COLUMN_DIRECTORY_ID} appended on the end.
+   *
+   * <p>This is needed to get the directoryId associated with each contact. directoryIds are needed
+   * to load the correct quick contact card.
+   */
+  private static final String[] PROJECTION = buildProjection();
+
+  private static final String COLUMN_DIRECTORY_ID = "directory_id";
+
+  /**
    * Returns a single cursor with headers inserted between each non-empty cursor. If all cursors are
    * empty, null or closed, this method returns null.
    */
@@ -78,18 +89,24 @@
         continue;
       }
 
-      cursorList.add(createHeaderCursor(context, directory.getDisplayName()));
+      cursorList.add(createHeaderCursor(context, directory.getDisplayName(), directory.getId()));
       cursorList.add(cursor);
     }
     return cursorList.toArray(new Cursor[cursorList.size()]);
   }
 
-  private static MatrixCursor createHeaderCursor(Context context, String name) {
-    MatrixCursor headerCursor = new MatrixCursor(HEADER_PROJECTION, 1);
-    headerCursor.addRow(new String[] {context.getString(R.string.directory, name)});
+  private static MatrixCursor createHeaderCursor(Context context, String name, int id) {
+    MatrixCursor headerCursor = new MatrixCursor(PROJECTION, 1);
+    headerCursor.addRow(new Object[] {context.getString(R.string.directory, name), id});
     return headerCursor;
   }
 
+  private static String[] buildProjection() {
+    String[] projection = Arrays.copyOf(HEADER_PROJECTION, HEADER_PROJECTION.length + 1);
+    projection[projection.length - 1] = COLUMN_DIRECTORY_ID;
+    return projection;
+  }
+
   /** Returns true if the current position is a header row. */
   @Override
   public boolean isHeader() {
@@ -97,6 +114,21 @@
   }
 
   @Override
+  public long getDirectoryId() {
+    int position = getPosition();
+    // proceed backwards until we reach the header row, which contains the directory ID.
+    while (moveToPrevious()) {
+      int id = getInt(getColumnIndex(COLUMN_DIRECTORY_ID));
+      if (id != -1) {
+        // return the cursor to it's original position/state
+        moveToPosition(position);
+        return id;
+      }
+    }
+    throw Assert.createIllegalStateFailException("No directory id for contact at: " + position);
+  }
+
+  @Override
   public boolean updateQuery(@Nullable String query) {
     // When the query changes, a new network request is made for nearby places. Meaning this cursor
     // will be closed and another created, so return false.
diff --git a/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java b/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java
index 327a62c..de71025 100644
--- a/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java
+++ b/java/com/android/dialer/searchfragment/remote/RemoteDirectoriesCursorLoader.java
@@ -67,7 +67,7 @@
       return new AutoValue_RemoteDirectoriesCursorLoader_Directory(id, displayName, supportsPhotos);
     }
 
-    abstract int getId();
+    public abstract int getId();
 
     /** Returns a user facing display name of the directory. Null if none exists. */
     abstract @Nullable String getDisplayName();
diff --git a/java/com/android/dialer/searchfragment/testing/TestSearchCursor.java b/java/com/android/dialer/searchfragment/testing/TestSearchCursor.java
index 9a0b957..7e6299e 100644
--- a/java/com/android/dialer/searchfragment/testing/TestSearchCursor.java
+++ b/java/com/android/dialer/searchfragment/testing/TestSearchCursor.java
@@ -44,4 +44,9 @@
   public boolean updateQuery(@Nullable String query) {
     return false;
   }
+
+  @Override
+  public long getDirectoryId() {
+    return 0;
+  }
 }