Displaying the Quick Contact Badge

This lesson shows you how to add a QuickContactBadge to your UI and how to bind data to it. A QuickContactBadge is a widget that initially appears as a thumbnail image. Although you can use any Bitmap for the thumbnail image, you usually use a Bitmap decoded from the contact's photo thumbnail image.

The small image acts as a control; when users click on the image, the QuickContactBadge expands into a dialog containing the following:

A large image
The large image associated with the contact, or no image is available, a placeholder graphic.
App icons
An app icon for each piece of detail data that can be handled by a built-in app. For example, if the contact's details include one or more email addresses, an email icon appears. When users click the icon, all of the contact's email addresses appear. When users click one of the addresses, the email app displays a screen for composing a message to the selected email address.

The QuickContactBadge view provides instant access to a contact's details, as well as a fast way of communicating with the contact. Users don't have to look up a contact, find and copy information, and then paste it into the appropriate app. Instead, they can click on the QuickContactBadge, choose the communication method they want to use, and send the information for that method directly to the appropriate app.

Add a QuickContactBadge View

To add a QuickContactBadge, insert a <QuickContactBadge> element in your layout. For example:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
...
    <QuickContactBadge
               android:id=@+id/quickbadge
               android:layout_height="wrap_content"
               android:layout_width="wrap_content"
               android:scaleType="centerCrop"/>
    ...
</RelativeLayout>

Retrieve provider data

To display a contact in the QuickContactBadge, you need a content URI for the contact and a Bitmap for the small image. You generate both the content URI and the Bitmap from columns retrieved from the Contacts Provider. Specify these columns as part of the projection you use to load data into your Cursor.

For Android 3.0 (API level 11) and later, include the following columns in your projection:

For Android 2.3.3 (API level 10) and earlier, use the following columns:

The remainder of this lesson assumes that you've already loaded a Cursor that contains these columns as well as others you may have chosen. To learn how to retrieve this columns in a Cursor, read the lesson Retrieving a List of Contacts.

Set the Contact URI and Thumbnail

Once you have the necessary columns, you can bind data to the QuickContactBadge.

Set the Contact URI

To set the content URI for the contact, call getLookupUri(id,lookupKey) to get a CONTENT_LOOKUP_URI, then call assignContactUri() to set the contact. For example:

    // The Cursor that contains contact rows
    Cursor mCursor;
    // The index of the _ID column in the Cursor
    int mIdColumn;
    // The index of the LOOKUP_KEY column in the Cursor
    int mLookupKeyColumn;
    // A content URI for the desired contact
    Uri mContactUri;
    // A handle to the QuickContactBadge view
    QuickContactBadge mBadge;
    ...
    mBadge = (QuickContactBadge) findViewById(R.id.quickbadge);
    /*
     * Insert code here to move to the desired cursor row
     */
    // Gets the _ID column index
    mIdColumn = mCursor.getColumnIndex(Contacts._ID);
    // Gets the LOOKUP_KEY index
    mLookupKeyColumn = mCursor.getColumnIndex(Contacts.LOOKUP_KEY);
    // Gets a content URI for the contact
    mContactUri =
            Contacts.getLookupUri(
                mCursor.getLong(mIdColumn),
                mCursor.getString(mLookupKeyColumn)
            );
    mBadge.assignContactUri(mContactUri);

When users click the QuickContactBadge icon, the contact's details automatically appear in the dialog.

Set the photo thumbnail

Setting the contact URI for the QuickContactBadge does not automatically load the contact's thumbnail photo. To load the photo, get a URI for the photo from the contact's Cursor row, use it to open the file containing the compressed thumbnail photo, and read the file into a Bitmap.

Note: The PHOTO_THUMBNAIL_URI column isn't available in platform versions prior to 3.0. For those versions, you must retrieve the URI from the Contacts.Photo subtable.

