/* * Copyright (c) 2012 Google Inc. * * 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.samples.cloudnotes; import com.google.android.gms.auth.GoogleAuthException; import com.google.android.gms.auth.GoogleAuthUtil; import com.google.android.gms.auth.TransientAuthException; import com.google.android.gms.common.AccountPicker; import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager; import com.google.api.client.http.BackOffPolicy; import com.google.api.client.http.ExponentialBackOffPolicy; 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.common.base.Joiner; import com.google.common.base.Preconditions; import android.accounts.Account; import android.content.Context; import android.content.Intent; import java.io.IOException; /** * Manages account selection and authorization for Google accounts. * * @author Yaniv Inbar */ public final class GoogleAccountCredential implements HttpRequestInitializer { final Context context; final String scope; private String accountName; private final GoogleAccountManager accountManager; private Account account; /** * @param context context * @param scope scope to use on {@link GoogleAuthUtil#authenticate} */ private GoogleAccountCredential(Context context, String scope) { accountManager = new GoogleAccountManager(context); this.context = context; this.scope = scope; } /** * Constructor a new instance using OAuth 2.0 scopes. * * @param context context * @param scopes OAuth 2.0 scopes * @return new instance */ public static GoogleAccountCredential usingOAuth2(Context context, String... scopes) { Preconditions.checkArgument(scopes.length != 0); String scope = "oauth2:" + Joiner.on(' ').join(scopes); return new GoogleAccountCredential(context, scope); } /** Sets the audience scope to use with Google Cloud Endpoints. */ public static GoogleAccountCredential usingAudience(Context context, String audience) { Preconditions.checkArgument(audience.length() != 0); String scope = "audience:" + audience; return new GoogleAccountCredential(context, scope); } /** * Sets the selected Google account name (e-mail address), for example {@code "johndoe@gmail.com"} * , or {@code null} for none. */ public GoogleAccountCredential setAccountName(String accountName) { account = accountManager.getAccountByName(accountName); // check if account has been deleted this.accountName = account == null ? null : accountName; return this; } public void initialize(HttpRequest request) { RequestHandler handler = new RequestHandler(); request.setInterceptor(handler); request.setUnsuccessfulResponseHandler(handler); request.setBackOffPolicy(new ExponentialBackOffPolicy()); } /** * Returns the selected Google account name (e-mail address), for example * {@code "johndoe@gmail.com"}, or {@code null} for none. */ public String getAccountName() { return accountName; } /** Returns the selected Google account or {@code null} for none. */ public Account getAccount() { return account; } /** Returns all Google accounts or {@code null} for none. */ public Account[] getAllAccounts() { return accountManager.getAccounts(); } /** * 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 Intent newChooseAccountIntent() { return AccountPicker.newChooseAccountIntent(account, 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 { BackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); while (true) { try { try { return GoogleAuthUtil.getToken(context, accountName, scope); } catch (TransientAuthException e) { // network or server error, so retry use long backOffMillis = backOffPolicy.getNextBackOffMillis(); if (backOffMillis == BackOffPolicy.STOP) { throw e; } // sleep try { Thread.sleep(backOffMillis); } catch (InterruptedException e2) { // ignore } } } catch (GoogleAuthException exception) { IOException io = new IOException(); io.initCause(exception); throw io; } } } class RequestHandler implements HttpExecuteInterceptor, HttpUnsuccessfulResponseHandler { /** Whether we've received a 401 error code indicating the token is invalid. */ boolean received401; String token; public void intercept(HttpRequest request) throws IOException { token = getToken(); request.getHeaders().setAuthorization("Bearer " + token); } public boolean handleResponse(HttpRequest request, HttpResponse response, boolean supportsRetry) { if (response.getStatusCode() == 401 && !received401) { received401 = true; GoogleAuthUtil.invalidateToken(context, token); return true; } return false; } } }