FileDocCategorySizeDatePackage
BaseEmailAddressAdapter.javaAPI DocAndroid 5.1 API23651Thu Mar 12 22:22:48 GMT 2015com.android.common.contacts

BaseEmailAddressAdapter

public abstract class BaseEmailAddressAdapter extends com.android.common.widget.CompositeCursorAdapter implements android.widget.Filterable
A base class for email address autocomplete adapters. It uses {@link Email#CONTENT_FILTER_URI} to search for data rows by email address and/or contact name. It also searches registered {@link Directory}'s.

Fields Summary
private static final String
TAG
private static final long
DIRECTORY_LOCAL_INVISIBLE
private static final String
DIRECTORY_PARAM_KEY
private static final String
LIMIT_PARAM_KEY
private static final String
PRIMARY_ACCOUNT_NAME
private static final String
PRIMARY_ACCOUNT_TYPE
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.
private static final int
ALLOWANCE_FOR_DUPLICATES
The number of extra entries requested to allow for duplicates. Duplicates are removed from the overall result.
private static final int
MESSAGE_SEARCH_PENDING_DELAY
The "Searching..." message will be displayed if search is not complete within this many milliseconds.
private static final int
MESSAGE_SEARCH_PENDING
private static final String
SEARCHING_CURSOR_MARKER
A fake column name that indicates a "Searching..." item in the list.
protected final android.content.ContentResolver
mContentResolver
private boolean
mDirectoriesLoaded
private android.accounts.Account
mAccount
private int
mPreferredMaxResultCount
private android.os.Handler
mHandler
Constructors Summary
public BaseEmailAddressAdapter(android.content.Context context)

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

        super(context);
        mContentResolver = context.getContentResolver();
        mPreferredMaxResultCount = preferredMaxResultCount;

        mHandler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                showSearchPendingIfNotComplete(msg.arg1);
            }
        };
    
Methods Summary
public booleanareAllItemsEnabled()

        return false;
    
protected voidbindView(android.view.View v, int partition, android.database.Cursor cursor, int position)

        DirectoryPartition directoryPartition = (DirectoryPartition)getPartition(partition);
        String directoryType = directoryPartition.directoryType;
        String directoryName = directoryPartition.displayName;
        if (directoryPartition.loading) {
            bindViewLoading(v, directoryType, directoryName);
        } else {
            String displayName = cursor.getString(EmailQuery.NAME);
            String emailAddress = cursor.getString(EmailQuery.ADDRESS);
            if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, emailAddress)) {
                displayName = emailAddress;
                emailAddress = null;
            }
            bindView(v, directoryType, directoryName, displayName, emailAddress);
        }
    
protected abstract voidbindView(android.view.View view, java.lang.String directoryType, java.lang.String directoryName, java.lang.String displayName, java.lang.String emailAddress)
Override to populate the autocomplete suggestion line item UI with data.

protected abstract voidbindViewLoading(android.view.View view, java.lang.String directoryType, java.lang.String directoryName)
Override to populate the "Searching directory" line item UI with data.

private android.database.CursorcreateLoadingCursor()
Creates a dummy cursor to represent the "Searching directory..." item.

        MatrixCursor cursor = new MatrixCursor(new String[]{SEARCHING_CURSOR_MARKER});
        cursor.addRow(new Object[]{""});
        return cursor;
    
public android.widget.FiltergetFilter()

        return new DefaultPartitionFilter();
    
protected intgetItemViewType(int partitionIndex, int position)

        DirectoryPartition partition = (DirectoryPartition)getPartition(partitionIndex);
        return partition.loading ? 1 : 0;
    
private booleanhasDuplicates(android.database.Cursor cursor, int partition)

        cursor.moveToPosition(-1);
        while (cursor.moveToNext()) {
            String emailAddress = cursor.getString(EmailQuery.ADDRESS);
            if (isDuplicate(emailAddress, partition)) {
                return true;
            }
        }
        return false;
    
protected abstract android.view.ViewinflateItemView(android.view.ViewGroup parent)
Override to create a view for line item in the autocomplete suggestion list UI.