First, set up variables for accessing the Cursor containing the Contacts._ID and Contacts.LOOKUP_KEY columns, as described previously:

    // The column in which to find the thumbnail ID
    int mThumbnailColumn;
    /*
     * The thumbnail URI, expressed as a String.
     * Contacts Provider stores URIs as String values.
     */
    String mThumbnailUri;
    ...
    /*
     * Gets the photo thumbnail column index if
     * platform version >= Honeycomb
     */
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
        mThumbnailColumn =
                mCursor.getColumnIndex(Contacts.PHOTO_THUMBNAIL_URI);
    // Otherwise, sets the thumbnail column to the _ID column
    } else {
        mThumbnailColumn = mIdColumn;
    }
    /*
     * Assuming the current Cursor position is the contact you want,
     * gets the thumbnail ID
     */
    mThumbnailUri = mCursor.getString(mThumbnailColumn);
    ...

Define a method that takes photo-related data for the contact and dimensions for the destination view, and returns the properly-sized thumbnail in a Bitmap. Start by constructing a URI that points to the thumbnail:

    /**
     * Load a contact photo thumbnail and return it as a Bitmap,
     * resizing the image to the provided image dimensions as needed.
     * @param photoData photo ID Prior to Honeycomb, the contact's _ID value.
     * For Honeycomb and later, the value of PHOTO_THUMBNAIL_URI.
     * @return A thumbnail Bitmap, sized to the provided width and height.
     * Returns null if the thumbnail is not found.
     */
    private Bitmap loadContactPhotoThumbnail(String photoData) {
        // Creates an asset file descriptor for the thumbnail file.
        AssetFileDescriptor afd = null;
        // try-catch block for file not found
        try {
            // Creates a holder for the URI.
            Uri thumbUri;
            // If Android 3.0 or later
            if (Build.VERSION.SDK_INT
                    >=
                Build.VERSION_CODES.HONEYCOMB) {
                // Sets the URI from the incoming PHOTO_THUMBNAIL_URI
                thumbUri = Uri.parse(photoData);
            } else {
            // Prior to Android 3.0, constructs a photo Uri using _ID
                /*
                 * Creates a contact URI from the Contacts content URI
                 * incoming photoData (_ID)
                 */
                final Uri contactUri = Uri.withAppendedPath(
                        Contacts.CONTENT_URI, photoData);
                /*
                 * Creates a photo URI by appending the content URI of
                 * Contacts.Photo.
                 */
                thumbUri =
                        Uri.withAppendedPath(
                                contactUri, Photo.CONTENT_DIRECTORY);
            }
    
        /*
         * Retrieves an AssetFileDescriptor object for the thumbnail
         * URI
         * using ContentResolver.openAssetFileDescriptor
         */
        afd = getActivity().getContentResolver().
                openAssetFileDescriptor(thumbUri, "r");
        /*
         * Gets a file descriptor from the asset file descriptor.
         * This object can be used across processes.
         */
        FileDescriptor fileDescriptor = afd.getFileDescriptor();
        // Decode the photo file and return the result as a Bitmap
        // If the file descriptor is valid
        if (fileDescriptor != null) {
            // Decodes the bitmap
            return BitmapFactory.decodeFileDescriptor(
                    fileDescriptor, null, null);
            }
        // If the file isn't found
        } catch (FileNotFoundException e) {
            /*
             * Handle file not found errors
             */
        }
        // In all cases, close the asset file descriptor
        } finally {
            if (afd != null) {
                try {
                    afd.close();
                } catch (IOException e) {}
            }
        }
        return null;
    }

Call the loadContactPhotoThumbnail() method in your code to get the thumbnail Bitmap, and use the result to set the photo thumbnail in your QuickContactBadge:

    ...
    /*
     * Decodes the thumbnail file to a Bitmap.
     */
    Bitmap mThumbnail =
            loadContactPhotoThumbnail(mThumbnailUri);
    /*
     * Sets the image in the QuickContactBadge
     * QuickContactBadge inherits from ImageView, so
     */
    mBadge.setImageBitmap(mThumbnail);

