dcsimg
 

Accessing Contacts Data on Android Devices

Wednesday Feb 28th 2018 by Chunyen Liu

Learn how to utilize Google's contacts data to our software's benefit.

You are already making use of Google's contacts data everywhere on a daily basis; for example, on mobile phones, tablets, desktop computers, laptops, and so on. In Google's ecosystem, many of its software and hardware applications get access to the centralized contacts data storage. This practice increases data inter-operability, integration, management, usability, and many others. Of course, it also poses concerns of data security and privacy for individuals. Those are way beyond our short tutorial's scope, so we are going to only focus on how to utilize the contacts data to our software's benefit.

We'll start by pointing out some of the most common places connecting with the contacts data. Then, some key Android-specific terminologies and programming objects are introduced. Some preparation work needs to be done before accessing the data. Finally, we will provide a couple of examples to walk through the process of fetching a contact and its details. Once you get the hang of this, it will make a powerful addition to your app features.

Contacts Data in Google's Ecosystem

Google has been relying on account and contacts data all over the place for many good and some probably bad reasons, from a user's perspective. Modifying the contacts data through any of these applications will be synced up automatically if not done immediately. Because you are browsing this tutorial, it means you should have an Android mobile device already that links to a Gmail account. A drop-down menu arrow symbol right by the Gmail title will take you to the Web-based Google contacts data page, as shown in Figures 1 and 2. Lots of info can be entered into a contact, including photos and links.

Contacts through Gmail
Figure 1: Contacts through Gmail

Contacts on Chrome
Figure 2: Contacts on Chrome

On your Android mobile devices, as in Figure 3, you can find a stand-alone app, named "Contacts," which basically is the equivalent of the Web-based version. You also can connect with contacts data through your phone calling app as well as from other apps requesting the contacts data. As mentioned previously, once you modify the contacts data in one app and push the results back, other apps will subsequently get the same data because it is at a central repository. This makes your life easier without the need of updating all the contacts data in different apps.

Contacts on Android Devices
Figure 3: Contacts on Android Devices

Related Android Objects and Definitions

Within this short tutorial, we are trying to focus on the core utilities of accessing contacts data on Android devices. There are some terminologies used in Android APIs that we should at least need to have a basic knowledge of. They are the following:

  • ContentProvider: This is a small SQLite database that manages data interaction. We are interested in the specific Contacts Provider type dealing with data about people.
  • ContentResolver: This object communicates with the provider object and provides applications access to the content model.
  • CONTENT_URI: This identifies data in a provider. It includes the symbolic name of the entire provider and a name that points to a table.
  • ContactsContract: This is the contract between the contacts provider and applications. It contains definitions for the supported URIs and columns.
  • Cursor: When a database query returns a result, this is the interface providing random read-write access.
  • CursorLoader: This is used to run an asynchronous query in the background.
  • CursorAdapter: This a data adapter that binds cursor to a UI widget; for instance, a ListView.
  • Intents: The intent allowing you to start the activity for selecting a contact is Intent.ACTION_PICK with the MIME type Contacts.CONTENT_TYPE. Of course, there are also intents for you to view, edit, or insert contacts data.

Permissions and Initial Setup

In your AndroidManifest.xml file, you need to request the permission to read or write contacts data before doing any search, lookup, or update. If you run into permission issues, I suggest you dynamically request the permissions, as in another tutorial, "Working with a Camera in Android."

<uses-permission android:name=
   "android.permission.READ_CONTACTS" />
<uses-permission android:name=
   "android.permission.WRITE_CONTACTS" />

To simplify the process as well as mainly focus on how to access the contacts provider, we intentionally leave out the usage of CursorLoader for asynchronous queries and CursorAdapter for user interface integration. Instead, only a simple scrollable text view for the layout is used to display the result, as demonstrated in Listing 1.