protected abstract android.view.ViewinflateItemViewLoading(android.view.ViewGroup parent)
Override to create a view for a "Searching directory" line item, which is displayed temporarily while the corresponding filter is running.

private booleanisDuplicate(java.lang.String emailAddress, int excludePartition)
Checks if the supplied email address is already present in partitions other than the supplied one.

        int partitionCount = getPartitionCount();
        for (int partition = 0; partition < partitionCount; partition++) {
            if (partition != excludePartition && !isLoading(partition)) {
                Cursor cursor = getCursor(partition);
                if (cursor != null) {
                    cursor.moveToPosition(-1);
                    while (cursor.moveToNext()) {
                        String address = cursor.getString(EmailQuery.ADDRESS);
                        if (TextUtils.equals(emailAddress, address)) {
                            return true;
                        }
                    }
                }
            }
        }

        return false;
    
protected booleanisEnabled(int partitionIndex, int position)

        // The "Searching..." item should not be selectable
        return !isLoading(partitionIndex);
    
private booleanisLoading(int partitionIndex)

        return ((DirectoryPartition)getPartition(partitionIndex)).loading;
    
private final java.lang.StringmakeDisplayString(android.database.Cursor cursor)

        if (cursor.getColumnName(0).equals(SEARCHING_CURSOR_MARKER)) {
            return "";
        }

        String displayName = cursor.getString(EmailQuery.NAME);
        String emailAddress = cursor.getString(EmailQuery.ADDRESS);
        if (TextUtils.isEmpty(displayName) || TextUtils.equals(displayName, emailAddress)) {
             return emailAddress;
        } else {
            return new Rfc822Token(displayName, emailAddress, null).toString();
        }
    
protected android.view.ViewnewView(android.content.Context context, int partitionIndex, android.database.Cursor cursor, int position, android.view.ViewGroup parent)

        DirectoryPartition partition = (DirectoryPartition)getPartition(partitionIndex);
        if (partition.loading) {
            return inflateItemViewLoading(parent);
        } else {
            return inflateItemView(parent);
        }
    
