Using Google Play services for OAuth 2.0 Authorization

OAuth Tutorial LogoIf you own and use an Android phone there is a high probability that you also use one or more of Google’s services, such as Gmail, Google + or even Google Calendar. This tutorial is going to examine how to use Google Play services to authorize an application to access your Google user profile information. Although this tutorial is limited to only  accessing the user profile information this process would be same for accessing other Google APIs such as Calendar and Drive.

Why would you want to do this?
If you are developing an Android application you may want your app to interact with some of the user data that is already stored on an existing Google Services. For example you may want to create a new calendar application, but rather than asking the user to re enter that information into your application it would be easier and quicker for the user to authorize you to get the information directly from the Google Calendar API, additionally the changes made on your app can be synced with their Google Calendar.

 

High level Overview of the Authorization process.

Auth_process

Note: the user will be prompted to allow the application access before the authorization process happens.

Below are the various steps that will be outlined in this tutorial.

  1. Verify that Google Services are available and up to date on the device.
  2. Ensure an active Internet connection is available.
  3. Use Android Accountpicker to select or create a Google Account.
  4. Request an access token using GoogleAuthUtil
  5. Handling access token errors
  6. Using token to request user profile information via HTTP Request.

For this tutorial Android Studio will be used. This tutorial assumes that Android Studio is installed and configured on your development machine and while I will try to explain each step as much as possible, I am going to assume you have some basic programming knowledge with Android.

Set up new Android Project

Create a new Android Project

SetupProject1

 

In my example I named it “GoogleAuthTutorial”, you can name yours whatever you like. Google Play Services (which is required) runs on Android 2.3 or higher, so make sure that the Minimum Required SDK is API 9: Android 2.3 (Gingerbread) or higher.

Click Next

SetupProject2

Select Blank Activity

 

SetupProject3

Choose an Activity and Layout name, in my example I used GoogleAuth and activity_google_auth.

Click Finish and Android Studio will begin building the Project.

The new Project should now have some skeleton code for the following methods

  • onCreate(Bundle savedInstance)
  • onCreateOptionsMenu(Men Menu)
  • onOptionsItemsSelected(MenuItem item)

and the activity file should be something like the following

package com.shawnbe.googleauthtutorial.googleauthtutorial;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

public class GoogleAuth extends ActionBarActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_google_auth);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.google_auth, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

Ensure you have google play services SDK installed.

Google Play services is needed for this tutorial, so ensure that it is setup on your development computer. To do this launch your SDK Manager, (in Android Studio, this is located Tools > Android > SDK Manager)

installGoogleplay1

 

When the  Android SDK Manager Window opens scroll to the end of the Packages list and check the “Extras” Folder ensure that Google Play Services as well as Google Repository is installed, if not select the checkboxes and click on the “Install Packages” button to install them.

installGoogleplay2

 

Set up Project to Use Google Play Services

Now that the Google Play services SDK is downloaded and installed on your development computer, we need to configure the project to use it.

Open the build.gradle file which is located at <Project>/Module/build.gradle, in the case of my project this would be GoogleAuthTutorial / GoogleAuthTutorial/build.gradle.

It is very important to note that there are two (2) build.gradle files, one is located in the main project root and the other in the project module folder. Ensure the latter one (in the project module folder) is used. The picture below shows the correct one to use outlined by the green box.

SetupGooglePlay1

 

In the build.gradle file look for the section with dependencies (show below)

dependencies {
    compile 'com.android.support:appcompat-v7:19.+'
    compile fileTree(dir: 'libs', include: ['*.jar'])
}

and add the following line of code

compile 'com.google.android.gms:play-services:4.3.23'

The block of code should now be

dependencies {
    compile 'com.android.support:appcompat-v7:19.+'
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.android.gms:play-services:4.3.23'
}

Note: that 4.3.23 is the version available at the time of this tutorial, it may be different at the time you are reading this.

Save the changes and Sync the Project with Gradle Files by clicking on the button on the toolbar shown below highlighted by red circle.

SetupGooglePlay3

Open the AndroidManifest.xml file, located at

