am ae07c2be: Fix directory lookups

* commit 'ae07c2be104a1f95426b697f3074bea72bc140e1':
  Fix directory lookups
diff --git a/src/com/android/ex/chips/BaseRecipientAdapter.java b/src/com/android/ex/chips/BaseRecipientAdapter.java
index 531fd37..8233081 100644
--- a/src/com/android/ex/chips/BaseRecipientAdapter.java
+++ b/src/com/android/ex/chips/BaseRecipientAdapter.java
@@ -23,6 +23,8 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.Resources;
 import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
@@ -45,6 +47,7 @@
 import com.android.ex.chips.DropdownChipLayouter.AdapterType;
 
 import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
@@ -147,10 +150,11 @@
         public final int destinationType;
         public final String destinationLabel;
         public final long contactId;
+        public final Long directoryId;
         public final long dataId;
         public final String thumbnailUriString;
         public final int displayNameSource;
-        public final boolean isGalContact;
+        public final String lookupKey;
 
         public TemporaryEntry(
                 String displayName,
@@ -158,31 +162,34 @@
                 int destinationType,
                 String destinationLabel,
                 long contactId,
+                Long directoryId,
                 long dataId,
                 String thumbnailUriString,
                 int displayNameSource,
-                boolean isGalContact) {
+                String lookupKey) {
             this.displayName = displayName;
             this.destination = destination;
             this.destinationType = destinationType;
             this.destinationLabel = destinationLabel;
             this.contactId = contactId;
+            this.directoryId = directoryId;
             this.dataId = dataId;
             this.thumbnailUriString = thumbnailUriString;
             this.displayNameSource = displayNameSource;
-            this.isGalContact = isGalContact;
+            this.lookupKey = lookupKey;
         }
 
-        public TemporaryEntry(Cursor cursor, boolean isGalContact) {
+        public TemporaryEntry(Cursor cursor, Long directoryId) {
             this.displayName = cursor.getString(Queries.Query.NAME);
             this.destination = cursor.getString(Queries.Query.DESTINATION);
             this.destinationType = cursor.getInt(Queries.Query.DESTINATION_TYPE);
             this.destinationLabel = cursor.getString(Queries.Query.DESTINATION_LABEL);
             this.contactId = cursor.getLong(Queries.Query.CONTACT_ID);
+            this.directoryId = directoryId;
             this.dataId = cursor.getLong(Queries.Query.DATA_ID);
             this.thumbnailUriString = cursor.getString(Queries.Query.PHOTO_THUMBNAIL_URI);
             this.displayNameSource = cursor.getInt(Queries.Query.DISPLAY_NAME_SOURCE);
-            this.isGalContact = isGalContact;
+            this.lookupKey = cursor.getString(Queries.Query.LOOKUP_KEY);
         }
     }
 
@@ -234,7 +241,8 @@
             }
 
             try {
-                defaultDirectoryCursor = doQuery(constraint, mPreferredMaxResultCount, null);
+                defaultDirectoryCursor = doQuery(constraint, mPreferredMaxResultCount,
+                        null /* directoryId */);
 
                 if (defaultDirectoryCursor == null) {
                     if (DEBUG) {
@@ -254,7 +262,7 @@
                         // Note: At this point each entry doesn't contain any photo
                         // (thus getPhotoBytes() returns null).
                         putOneEntry(new TemporaryEntry(defaultDirectoryCursor,
-                                false /* isGalContact */),
+                                null /* directoryId */),
                                 true, entryMap, nonAggregatedEntries, existingDestinations);
                     }
 
@@ -385,7 +393,7 @@
 
                     if (cursor != null) {
                         while (cursor.moveToNext()) {
-                            tempEntries.add(new TemporaryEntry(cursor, true /* isGalContact */));
+                            tempEntries.add(new TemporaryEntry(cursor, mParams.directoryId));
                         }
                     }
                 } finally {
@@ -695,8 +703,8 @@
                     entry.displayName,
                     entry.displayNameSource,
                     entry.destination, entry.destinationType, entry.destinationLabel,
-                    entry.contactId, entry.dataId, entry.thumbnailUriString, true,
-                    entry.isGalContact));
+                    entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
+                    true, entry.lookupKey));
         } else if (entryMap.containsKey(entry.contactId)) {
             // We already have a section for the person.
             final List<RecipientEntry> entryList = entryMap.get(entry.contactId);
@@ -704,16 +712,16 @@
                     entry.displayName,
                     entry.displayNameSource,
                     entry.destination, entry.destinationType, entry.destinationLabel,
-                    entry.contactId, entry.dataId, entry.thumbnailUriString, true,
-                    entry.isGalContact));
+                    entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
+                    true, entry.lookupKey));
         } else {
             final List<RecipientEntry> entryList = new ArrayList<RecipientEntry>();
             entryList.add(RecipientEntry.constructTopLevelEntry(
                     entry.displayName,
                     entry.displayNameSource,
                     entry.destination, entry.destinationType, entry.destinationLabel,
-                    entry.contactId, entry.dataId, entry.thumbnailUriString, true,
-                    entry.isGalContact));
+                    entry.contactId, entry.directoryId, entry.dataId, entry.thumbnailUriString,
+                    true, entry.lookupKey));
             entryMap.put(entry.contactId, entryList);
         }
     }
