diff options
author | Joe Malin <jmalin@google.com> | 2013-03-26 09:42:51 -0700 |
---|---|---|
committer | Android Git Automerger <android-git-automerger@android.com> | 2013-03-26 09:42:51 -0700 |
commit | 3be53a86dc5ee08e45c7fd657084107fa7ed2e1b (patch) | |
tree | e084804e0432c9edf5889966f260b46b04d629c4 /docs | |
parent | df4144f4dfaa3a344cfdf923121f6113463978da (diff) | |
parent | 3e904d62b6df1b8b99aa53ff62aee752b41c5163 (diff) | |
download | frameworks_base-3be53a86dc5ee08e45c7fd657084107fa7ed2e1b.zip frameworks_base-3be53a86dc5ee08e45c7fd657084107fa7ed2e1b.tar.gz frameworks_base-3be53a86dc5ee08e45c7fd657084107fa7ed2e1b.tar.bz2 |
am 3e904d62: am e4ba94e5: am e64253fb: am 0e84afdb: am 1be81a96: am 98827948: Merge "Android Training: Accessing Contacts" into jb-mr1-dev
* commit '3e904d62b6df1b8b99aa53ff62aee752b41c5163':
Android Training: Accessing Contacts
Diffstat (limited to 'docs')
-rw-r--r-- | docs/html/training/contacts-provider/ContactsList.zip | bin | 0 -> 483676 bytes | |||
-rw-r--r-- | docs/html/training/contacts-provider/display-contact-badge.jd | 635 | ||||
-rw-r--r-- | docs/html/training/contacts-provider/index.jd | 97 | ||||
-rw-r--r-- | docs/html/training/contacts-provider/modify-data.jd | 305 | ||||
-rw-r--r-- | docs/html/training/contacts-provider/retrieve-details.jd | 378 | ||||
-rw-r--r-- | docs/html/training/contacts-provider/retrieve-names.jd | 815 | ||||
-rw-r--r-- | docs/html/training/training_toc.cs | 34 |
7 files changed, 2262 insertions, 2 deletions
diff --git a/docs/html/training/contacts-provider/ContactsList.zip b/docs/html/training/contacts-provider/ContactsList.zip Binary files differnew file mode 100644 index 0000000..d2a5cfb --- /dev/null +++ b/docs/html/training/contacts-provider/ContactsList.zip diff --git a/docs/html/training/contacts-provider/display-contact-badge.jd b/docs/html/training/contacts-provider/display-contact-badge.jd new file mode 100644 index 0000000..f08935d --- /dev/null +++ b/docs/html/training/contacts-provider/display-contact-badge.jd @@ -0,0 +1,635 @@ +page.title=Displaying the Quick Contact Badge + +trainingnavtop=true +@jd:body + + +<div id="tb-wrapper"> +<div id="tb"> + +<!-- table of contents --> +<h2>This lesson teaches you to</h2> +<ol> + <li> + <a href="#AddView">Add a QuickContactBadge View</a> + </li> + <li> + <a href="#SetURIThumbnail">Set the Contact URI and Thumbnail</a> + </li> + <li> + <a href="#ListView"> + Add a QuickContactBadge to a ListView + </a> + </li> +</ol> + +<!-- other docs (NOT javadocs) --> +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics + </a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/contacts-provider.html"> + Contacts Provider + </a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button"> + Download the sample + </a> + <p class="filename">ContactsList.zip</p> +</div> + +</div> +</div> +<p> + This lesson shows you how to add a {@link android.widget.QuickContactBadge} to your UI + and how to bind data to it. A {@link android.widget.QuickContactBadge} is a widget that + initially appears as a thumbnail image. Although you can use any {@link android.graphics.Bitmap} + for the thumbnail image, you usually use a {@link android.graphics.Bitmap} decoded from the + contact's photo thumbnail image. +</p> +<p> + The small image acts as a control; when users click on the image, the + {@link android.widget.QuickContactBadge} expands into a dialog containing the following: +</p> +<dl> + <dt>A large image</dt> + <dd> + The large image associated with the contact, or no image is available, a placeholder + graphic. + </dd> + <dt> + App icons + </dt> + <dd> + 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. + </dd> +</dl> +<p> + The {@link android.widget.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 {@link android.widget.QuickContactBadge}, choose the communication method they + want to use, and send the information for that method directly to the appropriate app. +</p> +<h2 id="AddView">Add a QuickContactBadge View</h2> +<p> + To add a {@link android.widget.QuickContactBadge}, insert a + <code><QuickContactBadge></code> element in your layout. For example: +</p> +<pre> +<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> +</pre> +<h2 id="">Retrieve provider data</h2> +<p> + To display a contact in the {@link android.widget.QuickContactBadge}, you need a content URI + for the contact and a {@link android.graphics.Bitmap} for the small image. You generate + both the content URI and the {@link android.graphics.Bitmap} from columns retrieved from the + Contacts Provider. Specify these columns as part of the projection you use to load data into + your {@link android.database.Cursor}. +</p> +<p> + For Android 3.0 (API level 11) and later, include the following columns in your projection:</p> +<ul> + <li>{@link android.provider.ContactsContract.Contacts#_ID Contacts._ID}</li> + <li>{@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY}</li> + <li> + {@link android.provider.ContactsContract.Contacts#PHOTO_THUMBNAIL_URI + Contacts.PHOTO_THUMBNAIL_URI} + </li> +</ul> +<p> + For Android 2.3.3 (API level 10) and earlier, use the following columns: +</p> +<ul> + <li>{@link android.provider.ContactsContract.Contacts#_ID Contacts._ID}</li> + <li>{@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY}</li> +</ul> +<p> + The remainder of this lesson assumes that you've already loaded a + {@link android.database.Cursor} that contains these columns as well as others you may have + chosen. To learn how to retrieve this columns in a {@link android.database.Cursor}, read the + lesson <a href="retrieve-names.html">Retrieving a List of Contacts</a>. +</p> +<h2 id="SetURIThumbnail">Set the Contact URI and Thumbnail</h2> +<p> + Once you have the necessary columns, you can bind data to the + {@link android.widget.QuickContactBadge}. +</p> +<h3>Set the Contact URI</h3> +<p> + To set the content URI for the contact, call + {@link android.provider.ContactsContract.Contacts#getLookupUri getLookupUri(id,lookupKey)} to + get a {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, then + call {@link android.widget.QuickContactBadge#assignContactUri assignContactUri()} to set the + contact. For example: +</p> +<pre> + // 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( + Cursor.getLong(mIdColumn), + Cursor.getString(mLookupKeyColumn) + ); + mBadge.assignContactUri(mContactUri); +</pre> +<p> + When users click the {@link android.widget.QuickContactBadge} icon, the contact's + details automatically appear in the dialog. +</p> +<h3>Set the photo thumbnail</h3> +<p> + Setting the contact URI for the {@link android.widget.QuickContactBadge} does not automatically + load the contact's thumbnail photo. To load the photo, get a URI for the photo from the + contact's {@link android.database.Cursor} row, use it to open the file containing the compressed + thumbnail photo, and read the file into a {@link android.graphics.Bitmap}. +</p> +<p class="note"> + <strong>Note:</strong> The + {@link android.provider.ContactsContract.Contacts#PHOTO_THUMBNAIL_URI} column isn't available + in platform versions prior to 3.0. For those versions, you must retrieve the URI + from the {@link android.provider.ContactsContract.Contacts.Photo Contacts.Photo} subtable. +</p> +<p> + First, set up variables for accessing the {@link android.database.Cursor} containing the + {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} and + {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY} columns, as + described previously: +</p> +<pre> + // 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 = Cursor.getString(mThumbnailColumn); + ... +</pre> +<p> + 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 + {@link android.graphics.Bitmap}. Start by constructing a URI that points to the + thumbnail: +<p> +<pre> + /** + * 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; + } +</pre> +<p> + Call the <code>loadContactPhotoThumbnail()</code> method in your code to get the + thumbnail {@link android.graphics.Bitmap}, and use the result to set the photo thumbnail in + your {@link android.widget.QuickContactBadge}: +</p> +<pre> + ... + /* + * 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); +</pre> +<h2 id="ListView">Add a QuickContactBadge to a ListView</h2> +<p> + A {@link android.widget.QuickContactBadge} is a useful addition to a + {@link android.widget.ListView} that displays a list of contacts. Use the + {@link android.widget.QuickContactBadge} to display a thumbnail photo for each contact; when + users click the thumbnail, the {@link android.widget.QuickContactBadge} dialog appears. +</p> +<h3>Add the QuickContactBadge element</h3> +<p> + To start, add a {@link android.widget.QuickContactBadge} view element to your item layout + For example, if you want to display a {@link android.widget.QuickContactBadge} and a name for + each contact you retrieve, put the following XML into a layout file: +</p> +<pre> +<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> +</pre> +<p> + In the following sections, this file is referred to as <code>contact_item_layout.xml</code>. +</p> +<h3>Set up a custom CursorAdapter</h3> +<p> + To bind a {@link android.support.v4.widget.CursorAdapter} to a {@link android.widget.ListView} + containing a {@link android.widget.QuickContactBadge}, define a custom adapter that + extends {@link android.support.v4.widget.CursorAdapter}. This approach allows you to process the + data in the {@link android.database.Cursor} before you bind it to the + {@link android.widget.QuickContactBadge}. This approach also allows you to bind multiple + {@link android.database.Cursor} columns to the {@link android.widget.QuickContactBadge}. Neither + of these operations is possible in a regular {@link android.support.v4.widget.CursorAdapter}. +</p> +<p> + The subclass of {@link android.support.v4.widget.CursorAdapter} that you define must + override the following methods: +</p> +<dl> + <dt>{@link android.support.v4.widget.CursorAdapter#newView CursorAdapter.newView()}</dt> + <dd> + Inflates a new {@link android.view.View} object to hold the item layout. In the override + of this method, store handles to the child {@link android.view.View} objects of the layout, + including the child {@link android.widget.QuickContactBadge}. By taking this approach, you + avoid having to get handles to the child {@link android.view.View} objects each time you + inflate a new layout. + <p> + You must override this method so you can get handles to the individual child + {@link android.view.View} objects. This technique allows you to control their binding in + {@link android.support.v4.widget.CursorAdapter#bindView CursorAdapter.bindView()}. + </p> + </dd> + <dt>{@link android.support.v4.widget.CursorAdapter#bindView CursorAdapter.bindView()}</dt> + <dd> + Moves data from the current {@link android.database.Cursor} row to the child + {@link android.view.View} objects of the item layout. You must override this method so + you can bind both the contact's URI and thumbnail to the + {@link android.widget.QuickContactBadge}. The default implementation only allows a 1-to-1 + mapping between a column and a {@link android.view.View} + </dd> +</dl> +<p> + The following code snippet contains an example of a custom subclass of + {@link android.support.v4.widget.CursorAdapter}: +</p> +<h3>Define the custom list adapter</h3> +<p> + Define the subclass of {@link android.support.v4.widget.CursorAdapter} including its + constructor, and override + {@link android.support.v4.widget.CursorAdapter#newView newView()} and + {@link android.support.v4.widget.CursorAdapter#bindView bindView()}: +</p> +<pre> + /** + * + * + */ + 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); + } +</pre> + +<h3>Set up variables</h3> +<p> + In your code, set up variables, including a {@link android.database.Cursor} projection that + includes the necessary columns. +</p> +<p class="note"> + <strong>Note:</strong> The following code snippets use the method + <code>loadContactPhotoThumbnail()</code>, which is defined in the section + <a href="#SetURIThumbnail">Set the Contact URI and Thumbnail</a> +</p> +<p> + For example: +</p> +<pre> +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; + ... +</pre> +<h3>Set up the ListView</h3> +<p> + In {@link android.support.v4.app.Fragment#onCreate Fragment.onCreate()}, instantiate the custom + cursor adapter and get a handle to the {@link android.widget.ListView}: +</p> +<pre> + @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); + ... + } + ... +</pre> +<p> + In {@link android.support.v4.app.Fragment#onActivityCreated onActivityCreated()}, bind the + <code>ContactsAdapter</code> to the {@link android.widget.ListView}: +</p> +<pre> + @Override + public void onActivityCreated(Bundle savedInstanceState) { + ... + // Sets up the adapter for the ListView + mListView.setAdapter(mAdapter); + ... + } + ... +</pre> +<p> + When you get back a {@link android.database.Cursor} containing the contacts data, usually in + {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()}, + call {@link android.support.v4.widget.CursorAdapter#swapCursor swapCursor()} to move the + {@link android.database.Cursor} data to the {@link android.widget.ListView}. This displays the + {@link android.widget.QuickContactBadge} for each entry in the list of contacts: +</p> +<pre> + public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { + // When the loader has completed, swap the cursor into the adapter. + mContactsAdapter.swapCursor(cursor); + } +</pre> +<p> + When you bind a {@link android.database.Cursor} to a + {@link android.widget.ListView} with a {@link android.support.v4.widget.CursorAdapter} + (or subclass), and you use a {@link android.support.v4.content.CursorLoader} to load the + {@link android.database.Cursor}, always clear references to the {@link android.database.Cursor} + in your implementation of + {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()}. + For example: +</p> +<pre> + @Override + public void onLoaderReset(Loader<Cursor> loader) { + // Removes remaining reference to the previous Cursor + mContactsAdapter.swapCursor(null); + } +</pre> diff --git a/docs/html/training/contacts-provider/index.jd b/docs/html/training/contacts-provider/index.jd new file mode 100644 index 0000000..f380d95 --- /dev/null +++ b/docs/html/training/contacts-provider/index.jd @@ -0,0 +1,97 @@ +page.title=Accessing Contacts Data + +trainingnavtop=true +startpage=true + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<!-- Required platform, tools, add-ons, devices, knowledge, etc. --> +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 2.0 (API Level 5) or higher</li> + <li>Experience in using {@link android.content.Intent} objects</li> + <li>Experience in using content providers</li> +</ul> + +<!-- related docs (NOT javadocs) --> +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/contacts-provider.html"> + Contacts Provider</a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button"> + Download the sample + </a> + <p class="filename">ContactsList.zip</p> +</div> + +</div> +</div> + +<p> + The <a href="{@docRoot}guide/topics/providers/contacts-provider.html">Contacts Provider</a> is + the central repository of the user's contacts information, including data from contacts apps and + social networking apps. In your apps, you can access Contacts Provider information directly by + calling {@link android.content.ContentResolver} methods or by sending intents to a contacts app. +</p> +<p> + This class focuses on retrieving lists of contacts, displaying the details for a particular + contact, and modifying contacts using intents. The basic techniques described + here can be extended to perform more complex tasks. In addition, this class helps you + understand the overall structure and operation of the + <a href="{@docRoot}guide/topics/providers/contacts-provider.html">Contacts Provider</a>. +</p> +<h2>Lessons</h2> + +<dl> + <dt> + <b><a href="retrieve-names.html">Retrieving a List of Contacts</a></b> + </dt> + <dd> + Learn how to retrieve a list of contacts for which the data matches all or part of a search + string, using the following techniques: + <ul> + <li>Match by contact name</li> + <li>Match any type of contact data</li> + <li>Match a specific type of contact data, such as a phone number</li> + </ul> + </dd> + <dt> + <b><a href="retrieve-details.html">Retrieving Details for a Contact</a></b> + </dt> + <dd> + Learn how to retrieve the details for a single contact. A contact's details are data + such as phone numbers and email addresses. You can retrieve all details, or you can + retrieve details of a specific type, such as all email addresses. + </dd> + <dt> + <b><a href="modify-data.html">Modifying Contacts Using Intents</a></b> + </dt> + <dd> + Learn how to modify a contact by sending an intent to the People app. + </dd> + <dt> + <b> + <a href="display-contact-badge.html">Displaying the Quick Contact Badge</a> + </b> + </dt> + <dd> + Learn how to display the {@link android.widget.QuickContactBadge} widget. When the user + clicks the contact badge widget, a dialog opens that displays the contact's details and + action buttons for apps that can handle the details. For example, if the contact has an + email address, the dialog displays an action button for the default email app. + </dd> +</dl> diff --git a/docs/html/training/contacts-provider/modify-data.jd b/docs/html/training/contacts-provider/modify-data.jd new file mode 100644 index 0000000..64853ef --- /dev/null +++ b/docs/html/training/contacts-provider/modify-data.jd @@ -0,0 +1,305 @@ +page.title=Modifying Contacts Using Intents +trainingnavtop=true +@jd:body +<div id="tb-wrapper"> +<div id="tb"> + +<!-- table of contents --> +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#InsertContact">Insert a New Contact Using an Intent</a></li> + <li><a href="#EditContact">Edit an Existing Contact Using an Intent</a></li> + <li><a href="#InsertEdit">Let Users Choose to Insert or Edit Using an Intent</a> +</ol> +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics + </a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/contacts-provider.html"> + Contacts Provider + </a> + </li> + <li> + <a href="{@docRoot}guide/components/intents-filters.html">Intents and Intent Filters</a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button"> + Download the sample + </a> + <p class="filename">ContactsList.zip</p> +</div> + +</div> +</div> +<p> + This lesson shows you how to use an {@link android.content.Intent} to insert a new contact or + modify a contact's data. Instead of accessing the Contacts Provider directly, an + {@link android.content.Intent} starts the contacts app, which runs the appropriate + {@link android.app.Activity}. For the modification actions described in this lesson, + if you send extended data in the {@link android.content.Intent} it's entered into the UI of the + {@link android.app.Activity} that is started. +</p> +<p> + Using an {@link android.content.Intent} to insert or update a single contact is the preferred + way of modifying the Contacts Provider, for the following reasons: +</p> +<ul> + <li>It saves you the time and and effort of developing your own UI and code.</li> + <li> + It avoids introducing errors caused by modifications that don't follow the + Contacts Provider's rules. + </li> + <li> + It reduces the number of permissions you need to request. Your app doesn't need permission + to write to the Contacts Provider, because it delegates modifications to the contacts app, + which already has that permission. + </li> +</ul> +<h2 id="InsertContact">Insert a New Contact Using an Intent</h2> +<p> + You often want to allow the user to insert a new contact when your app receives new data. For + example, a restaurant review app can allow users to add the restaurant as a contact as they're + reviewing it. To do this using an intent, create the intent using as much data as you have + available, and then send the intent to the contacts app. +</p> +<p> + Inserting a contact using the contacts app inserts a new <em>raw</em> contact into the Contacts + Provider's {@link android.provider.ContactsContract.RawContacts} table. If necessary, + the contacts app prompts users for the account type and account to use when creating the raw + contact. The contacts app also notifies users if the raw contact already exists. Users then have + option of canceling the insertion, in which case no contact is created. To learn + more about raw contacts, see the + <a href="{@docRoot}guide/topics/providers/contacts-provider.html">Contacts Provider</a> + API guide. +</p> + +<h3>Create an Intent</h3> +<p> + To start, create a new {@link android.content.Intent} object with the action + {@link android.provider.ContactsContract.Intents.Insert#ACTION Intents.Insert.ACTION}. + Set the MIME type to {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE + RawContacts.CONTENT_TYPE}. For example: +</p> +<pre> +... +// Creates a new Intent to insert a contact +Intent intent = new Intent(Intents.Insert.ACTION); +// Sets the MIME type to match the Contacts Provider +intent.setType(ContactsContract.RawContacts.CONTENT_TYPE); +</pre> +<p> + If you already have details for the contact, such as a phone number or email address, you can + insert them into the intent as extended data. For a key value, use the appropriate constant from + {@link android.provider.ContactsContract.Intents.Insert Intents.Insert}. The contacts app + displays the data in its insert screen, allowing users to make further edits and additions. +</p> +<pre> +/* Assumes EditText fields in your UI contain an email address + * and a phone number. + * + */ +private EditText mEmailAddress = (EditText) findViewById(R.id.email); +private EditText mPhoneNumber = (EditText) findViewById(R.id.phone); +... +/* + * Inserts new data into the Intent. This data is passed to the + * contacts app's Insert screen + */ +// Inserts an email address +intent.putExtra(Intents.Insert.EMAIL, mEmailAddress.getText()) +/* + * In this example, sets the email type to be a work email. + * You can set other email types as necessary. + */ + .putExtra(Intents.Insert.EMAIL_TYPE, CommonDataKinds.Email.TYPE_WORK) +// Inserts a phone number + .putExtra(Intents.Insert.PHONE, mPhoneNumber.getText()) +/* + * In this example, sets the phone type to be a work phone. + * You can set other phone types as necessary. + */ + .putExtra(Intents.Insert.PHONE_TYPE, Phone.TYPE_WORK); + +</pre> +<p> + Once you've created the {@link android.content.Intent}, send it by calling + {@link android.support.v4.app.Fragment#startActivity startActivity()}. +</p> +<pre> + /* Sends the Intent + */ + startActivity(intent); +</pre> +<p> + This call opens a screen in the contacts app that allows users to enter a new contact. The + account type and account name for the contact is listed at the top of the screen. Once users + enter the data and click <i>Done</i>, the contacts app's contact list appears. Users return to + your app by clicking <i>Back</i>. +</p> +<h2 id="EditContact">Edit an Existing Contact Using an Intent</h2> +<p> + Editing an existing contact using an {@link android.content.Intent} is useful if the user + has already chosen a contact of interest. For example, an app that finds contacts that have + postal addresses but lack a postal code could give users the option of looking up the code and + then adding it to the contact. +</p> +<p> + To edit an existing contact using an intent, use a procedure similar to + inserting a contact. Create an intent as described in the section + <a href="#InsertContact">Insert a New Contact Using an Intent</a>, but add the contact's + {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI + Contacts.CONTENT_LOOKUP_URI} and the MIME type + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE + Contacts.CONTENT_ITEM_TYPE} to the intent. If you want to edit the contact with details you + already have, you can put them in the intent's extended data. Notice that some + name columns can't be edited using an intent; these columns are listed in the summary + section of the API reference for the class {@link android.provider.ContactsContract.Contacts} + under the heading "Update". +</p> +<p> + Finally, send the intent. In response, the contacts app displays an edit screen. When the user + finishes editing and saves the edits, the contacts app displays a contact list. When the user + clicks <i>Back</i>, your app is displayed. +</p> +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h2>Contacts Lookup Key</h2> + <p> + A contact's {@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} value is + the identifier that you should use to retrieve a contact. It remains constant, + even if the provider changes the contact's row ID to handle internal operations. + </p> +</div> +</div> +<h3>Create the Intent</h3> +<p> + To edit a contact, call {@link android.content.Intent#Intent Intent(action)} to + create an intent with the action {@link android.content.Intent#ACTION_EDIT}. Call + {@link android.content.Intent#setDataAndType setDataAndType()} to set the data value for the + intent to the contact's {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI + Contacts.CONTENT_LOOKUP_URI} and the MIME type to + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE + Contacts.CONTENT_ITEM_TYPE} MIME type; because a call to + {@link android.content.Intent#setType setType()} overwrites the current data value for the + {@link android.content.Intent}, you must set the data and the MIME type at the same time. +</p> +<p> + To get a contact's {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI + Contacts.CONTENT_LOOKUP_URI}, call + {@link android.provider.ContactsContract.Contacts#getLookupUri + Contacts.getLookupUri(id, lookupkey)} with the contact's + {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} and + {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY} values as + arguments. +</p> +<p> + The following snippet shows you how to create an intent: +</p> +<pre> + // The Cursor that contains the Contact row + public Cursor mCursor; + // The index of the lookup key column in the cursor + public int mLookupKeyIndex; + // The index of the contact's _ID value + public int mIdIndex; + // The lookup key from the Cursor + public String mCurrentLookupKey; + // The _ID value from the Cursor + public long mCurrentId; + // A content URI pointing to the contact + Uri mSelectedContactUri; + ... + /* + * Once the user has selected a contact to edit, + * this gets the contact's lookup key and _ID values from the + * cursor and creates the necessary URI. + */ + // Gets the lookup key column index + mLookupKeyIndex = mCursor.getColumnIndex(Contacts.LOOKUP_KEY); + // Gets the lookup key value + mCurrentLookupKey = mCursor.getString(mLookupKeyIndex); + // Gets the _ID column index + mIdIndex = mCursor.getColumnIndex(Contacts._ID); + mCurrentId = mCursor.getLong(mIdIndex); + mSelectedContactUri = + Contacts.getLookupUri(mCurrentId, mCurrentLookupKey); + ... + // Creates a new Intent to edit a contact + Intent editIntent = new Intent(Intent.ACTION_EDIT); + /* + * Sets the contact URI to edit, and the data type that the + * Intent must match + */ + editIntent.setDataAndType(mSelectedContactUri,Contacts.CONTENT_ITEM_TYPE); +</pre> +<h3>Add the navigation flag</h3> +<p> + In Android 4.0 (API version 14) and later, a problem in the contacts app causes incorrect + navigation. When your app sends an edit intent to the contacts app, and users edit and save a + contact, when they click <i>Back</i> they see the contacts list screen. To navigate back to + your app, they have to click <i>Recents</i> and choose your app. +</p> +<p> + To work around this problem in Android 4.0.3 (API version 15) and later, add the extended + data key {@code finishActivityOnSaveCompleted} to the intent, with a value of {@code true}. + Android versions prior to Android 4.0 accept this key, but it has no effect. To set the + extended data, do the following: +</p> +<pre> + // Sets the special extended data for navigation + editIntent.putExtra("finishActivityOnSaveCompleted", true); +</pre> +<h3>Add other extended data</h3> +<p> + To add additional extended data to the {@link android.content.Intent}, call + {@link android.content.Intent#putExtra putExtra()} as desired. + You can add extended data for common contact fields by using the key values specified in + {@link android.provider.ContactsContract.Intents.Insert Intents.Insert}. Remember that some + columns in the {@link android.provider.ContactsContract.Contacts} table can't be modified. + These columns are listed in the summary section of the API reference for the class + {@link android.provider.ContactsContract.Contacts} under the heading "Update". +</p> + +<h3>Send the Intent</h3> +<p> + Finally, send the intent you've constructed. For example: +</p> +<pre> + // Sends the Intent + startActivity(editIntent); +</pre> +<h2 id="InsertEdit">Let Users Choose to Insert or Edit Using an Intent</h2> +<p> + You can allow users to choose whether to insert a contact or edit an existing one by sending + an {@link android.content.Intent} with the action + {@link android.content.Intent#ACTION_INSERT_OR_EDIT}. For example, an email client app could + allow users to add an incoming email address to a new contact, or add it as an additional + address for an existing contact. Set the MIME type for this intent to + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE Contacts.CONTENT_ITEM_TYPE}, + but don't set the data URI. +</p> +<p> + When you send this intent, the contacts app displays a list of contacts. + Users can either insert a new contact or pick an existing contact and edit it. + Any extended data fields you add to the intent populates the screen that appears. You can use + any of the key values specified in {@link android.provider.ContactsContract.Intents.Insert + Intents.Insert}. The following code snippet shows how to construct and send the intent: +</p> +<pre> + // Creates a new Intent to insert or edit a contact + Intent intentInsertEdit = new Intent(Intent.ACTION_INSERT_OR_EDIT); + // Sets the MIME type + intentInsertEdit.setType(Contacts.CONTENT_ITEM_TYPE); + // Add code here to insert extended data, if desired + ... + // Sends the Intent with an request ID + startActivity(intentInsertEdit); +</pre> diff --git a/docs/html/training/contacts-provider/retrieve-details.jd b/docs/html/training/contacts-provider/retrieve-details.jd new file mode 100644 index 0000000..0de3b67 --- /dev/null +++ b/docs/html/training/contacts-provider/retrieve-details.jd @@ -0,0 +1,378 @@ +page.title=Retrieving Details for a Contact + +trainingnavtop=true +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<!-- table of contents --> +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#RetrieveAll">Retrieve All Details for a Contact</a></li> + <li><a href="#RetrieveSpecific">Retrieve Specific Details for a Contact</a></li> +</ol> + +<!-- other docs (NOT javadocs) --> +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/contacts-provider.html"> + Contacts Provider</a> + </li> + <li> + <a href="{@docRoot}guide/components/loaders.html">Loaders</a> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button"> + Download the sample + </a> + <p class="filename">ContactsList.zip</p> +</div> + +</div> +</div> +<p> + This lesson shows how to retrieve detail data for a contact, such as email addresses, phone + numbers, and so forth. It's the details that users are looking for when they retrieve a contact. + You can give them all the details for a contact, or only display details of a particular type, + such as email addresses. +</p> +<p> + The steps in this lesson assume that you already have a + {@link android.provider.ContactsContract.Contacts} row for a contact the user has picked. + The <a href="retrieve-names.html">Retrieving Contact Names</a> lesson shows how to + retrieve a list of contacts. +</p> +<h2 id="RetrieveAll">Retrieve All Details for a Contact</h2> +<p> + To retrieve all the details for a contact, search the + {@link android.provider.ContactsContract.Data} table for any rows that contain the contact's + {@link android.provider.ContactsContract.Data#LOOKUP_KEY}. This column is available in + the {@link android.provider.ContactsContract.Data} table, because the Contacts + Provider makes an implicit join between the {@link android.provider.ContactsContract.Contacts} + table and the {@link android.provider.ContactsContract.Data} table. The + {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY} column is described + in more detail in the <a href="retrieve-names.html">Retrieving Contact Names</a> lesson. +</p> +<p class="note"> + <strong>Note:</strong> Retrieving all the details for a contact reduces the performance of a + device, because it needs to retrieve all of the columns in the + {@link android.provider.ContactsContract.Data} table. Consider the performance impact before + you use this technique. +</p> +<h3>Request permissions</h3> +<p> + To read from the Contacts Provider, your app must have + {@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS} permission. + To request this permission, add the following child element of + <code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"> + <manifest></a></code> to your manifest file: +</p> +<pre> + <uses-permission android:name="android.permission.READ_CONTACTS" /> +</pre> +<h3>Set up a projection</h3> +<p> + Depending on the data type a row contains, it may use only a few columns or many. In addition, + the data is in different columns depending on the data type. + To ensure you get all the possible columns for all possible data types, you need to add all the + column names to your projection. Always retrieve + {@link android.provider.ContactsContract.Data#_ID Data._ID} if you're binding the result + {@link android.database.Cursor} to a {@link android.widget.ListView}; otherwise, the binding + won't work. Also retrieve {@link android.provider.ContactsContract.Data#MIMETYPE Data.MIMETYPE} + so you can identify the data type of each row you retrieve. For example: +</p> +<pre> + private static final String PROJECTION = + { + Data._ID, + Data.MIMETYPE, + Data.DATA1, + Data.DATA2, + Data.DATA3, + Data.DATA4, + Data.DATA5, + Data.DATA6, + Data.DATA7, + Data.DATA8, + Data.DATA9, + Data.DATA10, + Data.DATA11, + Data.DATA12, + Data.DATA13, + Data.DATA14, + Data.DATA15 + }; +</pre> +<p> + This projection retrieves all the columns for a row in the + {@link android.provider.ContactsContract.Data} table, using the column names defined in + the {@link android.provider.ContactsContract.Data} class. +</p> +<p> + Optionally, you can also use any other column constants defined in or inherited by the + {@link android.provider.ContactsContract.Data} class. Notice, however, that the columns + {@link android.provider.ContactsContract.DataColumns#SYNC1} through + {@link android.provider.ContactsContract.DataColumns#SYNC4} are meant to be used by sync + adapters, so their data is not useful. +</p> +<h3>Define the selection criteria</h3> +<p> + Define a constant for your selection clause, an array to hold selection arguments, and a + variable to hold the selection value. Use + the {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY Contacts.LOOKUP_KEY} column to + find the contact. For example: +</p> +<pre> + // Defines the selection clause + private static final String SELECTION = Data.LOOKUP_KEY + " = ?"; + // Defines the array to hold the search criteria + private String[] mSelectionArgs = { "" }; + /* + * Defines a variable to contain the selection value. Once you + * have the Cursor from the Contacts table, and you've selected + * the desired row, move the row's LOOKUP_KEY value into this + * variable. + */ + private String mLookupKey; +</pre> +<p> + Using "?" as a placeholder in your selection text expression ensures that the resulting search + is generated by binding rather than SQL compilation. This approach eliminates the + possibility of malicious SQL injection. +</p> +<h3>Define the sort order</h3> +<p> + Define the sort order you want in the resulting {@link android.database.Cursor}. To + keep all rows for a particular data type together, sort by + {@link android.provider.ContactsContract.Data#MIMETYPE Data.MIMETYPE}. This query argument + groups all email rows together, all phone rows together, and so forth. For example: +</p> +<pre> + /* + * Defines a string that specifies a sort order of MIME type + */ + private static final String SORT_ORDER = Data.MIMETYPE; +</pre> +<p class="note"> + <strong>Note:</strong> Some data types don't use a subtype, so you can't sort on subtype. + Instead, you have to iterate through the returned {@link android.database.Cursor}, + determine the data type of the current row, and store data for rows that use a subtype. When + you finish reading the cursor, you can then sort each data type by subtype and display the + results. +</p> +<h3>Initialize the Loader</h3> +<p> + Always do retrievals from the Contacts Provider (and all other content providers) in a + background thread. Use the Loader framework defined by the + {@link android.support.v4.app.LoaderManager} class and the + {@link android.support.v4.app.LoaderManager.LoaderCallbacks} interface to do background + retrievals. +</p> +<p> + When you're ready to retrieve the rows, initialize the loader framework by + calling {@link android.support.v4.app.LoaderManager#initLoader initLoader()}. Pass an + integer identifier to the method; this identifier is passed to + {@link android.support.v4.app.LoaderManager.LoaderCallbacks} methods. The identifier helps you + use multiple loaders in an app by allowing you to differentiate between them. +</p> +<p> + The following snippet shows how to initialize the loader framework: +</p> +<pre> +public class DetailsFragment extends Fragment implements + LoaderManager.LoaderCallbacks<Cursor> { + ... + // Defines a constant that identifies the loader + DETAILS_QUERY_ID = 0; + ... + /* + * Invoked when the parent Activity is instantiated + * and the Fragment's UI is ready. Put final initialization + * steps here. + */ + @Override + onActivityCreated(Bundle savedInstanceState) { + ... + // Initializes the loader framework + getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this); +</pre> +<h3>Implement onCreateLoader()</h3> +<p> + Implement the {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader + onCreateLoader()} method, which is called by the loader framework immediately after you call + {@link android.support.v4.app.LoaderManager#initLoader initLoader()}. Return a + {@link android.support.v4.content.CursorLoader} from this method. Since you're searching + the {@link android.provider.ContactsContract.Data} table, use the constant + {@link android.provider.ContactsContract.Data#CONTENT_URI Data.CONTENT_URI} as the content URI. + For example: +</p> +<pre> + @Override + public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) { + // Choose the proper action + switch (loaderId) { + case DETAILS_QUERY_ID: + // Assigns the selection parameter + mSelectionArgs[0] = mLookupKey; + // Starts the query + CursorLoader mLoader = + new CursorLoader( + getActivity(), + Data.CONTENT_URI, + PROJECTION, + SELECTION, + mSelectionArgs, + SORT_ORDER + ); + ... + } +</pre> +<h3>Implement onLoadFinished() and onLoaderReset()</h3> +<p> + Implement the + {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} + method. The loader framework calls + {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} + when the Contacts Provider returns the results of the query. For example: +</p> +<pre> + public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { + switch (loader.getId()) { + case DETAILS_QUERY_ID: + /* + * Process the resulting Cursor here. + */ + } + break; + ... + } + } +</pre> +<p> +<p> + The method {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset + onLoaderReset()} is invoked when the loader framework detects that the data backing the result + {@link android.database.Cursor} has changed. At this point, remove any existing references + to the {@link android.database.Cursor} by setting them to null. If you don't, the loader + framework won't destroy the old {@link android.database.Cursor}, and you'll get a memory + leak. For example: +<pre> + @Override + public void onLoaderReset(Loader<Cursor> loader) { + switch (loader.getId()) { + case DETAILS_QUERY_ID: + /* + * If you have current references to the Cursor, + * remove them here. + */ + } + break; + } +</pre> +<h2 id="RetrieveSpecific">Retrieve Specific Details for a Contact</h2> +<p> + Retrieving a specific data type for a contact, such as all the emails, follows the same pattern + as retrieving all details. These are the only changes you need to make to the code + listed in <a href="#RetrieveAll">Retrieve All Details for a Contact</a>: +</p> +<dl> + <dt> + Projection + </dt> + <dd> + Modify your projection to retrieve the columns that are specific to the + data type. Also modify the projection to use the column name constants defined in the + {@link android.provider.ContactsContract.CommonDataKinds} subclass corresponding to the + data type. + </dd> + <dt> + Selection + </dt> + <dd> + Modify the selection text to search for the + {@link android.provider.ContactsContract.Data#MIMETYPE MIMETYPE} value that's specific to + your data type. + </dd> + <dt> + Sort order + </dt> + <dd> + Since you're only selecting a single detail type, don't group the returned + {@link android.database.Cursor} by {@link android.provider.ContactsContract.Data#MIMETYPE + Data.MIMETYPE}. + </dd> +</dl> +<p> + These modifications are described in the following sections. +</p> +<h3>Define a projection</h3> +<p> + Define the columns you want to retrieve, using the column name constants in the subclass + of {@link android.provider.ContactsContract.CommonDataKinds} for the data type. + If you plan to bind your {@link android.database.Cursor} to a {@link android.widget.ListView}, + be sure to retrieve the <code>_ID</code> column. For example, to retrieve email data, define the + following projection: +</p> +<pre> + private static final String[] PROJECTION = + { + Email._ID, + Email.ADDRESS, + Email.TYPE, + Email.LABEL + }; +</pre> +<p> + Notice that this projection uses the column names defined in the class + {@link android.provider.ContactsContract.CommonDataKinds.Email}, instead of the column names + defined in the class {@link android.provider.ContactsContract.Data}. Using the email-specific + column names makes the code more readable. +</p> +<p> + In the projection, you can also use any of the other columns defined in the + {@link android.provider.ContactsContract.CommonDataKinds} subclass. +</p> +<h3>Define selection criteria</h3> +<p> + Define a search text expression that retrieves rows for a specific contact's + {@link android.provider.ContactsContract.Data#LOOKUP_KEY} and the + {@link android.provider.ContactsContract.Data#MIMETYPE Data.MIMETYPE} of the details you + want. Enclose the {@link android.provider.ContactsContract.Data#MIMETYPE MIMETYPE} value in + single quotes by concatenating a "<code>'</code>" (single-quote) character to the start and end + of the constant; otherwise, the provider interprets the constant as a variable name rather + than as a string value. You don't need to use a placeholder for this value, because you're + using a constant rather than a user-supplied value. For example: +</p> +<pre> + /* + * Defines the selection clause. Search for a lookup key + * and the Email MIME type + */ + private static final String SELECTION = + Data.LOOKUP_KEY + " = ?" + + " AND " + + Data.MIMETYPE + " = " + + "'" + Email.CONTENT_ITEM_TYPE + "'"; + // Defines the array to hold the search criteria + private String[] mSelectionArgs = { "" }; +</pre> +<h3>Define a sort order</h3> +<p> + Define a sort order for the returned {@link android.database.Cursor}. Since you're retrieving a + specific data type, omit the sort on {@link android.provider.ContactsContract.Data#MIMETYPE}. + Instead, if the type of detail data you're searching includes a subtype, sort on it. + For example, for email data you can sort on + {@link android.provider.ContactsContract.CommonDataKinds.Email#TYPE Email.TYPE}: +</p> +<pre> + private static final String SORT_ORDER = Email.TYPE + " ASC "; +</pre> diff --git a/docs/html/training/contacts-provider/retrieve-names.jd b/docs/html/training/contacts-provider/retrieve-names.jd new file mode 100644 index 0000000..b034a6a --- /dev/null +++ b/docs/html/training/contacts-provider/retrieve-names.jd @@ -0,0 +1,815 @@ +page.title=Retrieving a List of Contacts + +trainingnavtop=true +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<!-- table of contents --> +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#Permissions">Request Permission to Read the Provider</a> + <li><a href="#NameMatch">Match a Contact by Name and List the Results</a></li> + <li><a href="#TypeMatch">Match a Contact By a Specific Type of Data</a></li> + <li><a href="#GeneralMatch">Match a Contact By Any Type of Data</a></li> +</ol> + +<!-- other docs (NOT javadocs) --> +<h2>You should also read</h2> +<ul> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + Content Provider Basics</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/contacts-provider.html"> + Contacts Provider</a> + </li> + <li> + <a href="{@docRoot}guide/components/loaders.html">Loaders</a> + </li> + <li> + <a href="{@docRoot}guide/topics/search/search-dialog.html">Creating a Search Interface</a> + </li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="http://developer.android.com/shareables/training/ContactsList.zip" class="button"> + Download the sample + </a> + <p class="filename">ContactsList.zip</p> +</div> + +</div> +</div> +<p> + This lesson shows you how to retrieve a list of contacts whose data matches all or part of a + search string, using the following techniques: +</p> +<dl> + <dt>Match contact names</dt> + <dd> + Retrieve a list of contacts by matching the search string to all or part of the contact + name data. The Contacts Provider allows multiple instances of the same name, so this + technique can return a list of matches. + </dd> + <dt>Match a specific type of data, such as a phone number</dt> + <dd> + Retrieve a list of contacts by matching the search string to a particular type of detail + data such as an email address. For example, this technique allows you to list all of the + contacts whose email address matches the search string. + </dd> + <dt>Match any type of data</dt> + <dd> + Retrieve a list of contacts by matching the search string to any type of detail data, + including name, phone number, street address, email address, and so forth. For example, + this technique allows you to accept any type of data for a search string and then list the + contacts for which the data matches the string. + </dd> +</dl> +<p class="note"> + <strong>Note:</strong> All the examples in this lesson use a + {@link android.support.v4.content.CursorLoader} to retrieve data from the Contacts + Provider. A {@link android.support.v4.content.CursorLoader} runs its query on a + thread that's separate from the UI thread. This ensures that the query doesn't slow down UI + response times and cause a poor user experience. For more information, see the Android + training class <a href="{@docRoot}training/load-data-background/index.html"> + Loading Data in the Background</a>. +</p> +<h2 id="Permissions">Request Permission to Read the Provider</h2> +<p> + To do any type of search of the Contacts Provider, your app must have + {@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS} permission. + To request this, add this +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> + element to your manifest file as a child element of +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code>: +</p> +<pre> + <uses-permission android:name="android.permission.READ_CONTACTS" /> +</pre> +<h2 id="NameMatch">Match a Contact by Name and List the Results</h2> +<p> + This technique tries to match a search string to the name of a contact or contacts in the + Contact Provider's {@link android.provider.ContactsContract.Contacts} table. You usually want + to display the results in a {@link android.widget.ListView}, to allow the user to choose among + the matched contacts. +</p> +<h3 id="DefineListView">Define ListView and item layouts</h3> +<p> + To display the search results in a {@link android.widget.ListView}, you need a main layout file + that defines the entire UI including the {@link android.widget.ListView}, and an item layout + file that defines one line of the {@link android.widget.ListView}. For example, you can define + the main layout file <code>res/layout/contacts_list_view.xml</code> that contains the + following XML: +</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<ListView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/list" + android:layout_width="match_parent" + android:layout_height="match_parent"/> +</pre> +<p> + This XML uses the built-in Android {@link android.widget.ListView} widget + {@link android.R.id#list android:id/list}. +</p> +<p> + Define the item layout file <code>contacts_list_item.xml</code> with the following XML: +</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/text1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:clickable="true"/> +</pre> +<p> + This XML uses the built-in Android {@link android.widget.TextView} widget + {@link android.R.id#text1 android:text1}. +</p> +<p class="note"> + <strong>Note:</strong> This lesson doesn't describe the UI for getting a search string from the + user, because you may want to get the string indirectly. For example, you can give the user + an option to search for contacts whose name matches a string in an incoming text message. +</p> +<p> + The two layout files you've written define a user interface that shows a + {@link android.widget.ListView}. The next step is to write code that uses this UI to display a + list of contacts. +</p> +<h3 id="Fragment">Define a Fragment that displays the list of contacts</h3> +<p> + To display the list of contacts, start by defining a {@link android.support.v4.app.Fragment} + that's loaded by an {@link android.app.Activity}. Using a + {@link android.support.v4.app.Fragment} is a more flexible technique, because you can use + one {@link android.support.v4.app.Fragment} to display the list and a second + {@link android.support.v4.app.Fragment} to display the details for a contact that the user + chooses from the list. Using this approach, you can combine one of the techniques presented in + this lesson with one from the lesson <a href="retrieve-details.html"> + Retrieving Details for a Contact</a>. +</p> +<p> + To learn how to use one or more {@link android.support.v4.app.Fragment} objects from an + an {@link android.app.Activity}, read the training class + <a href="{@docRoot}training/basics/fragments/index.html"> + Building a Dynamic UI with Fragments</a>. +</p> +<p> + To help you write queries against the Contacts Provider, the Android framework provides a + contracts class called {@link android.provider.ContactsContract}, which defines useful + constants and methods for accessing the provider. When you use this class, you don't have to + define your own constants for content URIs, table names, or columns. To use this class, + include the following statement: +</p> +<pre> +import android.provider.ContactsContract; +</pre> +<p> + Since the code uses a {@link android.support.v4.content.CursorLoader} to retrieve data + from the provider, you must specify that it implements the loader interface + {@link android.support.v4.app.LoaderManager.LoaderCallbacks}. Also, to help detect which contact + the user selects from the list of search results, implement the adapter interface + {@link android.widget.AdapterView.OnItemClickListener}. For example: +</p> +<pre> +... +import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.widget.AdapterView; +... +public class ContactsFragment extends Fragment implements + LoaderManager.LoaderCallbacks<Cursor>, + AdapterView.OnItemClickListener { +</pre> +<h3 id="DefineVariables">Define global variables</h3> +<p> + Define global variables that are used in other parts of the code: +</p> +<pre> + ... + /* + * Defines an array that contains column names to move from + * the Cursor to the ListView. + */ + @SuppressLint("InlinedApi") + private final static String[] FROM_COLUMNS = { + Build.VERSION.SDK_INT + >= Build.VERSION_CODES.HONEYCOMB ? + Contacts.DISPLAY_NAME_PRIMARY : + Contacts.DISPLAY_NAME + }; + /* + * Defines an array that contains resource ids for the layout views + * that get the Cursor column contents. The id is pre-defined in + * the Android framework, so it is prefaced with "android.R.id" + */ + private final static int[] TO_IDS = { + android.R.id.text1 + }; + // Define global mutable variables + // Define a ListView object + ListView mContactsList; + // Define variables for the contact the user selects + // The contact's _ID value + long mContactId; + // The contact's LOOKUP_KEY + String mContactKey; + // A content URI for the selected contact + Uri mContactUri; + // An adapter that binds the result Cursor to the ListView + private SimpleCursorAdapter mCursorAdapter; + ... +</pre> +<p class="note"> + <strong>Note:</strong> Since + {@link android.provider.ContactsContract.Contacts#DISPLAY_NAME_PRIMARY + Contacts.DISPLAY_NAME_PRIMARY} requires Android 3.0 (API version 11) or later, setting your + app's <code>minSdkVersion</code> to 10 or below generates an Android Lint warning in + Eclipse with ADK. To turn off this warning, add the annotation + <code>@SuppressLint("InlinedApi")</code> before the definition of <code>FROM_COLUMNS</code>. +</p> +<h3 id="InitializeFragment">Initialize the Fragment</h3> +<p> + + Initialize the {@link android.support.v4.app.Fragment}. Add the empty, public constructor + required by the Android system, and inflate the {@link android.support.v4.app.Fragment} object's + UI in the callback method {@link android.support.v4.app.Fragment#onCreateView onCreateView()}. + For example: +</p> +<pre> + // Empty public constructor, required by the system + public ContactsFragment() {} + + // A UI Fragment must inflate its View + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the fragment layout + return inflater.inflate(R.layout.contacts_list_layout, container, false); + } +</pre> +<h3 id="DefineAdapter">Set up the CursorAdapter for the ListView</h3> +<p> + Set up the {@link android.support.v4.widget.SimpleCursorAdapter} that binds the results of the + search to the {@link android.widget.ListView}. To get the {@link android.widget.ListView} object + that displays the contacts, you need to call {@link android.app.Activity#findViewById + Activity.findViewById()} using the parent activity of the + {@link android.support.v4.app.Fragment}. Use the {@link android.content.Context} of the + parent activity when you call {@link android.widget.ListView#setAdapter setAdapter()}. + For example: +</p> +<pre> + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + ... + // Gets the ListView from the View list of the parent activity + mContactsList = (ListView) getActivity().findViewById(R.layout.contact_list_view); + // Gets a CursorAdapter + mCursorAdapter = new SimpleCursorAdapter( + getActivity(), + R.layout.contact_list_item, + null, + FROM_COLUMNS, TO_IDS, + 0); + // Sets the adapter for the ListView + mContactsList.setAdapter(mCursorAdapter); + } +</pre> +<h3 id="SetListener">Set the selected contact listener</h3> +<p> + When you display the results of a search, you usually want to allow the user to select a + single contact for further processing. For example, when the user clicks a contact you can + display the contact's address on a map. To provide this feature, you first defined the current + {@link android.support.v4.app.Fragment} as the click listener by specifying that the class + implements {@link android.widget.AdapterView.OnItemClickListener}, as shown in the section + <a href="#Fragment">Define a Fragment that displays the list of contacts</a>. +</p> +<p> + To continue setting up the listener, bind it to the {@link android.widget.ListView} by + calling the method {@link android.widget.ListView#setOnItemClickListener + setOnItemClickListener()} in {@link android.support.v4.app.Fragment#onActivityCreated + onActivityCreated()}. For example: +</p> +<pre> + public void onActivityCreated(Bundle savedInstanceState) { + ... + // Set the item click listener to be the current fragment. + mContactsList.setOnItemClickListener(this); + ... + } +</pre> +<p> + Since you specified that the current {@link android.support.v4.app.Fragment} is the + {@link android.widget.AdapterView.OnItemClickListener OnItemClickListener} for the + {@link android.widget.ListView}, you now need to implement its required method + {@link android.widget.AdapterView.OnItemClickListener#onItemClick onItemClick()}, which + handles the click event. This is described in a succeeding section. +</p> +<h3 id="DefineProjection">Define a projection</h3> +<p> + Define a constant that contains the columns you want to return from your query. Each item in + the {@link android.widget.ListView} displays the contact's display name, + which contains the main form of the contact's name. In Android 3.0 (API version 11) and later, + the name of this column is + {@link android.provider.ContactsContract.Contacts#DISPLAY_NAME_PRIMARY + Contacts.DISPLAY_NAME_PRIMARY}; in versions previous to that, its name is + {@link android.provider.ContactsContract.Contacts#DISPLAY_NAME Contacts.DISPLAY_NAME}. +</p> +<p> + The column {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} is used by the + {@link android.support.v4.widget.SimpleCursorAdapter} binding process. + {@link android.provider.ContactsContract.Contacts#_ID Contacts._ID} and + {@link android.provider.ContactsContract.Contacts#LOOKUP_KEY} are used together to + construct a content URI for the contact the user selects. +</p> +<pre> +... +@SuppressLint("InlinedApi") +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 + + }; +</pre> +<h3 id="DefineConstants">Define constants for the Cursor column indexes</h3> +<p> + To get data from an individual column in a {@link android.database.Cursor}, you need + the column's index within the {@link android.database.Cursor}. You can define constants + for the indexes of the {@link android.database.Cursor} columns, because the indexes are + the same as the order of the column names in your projection. For example: +</p> +<pre> +// The column index for the _ID column +private static final int CONTACT_ID_INDEX = 0; +// The column index for the LOOKUP_KEY column +private static final int LOOKUP_KEY_INDEX = 1; +</pre> +<h3 id="SelectionCriteria">Specify the selection criteria</h3> +<p> + To specify the data you want, create a combination of text expressions and variables + that tell the provider the data columns to search and the values to find. +</p> +<p> + For the text expression, define a constant that lists the search columns. Although this + expression can contain values as well, the preferred practice is to represent the values with + a "?" placeholder. During retrieval, the placeholder is replaced with values from an + array. Using "?" as a placeholder ensures that the search specification is generated by binding + rather than by SQL compilation. This practice eliminates the possibility of malicious SQL + injection. For example: +</p> +<pre> + // Defines the text expression + @SuppressLint("InlinedApi") + private static final String SELECTION = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? + Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" : + Contacts.DISPLAY_NAME + " LIKE ?"; + // Defines a variable for the search string + private String mSearchString; + // Defines the array to hold values that replace the ? + private String[] mSelectionArgs = { mSearchString }; +</pre> +<h3 id="OnItemClick">Define the onItemClick() method</h3> +<p> + In a previous section, you set the item click listener for the {@link android.widget.ListView}. + Now implement the action for the listener by defining the method + {@link android.widget.AdapterView.OnItemClickListener#onItemClick + AdapterView.OnItemClickListener.onItemClick()}: +</p> +<pre> + @Override + public void onItemClick( + AdapterView<?> parent, View item, int position, long rowID) { + // Get the Cursor + Cursor cursor = parent.getAdapter().getCursor(); + // Move to the selected contact + cursor.moveToPosition(position); + // Get the _ID value + mContactId = getLong(CONTACT_ID_INDEX); + // Get the selected LOOKUP KEY + mContactKey = getString(CONTACT_KEY_INDEX); + // Create the contact's content Uri + mContactUri = Contacts.getLookupUri(mContactId, mContactKey); + /* + * You can use mContactUri as the content URI for retrieving + * the details for a contact. + */ + } +</pre> +<h3 id="InitializeLoader">Initialize the loader</h3> +<p> + Since you're using a {@link android.support.v4.content.CursorLoader} to retrieve data, + you must initialize the background thread and other variables that control asynchronous + retrieval. Do the initialization in + {@link android.support.v4.app.Fragment#onActivityCreated onActivityCreated()}, which + is invoked immediately before the {@link android.support.v4.app.Fragment} UI appears, as + shown in the following example: +</p> +<pre> +public class ContactsFragment extends Fragment implements + LoaderManager.LoaderCallbacks<Cursor> { + ... + // Called just before the Fragment displays its UI + @Override + public void onActivityCreated(Bundle savedInstanceState) { + // Always call the super method first + super.onActivityCreated(savedInstanceState); + ... + // Initializes the loader + getLoaderManager().initLoader(0, null, this); +</pre> +<h3 id="OnCreateLoader">Implement onCreateLoader()</h3> +<p> + Implement the method + {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}, + which is called by the loader framework immediately after you call + {@link android.support.v4.app.LoaderManager#initLoader initLoader()}. +<p> + In {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}, + set up the search string pattern. To make a string into a pattern, insert "%" + (percent) characters to represent a sequence of zero or more characters, or "_" (underscore) + characters to represent a single character, or both. For example, the pattern "%Jefferson%" + would match both "Thomas Jefferson" and "Jefferson Davis". +</p> +<p> + Return a new {@link android.support.v4.content.CursorLoader} from the method. For the content + URI, use {@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI}. + This URI refers to the entire table, as shown in the following example: +</p> +<pre> + ... + @Override + public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) { + /* + * Makes search string into pattern and + * stores it in the selection array + */ + mSelectionArgs[0] = "%" + mSearchString + "%"; + // Starts the query + return new CursorLoader( + getActivity(), + Contacts.CONTENT_URI, + PROJECTION, + SELECTION, + mSelectionArgs, + null + ); + } +</pre> +<h3 id="FinishedReset">Implement onLoadFinished() and onLoaderReset()</h3> +<p> + Implement the + {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} + method. The loader framework calls + {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} + when the Contacts Provider returns the results of the query. In this method, put the + result {@link android.database.Cursor} in the + {@link android.support.v4.widget.SimpleCursorAdapter}. This automatically updates the + {@link android.widget.ListView} with the search results: +</p> +<pre> + public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { + // Put the result Cursor in the adapter for the ListView + mCursorAdapter.swapCursor(cursor); + } +</pre> +<p> + The method {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onLoaderReset + onLoaderReset()} is invoked when the loader framework detects that the + result {@link android.database.Cursor} contains stale data. Delete the + {@link android.support.v4.widget.SimpleCursorAdapter} reference to the existing + {@link android.database.Cursor}. If you don't, the loader framework will not + recycle the {@link android.database.Cursor}, which causes a memory leak. For example: +</p> +<pre> + @Override + public void onLoaderReset(Loader<Cursor> loader) { + // Delete the reference to the existing Cursor + mCursorAdapter.swapCursor(null); + + } +</pre> + +<p> + You now have the key pieces of an app that matches a search string to contact names and returns + the result in a {@link android.widget.ListView}. The user can click a contact name to select it. + This triggers a listener, in which you can work further with the contact's data. For example, + you can retrieve the contact's details. To learn how to do this, continue with the next + lesson, <a href="#retrieve-details.html">Retrieving Details for a Contact</a>. +</p> +<p> + To learn more about search user interfaces, read the API guide + <a href="{@docRoot}guide/topics/search/search-dialog.html">Creating a Search Interface</a>. +</p> +<p> + The remaining sections in this lesson demonstrate other ways of finding contacts in the + Contacts Provider. +</p> +<h2 id="TypeMatch">Match a Contact By a Specific Type of Data</h2> +<p> + This technique allows you to specify the type of data you want to match. Retrieving + by name is a specific example of this type of query, but you can also do it for any of the types + of detail data associated with a contact. For example, you can retrieve contacts that have a + specific postal code; in this case, the search string has to match data stored in a postal code + row. +</p> +<p> + To implement this type of retrieval, first implement the following code, as listed in + previous sections: +</p> +<ul> + <li> + Request Permission to Read the Provider. + </li> + <li> + Define ListView and item layouts. + </li> + <li> + Define a Fragment that displays the list of contacts. + </li> + <li> + Define global variables. + </li> + <li> + Initialize the Fragment. + </li> + <li> + Set up the CursorAdapter for the ListView. + </li> + <li> + Set the selected contact listener. + </li> + <li> + Define constants for the Cursor column indexes. + <p> + Although you're retrieving data from a different table, the order of the columns in + the projection is the same, so you can use the same indexes for the Cursor. + </p> + </li> + <li> + Define the onItemClick() method. + </li> + <li> + Initialize the loader. + </li> + <li> + + Implement onLoadFinished() and onLoaderReset(). + </li> +</ul> +<p> + The following steps show you the additional code you need to match a search string to + a particular type of detail data and display the results. +</p> +<h3>Choose the data type and table</h3> +<p> + To search for a particular type of detail data, you have to know the custom MIME type value + for the data type. Each data type has a unique MIME type + value defined by a constant <code>CONTENT_ITEM_TYPE</code> in the subclass of + {@link android.provider.ContactsContract.CommonDataKinds} associated with the data type. + The subclasses have names that indicate their data type; for example, the subclass for email + data is {@link android.provider.ContactsContract.CommonDataKinds.Email}, and the custom MIME + type for email data is defined by the constant + {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE}. +</p> +<p> + Use the {@link android.provider.ContactsContract.Data} table for your search. All of the + constants you need for your projection, selection clause, and sort order are defined in or + inherited by this table. +</p> +<h3 id="SpecificProjection">Define a projection</h3> +<p> + To define a projection, choose one or more of the columns defined in + {@link android.provider.ContactsContract.Data} or the classes from which it inherits. The + Contacts Provider does an implicit join between {@link android.provider.ContactsContract.Data} + and other tables before it returns rows. For example: +</p> +<pre> + @SuppressLint("InlinedApi") + private static final String[] PROJECTION = + { + /* + * The detail data row ID. To make a ListView work, + * this column is required. + */ + Data._ID, + // The primary display name + Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? + Data.DISPLAY_NAME_PRIMARY : + Data.DISPLAY_NAME, + // The contact's _ID, to construct a content URI + Data.CONTACT_ID + // The contact's LOOKUP_KEY, to construct a content URI + Data.LOOKUP_KEY (a permanent link to the contact + }; +</pre> +<h3 id="SpecificCriteria">Define search criteria</h3> +<p> + To search for a string within a particular type of data, construct a selection clause from + the following: +</p> +<ul> + <li> + The name of the column that contains your search string. This name varies by data type, + so you need to find the subclass of + {@link android.provider.ContactsContract.CommonDataKinds} that corresponds to the data type + and then choose the column name from that subclass. For example, to search for + email addresses, use the column + {@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS Email.ADDRESS}. + </li> + <li> + The search string itself, represented as the "?" character in the selection clause. + </li> + <li> + The name of the column that contains the custom MIME type value. This name is always + {@link android.provider.ContactsContract.Data#MIMETYPE Data.MIMETYPE}. + </li> + <li> + The custom MIME type value for the data type. As described previously, this is the constant + <code>CONTENT_ITEM_TYPE</code> in the + {@link android.provider.ContactsContract.CommonDataKinds} subclass. For example, the MIME + type value for email data is + {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE}. Enclose the value in single quotes by concatenating a + "<code>'</code>" (single quote) character to the start and end of the constant; otherwise, + the provider interprets the value as a variable name rather than as a string value. + You don't need to use a placeholder for this value, because you're using a constant + rather than a user-supplied value. + </li> +</ul> +<p> + For example: +</p> +<pre> + /* + * Constructs search criteria from the search string + * and email MIME type + */ + private static final String SELECTION = + /* + * Searches for an email address + * that matches the search string + */ + Email.ADDRESS + " LIKE ? " + "AND " + + /* + * Searches for a MIME type that matches + * the value of the constant + * Email.CONTENT_ITEM_TYPE. Note the + * single quotes surrounding Email.CONTENT_ITEM_TYPE. + */ + Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'"; +</pre> +<p> + Next, define variables to contain the selection argument: +</p> +<pre> + String mSearchString; + String[] mSelectionArgs = { "" }; +</pre> +<h3 id="SpecificLoader">Implement onCreateLoader()</h3> +<p> + Now that you've specified the data you want and how to find it, define a query in your + implementation of {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader + onCreateLoader()}. Return a new {@link android.support.v4.content.CursorLoader} from this + method, using your projection, selection text expression, and selection array as + arguments. For a content URI, use + {@link android.provider.ContactsContract.Data#CONTENT_URI Data.CONTENT_URI}. For example: +</p> +<pre> + @Override + public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) { + // OPTIONAL: Makes search string into pattern + mSearchString = "%" + mSearchString + "%"; + // Puts the search string into the selection criteria + mSelectionArgs[0] = mSearchString; + // Starts the query + return new CursorLoader( + getActivity(), + Data.CONTENT_URI, + PROJECTION, + SELECTION, + mSelectionArgs, + null + ); + } +</pre> +<p> + These code snippets are the basis of a simple reverse lookup based on a specific type of detail + data. This is the best technique to use if your app focuses on a particular type of data, such + as emails, and you want allow users to get the names associated with a piece of data. +</p> +<h2 id="GeneralMatch">Match a Contact By Any Type of Data</h2> +<p> + Retrieving a contact based on any type of data returns contacts if any of their data matches a + the search string, including name, email address, postal address, phone number, and so forth. + This results in a broad set of search results. For example, if the search string + is "Doe", then searching for any data type returns the contact "John Doe"; it also returns + contacts who live on "Doe Street". +</p> +<p> + To implement this type of retrieval, first implement the following code, as listed in + previous sections: +</p> +<ul> + <li> + Request Permission to Read the Provider. + </li> + <li> + Define ListView and item layouts. + </li> + <li> + <li> + Define a Fragment that displays the list of contacts. + </li> + <li> + Define global variables. + </li> + <li> + Initialize the Fragment. + </li> + <li> + Set up the CursorAdapter for the ListView. + </li> + <li> + Set the selected contact listener. + </li> + <li> + Define a projection. + </li> + <li> + Define constants for the Cursor column indexes. + <p> + For this type of retrieval, you're using the same table you used in the section + <a href="#NameMatch">Match a Contact by Name and List the Results</a>. Use the + same column indexes as well. + </p> + </li> + <li> + Define the onItemClick() method. + </li> + <li> + Initialize the loader. + </li> + <li> + + Implement onLoadFinished() and onLoaderReset(). + </li> +</ul> +<p> + The following steps show you the additional code you need to match a search string to + any type of data and display the results. +</p> +<h3 id="NoSelection">Remove selection criteria</h3> +<p> + Don't define the <code>SELECTION</code> constants or the <code>mSelectionArgs</code> variable. + These aren't used in this type of retrieval. +</p> +<h3 id="CreateLoaderAny">Implement onCreateLoader()</h3> +<p> + Implement the {@link android.support.v4.app.LoaderManager.LoaderCallbacks#onCreateLoader + onCreateLoader()} method, returning a new {@link android.support.v4.content.CursorLoader}. + You don't need to convert the search string into a pattern, because the Contacts Provider does + that automatically. Use + {@link android.provider.ContactsContract.Contacts#CONTENT_FILTER_URI + Contacts.CONTENT_FILTER_URI} as the base URI, and append your search string to it by calling + {@link android.net.Uri#withAppendedPath Uri.withAppendedPath()}. Using this URI + automatically triggers searching for any data type, as shown in the following example: +</p> +<pre> + @Override + public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) { + /* + * Appends the search string to the base URI. Always + * encode search strings to ensure they're in proper + * format. + */ + Uri contentUri = Uri.withAppendedPath( + Contacts.CONTENT_FILTER_URI, + Uri.encode(mSearchString)); + // Starts the query + return new CursorLoader( + getActivity(), + contentUri, + PROJECTION, + null, + null, + null + ); + } +</pre> +<p> + These code snippets are the basis of an app that does a broad search of the Contacts Provider. + The technique is useful for apps that want to implement functionality similar to the + People app's contact list screen. +</p> diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs index 985fc44..7a3f2ca 100644 --- a/docs/html/training/training_toc.cs +++ b/docs/html/training/training_toc.cs @@ -484,7 +484,37 @@ </a> </div> <ul> - + <li class="nav-section"> + <div class="nav-section-header"> + <a href="<?cs var:toroot ?>training/contacts-provider/index.html" + description= + "How to use Android's central address book, the Contacts Provider, to + display contacts and their details and modify contact information."> + Accessing Contacts Data</a> + </div> + <ul> + <li> + <a href="<?cs var:toroot ?>training/contacts-provider/retrieve-names.html"> + Retrieving a List of Contacts + </a> + </li> + <li> + <a href="<?cs var:toroot ?>training/contacts-provider/retrieve-details.html"> + Retrieving Details for a Contact + </a> + </li> + <li> + <a href="<?cs var:toroot ?>training/contacts-provider/modify-data.html"> + Modifying Contacts Using Intents + </a> + </li> + <li> + <a href="<?cs var:toroot ?>training/contacts-provider/display-contact-badge.html"> + Displaying the Quick Contact Badge + </a> + </li> + </ul> + </li> <li class="nav-section"> <div class="nav-section-header"> <a href="<?cs var:toroot ?>training/id-auth/index.html" @@ -879,7 +909,7 @@ </ul> </li> </ul> - </li> <!-- end of User Input --> + </li> <!-- end of User Input --> <li class="nav-section"> <div class="nav-section-header"> |