<ScrollView xmlns:android="http://schemas.android.com/apk/
      res/android"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   android:scrollbars="none" >

   <LinearLayout xmlns:android="http://schemas.android.com/apk/
         res/android"
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:orientation="vertical">

      <TextView
         android:id="@+id/tvbyintent"
         android:textSize="18sp"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
   </LinearLayout>
</ScrollView>

Listing 1: Layout for Results

Fetching a Contact Through an Android Intent

In Listing 2, we start by using the system intent Intent.ACTION_PICK along with the MIME type ContactsContract.Contacts.CONTENT_URI to start the activity for picking a contact and capturing the result in onActivityResult(). The selected contact ID is extracted from the ending portion of the result.

Inside getContactName(), we set up the query for ContentResolver by a selection with ContactsContract.Data.CONTACT_ID, an empty projection, and an empty sorting order. The query result is accessible in cursor. Because we are interested only in the contact name, we then use ContactsContract.Data.DISPLAY_NAME to figure out which column in the cursor has the right info.

We also take this opportunity to get a complete list of all the available columns' names and their data for illustration purpose. You can see this in Listing 2. Figure 4 shows our sample result.

public class ContactByIntent extends Activity {
   private static final int CONTACT_PICKER_RESULT = 1001;
   private TextView mResult;
   private String mContactName = "";
   private String mAllCols = "";

   @Override
   public void onCreate(Bundle icicle) {
      super.onCreate(icicle);

      setContentView(R.layout.byintent);
      mResult = (TextView)findViewById(R.id.tvbyintent);

      // Start the intent of fetch a contact
      Intent contactPickerIntent = new Intent(Intent.ACTION_PICK,
         ContactsContract.Contacts.CONTENT_URI);
      startActivityForResult(contactPickerIntent,
         CONTACT_PICKER_RESULT);
   }