@@ -879,6 +887,39 @@
             } finally {
                 photoCursor.close();
             }
+        } else {
+            InputStream inputStream = null;
+            ByteArrayOutputStream outputStream = null;
+            try {
+                inputStream = mContentResolver.openInputStream(photoThumbnailUri);
+                final Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
+
+                if (bitmap != null) {
+                    outputStream = new ByteArrayOutputStream();
+                    bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
+                    photoBytes = outputStream.toByteArray();
+
+                    entry.setPhotoBytes(photoBytes);
+                    mPhotoCacheMap.put(photoThumbnailUri, photoBytes);
+                }
+            } catch (final FileNotFoundException e) {
+                Log.w(TAG, "Error opening InputStream for photo", e);
+            } finally {
+                try {
+                    if (inputStream != null) {
+                        inputStream.close();
+                    }
+                } catch (IOException e) {
+                    Log.e(TAG, "Error closing photo input stream", e);
+                }
+                try {
+                    if (outputStream != null) {
+                        outputStream.close();
+                    }
+                } catch (IOException e) {
+                    Log.e(TAG, "Error closing photo output stream", e);
+                }
+            }
         }
     }
 
diff --git a/src/com/android/ex/chips/DropdownChipLayouter.java b/src/com/android/ex/chips/DropdownChipLayouter.java
index aceefab..6b0e78e 100644
--- a/src/com/android/ex/chips/DropdownChipLayouter.java
+++ b/src/com/android/ex/chips/DropdownChipLayouter.java
@@ -171,7 +171,7 @@
                     if (thumbnailUri != null) {
                         // TODO: see if this needs to be done outside the main thread
                         // as it may be too slow to get immediately.
-                        view.setImageURI(entry.getPhotoThumbnailUri());
+                        view.setImageURI(thumbnailUri);
                     } else {
                         view.setImageResource(getDefaultPhotoResId());
                     }
diff --git a/src/com/android/ex/chips/Queries.java b/src/com/android/ex/chips/Queries.java
index 9d31aec..1e66b96 100644
--- a/src/com/android/ex/chips/Queries.java
+++ b/src/com/android/ex/chips/Queries.java
@@ -18,6 +18,7 @@
 
 import android.content.res.Resources;
 import android.net.Uri;
+import android.provider.ContactsContract;
 import android.provider.ContactsContract.CommonDataKinds.Email;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.Contacts;
