FileDocCategorySizeDatePackage
RecipientAlternatesAdapter.javaAPI DocAndroid 5.1 API26601Thu Mar 12 22:22:50 GMT 2015com.android.ex.chips

RecipientAlternatesAdapter

public class RecipientAlternatesAdapter extends android.widget.CursorAdapter
RecipientAlternatesAdapter backs the RecipientEditTextView for managing contacts queried by email or by phone number.

Fields Summary
public static final int
MAX_LOOKUPS
private final long
mCurrentId
private int
mCheckedItemPosition
private OnCheckedItemChangedListener
mCheckedItemChangedListener
private static final String
TAG
public static final int
QUERY_TYPE_EMAIL
public static final int
QUERY_TYPE_PHONE
private final Long
mDirectoryId
private DropdownChipLayouter
mDropdownChipLayouter
private final android.graphics.drawable.StateListDrawable
mDeleteDrawable
private static final Map
sCorrectedPhotoUris
Constructors Summary
public RecipientAlternatesAdapter(android.content.Context context, long contactId, Long directoryId, String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener, DropdownChipLayouter dropdownChipLayouter, android.graphics.drawable.StateListDrawable deleteDrawable)

        super(context,
                getCursorForConstruction(context, contactId, directoryId, lookupKey, queryMode), 0);
        mCurrentId = currentId;
        mDirectoryId = directoryId;
        mCheckedItemChangedListener = listener;

        mDropdownChipLayouter = dropdownChipLayouter;
        mDeleteDrawable = deleteDrawable;
    
public RecipientAlternatesAdapter(android.content.Context context, long contactId, Long directoryId, String lookupKey, long currentId, int queryMode, OnCheckedItemChangedListener listener, DropdownChipLayouter dropdownChipLayouter)

        this(context, contactId, directoryId, lookupKey, currentId, queryMode, listener,
                dropdownChipLayouter, null);
    
Methods Summary
public voidbindView(android.view.View view, android.content.Context context, android.database.Cursor cursor)

        int position = cursor.getPosition();
        RecipientEntry entry = getRecipientEntry(position);

        mDropdownChipLayouter.bindView(view, null, entry, position,
                AdapterType.RECIPIENT_ALTERNATES, null, mDeleteDrawable);
    
private static android.database.CursordoQuery(java.lang.CharSequence constraint, int limit, java.lang.Long directoryId, android.accounts.Account account, android.content.ContentResolver resolver, com.android.ex.chips.Queries.Query query)

        final Uri.Builder builder = query
                .getContentFilterUri()
                .buildUpon()
                .appendPath(constraint.toString())
                .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
                        String.valueOf(limit + BaseRecipientAdapter.ALLOWANCE_FOR_DUPLICATES));
        if (directoryId != null) {
            builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
                    String.valueOf(directoryId));
        }
        if (account != null) {
            builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_NAME, account.name);
            builder.appendQueryParameter(BaseRecipientAdapter.PRIMARY_ACCOUNT_TYPE, account.type);
        }
        final Cursor cursor = resolver.query(builder.build(), query.getProjection(), null, null,
                null);
        return cursor;
    
static RecipientEntrygetBetterRecipient(RecipientEntry entry1, RecipientEntry entry2)
Given two {@link RecipientEntry}s for the same email address, this will return the one that contains more complete information for display purposes. Defaults to entry2 if no significant differences are found.

        // If only one has passed in, use it
        if (entry2 == null) {
            return entry1;
        }

        if (entry1 == null) {
            return entry2;
        }

        // If only one has a display name, use it
        if (!TextUtils.isEmpty(entry1.getDisplayName())
                && TextUtils.isEmpty(entry2.getDisplayName())) {
            return entry1;
        }

        if (!TextUtils.isEmpty(entry2.getDisplayName())
                && TextUtils.isEmpty(entry1.getDisplayName())) {
            return entry2;
        }

        // If only one has a display name that is not the same as the destination, use it
        if (!TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())
                && TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())) {
            return entry1;
        }

        if (!TextUtils.equals(entry2.getDisplayName(), entry2.getDestination())
                && TextUtils.equals(entry1.getDisplayName(), entry1.getDestination())) {
            return entry2;
        }

        // If only one has a photo, use it
        if ((entry1.getPhotoThumbnailUri() != null || entry1.getPhotoBytes() != null)
                && (entry2.getPhotoThumbnailUri() == null && entry2.getPhotoBytes() == null)) {
            return entry1;
        }

        if ((entry2.getPhotoThumbnailUri() != null || entry2.getPhotoBytes() != null)
                && (entry1.getPhotoThumbnailUri() == null && entry1.getPhotoBytes() == null)) {
            return entry2;
        }

        // Go with the second option as a default
        return entry2;
    
