In this document
- Register Your App
- Invoke the Account Picker
- Retrieve the Account Name
- Extend AsyncTask to Get the Auth Token
- Handle Exceptions
Try it out
GoogleAuth.zip
When you want your Android app to access Google APIs using the user's Google account over
HTTP, the GoogleAuthUtil
class and related APIs provide your users a secure and consistent experience for picking an
account and retrieving an OAuth 2.0 token for your app.
You can then use that token in your HTTP-based communications with Google API services that are not included in the Google Play services library, such as the Blogger or Translate APIs.
Note: An OAuth 2.0 token using GoogleAuthUtil
is required only for certain types of Google
APIs that you need to access over HTTP. If you're instead using the Google Play services library to access Google
APIs such as Google+ or Play Games, you don't need an OAuth 2.0
token and you can instead access these services using the GoogleApiClient
. For more
information, read Accessing Google Play
Services APIs.
To get started with GoogleAuthUtil
for accessing Google's REST APIs, you must set up your Android app project with the Google Play
services library. Follow the procedures in Setup Google Play Services SDK.
Register Your App
Before you can publish an app that retrieves an OAuth 2.0 token for Google REST APIs, you must register your Android app with the Google Cloud Console by providing your app's package name and the SHA1 fingerprint of the keystore with which you sign your release APK.
Caution: While you are testing an APK that's signed with a debug key, Google does not require that your app be registered in Google Cloud Console. However, your app must be registered in Google Cloud Console in order to continue working once it is signed with a release key.
To register your Android app with Google Cloud Console:
- Visit Google Cloud Console.
- If you have an existing project to which you're adding an Android app, select the project.
Otherwise, click Create project at the top, enter your project name and ID,
then click Create.
Note: The name you provide for the project is the name that appears to users in the Google Settings app in the list of Connected apps.
- In the left-side navigation, select APIs & auth.
- Enable the API you'd like to use by setting the Status to ON.
- In the left-side navigation, select Credentials.
- Click Create new client ID or Create new key as appropriate for your app.
- Complete the form that appears by filling in your Android app details.
To get the SHA1 fingerprint for your app, run the following command in a terminal:
keytool -exportcert -alias <keystore_alias> -keystore <keystore_path> -list -v
For example, you're using a debug-key with Eclipse, then the command looks like this:
keytool -exportcert -alias androiddebugkey-keystore ~/.android/debug.keystore -list -v
Then the keystore password is "android".
- Click Create.
The Credentials page then provides the available credentials such as an OAuth 2.0 client ID and an Android Key, but you don't need these to authorize your Android users. Simply registering your app with the package name and SHA1 makes the Google services accessible by your app.
To acquire the OAuth 2.0 token that will grant you access to Google APIs over HTTP, you need to
first identify the user's Google account with which you'll query the servers. For this task, the
Google Play services library provides a convenient account picker dialog you can invoke using
AccountPicker
. The result delivered to your activity from the account picker is the account
name you'll use to request the OAuth 2.0 token in the next lesson.
Note: In order to use the APIs discussed here, you must include the Google Play services library with your project. If you haven't set up your project with the library yet, read the guide to Setup Google Play Services SDK.
Invoke the Account Picker
To open the account picker dialog that's managed by the Google Play services library, call
startActivityForResult()
using an Intent
returned by
AccountPicker.newChooseAccountIntent
.
For example:
static final int REQUEST_CODE_PICK_ACCOUNT = 1000; private void pickUserAccount() { String[] accountTypes = new String[]{"com.google"}; Intent intent = AccountPicker.newChooseAccountIntent(null, null, accountTypes, false, null, null, null, null); startActivityForResult(intent, REQUEST_CODE_PICK_ACCOUNT); }
When this code executes, a dialog appears for the user to pick an account. When the user
selects the account, your activity receives the result in the onActivityResult()
callback.
Most apps should pass the
newChooseAccountIntent()
method the same arguments shown in the above example,
which indicate that:
- There is no currently selected account.
- There is no restricted list of accounts.
- The dialog should list only accounts from the "com.google" domain.
- Don't prompt the user to pick an account if there's only one available account (just use that one). However, even if only one account currently exists, the dialog may include an option for the user to add a new account.
- There is no custom title for the dialog.
- There is no specific auth token type required.
- There are no restrictions based on account features.
- There are no authenticator-specific options.
For more details about these arguments, see the
newChooseAccountIntent()
method documentation.
Retrieve the Account Name
Once the user selects an account, your activity receives a call to its
onActivityResult()
method. The received
Intent
includes an extra for
KEY_ACCOUNT_NAME
, specifying the account name
(an email address) you must use to acquire the OAuth 2.0 token.
Here's an example implementation of the callback onActivityResult()
that receives the selected account:
String mEmail; // Received fromnewChooseAccountIntent()
; passed togetToken()
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_PICK_ACCOUNT) { // Receiving a result from the AccountPicker if (resultCode == RESULT_OK) { mEmail = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); // With the account name acquired, go get the auth token getUsername(); } else if (resultCode == RESULT_CANCELED) { // The account picker dialog closed without selecting an account. // Notify users that they must pick an account to proceed. Toast.makeText(this, R.string.pick_account, Toast.LENGTH_SHORT).show(); } } // Later, more code will go here to handle the result from some exceptions... }
You can now pass the account name held by mEmail
to
GoogleAuthUtil.getToken()
(which is what the getUsername()
method
does), but because it performs network transactions, this method should not be called from the
UI thread. The next lesson shows how to create an AsyncTask
to get the auth token
on a separate thread.
Once you have retrieved the account name for the user's Google account, you can call
GoogleAuthUtil.getToken()
, which returns the access token string required by Google API
services.
Calling this method is generally a straightforward procedure, but you must be aware that:
- The
GoogleAuthUtil.getToken()
method requires a network connection, so your app must acquire theINTERNET
permission. You should also check whether the device has a network connection at runtime by queryingNetworkInfo
, which requires that your app also acquire theACCESS_NETWORK_STATE
permissions—for more details, read Connecting to the Network. - Because the
GoogleAuthUtil.getToken()
method performs a synchronous network transaction, you should always perform this call from a worker thread to avoid blocking your app's UI thread. - As is true when performing any network transaction, you should be prepared to handle
exceptions that may occur. There are also specific exceptions that
GoogleAuthUtil.getToken()
may throw, defined asGoogleAuthException
objects.
This lesson shows how you can gracefully handle these concerns by performing authentication in
an AsyncTask
and providing users with the appropriate information and available
actions during known exceptions.
Note: The code shown in this lesson, using GoogleAuthUtil.getToken()
,
is appropriate when you will be requesting the OAuth token from an Activity
.
However, if you need to request the OAuth token from a Service
, then you
should instead use getTokenWithNotification()
. This method works the same as GoogleAuthUtil.getToken()
, but if an error occurs, it
also creates an appropriate
notification
that allows the user can recover from the error.
The sample available for download above includes code showing how to use this method instead.
Extend AsyncTask to Get the Auth Token
The AsyncTask
class provides a simple way to create a worker thread for jobs
that should not run on your UI thread. This lesson focuses on how to create such a thread
to get your auth token; for a more complete discussion about AsyncTask
,
read Keeping Your
App Responsive and the AsyncTask
class reference.
The doInBackground()
method in your AsyncTask
class is where you should call the
GoogleAuthUtil.getToken()
method. You can also use it to catch some of the generic
exceptions that may occur during your network transactions.
For example, here's part of an AsyncTask
subclass that calls
GoogleAuthUtil.getToken()
:
public class GetUsernameTask extends AsyncTask{ Activity mActivity; String mScope; String mEmail; GetUsernameTask(Activity activity, String name, String scope) { this.mActivity = activity; this.mScope = scope; this.mEmail = name; } /** * Executes the asynchronous job. This runs when you call execute() * on the AsyncTask instance. */ @Override protected Void doInBackground(Void... params) { try { String token = fetchToken(); if (token != null) { // Insert the good stuff here. // Use the token to access the user's Google data. ... } } catch (IOException e) { // The fetchToken() method handles Google-specific exceptions, // so this indicates something went wrong at a higher level. // TIP: Check for network connectivity before starting the AsyncTask. ... } return null; } /** * Gets an authentication token from Google and handles any * GoogleAuthException that may occur. */ protected String fetchToken() throws IOException { try { return GoogleAuthUtil.getToken(mActivity, mEmail, mScope); } catch (UserRecoverableAuthException userRecoverableException) { // GooglePlayServices.apk is either old, disabled, or not present // so we need to show the user some UI in the activity to recover. mActivity.handleException(userRecoverableException); } catch (GoogleAuthException fatalException) { // Some other type of unrecoverable exception has occurred. // Report and log the error as appropriate for your app. ... } return null; } ... }
In order to call
GoogleAuthUtil.getToken()
, you must provide the app Context
,
the account name retrieved from the account picker, and the scope for your auth
token request. The above sample code (and the attached sample) defines these arguments with
class members that the host activity passes to
the AsyncTask
class constructor.
Note:
As shown by the fetchToken()
method above, you must handle
special exceptions that may occur during the
GoogleAuthUtil.getToken()
method. The next section shows how you should
respond to these exceptions.
Once you have an AsyncTask
subclass defined,
you can instantiate and execute an instance after you get the user's
account name from the account picker.
For example, back in the Activity
class you can do something like this:
String mEmail; // Received fromnewChooseAccountIntent()
; passed togetToken()
private static final String SCOPE = "oauth2:https://www.googleapis.com/auth/userinfo.profile"; /** * Attempts to retrieve the username. * If the account is not yet known, invoke the picker. Once the account is known, * start an instance of the AsyncTask to get the auth token and do work with it. */ private void getUsername() { if (mEmail == null) { pickUserAccount(); } else { if (isDeviceOnline()) { new GetUsernameTask(HelloActivity.this, mEmail, SCOPE).execute(); } else { Toast.makeText(this, R.string.not_online, Toast.LENGTH_LONG).show(); } } }
The pickUserAccount()
method is shown in the first lesson, Picking the User's Account.
For information about how to check whether the device is currently online (as performed by
the isDeviceOnline()
method above), see the attached sample app or the
Connecting to the Network lesson.
The only part left is how you should handle the exceptions that may occur when you call
GoogleAuthUtil.getToken()
.
Handle Exceptions
As shown in the fetchToken()
method above, you must catch all occurrences of GoogleAuthException
when you call
GoogleAuthUtil.getToken()
.
To provide users information and a proper solution to issues that may occur while acquiring the
auth token, it's important that you properly handle the following subclasses of GoogleAuthException
:
UserRecoverableAuthException
- This is an error that users can resolve through some verification. For example, users may
need to confirm that your app is allowed to access their Google data or they may need to re-enter
their account password. When you receive this exception, call
getIntent()
on the instance and pass the returnedIntent
tostartActivityForResult()
to give users the opportunity to solve the problem, such as by logging in. GooglePlayServicesAvailabilityException
- This is a specific type of
UserRecoverableAuthException
indicating that the user's current version of Google Play services is outdated. Although the recommendation above forUserRecoverableAuthException
also works for this exception, callingstartActivityForResult()
will immediately send users to Google Play Store to install an update, which may be confusing. So you should instead callgetConnectionStatusCode()
and pass the result toGooglePlayServicesUtil.getErrorDialog()
. This returns aDialog
that includes an appropriate message and a button to take users to Google Play Store so they can install an update.
For example, the fetchToken()
method in the above sample code catches any
occurrence of UserRecoverableAuthException
and passes it back to the activity with a method called
handleException()
. Here's what that method in the activity may look like:
static final int REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR = 1001; /** * This method is a hook for background threads and async tasks that need to * provide the user a response UI when an exception occurs. */ public void handleException(final Exception e) { // Because this call comes from the AsyncTask, we must ensure that the following // code instead executes on the UI thread. runOnUiThread(new Runnable() { @Override public void run() { if (e instanceof GooglePlayServicesAvailabilityException) { // The Google Play services APK is old, disabled, or not present. // Show a dialog created by Google Play services that allows // the user to update the APK int statusCode = ((GooglePlayServicesAvailabilityException)e) .getConnectionStatusCode(); Dialog dialog = GooglePlayServicesUtil.getErrorDialog(statusCode, HelloActivity.this, REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR); dialog.show(); } else if (e instanceof UserRecoverableAuthException) { // Unable to authenticate, such as when the user has not yet granted // the app access to the account, but the user can fix this. // Forward the user to an activity in Google Play services. Intent intent = ((UserRecoverableAuthException)e).getIntent(); startActivityForResult(intent, REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR); } } }); }
Notice that in both cases, the REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR
request code is passed with the request to handle the exception with a dialog or activity.
This way, when the user completes the appropriate action to resolve the exception,
your onActivityResult()
method receives an
intent that includes this request code and you can try to acquire the auth
token again.
For example, the following code is a complete implementation of onActivityResult()
that handles results for
both the REQUEST_CODE_PICK_ACCOUNT
action (shown in the previous lesson, Picking the User's Account)
and the REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR
action, which occurs after the user
completes one of the actions above to resolve an exception.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == REQUEST_CODE_PICK_ACCOUNT) { // Receiving a result from the AccountPicker if (resultCode == RESULT_OK) { mEmail = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME); // With the account name acquired, go get the auth token getUsername(); } else if (resultCode == RESULT_CANCELED) { // The account picker dialog closed without selecting an account. // Notify users that they must pick an account to proceed. Toast.makeText(this, R.string.pick_account, Toast.LENGTH_SHORT).show(); } } else if ((requestCode == REQUEST_CODE_RECOVER_FROM_AUTH_ERROR || requestCode == REQUEST_CODE_RECOVER_FROM_PLAY_SERVICES_ERROR) && resultCode == RESULT_OK) { // Receiving a result that follows a GoogleAuthException, try auth again getUsername(); } }
For a complete set of code that acquires the OAuth token and queries a Google service
over HTTP (including how to use getTokenWithNotification()
when you need to acquire the token from
a Service
), see the sample app available for download at the top
of this page.