@@ -28,14 +29,16 @@
 /* package */ class Queries {
 
     public static final Query PHONE = new Query(new String[] {
-            Contacts.DISPLAY_NAME,       // 0
-            Phone.NUMBER,                // 1
-            Phone.TYPE,                  // 2
-            Phone.LABEL,                 // 3
-            Phone.CONTACT_ID,            // 4
-            Phone._ID,                   // 5
-            Contacts.PHOTO_THUMBNAIL_URI,// 6
-            Contacts.DISPLAY_NAME_SOURCE // 7
+            Contacts.DISPLAY_NAME,                          // 0
+            Phone.NUMBER,                                   // 1
+            Phone.TYPE,                                     // 2
+            Phone.LABEL,                                    // 3
+            Phone.CONTACT_ID,                               // 4
+            Phone._ID,                                      // 5
+            Contacts.PHOTO_THUMBNAIL_URI,                   // 6
+            Contacts.DISPLAY_NAME_SOURCE,                   // 7
+            Contacts.LOOKUP_KEY,                            // 8
+            ContactsContract.CommonDataKinds.Email.MIMETYPE // 9
         }, Phone.CONTENT_FILTER_URI, Phone.CONTENT_URI) {
 
             @Override
@@ -46,14 +49,16 @@
     };
 
     public static final Query EMAIL = new Query(new String[]{
-            Contacts.DISPLAY_NAME,       // 0
-            Email.DATA,                  // 1
-            Email.TYPE,                  // 2
-            Email.LABEL,                 // 3
-            Email.CONTACT_ID,            // 4
-            Email._ID,                   // 5
-            Contacts.PHOTO_THUMBNAIL_URI,// 6
-            Contacts.DISPLAY_NAME_SOURCE // 7
+            Contacts.DISPLAY_NAME,                          // 0
+            Email.DATA,                                     // 1
+            Email.TYPE,                                     // 2
+            Email.LABEL,                                    // 3
+            Email.CONTACT_ID,                               // 4
+            Email._ID,                                      // 5
+            Contacts.PHOTO_THUMBNAIL_URI,                   // 6
+            Contacts.DISPLAY_NAME_SOURCE,                   // 7
+            Contacts.LOOKUP_KEY,                            // 8
+            ContactsContract.CommonDataKinds.Email.MIMETYPE // 9
         }, Email.CONTENT_FILTER_URI, Email.CONTENT_URI) {
 
             @Override
@@ -76,8 +81,10 @@
         public static final int DATA_ID = 5;             // long
         public static final int PHOTO_THUMBNAIL_URI = 6; // String
         public static final int DISPLAY_NAME_SOURCE = 7; // int
+        public static final int LOOKUP_KEY = 8;          // String
+        public static final int MIME_TYPE = 9;           // String
 
-        public Query (String[] projection, Uri contentFilter, Uri content) {
+        public Query(String[] projection, Uri contentFilter, Uri content) {
             mProjection = projection;
             mContentFilterUri = contentFilter;
             mContentUri = content;
diff --git a/src/com/android/ex/chips/RecipientAlternatesAdapter.java b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
index 547a76b..f6f662d 100644
--- a/src/com/android/ex/chips/RecipientAlternatesAdapter.java
+++ b/src/com/android/ex/chips/RecipientAlternatesAdapter.java
@@ -23,6 +23,7 @@
 import android.database.MatrixCursor;
 import android.net.Uri;
 import android.provider.ContactsContract;
+import android.provider.ContactsContract.Contacts;
 import android.text.TextUtils;
 import android.text.util.Rfc822Token;
 import android.text.util.Rfc822Tokenizer;
@@ -60,9 +61,11 @@
 
     public static final int QUERY_TYPE_EMAIL = 0;
     public static final int QUERY_TYPE_PHONE = 1;
-    private Query mQuery;
+    private final Long mDirectoryId;
     private DropdownChipLayouter mDropdownChipLayouter;
 
+    private static final Map<String, String> sCorrectedPhotoUris = new HashMap<String, String>();
+
     public interface RecipientMatchCallback {
         public void matchesFound(Map<String, RecipientEntry> results);
         /**
@@ -123,7 +126,7 @@
                     query.getProjection(),
                     query.getProjection()[Queries.Query.DESTINATION] + " IN ("
                             + bindString.toString() + ")", addressArray, null);
-            recipientEntries = processContactEntries(c);
+            recipientEntries = processContactEntries(c, null /* directoryId */);
             callback.matchesFound(recipientEntries);
         } finally {
             if (c != null) {
@@ -163,6 +166,7 @@
             if (paramsList != null) {
                 Cursor directoryContactsCursor = null;
                 for (String unresolvedAddress : unresolvedAddresses) {
+                    Long directoryId = null;
                     for (int i = 0; i < paramsList.size(); i++) {
                         try {
                             directoryContactsCursor = doQuery(unresolvedAddress, 1,
@@ -174,6 +178,7 @@
                                 directoryContactsCursor.close();
                                 directoryContactsCursor = null;
                             } else {
+                                directoryId = paramsList.get(i).directoryId;
                                 break;
                             }
                         }
@@ -181,7 +186,7 @@
                     if (directoryContactsCursor != null) {
                         try {
                             final Map<String, RecipientEntry> entries =
-                                    processContactEntries(directoryContactsCursor);
+                                    processContactEntries(directoryContactsCursor, directoryId);
 
                             for (final String address : entries.keySet()) {
                                 matchesNotFound.remove(address);
@@ -212,7 +217,8 @@
         callback.matchesNotFound(matchesNotFound);
     }
 
-    private static HashMap<String, RecipientEntry> processContactEntries(Cursor c) {
+    private static HashMap<String, RecipientEntry> processContactEntries(Cursor c,
+            Long directoryId) {
         HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
         if (c != null && c.moveToFirst()) {
             do {
@@ -225,10 +231,11 @@
                         c.getInt(Queries.Query.DESTINATION_TYPE),
                         c.getString(Queries.Query.DESTINATION_LABEL),
                         c.getLong(Queries.Query.CONTACT_ID),
+                        directoryId,
                         c.getLong(Queries.Query.DATA_ID),
                         c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
                         true,
-                        false /* isGalContact TODO(skennedy) We should look these up eventually */);
+                        c.getString(Queries.Query.LOOKUP_KEY));
 
                 /*
                  * In certain situations, we may have two results for one address, where one of the
@@ -325,43 +332,74 @@
         return cursor;
     }
 
-    public RecipientAlternatesAdapter(Context context, long contactId, long currentId,
-            int queryMode, OnCheckedItemChangedListener listener,
-        DropdownChipLayouter dropdownChipLayouter) {
-        super(context, getCursorForConstruction(context, contactId, queryMode), 0);
+    public RecipientAlternatesAdapter(Context context, long contactId, Long directoryId,
+            String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener,
+            DropdownChipLayouter dropdownChipLayouter) {
+        super(context,
+                getCursorForConstruction(context, contactId, directoryId, lookupKey, queryMode), 0);
         mCurrentId = currentId;
+        mDirectoryId = directoryId;
         mCheckedItemChangedListener = listener;
 
-        if (queryMode == QUERY_TYPE_EMAIL) {
-            mQuery = Queries.EMAIL;
-        } else if (queryMode == QUERY_TYPE_PHONE) {
-            mQuery = Queries.PHONE;
-        } else {
-            mQuery = Queries.EMAIL;
-            Log.e(TAG, "Unsupported query type: " + queryMode);
-        }
-
         mDropdownChipLayouter = dropdownChipLayouter;
     }
 
-    private static Cursor getCursorForConstruction(Context context, long contactId, int queryType) {
+    private static Cursor getCursorForConstruction(Context context, long contactId,
+            Long directoryId, String lookupKey, int queryType) {
         final Cursor cursor;
+        final String desiredMimeType;
         if (queryType == QUERY_TYPE_EMAIL) {
+            final Uri uri;
+            final StringBuilder selection = new StringBuilder();
+            selection.append(Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID]);
+            selection.append(" = ?");
+
+            if (directoryId == null || lookupKey == null) {
+                uri = Queries.EMAIL.getContentUri();
+                desiredMimeType = null;
+            } else {
+                final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
+                builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
+                        .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                                String.valueOf(directoryId));
+                uri = builder.build();
+                desiredMimeType = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE;
+            }
             cursor = context.getContentResolver().query(
-                    Queries.EMAIL.getContentUri(),
+                    uri,
                     Queries.EMAIL.getProjection(),
-                    Queries.EMAIL.getProjection()[Queries.Query.CONTACT_ID] + " =?", new String[] {
+                    selection.toString(), new String[] {
                         String.valueOf(contactId)
                     }, null);
         } else {
+            final Uri uri;
+            final StringBuilder selection = new StringBuilder();
+            selection.append(Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID]);
+            selection.append(" = ?");
+
+            if (lookupKey == null) {
+                uri = Queries.PHONE.getContentUri();
+                desiredMimeType = null;
+            } else {
+                final Uri.Builder builder = Contacts.getLookupUri(contactId, lookupKey).buildUpon();
+                builder.appendPath(Contacts.Entity.CONTENT_DIRECTORY)
+                        .appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
+                                String.valueOf(directoryId));
+                uri = builder.build();
+                desiredMimeType = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE;
+            }
             cursor = context.getContentResolver().query(
-                    Queries.PHONE.getContentUri(),
+                    uri,
                     Queries.PHONE.getProjection(),
-                    Queries.PHONE.getProjection()[Queries.Query.CONTACT_ID] + " =?", new String[] {
+                    selection.toString(), new String[] {
                         String.valueOf(contactId)
                     }, null);
         }
-        return removeDuplicateDestinations(cursor);
+
+        final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
+        cursor.close();
+
+        return resultCursor;
     }
 
     /**
@@ -374,22 +412,53 @@
      * - This method creates a MatrixCursor, so all data will be kept in memory.  We wouldn't want
      * to do this if the original cursor is large, but it's okay here because the alternate list
      * won't be that big.
+     *
+     * @param desiredMimeType If this is non-<code>null</code>, only entries with this mime type
+     *            will be added to the cursor
+     * @param lookupKey The lookup key used for this contact if there isn't one in the cursor. This
+     *            should be the same one used in the query that returned the cursor
      */
     // Visible for testing
-    /* package */ static Cursor removeDuplicateDestinations(Cursor original) {
+    static Cursor removeUndesiredDestinations(final Cursor original, final String desiredMimeType,
+            final String lookupKey) {
         final MatrixCursor result = new MatrixCursor(
                 original.getColumnNames(), original.getCount());
         final HashSet<String> destinationsSeen = new HashSet<String>();
 
+        String defaultDisplayName = null;
+        String defaultPhotoThumbnailUri = null;
+        int defaultDisplayNameSource = 0;
+
+        // Find some nice defaults in case we need them
         original.moveToPosition(-1);
         while (original.moveToNext()) {
+            final String mimeType = original.getString(Query.MIME_TYPE);
+
+            if (ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE.equals(
+                    mimeType)) {
+                // Store this data
+                defaultDisplayName = original.getString(Query.NAME);
+                defaultPhotoThumbnailUri = original.getString(Query.PHOTO_THUMBNAIL_URI);
+                defaultDisplayNameSource = original.getInt(Query.DISPLAY_NAME_SOURCE);
+                break;
+            }
+        }
+
+        original.moveToPosition(-1);
+        while (original.moveToNext()) {
+            if (desiredMimeType != null) {
+                final String mimeType = original.getString(Query.MIME_TYPE);
+                if (!desiredMimeType.equals(mimeType)) {
+                    continue;
+                }
+            }
             final String destination = original.getString(Query.DESTINATION);
             if (destinationsSeen.contains(destination)) {
                 continue;
             }
             destinationsSeen.add(destination);
 
-            result.addRow(new Object[] {
+            final Object[] row = new Object[] {
                     original.getString(Query.NAME),
                     original.getString(Query.DESTINATION),
                     original.getInt(Query.DESTINATION_TYPE),
@@ -397,8 +466,48 @@
                     original.getLong(Query.CONTACT_ID),
                     original.getLong(Query.DATA_ID),
                     original.getString(Query.PHOTO_THUMBNAIL_URI),
-                    original.getInt(Query.DISPLAY_NAME_SOURCE)
-                    });
+                    original.getInt(Query.DISPLAY_NAME_SOURCE),
+                    original.getString(Query.LOOKUP_KEY),
+                    original.getString(Query.MIME_TYPE)
+            };
+
+            if (row[Query.NAME] == null) {
+                row[Query.NAME] = defaultDisplayName;
+            }
+            if (row[Query.PHOTO_THUMBNAIL_URI] == null) {
+                row[Query.PHOTO_THUMBNAIL_URI] = defaultPhotoThumbnailUri;
+            }
+            if ((Integer) row[Query.DISPLAY_NAME_SOURCE] == 0) {
+                row[Query.DISPLAY_NAME_SOURCE] = defaultDisplayNameSource;
+            }
+            if (row[Query.LOOKUP_KEY] == null) {
+                row[Query.LOOKUP_KEY] = lookupKey;
+            }
+
+            // Ensure we don't have two '?' like content://.../...?account_name=...?sz=...
+            final String photoThumbnailUri = (String) row[Query.PHOTO_THUMBNAIL_URI];
+            if (photoThumbnailUri != null) {
+                if (sCorrectedPhotoUris.containsKey(photoThumbnailUri)) {
+                    row[Query.PHOTO_THUMBNAIL_URI] = sCorrectedPhotoUris.get(photoThumbnailUri);
+                } else if (photoThumbnailUri.indexOf('?') != photoThumbnailUri.lastIndexOf('?')) {
+                    final String[] parts = photoThumbnailUri.split("\\?");
+                    final StringBuilder correctedUriBuilder = new StringBuilder();
+                    for (int i = 0; i < parts.length; i++) {
+                        if (i == 1) {
+                            correctedUriBuilder.append("?"); // We only want one of these
+                        } else if (i > 1) {
+                            correctedUriBuilder.append("&"); // And we want these elsewhere
+                        }
+                        correctedUriBuilder.append(parts[i]);
+                    }
+
+                    final String correctedUri = correctedUriBuilder.toString();
+                    sCorrectedPhotoUris.put(photoThumbnailUri, correctedUri);
+                    row[Query.PHOTO_THUMBNAIL_URI] = correctedUri;
+                }
+            }
+
+            result.addRow(row);
         }
 
         return result;
@@ -423,10 +532,11 @@
                 c.getInt(Queries.Query.DESTINATION_TYPE),
                 c.getString(Queries.Query.DESTINATION_LABEL),
                 c.getLong(Queries.Query.CONTACT_ID),
+                mDirectoryId,
                 c.getLong(Queries.Query.DATA_ID),
                 c.getString(Queries.Query.PHOTO_THUMBNAIL_URI),
                 true,
-                false /* isGalContact TODO(skennedy) We should look these up eventually */);
+                c.getString(Queries.Query.LOOKUP_KEY));
     }
 
     @Override
diff --git a/src/com/android/ex/chips/RecipientEditTextView.java b/src/com/android/ex/chips/RecipientEditTextView.java
index 9a4609d..eca0afb 100644
--- a/src/com/android/ex/chips/RecipientEditTextView.java
+++ b/src/com/android/ex/chips/RecipientEditTextView.java
@@ -483,7 +483,7 @@
     }
 
     protected ScrollView getScrollView() {
-      return mScrollView;
+        return mScrollView;
     }
 
     @Override
@@ -1571,7 +1571,8 @@
     }
 
     private ListAdapter createAlternatesAdapter(DrawableRecipientChip chip) {
-        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(), chip.getDataId(),
+        return new RecipientAlternatesAdapter(getContext(), chip.getContactId(),
+                chip.getDirectoryId(), chip.getLookupKey(), chip.getDataId(),
                 getAdapter().getQueryType(), this, mDropdownChipLayouter);
     }
 
@@ -2046,8 +2047,7 @@
             return constructChipSpan(
                     RecipientEntry.constructFakeEntry((String) text, isValid(text.toString())),
                     true, false);
-        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT
-                || currentChip.isGalContact()) {
+        } else if (currentChip.getContactId() == RecipientEntry.GENERATED_CONTACT) {
             int start = getChipStart(currentChip);
             int end = getChipEnd(currentChip);
             getSpannable().removeSpan(currentChip);
@@ -2590,7 +2590,7 @@
                     addresses.add(createAddressText(chip.getEntry()));
                 }
             }
-            final BaseRecipientAdapter adapter = (BaseRecipientAdapter) getAdapter();
+            final BaseRecipientAdapter adapter = getAdapter();
             RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
                     adapter.getAccount(), new RecipientMatchCallback() {
                         @Override
@@ -2718,7 +2718,7 @@
                     addresses.add(createAddressText(chip.getEntry()));
                 }
             }
-            final BaseRecipientAdapter adapter = (BaseRecipientAdapter) getAdapter();
+            final BaseRecipientAdapter adapter = getAdapter();
             RecipientAlternatesAdapter.getMatchingRecipients(getContext(), adapter, addresses,
                     adapter.getAccount(),
                     new RecipientMatchCallback() {
diff --git a/src/com/android/ex/chips/RecipientEntry.java b/src/com/android/ex/chips/RecipientEntry.java
index 30fccae..7d9b87f 100644
--- a/src/com/android/ex/chips/RecipientEntry.java
+++ b/src/com/android/ex/chips/RecipientEntry.java
@@ -61,6 +61,8 @@
     private final String mDestinationLabel;
     /** ID for the person */
     private final long mContactId;
+    /** ID for the directory this contact came from, or <code>null</code> */
+    private final Long mDirectoryId;
     /** ID for the destination */
     private final long mDataId;
     private final boolean mIsDivider;
@@ -74,11 +76,13 @@
      */
     private byte[] mPhotoBytes;
 
-    private final boolean mIsGalContact;
+    /** See {@link ContactsContract.Contacts#LOOKUP_KEY} */
+    private final String mLookupKey;
 
     private RecipientEntry(int entryType, String displayName, String destination,
-            int destinationType, String destinationLabel, long contactId, long dataId,
-            Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid, boolean isGalContact) {
+            int destinationType, String destinationLabel, long contactId, Long directoryId,
+            long dataId, Uri photoThumbnailUri, boolean isFirstLevel, boolean isValid,
+            String lookupKey) {
         mEntryType = entryType;
         mIsFirstLevel = isFirstLevel;
         mDisplayName = displayName;
@@ -86,12 +90,13 @@
         mDestinationType = destinationType;
         mDestinationLabel = destinationLabel;
         mContactId = contactId;
+        mDirectoryId = directoryId;
         mDataId = dataId;
         mPhotoThumbnailUri = photoThumbnailUri;
         mPhotoBytes = null;
         mIsDivider = false;
         mIsValid = isValid;
-        mIsGalContact = isGalContact;
+        mLookupKey = lookupKey;
     }
 
     public boolean isValid() {
@@ -116,8 +121,8 @@
         final String tokenizedAddress = tokens.length > 0 ? tokens[0].getAddress() : address;
 
         return new RecipientEntry(ENTRY_TYPE_PERSON, tokenizedAddress, tokenizedAddress,
-                INVALID_DESTINATION_TYPE, null,
-                INVALID_CONTACT, INVALID_CONTACT, null, true, isValid, false /* isGalContact */);
+                INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
+                INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
     }
 
     /**
@@ -126,8 +131,8 @@
     public static RecipientEntry constructFakePhoneEntry(final String phoneNumber,
             final boolean isValid) {
         return new RecipientEntry(ENTRY_TYPE_PERSON, phoneNumber, phoneNumber,
-                INVALID_DESTINATION_TYPE, null,
-                INVALID_CONTACT, INVALID_CONTACT, null, true, isValid, false /* isGalContact */);
+                INVALID_DESTINATION_TYPE, null, INVALID_CONTACT, null /* directoryId */,
+                INVALID_CONTACT, null, true, isValid, null /* lookupKey */);
     }
 
     /**
@@ -149,35 +154,37 @@
     public static RecipientEntry constructGeneratedEntry(String display, String address,
             boolean isValid) {
         return new RecipientEntry(ENTRY_TYPE_PERSON, display, address, INVALID_DESTINATION_TYPE,
-                null, GENERATED_CONTACT, GENERATED_CONTACT, null, true, isValid,
-                false /* isGalContact */);
+                null, GENERATED_CONTACT, null /* directoryId */, GENERATED_CONTACT, null, true,
+                isValid, null /* lookupKey */);
     }
 
     public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
             String destination, int destinationType, String destinationLabel, long contactId,
-            long dataId, Uri photoThumbnailUri, boolean isValid, boolean isGalContact) {
+            Long directoryId, long dataId, Uri photoThumbnailUri, boolean isValid,
+            String lookupKey) {
         return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
                 displayName, destination), destination, destinationType, destinationLabel,
-                contactId, dataId, photoThumbnailUri, true, isValid, isGalContact);
+                contactId, directoryId, dataId, photoThumbnailUri, true, isValid, lookupKey);
     }
 
     public static RecipientEntry constructTopLevelEntry(String displayName, int displayNameSource,
             String destination, int destinationType, String destinationLabel, long contactId,
-            long dataId, String thumbnailUriAsString, boolean isValid, boolean isGalContact) {
+            Long directoryId, long dataId, String thumbnailUriAsString, boolean isValid,
+            String lookupKey) {
         return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
                 displayName, destination), destination, destinationType, destinationLabel,