<Project>/<Module>/src/main/AndroidManifest.xml

Based on the names I have used, for my Project it would be located in

GoogleAuthTutorial / GoogleAuthTutorial / src / main / AndroidManifest.xml

and add the following code

<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />

within the element.
Also add the following three lines of code before the element to grant the necessary permissions required to run the application.

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

The first permission, (Internet) is required since the application needs the get the access token from Google’s servers over the internet.
The second permission (Get Accounts) is required to allow the application to select an account from which the user profile information will be gathered.
The last permission (Access network state) is used to check the current network state to ensure that an active internet connection is available.

The AndroidManifest.xml file should now be

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.shawnbe.googleauthtutorial.googleauthtutorial" >

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <meta-data android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
        <activity
            android:name="com.shawnbe.googleauthtutorial.googleauthtutorial.GoogleAuth"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

The project is now set up and the coding can begin.

Create a basic User Interface

A basic user interface will be used to provide some feedback to the user during the various steps outlined at the beginning of the tutorial <here> link.

This is the basic layout.

createBasicUI1

 

Note: Not shown here, but I have decided to include a “Restart” button.

To create the layout, open the layout xml file (activity_goolge_auth.xml in my example) which is located in the GoogleAuthTutorial / GoogleAuthTutorial / src / res / layout folder

createBasicUI2

There should be a RelativeLayout root element with a HelloWorld TextView, if not do not worry about it because the file will be completely changed anyway. Replace the existing xml code with the following

 

<LinearLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.shawnbe.googleauthtutorial.googleauthtutorial.GoogleAuth">

    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent">

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:orientation="vertical">
            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/activity_vertical_margin"
                android:id="@+id/googlePlayServicesTextView"/>

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/activity_vertical_margin"
                android:id="@+id/selectedAccountTextView"/>

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/activity_vertical_margin"
                android:id="@+id/networkConnectionTextView"/>

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="@dimen/activity_vertical_margin"
                android:scrollHorizontally="false"
                android:id="@+id/accessTokenTextView"/>

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:id="@+id/statusTextView"
                android:padding="@dimen/activity_vertical_margin"
                android:layout_marginTop="@dimen/activity_vertical_margin"
                android:layout_marginBottom="@dimen/activity_vertical_margin"
                android:background="#E6E6E6"/>

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:id="@+id/nameTextView"/>

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:id="@+id/familyNameTextView"/>

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:id="@+id/givenNameTextView"/>

            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:id="@+id/genderTextView"/>

            <LinearLayout
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:paddingTop="@dimen/activity_vertical_margin">
                <ImageView
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:scaleType="fitStart"
                    android:id="@+id/userImageView"
                    android:contentDescription="User Profile Image"
                    android:layout_weight="1"/>
                <LinearLayout
                    android:layout_width="fill_parent"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:gravity="right"
                    android:orientation="horizontal">
                    <Button
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:onClick="restart"
                        android:text="Restart"/>
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

Note: the above code has some values that are hard coded and this is not the best practice, but this is done for simplicity in this lesson.

The basic layout of the User Interface has been set up.

Step 1: Verify that Google Play services is available on the device.

Open the project’s main activity, in this example it is GoogleAuth.java located at GoogleAuthTutorial / GoogleAuthTutorial / src / main / java / com.shawnbe.googleauthtutorial.googleauthtutorial/GoogleAuth.java

step1image1

This path may be different depending on what you choose to name your project, module and activity.

The Google Play Services library includes some methods that make coding a lot easier, one such method is isGooglePlayServicesAvailable(Context context). This method verifies that Google Play services is installed, enabled and is not older than the required version.

If all of the above criteria is met it returns an integer value indicating success, if not it returns an integer indicating that there is a problem. The integer is a represented by a ConnectionResult error code.

In addition to verifying Google play services the library also contains another method that will display a dialog and some further instructions if GooglePlayServices is not available. This method is getErrorDialog (int errorCode, Activity activity, int requestCode).

In order to use these methods the required classes needs to be imported.

