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

BaseRecipientAdapter

public class BaseRecipientAdapter extends android.widget.BaseAdapter implements AccountSpecifier, android.widget.Filterable, PhotoManager.PhotoManagerCallback
Adapter for showing a recipient list.

Fields Summary
private static final String
TAG
private static final boolean
DEBUG
private static final int
DEFAULT_PREFERRED_MAX_RESULT_COUNT
The preferred number of results to be retrieved. This number may be exceeded if there are several directories configured, because we will use the same limit for all directories.
static final int
ALLOWANCE_FOR_DUPLICATES
The number of extra entries requested to allow for duplicates. Duplicates are removed from the overall result.
static final String
PRIMARY_ACCOUNT_NAME
static final String
PRIMARY_ACCOUNT_TYPE
private static final int
MESSAGE_SEARCH_PENDING_DELAY
The "Waiting for more contacts" message will be displayed if search is not complete within this many milliseconds.
private static final int
MESSAGE_SEARCH_PENDING
Used to prepare "Waiting for more contacts" message.
public static final int
QUERY_TYPE_EMAIL
public static final int
QUERY_TYPE_PHONE
private final Queries.Query
mQueryMode
private final int
mQueryType
private final android.content.Context
mContext
private final android.content.ContentResolver
mContentResolver
private android.accounts.Account
mAccount
protected final int
mPreferredMaxResultCount
private DropdownChipLayouter
mDropdownChipLayouter
private LinkedHashMap
mEntryMap
{@link #mEntries} is responsible for showing every result for this Adapter. To construct it, we use {@link #mEntryMap}, {@link #mNonAggregatedEntries}, and {@link #mExistingDestinations}. First, each destination (an email address or a phone number) with a valid contactId is inserted into {@link #mEntryMap} and grouped by the contactId. Destinations without valid contactId (possible if they aren't in local storage) are stored in {@link #mNonAggregatedEntries}. Duplicates are removed using {@link #mExistingDestinations}. After having all results from Cursor objects, all destinations in mEntryMap are copied to {@link #mEntries}. If the number of destinations is not enough (i.e. less than {@link #mPreferredMaxResultCount}), destinations in mNonAggregatedEntries are also used. These variables are only used in UI thread, thus should not be touched in performFiltering() methods.
private List
mNonAggregatedEntries
private Set
mExistingDestinations
private List
mEntries
Note: use {@link #updateEntries(List)} to update this variable.
private List
mTempEntries
private int
mRemainingDirectoryCount
The number of directories this adapter is waiting for results.
protected CharSequence
mCurrentConstraint
Used to ignore asynchronous queries with a different constraint, which may happen when users type characters quickly.
private PhotoManager
mPhotoManager
Performs all photo querying as well as caching for repeated lookups.
private final DelayedMessageHandler
mDelayedMessageHandler
private EntriesUpdatedObserver
mEntriesUpdatedObserver
Constructors Summary
public BaseRecipientAdapter(android.content.Context context)
Constructor for email queries.


             
       
        this(context, DEFAULT_PREFERRED_MAX_RESULT_COUNT, QUERY_TYPE_EMAIL);
    
public BaseRecipientAdapter(android.content.Context context, int preferredMaxResultCount)

        this(context, preferredMaxResultCount, QUERY_TYPE_EMAIL);
    
public BaseRecipientAdapter(int queryMode, android.content.Context context)

        this(context, DEFAULT_PREFERRED_MAX_RESULT_COUNT, queryMode);
    
public BaseRecipientAdapter(int queryMode, android.content.Context context, int preferredMaxResultCount)

        this(context, preferredMaxResultCount, queryMode);
    
public BaseRecipientAdapter(android.content.Context context, int preferredMaxResultCount, int queryMode)

        mContext = context;
        mContentResolver = context.getContentResolver();
        mPreferredMaxResultCount = preferredMaxResultCount;
        mPhotoManager = new DefaultPhotoManager(mContentResolver);
        mQueryType = queryMode;

        if (queryMode == QUERY_TYPE_EMAIL) {
            mQueryMode = Queries.EMAIL;
        } else if (queryMode == QUERY_TYPE_PHONE) {
            mQueryMode = Queries.PHONE;
        } else {
            mQueryMode = Queries.EMAIL;
            Log.e(TAG, "Unsupported query type: " + queryMode);
        }
    
Methods Summary
protected voidcacheCurrentEntries()

        mTempEntries = mEntries;
    
protected voidclearTempEntries()

        mTempEntries = null;
    
protected java.util.ListconstructEntryList()
Returns the actual list to use for this Adapter. Derived classes should override this method if overriding how the adapter stores and collates data.

        return constructEntryList(mEntryMap, mNonAggregatedEntries);
    
private java.util.ListconstructEntryList(java.util.LinkedHashMap entryMap, java.util.List nonAggregatedEntries)
Constructs an actual list for this Adapter using {@link #mEntryMap}. Also tries to fetch a cached photo for each contact entry (other than separators), or request another thread to get one from directories.

        final List<RecipientEntry> entries = new ArrayList<RecipientEntry>();
        int validEntryCount = 0;
        for (Map.Entry<Long, List<RecipientEntry>> mapEntry : entryMap.entrySet()) {
            final List<RecipientEntry> entryList = mapEntry.getValue();
            final int size = entryList.size();
            for (int i = 0; i < size; i++) {
                RecipientEntry entry = entryList.get(i);
                entries.add(entry);
                mPhotoManager.populatePhotoBytesAsync(entry, this);
                validEntryCount++;
            }
            if (validEntryCount > mPreferredMaxResultCount) {
                break;
            }
        }
        if (validEntryCount <= mPreferredMaxResultCount) {
            for (RecipientEntry entry : nonAggregatedEntries) {
                if (validEntryCount > mPreferredMaxResultCount) {
                    break;
                }
                entries.add(entry);
                mPhotoManager.populatePhotoBytesAsync(entry, this);
                validEntryCount++;
            }
        }

        return entries;
    
private android.database.CursordoQuery(java.lang.CharSequence constraint, int limit, java.lang.Long directoryId)

        final Uri.Builder builder = mQueryMode.getContentFilterUri().buildUpon()
                .appendPath(constraint.toString())
                .appendQueryParameter(ContactsContract.LIMIT_PARAM_KEY,
                        String.valueOf(limit + ALLOWANCE_FOR_DUPLICATES));
        if (directoryId != null) {
            builder.appendQueryParameter(ContactsContract.DIRECTORY_PARAM_KEY,
                    String.valueOf(directoryId));
        }
        if (mAccount != null) {
            builder.appendQueryParameter(PRIMARY_ACCOUNT_NAME, mAccount.name);
            builder.appendQueryParameter(PRIMARY_ACCOUNT_TYPE, mAccount.type);
        }
        final long start = System.currentTimeMillis();
        final Cursor cursor = mContentResolver.query(
                builder.build(), mQueryMode.getProjection(), null, null, null);
        final long end = System.currentTimeMillis();
        if (DEBUG) {
            Log.d(TAG, "Time for autocomplete (query: " + constraint
                    + ", directoryId: " + directoryId + ", num_of_results: "
                    + (cursor != null ? cursor.getCount() : "null") + "): "
                    + (end - start) + " ms");
        }
        return cursor;
    
protected voidfetchPhoto(RecipientEntry entry, PhotoManager.PhotoManagerCallback cb)

        mPhotoManager.populatePhotoBytesAsync(entry, cb);
    
public booleanforceShowAddress()
If true, forces using the {@link com.android.ex.chips.SingleRecipientArrayAdapter} instead of {@link com.android.ex.chips.RecipientAlternatesAdapter} when clicking on a chip. Default implementation returns {@code false}.

        return false;
    
public android.accounts.AccountgetAccount()

        return mAccount;
    
public android.content.ContextgetContext()

        return mContext;
    
public intgetCount()

        final List<RecipientEntry> entries = getEntries();
        return entries != null ? entries.size() : 0;
    
public DropdownChipLayoutergetDropdownChipLayouter()

        return mDropdownChipLayouter;
    
protected java.util.ListgetEntries()

        return mTempEntries != null ? mTempEntries : mEntries;
    
public android.widget.FiltergetFilter()
Will be called from {@link AutoCompleteTextView} to prepare auto-complete list.

        return new DefaultFilter();
    
public RecipientEntrygetItem(int position)

        return getEntries().get(position);
    
public longgetItemId(int position)

        return position;
    
public intgetItemViewType(int position)

        return getEntries().get(position).getEntryType();
    
public voidgetMatchingRecipients(java.util.ArrayList inAddresses, RecipientAlternatesAdapter.RecipientMatchCallback callback)
Used to replace email addresses with chips. Default behavior queries the ContactsProvider for contact information about the contact. Derived classes should override this method if they wish to use a new data source.

param
inAddresses addresses to query
param
callback callback to return results in case of success or failure

        RecipientAlternatesAdapter.getMatchingRecipients(
                getContext(), this, inAddresses, getAccount(), callback);
    
public java.util.MapgetMatchingRecipients(java.util.Set addresses)
An extension to {@link RecipientAlternatesAdapter#getMatchingRecipients} that allows additional sources of contacts to be considered as matching recipients.

param
addresses A set of addresses to be matched
return
A list of matches or null if none found

        return null;
    
public PhotoManagergetPhotoManager()

        return mPhotoManager;
    
public intgetQueryType()

        return mQueryType;
    
public android.view.ViewgetView(int position, android.view.View convertView, android.view.ViewGroup parent)

        final RecipientEntry entry = getEntries().get(position);

        final String constraint = mCurrentConstraint == null ? null :
                mCurrentConstraint.toString();

        return mDropdownChipLayouter.bindView(convertView, parent, entry, position,
                AdapterType.BASE_RECIPIENT, constraint);
    
public intgetViewTypeCount()

        return RecipientEntry.ENTRY_TYPE_SIZE;
    
public booleanisEnabled(int position)

        return getEntries().get(position).isSelectable();
    
public voidonPhotoBytesAsyncLoadFailed()

        // Default implementation does nothing
    
public voidonPhotoBytesAsynchronouslyPopulated()

        notifyDataSetChanged();
    
public voidonPhotoBytesPopulated()

        // Default implementation does nothing
    
protected voidputOneEntry(com.android.ex.chips.BaseRecipientAdapter$TemporaryEntry entry, boolean isAggregatedEntry)
Called whenever {@link com.android.ex.chips.BaseRecipientAdapter.DirectoryFilter} wants to add an additional entry to the results. Derived classes should override this method if they are not using the default data structures provided by {@link com.android.ex.chips.BaseRecipientAdapter} and are instead using their own data structures to store and collate data.

param
entry the entry being added
param
isAggregatedEntry

        putOneEntry(entry, isAggregatedEntry,
                mEntryMap, mNonAggregatedEntries, mExistingDestinations);
    
private static voidputOneEntry(com.android.ex.chips.BaseRecipientAdapter$TemporaryEntry entry, boolean isAggregatedEntry, java.util.LinkedHashMap entryMap, java.util.List nonAggregatedEntries, java.util.Set existingDestinations)

        if (existingDestinations.contains(entry.destination)) {
            return;
        }

        existingDestinations.add(entry.destination);

        if (!isAggregatedEntry) {
            nonAggregatedEntries.add(RecipientEntry.constructTopLevelEntry(
                    entry.displayName,
                    entry.displayNameSource,
                    entry.destination, entry.destinationType, entry.destinationLabel,
                    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);
            entryList.add(RecipientEntry.constructSecondLevelEntry(
                    entry.displayName,
                    entry.displayNameSource,
                    entry.destination, entry.destinationType, entry.destinationLabel,
                    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.directoryId, entry.dataId, entry.thumbnailUriString,
                    true, entry.lookupKey));
            entryMap.put(entry.contactId, entryList);
        }
    
public voidregisterUpdateObserver(com.android.ex.chips.BaseRecipientAdapter$EntriesUpdatedObserver observer)

        mEntriesUpdatedObserver = observer;
    
protected java.util.ListsearchOtherDirectories(java.util.Set existingDestinations)

        // After having local results, check the size of results. If the results are
        // not enough, we search remote directories, which will take longer time.
        final int limit = mPreferredMaxResultCount - existingDestinations.size();
        if (limit > 0) {
            if (DEBUG) {
                Log.d(TAG, "More entries should be needed (current: "
                        + existingDestinations.size()
                        + ", remaining limit: " + limit + ") ");
            }
            final Cursor directoryCursor = mContentResolver.query(
                    DirectoryListQuery.URI, DirectoryListQuery.PROJECTION,
                    null, null, null);
            return setupOtherDirectories(mContext, directoryCursor, mAccount);
        } else {
            // We don't need to search other directories.
            return null;
        }
    
public voidsetAccount(android.accounts.Account account)
Set the account when known. Causes the search to prioritize contacts from that account.

        mAccount = account;
    
public voidsetDropdownChipLayouter(DropdownChipLayouter dropdownChipLayouter)

        mDropdownChipLayouter = dropdownChipLayouter;
        mDropdownChipLayouter.setQuery(mQueryMode);
    
public voidsetPhotoManager(PhotoManager photoManager)
Enables overriding the default photo manager that is used.

        mPhotoManager = photoManager;
    
public static java.util.ListsetupOtherDirectories(android.content.Context context, android.database.Cursor directoryCursor, android.accounts.Account account)

        final PackageManager packageManager = context.getPackageManager();
        final List<DirectorySearchParams> paramsList = new ArrayList<DirectorySearchParams>();
        DirectorySearchParams preferredDirectory = null;
        while (directoryCursor.moveToNext()) {
            final long id = directoryCursor.getLong(DirectoryListQuery.ID);

            // Skip the local invisible directory, because the default directory already includes
            // all local results.
            if (id == Directory.LOCAL_INVISIBLE) {
                continue;
            }

            final DirectorySearchParams params = new DirectorySearchParams();
            final String packageName = directoryCursor.getString(DirectoryListQuery.PACKAGE_NAME);
            final int resourceId = directoryCursor.getInt(DirectoryListQuery.TYPE_RESOURCE_ID);
            params.directoryId = id;
            params.displayName = directoryCursor.getString(DirectoryListQuery.DISPLAY_NAME);
            params.accountName = directoryCursor.getString(DirectoryListQuery.ACCOUNT_NAME);
            params.accountType = directoryCursor.getString(DirectoryListQuery.ACCOUNT_TYPE);
            if (packageName != null && resourceId != 0) {
                try {
                    final Resources resources =
                            packageManager.getResourcesForApplication(packageName);
                    params.directoryType = resources.getString(resourceId);
                    if (params.directoryType == null) {
                        Log.e(TAG, "Cannot resolve directory name: "
                                + resourceId + "@" + packageName);
                    }
                } catch (NameNotFoundException e) {
                    Log.e(TAG, "Cannot resolve directory name: "
                            + resourceId + "@" + packageName, e);
                }
            }

            // If an account has been provided and we found a directory that
            // corresponds to that account, place that directory second, directly
            // underneath the local contacts.
            if (account != null && account.name.equals(params.accountName) &&
                    account.type.equals(params.accountType)) {
                preferredDirectory = params;
            } else {
                paramsList.add(params);
            }
        }

        if (preferredDirectory != null) {
            paramsList.add(1, preferredDirectory);
        }

        return paramsList;
    
protected voidstartSearchOtherDirectories(java.lang.CharSequence constraint, java.util.List paramsList, int limit)
Starts search in other directories using {@link Filter}. Results will be handled in {@link DirectoryFilter}.

        final int count = paramsList.size();
        // Note: skipping the default partition (index 0), which has already been loaded
        for (int i = 1; i < count; i++) {
            final DirectorySearchParams params = paramsList.get(i);
            params.constraint = constraint;
            if (params.filter == null) {
                params.filter = new DirectoryFilter(params);
            }
            params.filter.setLimit(limit);
            params.filter.filter(constraint);
        }

        // Directory search started. We may show "waiting" message if directory results are slow
        // enough.
        mRemainingDirectoryCount = count - 1;
        mDelayedMessageHandler.sendDelayedLoadMessage();
    
protected voidupdateEntries(java.util.List newEntries)
Resets {@link #mEntries} and notify the event to its parent ListView.

        mEntries = newEntries;
        mEntriesUpdatedObserver.onChanged(newEntries);
        notifyDataSetChanged();