-                contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
-                        : null), true, isValid, isGalContact);
+                contactId, directoryId, dataId, (thumbnailUriAsString != null
+                        ? Uri.parse(thumbnailUriAsString) : null), true, isValid, lookupKey);
     }
 
     public static RecipientEntry constructSecondLevelEntry(String displayName,
             int displayNameSource, String destination, int destinationType,
-            String destinationLabel, long contactId, long dataId, String thumbnailUriAsString,
-            boolean isValid, boolean isGalContact) {
+            String destinationLabel, long contactId, Long directoryId, long dataId,
+            String thumbnailUriAsString, boolean isValid, String lookupKey) {
         return new RecipientEntry(ENTRY_TYPE_PERSON, pickDisplayName(displayNameSource,
                 displayName, destination), destination, destinationType, destinationLabel,
-                contactId, dataId, (thumbnailUriAsString != null ? Uri.parse(thumbnailUriAsString)
-                        : null), false, isValid, isGalContact);
+                contactId, directoryId, dataId, (thumbnailUriAsString != null
+                        ? Uri.parse(thumbnailUriAsString) : null), false, isValid, lookupKey);
     }
 
     public int getEntryType() {
@@ -204,6 +211,10 @@
         return mContactId;
     }
 
+    public Long getDirectoryId() {
+        return mDirectoryId;
+    }
+
     public long getDataId() {
         return mDataId;
     }