private static android.database.CursorgetCursorForConstruction(android.content.Context context, long contactId, java.lang.Long directoryId, java.lang.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(
                    uri,
                    Queries.EMAIL.getProjection(),
                    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(
                    uri,
                    Queries.PHONE.getProjection(),
                    selection.toString(), new String[] {
                        String.valueOf(contactId)
                    }, null);
        }

        final Cursor resultCursor = removeUndesiredDestinations(cursor, desiredMimeType, lookupKey);
        cursor.close();

        return resultCursor;
    
public longgetItemId(int position)

        Cursor c = getCursor();
        if (c.moveToPosition(position)) {
            c.getLong(Queries.Query.DATA_ID);
        }
        return -1;
    
public static voidgetMatchingRecipients(android.content.Context context, BaseRecipientAdapter adapter, java.util.ArrayList inAddresses, android.accounts.Account account, com.android.ex.chips.RecipientAlternatesAdapter$RecipientMatchCallback callback)


       
            
                             
           
    

          
                  
        getMatchingRecipients(context, adapter, inAddresses, QUERY_TYPE_EMAIL, account, callback);
    
public static voidgetMatchingRecipients(android.content.Context context, BaseRecipientAdapter adapter, java.util.ArrayList inAddresses, int addressType, android.accounts.Account account, com.android.ex.chips.RecipientAlternatesAdapter$RecipientMatchCallback callback)
Get a HashMap of address to RecipientEntry that contains all contact information for a contact with the provided address, if one exists. This may block the UI, so run it in an async task.

param
context Context.
param
inAddresses Array of addresses on which to perform the lookup.
param
callback RecipientMatchCallback called when a match or matches are found.

        Queries.Query query;
        if (addressType == QUERY_TYPE_EMAIL) {
            query = Queries.EMAIL;
        } else {
            query = Queries.PHONE;
        }
        int addressesSize = Math.min(MAX_LOOKUPS, inAddresses.size());
        HashSet<String> addresses = new HashSet<String>();
        StringBuilder bindString = new StringBuilder();
        // Create the "?" string and set up arguments.
        for (int i = 0; i < addressesSize; i++) {
            Rfc822Token[] tokens = Rfc822Tokenizer.tokenize(inAddresses.get(i).toLowerCase());
            addresses.add(tokens.length > 0 ? tokens[0].getAddress() : inAddresses.get(i));
            bindString.append("?");
            if (i < addressesSize - 1) {
                bindString.append(",");
            }
        }

        if (Log.isLoggable(TAG, Log.DEBUG)) {
            Log.d(TAG, "Doing reverse lookup for " + addresses.toString());
        }

        String[] addressArray = new String[addresses.size()];
        addresses.toArray(addressArray);
        HashMap<String, RecipientEntry> recipientEntries = null;
        Cursor c = null;

        try {
            c = context.getContentResolver().query(
                    query.getContentUri(),
                    query.getProjection(),
                    query.getProjection()[Queries.Query.DESTINATION] + " IN ("
                            + bindString.toString() + ")", addressArray, null);
            recipientEntries = processContactEntries(c, null /* directoryId */);
            callback.matchesFound(recipientEntries);
        } finally {
            if (c != null) {
                c.close();
            }
        }

        final Set<String> matchesNotFound = new HashSet<String>();

        getMatchingRecipientsFromDirectoryQueries(context, recipientEntries,
                addresses, account, matchesNotFound, query, callback);

        getMatchingRecipientsFromExtensionMatcher(adapter, matchesNotFound, callback);
    
public static voidgetMatchingRecipientsFromDirectoryQueries(android.content.Context context, java.util.Map recipientEntries, java.util.Set addresses, android.accounts.Account account, java.util.Set matchesNotFound, com.android.ex.chips.RecipientAlternatesAdapter$RecipientMatchCallback callback)

        getMatchingRecipientsFromDirectoryQueries(
                context, recipientEntries, addresses, account,
                matchesNotFound, Queries.EMAIL, callback);
    
private static voidgetMatchingRecipientsFromDirectoryQueries(android.content.Context context, java.util.Map recipientEntries, java.util.Set addresses, android.accounts.Account account, java.util.Set matchesNotFound, com.android.ex.chips.Queries.Query query, com.android.ex.chips.RecipientAlternatesAdapter$RecipientMatchCallback callback)

        // See if any entries did not resolve; if so, we need to check other
        // directories

        if (recipientEntries.size() < addresses.size()) {
            final List<DirectorySearchParams> paramsList;
            Cursor directoryCursor = null;
            try {
                directoryCursor = context.getContentResolver().query(DirectoryListQuery.URI,
                        DirectoryListQuery.PROJECTION, null, null, null);
                if (directoryCursor == null) {
                    paramsList = null;
                } else {
                    paramsList = BaseRecipientAdapter.setupOtherDirectories(context,
                            directoryCursor, account);
                }
            } finally {
                if (directoryCursor != null) {
                    directoryCursor.close();
                }
            }
            // Run a directory query for each unmatched recipient.
            HashSet<String> unresolvedAddresses = new HashSet<String>();
            for (String address : addresses) {
                if (!recipientEntries.containsKey(address)) {
                    unresolvedAddresses.add(address);
                }
            }

            matchesNotFound.addAll(unresolvedAddresses);

            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,
                                    paramsList.get(i).directoryId, account,
                                    context.getContentResolver(), query);
                        } finally {
                            if (directoryContactsCursor != null
                                    && directoryContactsCursor.getCount() == 0) {
                                directoryContactsCursor.close();
                                directoryContactsCursor = null;
                            } else {
                                directoryId = paramsList.get(i).directoryId;
                                break;
                            }
                        }
                    }
                    if (directoryContactsCursor != null) {
                        try {
                            final Map<String, RecipientEntry> entries =
                                    processContactEntries(directoryContactsCursor, directoryId);

                            for (final String address : entries.keySet()) {
                                matchesNotFound.remove(address);
                            }

                            callback.matchesFound(entries);
                        } finally {
                            directoryContactsCursor.close();
                        }
                    }
                }
            }
        }
    