At the top of the java activity there are some import statements, under those statements add the following 4 new statements

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesUtil;
import android.widget.TextView;
import android.view.View;

We are going to create a new method that does a check to verify if Google Play services are available and returns true if it is and false if it is not. For the sake of simplicity we will simply notify the user via the UI if there is a problem.

Create the new method below in the Activity file (GoogleAuth.java)

public boolean isGoogleServicesAvailable(){
   int result = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
   TextView googlePlayServicesTextView = (TextView) findViewById(R.id.googlePlayServicesTextView);
   if(result == ConnectionResult.SUCCESS){
      //Google Play services installed, enabled and available
      googlePlayServicesTextView.setText("Google Play Services is available");
      return true;
    }
    else{
       //Google Play Services not available, show dialog with further instructions
       googlePlayServicesTextView.setText("Google Play Services is not enabled or installed, this is Required for this demo. Please install or update Google Play services from the Play Store.");
       return false;
    }
 }

The above method needs to be called when the activity is created. We will create a new method that is called in onCreate method, which in turn calls the isGoogleServicesAvailable() method (which was created above).

Create the getUserProfile method by adding the following code to the activity

public void getUserProfile(){
        if(isGoogleServicesAvailable()) {
            chooseAccount();
        }
 }

and call the above method by adding this line to end of the onCreate() method.

getUserProfile();

You may notice that chooseAccount() line is highlighted indicating and error, this is because the method has not been created as yet and will be done in Step 3.

The onCreate method should now look like this (may be different depending on your layout file name)

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_google_auth);
    getUserProfile();
}

Add a method to call the getUserProfile method when the restart button is clicked.

public void restart(View view){
   Intent intent = getIntent();
   finish();
   startActivity(intent);
}

If somewhere along the authentication process there is a failure, clicking the Restart button will restart the Activity.

Step 2: Check for Network Connection

In order for the device to communicate with the Google’s servers to obtain an access token and then retrieve the user profile information a working Internet connection is required. In this step we will create a new method that verifies if the device is connected to the Internet either via Wifi or a mobile data connection and returns true if it is and false if it is not.

The Android ConnectivityManager class will be used to check the network connection.

Add the following import statements at the top of the code with the already existing import statements

import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.content.Context;

Create the isDeviceConnected method below by add the following code to the activity.

public boolean isDeviceConnected(){
   ConnectivityManager connMgr = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
   NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
   TextView networkConnectionTextView = (TextView) findViewById(R.id.networkConnectionTextView);
   if (networkInfo != null && networkInfo.isConnected()) {
      networkConnectionTextView.setText("Internet Connection is available");
      return true;
      } 
      else {
         networkConnectionTextView.setText("No Internet Connection is available, please ensure that Wifi or Mobile Data is turned on");
         return false;
     }
}

This method will be called in step 3 after an account is choosen.

Step 3 : Select a User account.

In order for this demo to work a Google account is required, after all we are displaying basic user information from your Google account using Google Play services. In this step Android’s AccountPicker will be used to select a valid Google account. If the user has multiple Google Accounts the picker will allow them to choose one, if the user only has one, then it will automaticallt be selected and if the user does not have Google accounts the AccountPicker also allows them to create one.

The AccountPicker works by prompting the user to select an existing account or create a new one, once this is completed a Bundle with the account name and account type is returned to the current activity through the onActivityResult method. The data in the Bundle is identified by the key names KEY_ACCOUNT_NAME and KEY_ACCOUNT_TYPE.

Image of the AccountPicker below

step3image1

Declare a static final integer variable with a value of 2; a static final integer means that once it is assigned a value it cannot be changed, this is usually used for constants.

Add the following line of code above the onCreate method

static final int REQUEST_CODE_CHOOSE_ACCOUNT=2;

Just as the AccountPicker returns data to the current activity, other methods can also do this as well, the request code (declared above) is used to identify which method is returning data so that the data can be handled properly.

Create the choose account method, by adding the code below to the activity class.

public void chooseAccount(){
   Intent intent = AccountPicker.newChooseAccountIntent(null,null,new String[]{"com.google"},false,null,null,null,null);
   startActivityForResult(intent,REQUEST_CODE_CHOOSE_ACCOUNT);
}