@@ -234,8 +245,8 @@
         return mEntryType == ENTRY_TYPE_PERSON;
     }
 
-    public boolean isGalContact() {
-        return mIsGalContact;
+    public String getLookupKey() {
+        return mLookupKey;
     }
 
     @Override
diff --git a/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java b/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
index 032d3b2..8012b5c 100644
--- a/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
+++ b/src/com/android/ex/chips/recipientchip/BaseRecipientChip.java
@@ -50,6 +50,16 @@
     long getContactId();
 
     /**
+     * Get the directory id of the contact associated with this chip.
+     */
+    Long getDirectoryId();
+
+    /**
+     * Get the directory lookup key associated with this chip, or <code>null</code>.
+     */
+    String getLookupKey();
+
+    /**
      * Get the id of the data associated with this chip.
      */
     long getDataId();
@@ -70,11 +80,4 @@
      * before any reverse lookups.
      */
     CharSequence getOriginalText();
-
-    /**
-     * Checks if this contact was retrieved from a GAL lookup.
-     *
-     * @return <code>true</code> if it came from GAL, <code>false</code> otherwise
-     */
-    boolean isGalContact();
 }
diff --git a/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java b/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
index 11a66da..455f2cb 100644
--- a/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
+++ b/src/com/android/ex/chips/recipientchip/InvisibleRecipientChip.java
@@ -62,6 +62,16 @@
     }
 
     @Override