   @Override
   protected void onActivityResult(int requestCode, int resultCode,
         Intent data) {
      if (resultCode == RESULT_OK) {
         switch (requestCode) {
            case CONTACT_PICKER_RESULT:
               try {
                  Uri result = data.getData();
                  String id = result.getLastPathSegment();

                  getContactName(id);

                  mResult.setText("Selected Name: \""
                     + mContactName + "\"\n" + mAllCols);
               } catch (Exception e) {
                  Log.e(TutorialOnContacts.TAG, "Error parsing
                     contacts", e);
               }
               break;
         }
      } else {
         Log.e(TutorialOnContacts.TAG, "Error reading contacts");
      }
   }

   private void getContactName(String id) {
      Cursor cursor = null;
      String result = "";

      try {
         cursor = getContentResolver().query(ContactsContract
               .Data.CONTENT_URI,
            null,
            ContactsContract.Data.CONTACT_ID + "=?",
            new String[] { id },
            null);

         if (cursor.moveToFirst()) {
            result = cursor.getString(cursor.getColumnIndex
               (ContactsContract.Data.DISPLAY_NAME));

            // Get all columns
            mAllCols = "";
            String columns[] = cursor.getColumnNames();
            for (String column : columns) {
               int index = cursor.getColumnIndex(column);
               mAllCols += ("(" + index + ") [" + column + "] = ["
                  + cursor.getString(index) + "]\n");
               }

               cursor.close();
         }
      } catch (Exception e) {
         Log.e(TutorialOnContacts.TAG, "Error reading
            contacts", e);
      }

      mContactName = result;
   }

   @Override
   protected void onResume() {
      super.onResume();
   }

   @Override
   protected void onPause() {
      super.onPause();
   }

   @Override
   protected void onStop() {
      super.onStop();
   }
}

Listing 2: Picking a Contact and Showing All Columns

Result with Selected Contact and All Columns
Figure 4: Result with Selected Contact and All Columns

Fetching a Contact's Details

In Listing 3, our goal is to demonstrate how to retrieve a contact's details from the organization group. There are several groups defined in ContactsContract.CommonDataKinds and their ContactsContract.Data.MIMETYPE types need to be specified for the details. See the complete group listing in the reference link.

In the updated example, we added getContactCompanyJob(). The query is set up for ContentResolver by a selection with ContactsContract.Data.CONTACT_ID plus ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE as its ContactsContract.Data.MIMETYPE, an empty projection, and an empty sorting order.

Finally, we want to retrieve from the result cursor the columns of interest in ContactsContract.CommonDataKinds.Organization.COMPANY and ContactsContract.CommonDataKinds.Organization.TITLE. The sample result is in Figure 5.

public class ContactDetails extends Activity {
   private static final int CONTACT_PICKER_RESULT = 1001;
   private TextView mResult;
   private String mContactName = "";
   private String mContactCompany = "";
   private String mContactJob = "";

   @Override
   public void onCreate(Bundle icicle) {
      super.onCreate(icicle);

      setContentView(R.layout.details);
      mResult = (TextView)findViewById(R.id.tvdetails);
      ...
   }

   @Override
   protected void onActivityResult(int requestCode,
         int resultCode, Intent data) {
      if (resultCode == RESULT_OK) {
         switch (requestCode) {
            case CONTACT_PICKER_RESULT:
               try {
                  Uri result = data.getData();
                  String id = result.getLastPathSegment();

                  getContactName(id);
                  getContactCompanyJob(id);

                  mResult.setText("\n\n\nSelected Name: \"" +
                     mContactName + "\"\n" +
                     "Company: \"" + mContactCompany + "\"\n" +
                     "Title: \"" + mContactJob + "\"\n");
               } catch (Exception e) {
                     Log.e(TutorialOnContacts.TAG, "Error parsing
                        contacts", e);
               }
               break;
            }
     } else {
        Log.e(TutorialOnContacts.TAG, "Error reading contacts");
     }
   }
   ...

   // Get specific details from organization
   private void getContactCompanyJob(String id) {
      Cursor cursor = null;
      String company = "";
      String title = "";

      try {
         cursor = getContentResolver().query(ContactsContract
               .Data.CONTENT_URI,
            null,
            ContactsContract.Data.CONTACT_ID + "=? AND " +
               ContactsContract.Data.MIMETYPE + "=?",
            new String[] { id, ContactsContract.CommonDataKinds
               .Organization.CONTENT_ITEM_TYPE },
            null);

         if (cursor.moveToFirst()) {
            company = cursor.getString(cursor.getColumnIndex
               (ContactsContract.CommonDataKinds.Organization
                  .COMPANY));
            title = cursor.getString(cursor.getColumnIndex
               (ContactsContract.CommonDataKinds.Organization
                  .TITLE));

            cursor.close();
         }
      } catch (Exception e) {
         Log.e(TutorialOnContacts.TAG, "Error reading
            contacts", e);
      }

      mContactCompany = company;
      mContactJob = title;
  }
  ...
}

Listing 3: Fetching a Contact's Details

Contact's Sample Details
Figure 5: Contact's Sample Details

Conclusion

In Android, several APIs are made available for developers to get access to Google's contacts data through simple transactions. Once you are able to communicate with Contacts Provider, the possibilities are huge as far as expanding your app's capabilities is concerned. You easily can do format conversion and present the way you want the data to be displayed through your personalized user interface.

In this tutorial, we covered some basic terminologies, programming objects, and functionalities to access the contacts data. It should be good enough to get you going in the right direction. The complete sources are available for download in the references section.

References

About the Author

Author Chunyen Liu has been a software veteran in Taiwan and the United States. He is a published author of 40+ articles and 100+ tiny apps, a software patentee, technical reviewer, and programming contest winner by ACM/IBM/SUN. He holds advanced degrees in Computer Science with 20+ graduate-level classes. On the non-technical side, he is enthusiastic about the Olympic sport of table tennis, being a USA certified umpire, certified coach, certified referee, and categorized event winner at State Games and the US Open.
Home
Mobile Site | Full Site