com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential.java Source code

Java tutorial

Introduction

Here is the source code for com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential.java

Source

/*
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package com.google.api.client.googleapis.extensions.android.gms.auth;

import android.accounts.Account;
import android.content.Context;
import android.content.Intent;
import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.GooglePlayServicesAvailabilityException;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.gms.common.AccountPicker;
import com.google.api.client.googleapis.extensions.android.accounts.GoogleAccountManager;
import com.google.api.client.http.HttpExecuteInterceptor;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestInitializer;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
import com.google.api.client.util.BackOff;
import com.google.api.client.util.BackOffUtils;
import com.google.api.client.util.Beta;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.client.util.Joiner;
import com.google.api.client.util.Preconditions;
import com.google.api.client.util.Sleeper;
import java.io.IOException;
import java.util.Collection;

/**
 * {@link Beta} <br/>
 * Manages authorization and account selection for Google accounts.
 *
 * <p>
 * When fetching a token, any thrown {@link GoogleAuthException} would be wrapped:
 * <ul>
 * <li>{@link GooglePlayServicesAvailabilityException} would be wrapped inside of
 * {@link GooglePlayServicesAvailabilityIOException}</li>
 * <li>{@link UserRecoverableAuthException} would be wrapped inside of
 * {@link UserRecoverableAuthIOException}</li>
 * <li>{@link GoogleAuthException} when be wrapped inside of {@link GoogleAuthIOException}</li>
 * </ul>
 * </p>
 *
 * <p>
 * Upgrade warning: in prior version 1.14 exponential back-off was enabled by default when I/O
 * exception was thrown inside {@link #getToken}, but starting with version 1.15 you need to call
 * {@link #setBackOff} with {@link ExponentialBackOff} to enable it.
 * </p>
 *
 * @since 1.12
 * @author Yaniv Inbar
 */
@Beta
public class GoogleAccountCredential implements HttpRequestInitializer {

    /** Context. */
    final Context context;

    /** Scope to use on {@link GoogleAuthUtil#getToken}. */
    final String scope;

    /** Google account manager. */
    private final GoogleAccountManager accountManager;

    /**
     * Selected Google account name (e-mail address), for example {@code "johndoe@gmail.com"}, or
     * {@code null} for none.
     */
    private String accountName;

    /** Selected Google account or {@code null} for none. */
    private Account selectedAccount;

    /** Sleeper. */
    private Sleeper sleeper = Sleeper.DEFAULT;

    /**
     * Back-off policy which is used when an I/O exception is thrown inside {@link #getToken} or
     * {@code null} for none.
     */
    private BackOff backOff;

    /**
     * @param context context
     * @param scope scope to use on {@link GoogleAuthUtil#getToken}
     */
    public GoogleAccountCredential(Context context, String scope) {
        accountManager = new GoogleAccountManager(context);
        this.context = context;
        this.scope = scope;
    }

    /**
     * Constructs a new instance using OAuth 2.0 scopes.
     *
     * @param context context
     * @param scopes non empty OAuth 2.0 scope list
     * @return new instance
     *
     * @since 1.15
     */
    public static GoogleAccountCredential usingOAuth2(Context context, Collection<String> scopes) {
        Preconditions.checkArgument(scopes != null && scopes.iterator().hasNext());
        String scopesStr = "oauth2: " + Joiner.on(' ').join(scopes);
        return new GoogleAccountCredential(context, scopesStr);
    }

    /**
     * Sets the audience scope to use with Google Cloud Endpoints.
     *
     * @param context context
     * @param audience audience
     * @return new instance
     */
    public static GoogleAccountCredential usingAudience(Context context, String audience) {
        Preconditions.checkArgument(audience.length() != 0);
        return new GoogleAccountCredential(context, "audience:" + audience);
    }

    /**
     * Sets the selected Google account name (e-mail address) -- for example
     * {@code "johndoe@gmail.com"} -- or {@code null} for none.
     */
    public final GoogleAccountCredential setSelectedAccountName(String accountName) {
        selectedAccount = accountManager.getAccountByName(accountName);
        // check if account has been deleted
        this.accountName = selectedAccount == null ? null : accountName;
        return this;
    }

    /**
     * Sets the selected Google {@link Account} or {@code null} for none.
     *
     * <p>
     * Caller must ensure the given Google account exists.
     * </p>
     */
    public final GoogleAccountCredential setSelectedAccount(Account selectedAccount) {
        this.selectedAccount = selectedAccount;
        this.accountName = selectedAccount == null ? null : selectedAccount.name;
        return this;
    }