In the code above the third parameter (new String[] {“com.google”}) specifies that only Google accounts should be selected or created.

We will add the onActivityResult method which handles the data that is returned to the activity from the chooseAccount method.
Add the code to the activity (GoogleAuth.java in this example)

@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
       if(requestCode == REQUEST_CODE_CHOOSE_ACCOUNT){
           //This data is a result of the chooseAccount method
            if(resultCode == RESULT_OK){
                //A valid Google Account was chosen
                selectedAccountEmail = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
                //get the email from the returned data in Bundle using the KEY_ACCOUNT_NAME key
                TextView selectedAccountTextView = (TextView) findViewById(R.id.selectedAccountTextView);
                selectedAccountTextView.setText("Selected Account : " + selectedAccountEmail);
                if(isDeviceConnected()){
                    new getUserData(this, selectedAccountEmail, SCOPE).execute();
                }
                else{
                    TextView statusTextView = (TextView)findViewById(R.id.statusTextView);
                    statusTextView.setText("Tests for one or more of the requirements above has failed, please rectify the problem and try again.");
                }
            }
            else if(resultCode == RESULT_CANCELED){
                //The user did not choose a valid Google Account
                TextView selectedAccountTextView = (TextView) findViewById(R.id.selectedAccountTextView);
                selectedAccountTextView.setText("No Account has been selected, a Google Account is required for this demo");
            }
        }
    }

Explanation of the Code Above.

When the onActivityResult method is launched, the requestCode parameter is compared against the expected requestCodes to determine which intent the data is coming from.
If you recall in the chooseAccount method we created an intent and then specified that we wanted the result of the intent to be identified by the value REQUEST_CODE_CHOOSE_ACCOUNT in the line of code.

startActivityForResult(intent,REQUEST_CODE_CHOOSE_ACCOUNT);

Now in the onActivityResult method, the if statement