Add a QuickContactBadge to a ListView

A QuickContactBadge is a useful addition to a ListView that displays a list of contacts. Use the QuickContactBadge to display a thumbnail photo for each contact; when users click the thumbnail, the QuickContactBadge dialog appears.

Add the QuickContactBadge element

To start, add a QuickContactBadge view element to your item layout For example, if you want to display a QuickContactBadge and a name for each contact you retrieve, put the following XML into a layout file:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    <QuickContactBadge
        android:id="@+id/quickcontact"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:scaleType="centerCrop"/>
    <TextView android:id="@+id/displayname"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_toRightOf="@+id/quickcontact"
              android:gravity="center_vertical"
              android:layout_alignParentRight="true"
              android:layout_alignParentTop="true"/>
</RelativeLayout>

In the following sections, this file is referred to as contact_item_layout.xml.

Set up a custom CursorAdapter

To bind a CursorAdapter to a ListView containing a QuickContactBadge, define a custom adapter that extends CursorAdapter. This approach allows you to process the data in the Cursor before you bind it to the QuickContactBadge. This approach also allows you to bind multiple Cursor columns to the QuickContactBadge. Neither of these operations is possible in a regular CursorAdapter.

The subclass of CursorAdapter that you define must override the following methods:

CursorAdapter.newView()
Inflates a new View object to hold the item layout. In the override of this method, store handles to the child View objects of the layout, including the child QuickContactBadge. By taking this approach, you avoid having to get handles to the child View objects each time you inflate a new layout.

You must override this method so you can get handles to the individual child View objects. This technique allows you to control their binding in CursorAdapter.bindView().

CursorAdapter.bindView()
Moves data from the current Cursor row to the child View objects of the item layout. You must override this method so you can bind both the contact's URI and thumbnail to the QuickContactBadge. The default implementation only allows a 1-to-1 mapping between a column and a View

The following code snippet contains an example of a custom subclass of CursorAdapter:

Define the custom list adapter