+    public Long getDirectoryId() {
+        return mDelegate.getDirectoryId();
+    }
+
+    @Override
+    public String getLookupKey() {
+        return mDelegate.getLookupKey();
+    }
+
+    @Override
     public long getDataId() {
         return mDelegate.getDataId();
     }
@@ -82,11 +92,6 @@
     }
 
     @Override
-    public boolean isGalContact() {
-        return mDelegate.isGalContact();
-    }
-
-    @Override
     public void draw(final Canvas canvas, final CharSequence text, final int start, final int end,
             final float x, final int top, final int y, final int bottom, final Paint paint) {
         // Do nothing.
diff --git a/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java b/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
index ac8e897..533f53f 100644
--- a/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
+++ b/src/com/android/ex/chips/recipientchip/SimpleRecipientChip.java
@@ -27,6 +27,10 @@
 
     private final long mContactId;
 
+    private final Long mDirectoryId;
+
+    private final String mLookupKey;
+
     private final long mDataId;
 
     private final RecipientEntry mEntry;
@@ -39,6 +43,8 @@
         mDisplay = entry.getDisplayName();
         mValue = entry.getDestination().trim();
         mContactId = entry.getContactId();
+        mDirectoryId = entry.getDirectoryId();
+        mLookupKey = entry.getLookupKey();
         mDataId = entry.getDataId();
         mEntry = entry;
     }
