1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 package org.chromium.chrome.browser.signin;
7 import android.accounts.Account;
8 import android.app.Activity;
9 import android.content.Context;
10 import android.preference.PreferenceManager;
11 import android.util.Log;
13 import com.google.common.annotations.VisibleForTesting;
15 import org.chromium.base.CalledByNative;
16 import org.chromium.base.ObserverList;
17 import org.chromium.base.ThreadUtils;
18 import org.chromium.chrome.browser.profiles.Profile;
19 import org.chromium.sync.signin.AccountManagerHelper;
20 import org.chromium.sync.signin.ChromeSigninController;
22 import java.util.Arrays;
23 import java.util.HashSet;
25 import java.util.concurrent.Semaphore;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.atomic.AtomicReference;
29 import javax.annotation.Nullable;
32 * Java instance for the native OAuth2TokenService.
34 * This class forwards calls to request or invalidate access tokens made by native code to
35 * AccountManagerHelper and forwards callbacks to native code.
38 public final class OAuth2TokenService {
40 private static final String TAG = "OAuth2TokenService";
43 public static final String STORED_ACCOUNTS_KEY = "google.services.stored_accounts";
46 * Classes that want to listen for refresh token availability should
47 * implement this interface and register with {@link #addObserver}.
49 public interface OAuth2TokenServiceObserver {
50 void onRefreshTokenAvailable(Account account);
51 void onRefreshTokenRevoked(Account account);
52 void onRefreshTokensLoaded();
55 private static final String OAUTH2_SCOPE_PREFIX = "oauth2:";
57 private final long mNativeProfileOAuth2TokenService;
58 private final ObserverList<OAuth2TokenServiceObserver> mObservers;
60 private OAuth2TokenService(long nativeOAuth2Service) {
61 mNativeProfileOAuth2TokenService = nativeOAuth2Service;
62 mObservers = new ObserverList<OAuth2TokenServiceObserver>();
65 public static OAuth2TokenService getForProfile(Profile profile) {
66 ThreadUtils.assertOnUiThread();
67 return (OAuth2TokenService) nativeGetForProfile(profile);
71 private static OAuth2TokenService create(long nativeOAuth2Service) {
72 ThreadUtils.assertOnUiThread();
73 return new OAuth2TokenService(nativeOAuth2Service);
76 public void addObserver(OAuth2TokenServiceObserver observer) {
77 ThreadUtils.assertOnUiThread();
78 mObservers.addObserver(observer);
81 public void removeObserver(OAuth2TokenServiceObserver observer) {
82 ThreadUtils.assertOnUiThread();
83 mObservers.removeObserver(observer);
86 private static Account getAccountOrNullFromUsername(Context context, String username) {
87 if (username == null) {
88 Log.e(TAG, "Username is null");
92 AccountManagerHelper accountManagerHelper = AccountManagerHelper.get(context);
93 Account account = accountManagerHelper.getAccountFromName(username);
94 if (account == null) {
95 Log.e(TAG, "Account not found for provided username.");
102 * Called by native to list the activite accounts in the OS.
106 public static String[] getSystemAccounts(Context context) {
107 AccountManagerHelper accountManagerHelper = AccountManagerHelper.get(context);
108 java.util.List<String> accountNames = accountManagerHelper.getGoogleAccountNames();
109 return accountNames.toArray(new String[accountNames.size()]);
113 * Called by native to list the accounts with OAuth2 refresh tokens.
114 * This can differ from getSystemAccounts as the user add/remove accounts
115 * from the OS. validateAccounts should be called to keep these two
119 public static String[] getAccounts(Context context) {
120 return getStoredAccounts(context);
124 * Called by native to retrieve OAuth2 tokens.
126 * @param username The native username (full address).
127 * @param scope The scope to get an auth token for (without Android-style 'oauth2:' prefix).
128 * @param nativeCallback The pointer to the native callback that should be run upon completion.
131 public static void getOAuth2AuthToken(
132 Context context, String username, String scope, final long nativeCallback) {
133 Account account = getAccountOrNullFromUsername(context, username);
134 if (account == null) {
135 nativeOAuth2TokenFetched(null, false, nativeCallback);
138 String oauth2Scope = OAUTH2_SCOPE_PREFIX + scope;
140 AccountManagerHelper accountManagerHelper = AccountManagerHelper.get(context);
141 accountManagerHelper.getAuthTokenFromForeground(
142 null, account, oauth2Scope, new AccountManagerHelper.GetAuthTokenCallback() {
144 public void tokenAvailable(String token) {
145 nativeOAuth2TokenFetched(
146 token, token != null, nativeCallback);
152 * Call this method to retrieve an OAuth2 access token for the given account and scope.
154 * @param activity the current activity. May be null.
155 * @param account the account to get the access token for.
156 * @param scope The scope to get an auth token for (without Android-style 'oauth2:' prefix).
157 * @param callback called on successful and unsuccessful fetching of auth token.
159 public static void getOAuth2AccessToken(Context context, @Nullable Activity activity,
160 Account account, String scope,
161 AccountManagerHelper.GetAuthTokenCallback callback) {
162 String oauth2Scope = OAUTH2_SCOPE_PREFIX + scope;
163 AccountManagerHelper.get(context).getAuthTokenFromForeground(
164 activity, account, oauth2Scope, callback);
168 * Call this method to retrieve an OAuth2 access token for the given account and scope. This
169 * method times out after the specified timeout, and will return null if that happens.
171 * Given that this is a blocking method call, this should never be called from the UI thread.
173 * @param activity the current activity. May be null.
174 * @param account the account to get the access token for.
175 * @param scope The scope to get an auth token for (without Android-style 'oauth2:' prefix).
176 * @param timeout the timeout.
177 * @param unit the unit for |timeout|.
179 public static String getOAuth2AccessTokenWithTimeout(
180 Context context, @Nullable Activity activity, Account account, String scope,
181 long timeout, TimeUnit unit) {
182 assert !ThreadUtils.runningOnUiThread();
183 final AtomicReference<String> result = new AtomicReference<String>();
184 final Semaphore semaphore = new Semaphore(0);
185 getOAuth2AccessToken(
186 context, activity, account, scope,
187 new AccountManagerHelper.GetAuthTokenCallback() {
189 public void tokenAvailable(String token) {
195 if (semaphore.tryAcquire(timeout, unit)) {
198 Log.d(TAG, "Failed to retrieve auth token within timeout (" +
199 timeout + " + " + unit.name() + ")");
202 } catch (InterruptedException e) {
203 Log.w(TAG, "Got interrupted while waiting for auth token");
209 * Called by native to check wether the account has an OAuth2 refresh token.
212 public static boolean hasOAuth2RefreshToken(Context context, String accountName) {
213 return AccountManagerHelper.get(context).hasAccountForName(accountName);
217 * Called by native to invalidate an OAuth2 token.
220 public static void invalidateOAuth2AuthToken(Context context, String accessToken) {
221 if (accessToken != null) {
222 AccountManagerHelper.get(context).invalidateAuthToken(accessToken);
227 public void validateAccounts(Context context, boolean forceNotifications) {
228 ThreadUtils.assertOnUiThread();
229 String currentlySignedInAccount =
230 ChromeSigninController.get(context).getSignedInAccountName();
231 nativeValidateAccounts(mNativeProfileOAuth2TokenService, currentlySignedInAccount,
236 * Triggers a notification to all observers of the native and Java instance of the
237 * OAuth2TokenService that a refresh token is now available. This may cause observers to retry
238 * operations that require authentication.
240 public void fireRefreshTokenAvailable(Account account) {
241 ThreadUtils.assertOnUiThread();
242 assert account != null;
243 nativeFireRefreshTokenAvailableFromJava(mNativeProfileOAuth2TokenService, account.name);
247 public void notifyRefreshTokenAvailable(String accountName) {
248 assert accountName != null;
249 Account account = AccountManagerHelper.createAccountFromName(accountName);
250 for (OAuth2TokenServiceObserver observer : mObservers) {
251 observer.onRefreshTokenAvailable(account);
256 * Triggers a notification to all observers of the native and Java instance of the
257 * OAuth2TokenService that a refresh token is now revoked.
259 public void fireRefreshTokenRevoked(Account account) {
260 ThreadUtils.assertOnUiThread();
261 assert account != null;
262 nativeFireRefreshTokenRevokedFromJava(mNativeProfileOAuth2TokenService, account.name);
266 public void notifyRefreshTokenRevoked(String accountName) {
267 assert accountName != null;
268 Account account = AccountManagerHelper.createAccountFromName(accountName);
269 for (OAuth2TokenServiceObserver observer : mObservers) {
270 observer.onRefreshTokenRevoked(account);
275 * Triggers a notification to all observers of the native and Java instance of the
276 * OAuth2TokenService that all refresh tokens now have been loaded.
278 public void fireRefreshTokensLoaded() {
279 ThreadUtils.assertOnUiThread();
280 nativeFireRefreshTokensLoadedFromJava(mNativeProfileOAuth2TokenService);
284 public void notifyRefreshTokensLoaded() {
285 for (OAuth2TokenServiceObserver observer : mObservers) {
286 observer.onRefreshTokensLoaded();
290 private static String[] getStoredAccounts(Context context) {
291 Set<String> accounts =
292 PreferenceManager.getDefaultSharedPreferences(context)
293 .getStringSet(STORED_ACCOUNTS_KEY, null);
294 return accounts == null ? new String[]{} : accounts.toArray(new String[accounts.size()]);
298 private static void saveStoredAccounts(Context context, String[] accounts) {
299 Set<String> set = new HashSet<String>(Arrays.asList(accounts));
300 PreferenceManager.getDefaultSharedPreferences(context).edit().
301 putStringSet(STORED_ACCOUNTS_KEY, set).apply();
304 private static native Object nativeGetForProfile(Profile profile);
305 private static native void nativeOAuth2TokenFetched(
306 String authToken, boolean result, long nativeCallback);
307 private native void nativeValidateAccounts(
308 long nativeAndroidProfileOAuth2TokenService,
309 String currentlySignedInAccount,
310 boolean forceNotifications);
311 private native void nativeFireRefreshTokenAvailableFromJava(
312 long nativeAndroidProfileOAuth2TokenService, String accountName);
313 private native void nativeFireRefreshTokenRevokedFromJava(
314 long nativeAndroidProfileOAuth2TokenService, String accountName);
315 private native void nativeFireRefreshTokensLoadedFromJava(
316 long nativeAndroidProfileOAuth2TokenService);