One of the application I am developing for Android had a functionality to import Google contacts. I faced quite a few issues to implement this and was not able to find any tutorial / example on internet providing detailed information.
Now that I’ve implemented this feature, thought of sharing my knowledge with the community with this blog post. Hope this is helpful.
Important Links –
Google Contact API Documentation – https://developers.google.com/google-apps/contacts/
Google Developer Console – https://console.developers.google.com/
GData Java Client – https://code.google.com/p/gdata-java-client/downloads/
Guava Libraries – https://code.google.com/p/guava-libraries/
Contact API Scope – https://www.googleapis.com/auth/contacts.readonly
Google Project Setup – First of all, you will have to register your project with Google. To do so –
1) Login to Google Developer Console https://console.developers.google.com/project/ and create new Project.
2) Once project is created, we will have to generate identifiers for OAuth 2.0 authentication. Navigate to ‘APIS & AUTH’ > ‘Credentials’ and click on ‘Create new Client ID’.
3) From Create Client ID Popup, select Application Type as ‘Installed Application’ and Select ‘Other’ for ‘Installed Application Type’ and click ‘Create Client ID’ button.
4) This should generate the Client ID, Client Secret and Redirect URL for native application. These details will be required for authentication.
5) Now it’s time to enable Contact API for this project. Navigate to ‘APIS & AUTH’ > ‘APIs’, search for ‘Contacts API’ and Click ‘OFF’ button. This should enable Contacts API.
Authorizing request using OAuth 2.0
Typical flow for OAuth 2.0 authentication is –
1) Application makes request to Google to fetch contacts (Scope)
2) Google shows OAuth Dialog to users for their approval
3) Once user provides approval, a short lived ‘Access Token’ is sent to requesting application
4) Application then makes request to get contacts using this Access Token
Now lets look at the actual code to authorize and fetch contacts details from an Android application.
We will create an Activity which will show OAuth window to users using WebView and once user permission is granted, we will make a call to fetch Google Contacts.
Lets start with Layout –
1) activity_import_gmail_contacts.xml – Create a layout which will be used by our Activity
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.tusharwadekar.android.ttmm.ImportGmailContactsActivity" > <Button android:id="@+id/importContactButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:background="#33B5E5" android:text="@string/importContactButton" /> <ListView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:paddingBottom="5dp" android:layout_above="@id/importContactButton" /> </RelativeLayout>
2) auth_dialog.xml – Create a layout which will be used to display Google OAuth window
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <WebView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/webv"/> </LinearLayout>
Create a class GetAccessToken. This class will be used to parse Access Token received from Google.
package com.tusharwadekar.android.ttmm.oauth; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.json.JSONException; import org.json.JSONObject; import android.util.Log; public class GetAccessToken { static InputStream is = null; static JSONObject jObj = null; static String json = ""; public GetAccessToken() { } List<NameValuePair> params = new ArrayList<NameValuePair>(); Map<String, String> mapn; DefaultHttpClient httpClient; HttpPost httpPost; public JSONObject gettoken(String address,String token,String client_id,String client_secret,String redirect_uri,String grant_type) { // Making HTTP request try { // DefaultHttpClient httpClient = new DefaultHttpClient(); httpPost = new HttpPost(address); params.add(new BasicNameValuePair("code", token)); params.add(new BasicNameValuePair("client_id", client_id)); params.add(new BasicNameValuePair("client_secret", client_secret)); params.add(new BasicNameValuePair("redirect_uri", redirect_uri)); params.add(new BasicNameValuePair("grant_type", grant_type)); httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); httpPost.setEntity(new UrlEncodedFormEntity(params)); HttpResponse httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); is = httpEntity.getContent(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { BufferedReader reader = new BufferedReader(new InputStreamReader( is, "iso-8859-1"), 8); StringBuilder sb = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } is.close(); json = sb.toString(); Log.e("JSONStr", json); } catch (Exception e) { e.getMessage(); Log.e("Buffer Error", "Error converting result " + e.toString()); } // Parse the String to a JSON Object try { jObj = new JSONObject(json); } catch (JSONException e) { Log.e("JSON Parser", "Error parsing data " + e.toString()); } // Return JSON String return jObj; } }
Create a class GoogleConstants. This class will hold all the constants required for Authentication and fetching Google Contacts.
package com.tusharwadekar.android.ttmm.util; public class GoogleConstants { public static String CLIENT_ID = ""; // Use your own client id public static String CLIENT_SECRET = ""; // Use your own client secret public static String REDIRECT_URI = "http://localhost"; public static String GRANT_TYPE = "authorization_code"; public static String TOKEN_URL = "https://accounts.google.com/o/oauth2/token"; public static String OAUTH_URL = "https://accounts.google.com/o/oauth2/auth"; public static String OAUTH_SCOPE = "https://www.googleapis.com/auth/contacts.readonly"; public static final String CONTACTS_URL = "https://www.google.com/m8/feeds/contacts/default/full"; public static final int MAX_NB_CONTACTS = 1000; public static final String APP = ""; }
Now lets create an activity ImportGmailContactsActivity.
package com.tusharwadekar.android.ttmm; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.json.JSONException; import org.json.JSONObject; import android.app.Dialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.util.SparseBooleanArray; import android.view.View; import android.view.View.OnClickListener; import android.webkit.WebView; import android.webkit.WebViewClient; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ListView; import android.widget.Toast; import com.google.gdata.client.contacts.ContactsService; import com.google.gdata.data.contacts.ContactEntry; import com.google.gdata.data.contacts.ContactFeed; import com.google.gdata.data.extensions.Email; import com.google.gdata.data.extensions.Name; import com.tusharwadekar.android.ttmm.model.Contact; import com.tusharwadekar.android.ttmm.oauth.GetAccessToken; import com.tusharwadekar.android.ttmm.util.ConstantUtil; import com.tusharwadekar.android.ttmm.util.GoogleConstants; public class ImportGmailContactsActivity extends BaseActivity { final String TAG = getClass().getName(); private Dialog auth_dialog; private ListView list; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_import_gmail_contacts); list = (ListView) findViewById(R.id.list); launchAuthDialog(); } private void setContactList(List<Contact> contacts) { ArrayAdapter<Contact> adapter = new ArrayAdapter<Contact>(this, android.R.layout.simple_list_item_multiple_choice, contacts); list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); list.setAdapter(adapter); } private void launchAuthDialog() { final Context context = this; auth_dialog = new Dialog(context); auth_dialog.setTitle(getString(R.string.web_view_title)); auth_dialog.setCancelable(true); auth_dialog.setContentView(R.layout.auth_dialog); auth_dialog.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { finish(); } }); WebView web = (WebView) auth_dialog.findViewById(R.id.webv); web.getSettings().setJavaScriptEnabled(true); web.loadUrl(GoogleConstants.OAUTH_URL + "?redirect_uri=" + GoogleConstants.REDIRECT_URI + "&response_type=code&client_id=" + GoogleConstants.CLIENT_ID + "&scope=" + GoogleConstants.OAUTH_SCOPE); web.setWebViewClient(new WebViewClient() { boolean authComplete = false; @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { super.onPageStarted(view, url, favicon); } @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); if (url.contains("?code=") && authComplete != true) { Uri uri = Uri.parse(url); String authCode = uri.getQueryParameter("code"); authComplete = true; auth_dialog.dismiss(); new GoogleAuthToken(context).execute(authCode); } else if (url.contains("error=access_denied")) { Log.i("", "ACCESS_DENIED_HERE"); authComplete = true; auth_dialog.dismiss(); } } }); auth_dialog.show(); } private class GoogleAuthToken extends AsyncTask<String, String, JSONObject> { private ProgressDialog pDialog; private Context context; public GoogleAuthToken(Context context) { this.context = context; } @Override protected void onPreExecute() { super.onPreExecute(); pDialog = new ProgressDialog(context); pDialog.setMessage("Contacting Google ..."); pDialog.setIndeterminate(false); pDialog.setCancelable(true); pDialog.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { finish(); } }); pDialog.show(); } @Override protected JSONObject doInBackground(String... args) { String authCode = args[0]; GetAccessToken jParser = new GetAccessToken(); JSONObject json = jParser.gettoken(GoogleConstants.TOKEN_URL, authCode, GoogleConstants.CLIENT_ID, GoogleConstants.CLIENT_SECRET, GoogleConstants.REDIRECT_URI, GoogleConstants.GRANT_TYPE); return json; } @Override protected void onPostExecute(JSONObject json) { pDialog.dismiss(); if (json != null) { try { String tok = json.getString("access_token"); String expire = json.getString("expires_in"); String refresh = json.getString("refresh_token"); new GetGoogleContacts(context).execute(tok); } catch (JSONException e) { e.printStackTrace(); } } } } private class GetGoogleContacts extends AsyncTask<String, String, List<ContactEntry>> { private ProgressDialog pDialog; private Context context; public GetGoogleContacts(Context context) { this.context = context; } @Override protected void onPreExecute() { super.onPreExecute(); pDialog = new ProgressDialog(context); pDialog.setMessage("Authenticated. Getting Google Contacts ..."); pDialog.setIndeterminate(false); pDialog.setCancelable(true); pDialog.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { finish(); } }); pDialog.show(); } @Override protected List<ContactEntry> doInBackground(String... args) { String accessToken = args[0]; ContactsService contactsService = new ContactsService( GoogleConstants.APP); contactsService.setHeader("Authorization", "Bearer " + accessToken); contactsService.setHeader("GData-Version", "3.0"); List<ContactEntry> contactEntries = null; try { URL feedUrl = new URL(GoogleConstants.CONTACTS_URL); ContactFeed resultFeed = contactsService.getFeed(feedUrl, ContactFeed.class); contactEntries = resultFeed.getEntries(); } catch (Exception e) { pDialog.dismiss(); Toast.makeText(context, "Failed to get Contacts", Toast.LENGTH_SHORT).show(); } return contactEntries; } @Override protected void onPostExecute(List<ContactEntry> googleContacts) { if (null != googleContacts && googleContacts.size() > 0) { List<Contact> contacts = new ArrayList<Contact>(); for (ContactEntry contactEntry : googleContacts) { String name = ""; String email = ""; if (contactEntry.hasName()) { Name tmpName = contactEntry.getName(); if (tmpName.hasFullName()) { name = tmpName.getFullName().getValue(); } else { if (tmpName.hasGivenName()) { name = tmpName.getGivenName().getValue(); if (tmpName.getGivenName().hasYomi()) { name += " (" + tmpName.getGivenName().getYomi() + ")"; } if (tmpName.hasFamilyName()) { name += tmpName.getFamilyName().getValue(); if (tmpName.getFamilyName().hasYomi()) { name += " (" + tmpName.getFamilyName() .getYomi() + ")"; } } } } } List<Email> emails = contactEntry.getEmailAddresses(); if (null != emails && emails.size() > 0) { Email tempEmail = (Email) emails.get(0); email = tempEmail.getAddress(); } Contact contact = new Contact(name, email, ConstantUtil.CONTACT_TYPE_GOOGLE); contacts.add(contact); } setContactList(contacts); } else { Log.e(TAG, "No Contact Found."); Toast.makeText(context, "No Contact Found.", Toast.LENGTH_SHORT) .show(); } pDialog.dismiss(); } } }
This activity does following tasks –
1) Once activity is create, launchAuthDialog() is called. This method is responsible to show Auth Dialog.
2) Have a look at loadUrl. Here we are passing following key information –
OAuth URL – https://accounts.google.com/o/oauth2/auth
Redirect URL – http://localhost
Client ID – Your Project ID which was created while project setup.
Scope – Here we are using Contacts API so scope is – https://www.google.com/m8/feeds/contacts/default/full
web.loadUrl(GoogleConstants.OAUTH_URL + “?redirect_uri=”
+ GoogleConstants.REDIRECT_URI
+ “&response_type=code&client_id=” + GoogleConstants.CLIENT_ID
+ “&scope=” + GoogleConstants.OAUTH_SCOPE);
2) onPageFinished is called when user provides approval or denies the request.
3) If user provides approval, a special Auth Code is sent back from Google. We make a request to get Access Token from Google using this Auth Code. Here class GoogleAuthToken comes in picture.
Have a look at doInBackground method. Here we are making a request to get Access Token from Google using class GetAccessToken.
Token URL – https://accounts.google.com/o/oauth2/token
Auth Code – Which is received from Google when user approves permission in Google OAuth Dialog
Client ID – Your Project ID which was created in project setup.
Client Secret – Your Project Secret which was created in project setup.
Redirect URL – http://localhost
Grant Type – authorization_code
Once response from Google is received, a JSON object having access_token is received in onPostExecute callback.
4) Now that we have Access Token, we can make a request to get Contacts. Here class GetGoogleContacts comes in picture.
Have a look at doInBackground method, here we are creating an instance of ContactService. We are passing Access Token in Request Header.
Next we create instance of ContactFeed which will be used to get list of ContactEntry
Once response is received, onPostExecute method is called and list of ContactEntry is passed to the same. That’s it… each ContactEntry object will have Google contact details.
Apologies for late update but here is the Contact class as many have requested.
package com.tusharwadekar.android.ttmm.model; public class Contact { private int id; private String name; private String email; public Contact(){} public Contact(String name, String email) { super(); this.name = name; this.email = email; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } @Override public String toString() { //return "Contact [id=" + id + ", name=" + name + ", email=" + email + "]"; return name; } }