Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / android / java / src / org / chromium / chrome / browser / signin / OAuth2TokenService.java
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.
4
5 package org.chromium.chrome.browser.signin;
6
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;
12
13 import com.google.common.annotations.VisibleForTesting;
14
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;
21
22 import java.util.Arrays;
23 import java.util.HashSet;
24 import java.util.Set;
25 import java.util.concurrent.Semaphore;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.atomic.AtomicReference;
28
29 import javax.annotation.Nullable;
30
31 /**
32  * Java instance for the native OAuth2TokenService.
33  * <p/>
34  * This class forwards calls to request or invalidate access tokens made by native code to
35  * AccountManagerHelper and forwards callbacks to native code.
36  * <p/>
37  */
38 public final class OAuth2TokenService {
39
40     private static final String TAG = "OAuth2TokenService";
41
42     @VisibleForTesting
43     public static final String STORED_ACCOUNTS_KEY = "google.services.stored_accounts";
44
45     /**
46      * Classes that want to listen for refresh token availability should
47      * implement this interface and register with {@link #addObserver}.
48      */
49     public interface OAuth2TokenServiceObserver {
50         void onRefreshTokenAvailable(Account account);
51         void onRefreshTokenRevoked(Account account);
52         void onRefreshTokensLoaded();
53     }
54
55     private static final String OAUTH2_SCOPE_PREFIX = "oauth2:";
56
57     private final long mNativeProfileOAuth2TokenService;
58     private final ObserverList<OAuth2TokenServiceObserver> mObservers;
59
60     private OAuth2TokenService(long nativeOAuth2Service) {
61         mNativeProfileOAuth2TokenService = nativeOAuth2Service;
62         mObservers = new ObserverList<OAuth2TokenServiceObserver>();
63     }
64
65     public static OAuth2TokenService getForProfile(Profile profile) {
66         ThreadUtils.assertOnUiThread();
67         return (OAuth2TokenService) nativeGetForProfile(profile);
68     }
69
70     @CalledByNative
71     private static OAuth2TokenService create(long nativeOAuth2Service) {
72         ThreadUtils.assertOnUiThread();
73         return new OAuth2TokenService(nativeOAuth2Service);
74     }
75
76     public void addObserver(OAuth2TokenServiceObserver observer) {
77         ThreadUtils.assertOnUiThread();
78         mObservers.addObserver(observer);
79     }
80
81     public void removeObserver(OAuth2TokenServiceObserver observer) {
82         ThreadUtils.assertOnUiThread();
83         mObservers.removeObserver(observer);
84     }
85
86     private static Account getAccountOrNullFromUsername(Context context, String username) {
87         if (username == null) {
88             Log.e(TAG, "Username is null");
89             return null;
90         }
91
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.");
96             return null;
97         }
98         return account;
99     }
100
101     /**
102      * Called by native to list the activite accounts in the OS.
103      */
104     @VisibleForTesting
105     @CalledByNative
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()]);
110     }
111
112     /**
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
116      * in sync.
117      */
118     @CalledByNative
119     public static String[] getAccounts(Context context) {
120         return getStoredAccounts(context);
121     }
122
123     /**
124      * Called by native to retrieve OAuth2 tokens.
125      *
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.
129      */
130     @CalledByNative
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);
136             return;
137         }
138         String oauth2Scope = OAUTH2_SCOPE_PREFIX + scope;
139
140         AccountManagerHelper accountManagerHelper = AccountManagerHelper.get(context);
141         accountManagerHelper.getAuthTokenFromForeground(
142             null, account, oauth2Scope, new AccountManagerHelper.GetAuthTokenCallback() {
143                 @Override
144                 public void tokenAvailable(String token) {
145                     nativeOAuth2TokenFetched(
146                         token, token != null, nativeCallback);
147                 }
148             });
149     }
150
151     /**
152      * Call this method to retrieve an OAuth2 access token for the given account and scope.
153      *
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.
158      */
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);
165     }
166
167     /**
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.
170      *
171      * Given that this is a blocking method call, this should never be called from the UI thread.
172      *
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|.
178      */
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() {
188                     @Override
189                     public void tokenAvailable(String token) {
190                         result.set(token);
191                         semaphore.release();
192                     }
193                 });
194         try {
195             if (semaphore.tryAcquire(timeout, unit)) {
196                 return result.get();
197             } else {
198                 Log.d(TAG, "Failed to retrieve auth token within timeout (" +
199                         timeout + " + " + unit.name() + ")");
200                 return null;
201             }
202         } catch (InterruptedException e) {
203             Log.w(TAG, "Got interrupted while waiting for auth token");
204             return null;
205         }
206     }
207
208     /**
209      * Called by native to check wether the account has an OAuth2 refresh token.
210      */
211     @CalledByNative
212     public static boolean hasOAuth2RefreshToken(Context context, String accountName) {
213         return AccountManagerHelper.get(context).hasAccountForName(accountName);
214     }
215
216     /**
217     * Called by native to invalidate an OAuth2 token.
218     */
219     @CalledByNative
220     public static void invalidateOAuth2AuthToken(Context context, String accessToken) {
221         if (accessToken != null) {
222             AccountManagerHelper.get(context).invalidateAuthToken(accessToken);
223         }
224     }
225
226     @CalledByNative
227     public void validateAccounts(Context context, boolean forceNotifications) {
228         ThreadUtils.assertOnUiThread();
229         String currentlySignedInAccount =
230                 ChromeSigninController.get(context).getSignedInAccountName();
231         nativeValidateAccounts(mNativeProfileOAuth2TokenService, currentlySignedInAccount,
232                                forceNotifications);
233     }
234
235     /**
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.
239      */
240     public void fireRefreshTokenAvailable(Account account) {
241         ThreadUtils.assertOnUiThread();
242         assert account != null;
243         nativeFireRefreshTokenAvailableFromJava(mNativeProfileOAuth2TokenService, account.name);
244     }
245
246     @CalledByNative
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);
252         }
253     }
254
255     /**
256      * Triggers a notification to all observers of the native and Java instance of the
257      * OAuth2TokenService that a refresh token is now revoked.
258      */
259     public void fireRefreshTokenRevoked(Account account) {
260         ThreadUtils.assertOnUiThread();
261         assert account != null;
262         nativeFireRefreshTokenRevokedFromJava(mNativeProfileOAuth2TokenService, account.name);
263     }
264
265     @CalledByNative
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);
271         }
272     }
273
274     /**
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.
277      */
278     public void fireRefreshTokensLoaded() {
279         ThreadUtils.assertOnUiThread();
280         nativeFireRefreshTokensLoadedFromJava(mNativeProfileOAuth2TokenService);
281     }
282
283     @CalledByNative
284     public void notifyRefreshTokensLoaded() {
285         for (OAuth2TokenServiceObserver observer : mObservers) {
286             observer.onRefreshTokensLoaded();
287         }
288     }
289
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()]);
295     }
296
297     @CalledByNative
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();
302     }
303
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);
317 }