Define the subclass of CursorAdapter including its constructor, and override newView() and bindView():

    /**
     *
     *
     */
    private class ContactsAdapter extends CursorAdapter {
        private LayoutInflater mInflater;
        ...
        public ContactsAdapter(Context context) {
            super(context, null, 0);

            /*
             * Gets an inflater that can instantiate
             * the ListView layout from the file.
             */
            mInflater = LayoutInflater.from(context);
            ...
        }
        ...
        /**
         * Defines a class that hold resource IDs of each item layout
         * row to prevent having to look them up each time data is
         * bound to a row.
         */
        private class ViewHolder {
            TextView displayname;
            QuickContactBadge quickcontact;
        }
        ..
        @Override
        public View newView(
                Context context,
                Cursor cursor,
                ViewGroup viewGroup) {
            /* Inflates the item layout. Stores resource IDs in a
             * in a ViewHolder class to prevent having to look
             * them up each time bindView() is called.
             */
            final View itemView =
                    mInflater.inflate(
                            R.layout.contact_list_layout,
                            viewGroup,
                            false
                    );
            final ViewHolder holder = new ViewHolder();
            holder.displayname =
                    (TextView) view.findViewById(R.id.displayname);
            holder.quickcontact =
                    (QuickContactBadge)
                            view.findViewById(R.id.quickcontact);
            view.setTag(holder);
            return view;
        }
        ...
        @Override
        public void bindView(
                View view,
                Context context,
                Cursor cursor) {
            final ViewHolder holder = (ViewHolder) view.getTag();
            final String photoData =
                    cursor.getString(mPhotoDataIndex);
            final String displayName =
                    cursor.getString(mDisplayNameIndex);
            ...
            // Sets the display name in the layout
            holder.displayname = cursor.getString(mDisplayNameIndex);
            ...
            /*
             * Generates a contact URI for the QuickContactBadge.
             */
            final Uri contactUri = Contacts.getLookupUri(
                    cursor.getLong(mIdIndex),
                    cursor.getString(mLookupKeyIndex));
            holder.quickcontact.assignContactUri(contactUri);
            String photoData = cursor.getString(mPhotoDataIndex);
            /*
             * Decodes the thumbnail file to a Bitmap.
             * The method loadContactPhotoThumbnail() is defined
             * in the section "Set the Contact URI and Thumbnail"
             */
            Bitmap thumbnailBitmap =
                    loadContactPhotoThumbnail(photoData);
            /*
             * Sets the image in the QuickContactBadge
             * QuickContactBadge inherits from ImageView
             */
            holder.quickcontact.setImageBitmap(thumbnailBitmap);
    }

Set up variables

In your code, set up variables, including a Cursor projection that includes the necessary columns.

Note: The following code snippets use the method loadContactPhotoThumbnail(), which is defined in the section Set the Contact URI and Thumbnail

For example:

public class ContactsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
...
    // Defines a ListView
    private ListView mListView;
    // Defines a ContactsAdapter
    private ContactsAdapter mAdapter;
    ...
    // Defines a Cursor to contain the retrieved data
    private Cursor mCursor;
    /*
     * Defines a projection based on platform version. This ensures
     * that you retrieve the correct columns.
     */
    private static final String[] PROJECTION =
            {
                Contacts._ID,
                Contacts.LOOKUP_KEY,
                (Build.VERSION.SDK_INT >=
                 Build.VERSION_CODES.HONEYCOMB) ?
                        Contacts.DISPLAY_NAME_PRIMARY :
                        Contacts.DISPLAY_NAME
                (Build.VERSION.SDK_INT >=
                 Build.VERSION_CODES.HONEYCOMB) ?
                        Contacts.PHOTO_THUMBNAIL_ID :
                        /*
                         * Although it's not necessary to include the
                         * column twice, this keeps the number of
                         * columns the same regardless of version
                         */
                        Contacts_ID
                ...
            };
    /*
     * As a shortcut, defines constants for the
     * column indexes in the Cursor. The index is
     * 0-based and always matches the column order
     * in the projection.
     */
    // Column index of the _ID column
    private int mIdIndex = 0;
    // Column index of the LOOKUP_KEY column
    private int mLookupKeyIndex = 1;
    // Column index of the display name column
    private int mDisplayNameIndex = 3;
    /*
     * Column index of the photo data column.
     * It's PHOTO_THUMBNAIL_URI for Honeycomb and later,
     * and _ID for previous versions.
     */
    private int mPhotoDataIndex =
            Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
            3 :
            0;
    ...

Set up the ListView

In Fragment.onCreate(), instantiate the custom cursor adapter and get a handle to the ListView:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        /*
         * Instantiates the subclass of
         * CursorAdapter
         */
        ContactsAdapter mContactsAdapter =
                new ContactsAdapter(getActivity());
        /*
         * Gets a handle to the ListView in the file
         * contact_list_layout.xml
         */
        mListView = (ListView) findViewById(R.layout.contact_list_layout);
        ...
    }
    ...

In onActivityCreated(), bind the ContactsAdapter to the ListView:

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        ...
        // Sets up the adapter for the ListView
        mListView.setAdapter(mAdapter);
        ...
    }
    ...

When you get back a Cursor containing the contacts data, usually in onLoadFinished(), call swapCursor() to move the Cursor data to the ListView. This displays the QuickContactBadge for each entry in the list of contacts:

    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        // When the loader has completed, swap the cursor into the adapter.
        mContactsAdapter.swapCursor(cursor);
    }

When you bind a Cursor to a ListView with a CursorAdapter (or subclass), and you use a CursorLoader to load the Cursor, always clear references to the Cursor in your implementation of onLoaderReset(). For example:

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        // Removes remaining reference to the previous Cursor
        mContactsAdapter.swapCursor(null);
    }