    @Override
    public void initialize(HttpRequest request) {
        RequestHandler handler = new RequestHandler();
        request.setInterceptor(handler);
        request.setUnsuccessfulResponseHandler(handler);
    }

    /** Returns the context. */
    public final Context getContext() {
        return context;
    }

    /** Returns the scope to use on {@link GoogleAuthUtil#getToken}. */
    public final String getScope() {
        return scope;
    }

    /** Returns the Google account manager. */
    public final GoogleAccountManager getGoogleAccountManager() {
        return accountManager;
    }

    /** Returns all Google accounts or {@code null} for none. */
    public final Account[] getAllAccounts() {
        return accountManager.getAccounts();
    }

    /** Returns the selected Google account or {@code null} for none. */
    public final Account getSelectedAccount() {
        return selectedAccount;
    }

    /**
     * Returns the back-off policy which is used when an I/O exception is thrown inside
     * {@link #getToken} or {@code null} for none.
     *
     * @since 1.15
     */
    public BackOff getBackOff() {
        return backOff;
    }

    /**
     * Sets the back-off policy which is used when an I/O exception is thrown inside {@link #getToken}
     * or {@code null} for none.
     *
     * @since 1.15
     */
    public GoogleAccountCredential setBackOff(BackOff backOff) {
        this.backOff = backOff;
        return this;
    }

    /**
     * Returns the sleeper.
     *
     * @since 1.15
     */
    public final Sleeper getSleeper() {
        return sleeper;
    }

    /**
     * Sets the sleeper. The default value is {@link Sleeper#DEFAULT}.
     *
     * @since 1.15
     */
    public final GoogleAccountCredential setSleeper(Sleeper sleeper) {
        this.sleeper = Preconditions.checkNotNull(sleeper);
        return this;
    }

    /**
     * Returns the selected Google account name (e-mail address), for example
     * {@code "johndoe@gmail.com"}, or {@code null} for none.
     */
    public final String getSelectedAccountName() {
        return accountName;
    }

    /**
     * Returns an intent to show the user to select a Google account, or create a new one if there are
     * none on the device yet.
     *
     * <p>
     * Must be run from the main UI thread.
     * </p>
     */
    public final Intent newChooseAccountIntent() {
        return AccountPicker.newChooseAccountIntent(selectedAccount, null,
                new String[] { GoogleAccountManager.ACCOUNT_TYPE }, true, null, null, null, null);
    }

    /**
     * Returns an OAuth 2.0 access token.
     *
     * <p>
     * Must be run from a background thread, not the main UI thread.
     * </p>
     */
    public String getToken() throws IOException, GoogleAuthException {
        if (backOff != null) {
            backOff.reset();
        }

        while (true) {
            try {
                return GoogleAuthUtil.getToken(context, accountName, scope);
            } catch (IOException e) {
                // network or server error, so retry using back-off policy
                try {
                    if (backOff == null || !BackOffUtils.next(sleeper, backOff)) {
                        throw e;
                    }
                } catch (InterruptedException e2) {
                    // ignore
                }
            }
        }
    }

    @Beta
    class RequestHandler implements HttpExecuteInterceptor, HttpUnsuccessfulResponseHandler {

        /** Whether we've received a 401 error code indicating the token is invalid. */
        boolean received401;
        String token;

        @Override
        public void intercept(HttpRequest request) throws IOException {
            try {
                token = getToken();
                request.getHeaders().setAuthorization("Bearer " + token);
            } catch (GooglePlayServicesAvailabilityException e) {
                throw new GooglePlayServicesAvailabilityIOException(e);
            } catch (UserRecoverableAuthException e) {
                throw new UserRecoverableAuthIOException(e);
            } catch (GoogleAuthException e) {
                throw new GoogleAuthIOException(e);
            }
        }

        @Override
        public boolean handleResponse(HttpRequest request, HttpResponse response, boolean supportsRetry)
                throws IOException {
            try {
                if (response.getStatusCode() == 401 && !received401) {
                    received401 = true;
                    GoogleAuthUtil.clearToken(context, token);
                    return true;
                }
            } catch (GoogleAuthException e) {
                throw new GoogleAuthIOException(e);
            }
            return false;
        }
    }
}