public static voidgetMatchingRecipientsFromExtensionMatcher(BaseRecipientAdapter adapter, java.util.Set matchesNotFound, com.android.ex.chips.RecipientAlternatesAdapter$RecipientMatchCallback callback)

        // If no matches found in contact provider or the directories, try the extension
        // matcher.
        // todo (aalbert): This whole method needs to be in the adapter?
        if (adapter != null) {
            final Map<String, RecipientEntry> entries =
                    adapter.getMatchingRecipients(matchesNotFound);
            if (entries != null && entries.size() > 0) {
                callback.matchesFound(entries);
                for (final String address : entries.keySet()) {
                    matchesNotFound.remove(address);
                }
            }
        }
        callback.matchesNotFound(matchesNotFound);
    
public RecipientEntrygetRecipientEntry(int position)

        Cursor c = getCursor();
        c.moveToPosition(position);
        return RecipientEntry.constructTopLevelEntry(
                c.getString(Queries.Query.NAME),
                c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
                c.getString(Queries.Query.DESTINATION),
                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,
                c.getString(Queries.Query.LOOKUP_KEY));
    
public android.view.ViewgetView(int position, android.view.View convertView, android.view.ViewGroup parent)

        Cursor cursor = getCursor();
        cursor.moveToPosition(position);
        if (convertView == null) {
            convertView = mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
        }
        if (cursor.getLong(Queries.Query.DATA_ID) == mCurrentId) {
            mCheckedItemPosition = position;
            if (mCheckedItemChangedListener != null) {
                mCheckedItemChangedListener.onCheckedItemChanged(mCheckedItemPosition);
            }
        }
        bindView(convertView, convertView.getContext(), cursor);
        return convertView;
    
public android.view.ViewnewView(android.content.Context context, android.database.Cursor cursor, android.view.ViewGroup parent)

        return mDropdownChipLayouter.newView(AdapterType.RECIPIENT_ALTERNATES);
    
private static java.util.HashMapprocessContactEntries(android.database.Cursor c, java.lang.Long directoryId)

        HashMap<String, RecipientEntry> recipientEntries = new HashMap<String, RecipientEntry>();
        if (c != null && c.moveToFirst()) {
            do {
                String address = c.getString(Queries.Query.DESTINATION);

                final RecipientEntry newRecipientEntry = RecipientEntry.constructTopLevelEntry(
                        c.getString(Queries.Query.NAME),
                        c.getInt(Queries.Query.DISPLAY_NAME_SOURCE),
                        c.getString(Queries.Query.DESTINATION),
                        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,
                        c.getString(Queries.Query.LOOKUP_KEY));

                /*
                 * In certain situations, we may have two results for one address, where one of the
                 * results is just the email address, and the other has a name and photo, so we want
                 * to use the better one.
                 */
                final RecipientEntry recipientEntry =
                        getBetterRecipient(recipientEntries.get(address), newRecipientEntry);

                recipientEntries.put(address, recipientEntry);
                if (Log.isLoggable(TAG, Log.DEBUG)) {
                    Log.d(TAG, "Received reverse look up information for " + address
                            + " RESULTS: "
                            + " NAME : " + c.getString(Queries.Query.NAME)
                            + " CONTACT ID : " + c.getLong(Queries.Query.CONTACT_ID)
                            + " ADDRESS :" + c.getString(Queries.Query.DESTINATION));
                }
            } while (c.moveToNext());
        }
        return recipientEntries;
    
static android.database.CursorremoveUndesiredDestinations(android.database.Cursor original, java.lang.String desiredMimeType, java.lang.String lookupKey)

return
a new cursor based on the given cursor with all duplicate destinations removed. It's only intended to use for the alternate list, so... - This method ignores all other fields and dedupe solely on the destination. Normally, if a cursor contains multiple contacts and they have the same destination, we'd still want to show both. - 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-null, 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

        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);

            final Object[] row = new Object[] {
                    original.getString(Query.NAME),
                    original.getString(Query.DESTINATION),
                    original.getInt(Query.DESTINATION_TYPE),
                    original.getString(Query.DESTINATION_LABEL),
                    original.getLong(Query.CONTACT_ID),
                    original.getLong(Query.DATA_ID),
                    original.getString(Query.PHOTO_THUMBNAIL_URI),
                    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;