@@ -69,6 +75,16 @@
     }
 
     @Override
+    public Long getDirectoryId() {
+        return mDirectoryId;
+    }
+
+    @Override
+    public String getLookupKey() {
+        return mLookupKey;
+    }
+
+    @Override
     public long getDataId() {
         return mDataId;
     }
@@ -93,11 +109,6 @@
     }
 
     @Override
-    public boolean isGalContact() {
-        return mEntry.isGalContact();
-    }
-
-    @Override
     public String toString() {
         return mDisplay + " <" + mValue + ">";
     }
diff --git a/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java b/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
index 4637f69..ce32e3d 100644
--- a/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
+++ b/src/com/android/ex/chips/recipientchip/VisibleRecipientChip.java
@@ -63,6 +63,16 @@
     }
 
     @Override
+    public Long getDirectoryId() {
+        return mDelegate.getDirectoryId();
+    }
+
+    @Override
+    public String getLookupKey() {
+        return mDelegate.getLookupKey();
+    }
+
+    @Override
     public long getDataId() {
         return mDelegate.getDataId();
     }
@@ -83,11 +93,6 @@
     }
 
     @Override
-    public boolean isGalContact() {
-        return mDelegate.isGalContact();
-    }
-
-    @Override
     public Rect getBounds() {
         return getDrawable().getBounds();
     }