protected voidonDirectoryLoadFinished(java.lang.CharSequence constraint, android.database.Cursor directoryCursor, android.database.Cursor defaultPartitionCursor)
Handles the result of the initial call, which brings back the list of directories as well as the search results for the local directories.

        if (directoryCursor != null) {
            PackageManager packageManager = getContext().getPackageManager();
            DirectoryPartition preferredDirectory = null;
            List<DirectoryPartition> directories = new ArrayList<DirectoryPartition>();
            while (directoryCursor.moveToNext()) {
                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;
                }

                DirectoryPartition partition = new DirectoryPartition();
                partition.directoryId = id;
                partition.displayName = directoryCursor.getString(DirectoryListQuery.DISPLAY_NAME);
                partition.accountName = directoryCursor.getString(DirectoryListQuery.ACCOUNT_NAME);
                partition.accountType = directoryCursor.getString(DirectoryListQuery.ACCOUNT_TYPE);
                String packageName = directoryCursor.getString(DirectoryListQuery.PACKAGE_NAME);
                int resourceId = directoryCursor.getInt(DirectoryListQuery.TYPE_RESOURCE_ID);
                if (packageName != null && resourceId != 0) {
                    try {
                        Resources resources =
                                packageManager.getResourcesForApplication(packageName);
                        partition.directoryType = resources.getString(resourceId);
                        if (partition.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 (mAccount != null && mAccount.name.equals(partition.accountName) &&
                        mAccount.type.equals(partition.accountType)) {
                    preferredDirectory = partition;
                } else {
                    directories.add(partition);
                }
            }

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

            for (DirectoryPartition partition : directories) {
                addPartition(partition);
            }
        }

        int count = getPartitionCount();
        int limit = 0;

        // Since we will be changing several partitions at once, hold the data change
        // notifications
        setNotificationsEnabled(false);
        try {
            // The filter has loaded results for the default partition too.
            if (defaultPartitionCursor != null && getPartitionCount() > 0) {
                changeCursor(0, defaultPartitionCursor);
            }

            int defaultPartitionCount = (defaultPartitionCursor == null ? 0
                    : defaultPartitionCursor.getCount());

            limit = mPreferredMaxResultCount - defaultPartitionCount;

            // Show non-default directories as "loading"
            // Note: skipping the default partition (index 0), which has already been loaded
            for (int i = 1; i < count; i++) {
                DirectoryPartition partition = (DirectoryPartition) getPartition(i);
                partition.constraint = constraint;

                if (limit > 0) {
                    if (!partition.loading) {
                        partition.loading = true;
                        changeCursor(i, null);
                    }
                } else {
                    partition.loading = false;
                    changeCursor(i, null);
                }
            }
        } finally {
            setNotificationsEnabled(true);
        }

        // Start search in other directories
        // Note: skipping the default partition (index 0), which has already been loaded
        for (int i = 1; i < count; i++) {
            DirectoryPartition partition = (DirectoryPartition) getPartition(i);
            if (partition.loading) {
                mHandler.removeMessages(MESSAGE_SEARCH_PENDING, partition);
                Message msg = mHandler.obtainMessage(MESSAGE_SEARCH_PENDING, i, 0, partition);
                mHandler.sendMessageDelayed(msg, MESSAGE_SEARCH_PENDING_DELAY);
                if (partition.filter == null) {
                    partition.filter = new DirectoryPartitionFilter(i, partition.directoryId);
                }
                partition.filter.setLimit(limit);
                partition.filter.filter(constraint);
            } else {
                if (partition.filter != null) {
                    // Cancel any previous loading request
                    partition.filter.filter(null);
                }
            }
        }
    
public voidonPartitionLoadFinished(java.lang.CharSequence constraint, int partitionIndex, android.database.Cursor cursor)

        if (partitionIndex < getPartitionCount()) {
            DirectoryPartition partition = (DirectoryPartition) getPartition(partitionIndex);

            // Check if the received result matches the current constraint
            // If not - the user must have continued typing after the request
            // was issued
            if (partition.loading && TextUtils.equals(constraint, partition.constraint)) {
                partition.loading = false;
                mHandler.removeMessages(MESSAGE_SEARCH_PENDING, partition);
                changeCursor(partitionIndex, removeDuplicatesAndTruncate(partitionIndex, cursor));
            } else {
                // We got the result for an unexpected query (the user is still typing)
                // Just ignore this result
                if (cursor != null) {
                    cursor.close();
                }
            }
        } else if (cursor != null) {
            cursor.close();
        }
    
private android.database.CursorremoveDuplicatesAndTruncate(int partition, android.database.Cursor cursor)
Post-process the cursor to eliminate duplicates. Closes the original cursor and returns a new one.

        if (cursor == null) {
            return null;
        }

        if (cursor.getCount() <= DEFAULT_PREFERRED_MAX_RESULT_COUNT
                && !hasDuplicates(cursor, partition)) {
            return cursor;
        }

        int count = 0;
        MatrixCursor newCursor = new MatrixCursor(EmailQuery.PROJECTION);
        cursor.moveToPosition(-1);
        while (cursor.moveToNext() && count < DEFAULT_PREFERRED_MAX_RESULT_COUNT) {
            String displayName = cursor.getString(EmailQuery.NAME);
            String emailAddress = cursor.getString(EmailQuery.ADDRESS);
            if (!isDuplicate(emailAddress, partition)) {
                newCursor.addRow(new Object[]{displayName, emailAddress});
                count++;
            }
        }
        cursor.close();

        return newCursor;
    
public voidsetAccount(android.accounts.Account account)
Set the account when known. Causes the search to prioritize contacts from that account.

        mAccount = account;
    
voidshowSearchPendingIfNotComplete(int partitionIndex)

        if (partitionIndex < getPartitionCount()) {
            DirectoryPartition partition = (DirectoryPartition) getPartition(partitionIndex);
            if (partition.loading) {
                changeCursor(partitionIndex, createLoadingCursor());
            }
        }