if(requestCode == REQUEST_CODE_CHOOSE_ACCOUNT){

checks to see if the request code matches the one from the chooseAccount method, if it is it then ensure the result from that method is ok, (meaning there were no errors), if it is

if(resultCode == RESULT_OK){

Then get the email account from the Intent data bundle which is identified by the key name KEY_ACCOUNT_NAME and update the selectedAccountTextView to show some feedback on the User Interface.

selectedAccountEmail = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
//get the email from the returned data in Bundle using the KEY_ACCOUNT_NAME 
TextView selectedAccountTextView = (TextView) findViewById(R.id.selectedAccountTextView);
selectedAccountTextView.setText("Selected Account : " + selectedAccountEmail);

After the UI is updated, check to see if the device is connected to the Internet and if it is, start an asynchronous task to get the user Data. The getUserData class will be created in Step 4 below.

if(isDeviceConnected()){
   new getUserData(this, selectedAccountEmail, SCOPE).execute();
}

Also in the code, if the result was cancelled, then update the statusTextView to give some feedback to the user.

else if(resultCode == RESULT_CANCELED){
    //The user did not choose a valid Google Account
    TextView selectedAccountTextView = (TextView) findViewById(R.id.selectedAccountTextView);
    selectedAccountTextView.setText("No Account has been selected, a Google Account is required for this demo");
}

Screenshot of if the user cancels the AccountPicker.
step3image2

To summarize, if you recall in Step 1 the getUserProfile method checks for GooglePlay Services, then if it is avaialble, it calls the chooseAccount method, the chooseAccount method then invokes the accountPicker which sends the result to the onActivityResultMethod above. If a valid account is chosen, then we verify that there is a network connection and start the asynchronous task getUserData() which we will create below.

Step 4: Request Access Token

At this point we have checked to ensure we have the we have the necessary prerequisites to request the access token. The GoogleAuthUtil.getToken method will be used to make the request. This method take 3 arguments; the context, account name and scope.

The scope is a string that specifies what information your application would like to access and the account name specifies the account that you would like to get the information from.

Add the following String variable for the scope to the top of the Activity.

private static final String SCOPE = "oauth2:https://www.googleapis.com/auth/userinfo.profile";

Once the request is successful the access token that is returned would ensure that you only have access to the data you requested and cannot be used to access data outside of the specified scope.

If the request results in an error one of the following exceptions may be thrown

IOException – this is usually when there is a network related problem

UserRecoverableAuthException – if some user input is required, such as granting permissions or entering a password this exception is thrown. This exception will be examined further in the next step.

GoogleAuthException – when the error is network related and cannot be recovered from this exception is thrown.

The getToken method makes the request over a network, therefore it needs to be done as an asynchronous task which runs in background and will update the user interface when completed. Running as an asynchronous task prevents the application from hanging and becoming unresponsive when waiting on a response from the Google Oauth server. (This is a requirement in Android)

We will create an asynchronous task which will attempt to get an access token and if successful, request the user profile using the token and update the UI when the data is received. Note: the UI is updated at the end of the asynchronous task via the onPostExecute method.

Add the following code to the Activity class. (GoogleAuth.java in my example), note this has to go within the public class GoogleAuth extends ActionBarActivity{ } block of code. (shown in screen shot below)

Step4image1

public class getUserData extends AsyncTask<Void,Void,Void>{
        Activity activity;
        String accountName;
        String scope;
        String status;
        String token;

getUserData(Activity activity, String accountName, String scope){
        this.activity = activity;
        this.accountName = accountName; //Account Name is email.
        this.scope = scope;
}


@Override
protected Void doInBackground(Void... params){
   try{
                token = getAccessToken(this.activity,this.accountName,this.scope);
                if(token != null){
                    //Access Token sent
                    status = "Access Token Acquired";
                }
            }
            catch (Exception ex){
                status = ex.getMessage();
            }
            return null;
        }



public String getAccessToken(Activity activity,String accountName, String scope){
            try{
               return GoogleAuthUtil.getToken(activity,accountName,scope);
            }
            catch(UserRecoverableAuthException userRecoverableError){
                status = "User Recoverable Error";
            }
            catch(GoogleAuthException googleAuthException){
                status = "Google Auth Exception";
            }
            catch(IOException ioException) {
                status = "IO Exception";
            }
            return null;
        }


protected void onPostExecute(Void result) {
     TextView accessTokenTextView = (TextView) findViewById(R.id.accessTokenTextView);
     TextView statusTextView = (TextView)findViewById(R.id.statusTextView);
     accessTokenTextView.setText("Access Token :" + token );
     statusTextView.setText(status);
     }

}

Explanation of Code

The getUserData class above has 4 methods,

The first method, getUserData(Activity activity, String accountName, String scope) is the constructor, which is used to initialize a new instance of the getUserData class.

The second method, getAccessToken(Activity activity, String accountName, String scope) is the method that uses GoogleAuthUtil.getToken to try to retrieve the access token. Note the parameters for the getToken and the getAccessToken methods are the same. When a new instance of the getUserData class is created the parameters are set using the constructor, which is then passed to the getAccessToken method and finally to the GoogleAuthUtil.getToken method.
This method also handles the various exceptions that can be thrown as described at the beginning of this step.

The third method doInBackground(Void… params), contains the code that runs in background. This is the method that calls the getAccessToken method and waits for a result which is a token in the form of a String. If the getAccessToken returns a String value then that means that the token was received and if it is null then some error occurred. Depending on the outcome the appropriate status is set.

The fourth method, onPostExecute(Void result), is run automatically when the doInBackground is finished executing, this will be used to update the user interface by setting some text on some of the TextViews to provide some user feedback.

Step 5: Handle Errors

If the application is run at this time you will more than likely get the “User Recoverable Error” warning if the other tests have succeeded.

Step5image1

This is because the application hasn’t as yet been granted permission to have access to your user profile. This is a User Recoverable Error because it requires some sort of user input for the application to continue.

This error is handled in a similar fashion to how the AccountPicker works; a dialog is shown which notifies the user that the application wants to access their user profile and prompts the user to either allow or deny the application the permission. The result is then sent back to the activity which is accessed via the onActivityResult method.

The getAccessToken method that was created in the previous step needs to be updated. When a UserRecoverableAuthException error is caught, an intent which displays a pop up dialog box started. The dialog prompts the user to verify if they would allow the application to have access to the relevant data.

Update the section of code

catch(UserRecoverableAuthException userRecoverableError){
   status = "User Recoverable Error";
 }

to the following

catch(UserRecoverableAuthException userRecoverableError){
   Intent intent = ((UserRecoverableAuthException)userRecoverableError).getIntent();
   startActivityForResult(intent, REQUEST_CODE_USER_RECOVERABLE_ERROR);
   status = "User Recoverable Error";
 }

With the updated code when a UserRecoverable error is thrown an intent is created to get some user input to deal with the error, which then sends the result back to the onActivityResult method.

Step5image2

As we did before we need to declare a new request code to identify which intent is sending data to the onActivityResult method.

Declare new String final variable at the top of the activity with the other variable declarations.

static final int REQUEST_CODE_USER_RECOVERABLE_ERROR=3;

Since the data from the Intent is sent back to the activity to the onActivityResult method we need to update it to handle this data.

Add the following else if statement to the end of the onActivityResult method

else if (requestCode == REQUEST_CODE_USER_RECOVERABLE_ERROR){
   if(resultCode == RESULT_OK){
      new getUserData(this, selectedAccountEmail, SCOPE).execute();
   }
}

When the UserRecoverableAuthException is thrown, an Intent is started to try to recover (usually a dialog which needs some input from the user) and the result of the user input is sent to the onActivityMethod.

The code goes through a similar process as it did in step 3.
When the data is returned, it is compared against the expected request codes, the first comparison would be against REQUEST_CODE_CHOOSE_ACCOUNT, since this intent was started with the request code REQUEST_CODE_USER_RECOVERABLE_ERROR it will not match, so it moves on to the line

else if (requestCode == REQUEST_CODE_USER_RECOVERABLE_ERROR){

which it will match. A check is then made to ensure the resultCode is okay and no errors were thrown, once it is okay the getUserData asyncTask is re run to get the access token.

if(resultCode == RESULT_OK){
  new getUserData(this, selectedAccountEmail, SCOPE).execute();
}

Step 6: Use Access token to get User Profile data.

Once the access token is acquired it is used to request the user profile data via an http request, if that request is successful a stream of with the user profile info will be returned. This stream of data will be converted to a string which would then be parsed into a JSON Object. The JSON object, containing the user profile information will then be used to update the User Interface.

Note: one of the pieces of information that is returned is a URL that points to the user profile picture, we will be parsing the image url to create an image bitmap.

At the top of the activity a few new import statements needs to be added.

import org.apache.http.HttpRequest;
import org.apache.http.StatusLine;
import org.apache.http.HttpRequestFactory;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONObject;
import android.widget.ImageView;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.io.InputStream;

import java.io.IOException;
import java.net.URL;

Ensure that there are no duplicates when adding new import statements.

A new JSON object which will contain the userProfile data needs to be declared, at the top of the activity create the new JSON Object.

private JSONObject userProfileJSON;

Back to the asynchronous class.

WIthin the getUserData class a new method getUserDataJSON that accepts the token as a parameter, requests the user profile data and parses it as a JSON object has to be created. The method would set a boolean value, userDataAcquired to true if successful and false if an error occurred.

Add the two variables to getUserData asynchronous task class at the top.

Bitmap userProfileBitmap;
boolean userDataAcquired = false;

The Bitmap userProfileBitmap is used for the user’s profile image.

Create the getUserDataJSON method inside of the getUserData asynchronous task class by adding the code below.

public void getUserDataJSON(String token){
    try {
        String url = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=" + token;
        HttpClient httpclient = new DefaultHttpClient();
        HttpResponse response = httpclient.execute(new HttpGet(url));
        StatusLine statusLine = response.getStatusLine();
        if(statusLine.getStatusCode() == 200){
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            response.getEntity().writeTo(out);
            out.close();
            String responseString = out.toString();
            userProfileJSON = new JSONObject(responseString);
            userProfileBitmap = getBitMap(userProfileJSON.getString("picture"));
            userDataAcquired = true;
        }
    }
    catch(Exception ex) {
        status = ex.getMessage();
        userDataAcquired = false;
    }
}

Also add the getBitMap method below

public Bitmap getBitMap(String url){
    Bitmap bitmapImage = null;
    try {
        InputStream in = new java.net.URL(url).openStream();
        bitmapImage = BitmapFactory.decodeStream(in);
        return bitmapImage;
    } catch (Exception e) {
        status = e.getMessage();
        return null;
    }
}

Explanation of above code

The first method getUserDataJSON accepts the token as a parameter, makes an http request to get the user profile data.

String url = "https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=" + token;
HttpClient httpclient = new DefaultHttpClient();
HttpResponse response = httpclient.execute(new HttpGet(url));
StatusLine statusLine = response.getStatusLine();

If successful it converts the resulting output stream to a String.

if(statusLine.getStatusCode() == 200){
   ByteArrayOutputStream out = new ByteArrayOutputStream();
   response.getEntity().writeTo(out);
   out.close();
   String responseString = out.toString();

which is then parsed as a JSON Object.

userProfileJSON = new JSONObject(responseString);

Finally the “picture” name value pair, which contains the user profile image url is passed to the getBitmap method, which accepts the a url string as a parameter and returns a bitmap image which the url is pointing to.

userProfileBitmap = getBitMap(userProfileJSON.getString("picture"));

and the boolean userDataisAcquired is set to true to indicate the method was executed successfully.

userDataAcquired = true;

Since the getUserDataJSON method retrieves information and data from the internet this is also run in the background, so the doInBackground method will be updated to include this.

Add the line

getUserDataJSON(token);

into the

if( token!=null) {
}

section of code after the line

status = "Access Token Acquired";

The doInBackground method should now be.

@Override
protected Void doInBackground(Void... params){
    try{
        token = getAccessToken(this.activity,this.accountName,this.scope);
        if(token != null){
            //Access Token sent
            status = "Access Token Acquired";
            getUserDataJSON(token);
        }
    }
    catch (Exception ex){
        status = ex.getMessage();
    }
    return null;
}

Display the User Profile data

The onPostExecute method needs to be updated to display the user profile data on the activity screen by setting the relevant values to the various TextViews and ImageView if the userDataAcquired boolen is true.

Update the onPostExecute method to first check if the user data was acquired and if it was then update the UI.

The onPostExecute method should now be

protected void onPostExecute(Void result) {
    TextView accessTokenTextView = (TextView) findViewById(R.id.accessTokenTextView);
    TextView statusTextView = (TextView)findViewById(R.id.statusTextView);
    accessTokenTextView.setText("Access Token :" + token );

    if (userDataAcquired){
        try {
            status = "User Profile";
            TextView nameTextView = (TextView) findViewById(R.id.nameTextView);
            nameTextView.setText("Name : "+userProfileJSON.getString("name"));
            TextView givenNameTextView = (TextView) findViewById(R.id.givenNameTextView);
            givenNameTextView.setText("Given Name : " +userProfileJSON.getString("given_name"));
            TextView familyNameTextView = (TextView) findViewById(R.id.familyNameTextView);
            familyNameTextView.setText("Family Name : " +userProfileJSON.getString("family_name"));
            TextView genderTextView = (TextView) findViewById(R.id.genderTextView);
            genderTextView.setText("Gender : " +userProfileJSON.getString("gender"));
            ImageView picture = (ImageView) findViewById(R.id.userImageView);
            picture.setImageBitmap(userProfileBitmap);
        }
        catch(Exception ex){
            status = ex.getMessage();
        }
    }
    statusTextView.setText(status);
}

If you run the application should be ready to run, with the output being something like.

finalScreenshot

The end.
I hope the above tutorial was useful and it can also be applied to using OAuth with other Google APIs. If you have any questions or comments, feel free to either email me or leave a response here and I will get back to you as soon as possible.

 

Resources


Download Complete Code