diff --git a/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java b/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
index d4c0460..afb6a00 100644
--- a/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
+++ b/tests/src/com/android/ex/chips/RecipientAlternatesAdapterTest.java
@@ -27,25 +27,28 @@
 
 public class RecipientAlternatesAdapterTest extends AndroidTestCase {
 
-    public void testRemoveDuplicateDestinations() {
+    public void testRemoveUndesiredDestinations() {
         MatrixCursor c = new MatrixCursor(Queries.EMAIL.getProjection());
         Cursor result;
 
         // Test: Empty input
-        assertEquals(0, RecipientAlternatesAdapter.removeDuplicateDestinations(c).getCount());
+        assertEquals(0, RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */).getCount());
 
 
         // Test: One row
         addRow(c, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
 
-        result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+        result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */);
         assertEquals(1, result.getCount());
         assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
 
         // Test: two unique rows, different destinations
         addRow(c, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
 
-        result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+        result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */);
         assertEquals(2, result.getCount());
         assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
         assertRow(result, 1, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
@@ -54,7 +57,8 @@
         addRow(c, "ax", "1@android.com", 11, "homex", 10001, 2000, "xx", 1);
 
         // Third row should be removed.
-        result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+        result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */);
         assertEquals(2, result.getCount());
         assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
         assertRow(result, 1, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
@@ -63,7 +67,8 @@
         addRow(c, "ax", "2@android.com", 11, "homex", 10001, 2000, "xx", 1);
 
         // Forth row should also be removed.
-        result = RecipientAlternatesAdapter.removeDuplicateDestinations(c);
+        result = RecipientAlternatesAdapter.removeUndesiredDestinations(c,
+                null /* desiredMimeType */, null /* lookupKey */);
         assertEquals(2, result.getCount());
         assertRow(result, 0, "a", "1@android.com", 1, "home", 1000, 2000, "x", 0);
         assertRow(result, 1, "a", "2@android.com", 1, "home", 1000, 2000, "x", 0);
@@ -120,8 +125,8 @@
         {
             final RecipientEntry entry1 =
                     RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
-                            "1@android.com", 0, null, 0, 0, (Uri) null, true,
-                            false /* isGalContact */);
+                            "1@android.com", 0, null, 0, null /* directoryId */, 0, (Uri) null,
+                            true, null /* lookupKey */);
             final RecipientEntry entry2 = RecipientEntry.constructFakeEntry("1@android.com", true);
 
             assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
@@ -133,12 +138,12 @@
         {
             final RecipientEntry entry1 =
                     RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
-                            "1@android.com", 0, null, 0, 0, (Uri) null, true,
-                            false /* isGalContact */);
+                            "1@android.com", 0, null, 0, null /* directoryId */, 0, (Uri) null,
+                            true, null /* lookupKey */);
             final RecipientEntry entry2 =
                     RecipientEntry.constructTopLevelEntry("2@android.com", DisplayNameSources.EMAIL,
-                            "2@android.com", 0, null, 0, 0, (Uri) null, true,
-                            false /* isGalContact */);
+                            "2@android.com", 0, null, 0, null /* directoryId */, 0, (Uri) null,
+                            true, null /* lookupKey */);
 
             assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
             assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);
@@ -148,12 +153,12 @@
         {
             final RecipientEntry entry1 =
                     RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.NICKNAME,
-                            "1@android.com", 0, null, 0, 0, Uri.parse("http://www.android.com"),
-                            true, false /* isGalContact */);
+                            "1@android.com", 0, null, 0, null /* directoryId */, 0,
+                            Uri.parse("http://www.android.com"), true, null /* lookupKey */);
             final RecipientEntry entry2 =
                     RecipientEntry.constructTopLevelEntry("Android", DisplayNameSources.EMAIL,
-                            "2@android.com", 0, null, 0, 0, (Uri) null, true,
-                            false /* isGalContact */);
+                            "2@android.com", 0, null, 0, null /* directoryId */,
+                            0, (Uri) null, true, null /* lookupKey */);
 
             assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry1, entry2), entry1);
             assertEquals(RecipientAlternatesAdapter.getBetterRecipient(entry2, entry1), entry1);