Upstream version 7.36.149.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.concurrent.Semaphore;
24 import java.util.concurrent.TimeUnit;
25 import java.util.concurrent.atomic.AtomicReference;
26 import java.util.HashSet;
27 import java.util.Set;
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     public interface OAuth2TokenServiceObserver {
46         void onRefreshTokenAvailable(Account account);
47         void onRefreshTokenRevoked(Account account);
48         void onRefreshTokensLoaded();
49     }
50
51     private static final String OAUTH2_SCOPE_PREFIX = "oauth2:";
52
53     private final long mNativeProfileOAuth2TokenService;
54     private final ObserverList<OAuth2TokenServiceObserver> mObservers;
55
56     private OAuth2TokenService(long nativeOAuth2Service) {
57         mNativeProfileOAuth2TokenService = nativeOAuth2Service;
58         mObservers = new ObserverList<OAuth2TokenServiceObserver>();
59     }
60
61     public static OAuth2TokenService getForProfile(Profile profile) {
62         ThreadUtils.assertOnUiThread();
63         return (OAuth2TokenService) nativeGetForProfile(profile);
64     }
65
66     @CalledByNative
67     private static OAuth2TokenService create(long nativeOAuth2Service) {
68         ThreadUtils.assertOnUiThread();
69         return new OAuth2TokenService(nativeOAuth2Service);
70     }
71
72     public void addObserver(OAuth2TokenServiceObserver observer) {
73         ThreadUtils.assertOnUiThread();
74         mObservers.addObserver(observer);
75     }
76
77     public void removeObserver(OAuth2TokenServiceObserver observer) {
78         ThreadUtils.assertOnUiThread();
79         mObservers.removeObserver(observer);
80     }
81
82     private static Account getAccountOrNullFromUsername(Context context, String username) {
83         if (username == null) {
84             Log.e(TAG, "Username is null");
85             return null;
86         }
87
88         AccountManagerHelper accountManagerHelper = AccountManagerHelper.get(context);
89         Account account = accountManagerHelper.getAccountFromName(username);
90         if (account == null) {
91             Log.e(TAG, "Account not found for provided username.");
92             return null;
93         }
94         return account;
95     }
96
97     /**
98      * Called by native to list the activite accounts in the OS.
99      */
100     @VisibleForTesting
101     @CalledByNative
102     public static String[] getSystemAccounts(Context context) {
103         AccountManagerHelper accountManagerHelper = AccountManagerHelper.get(context);
104         java.util.List<String> accountNames = accountManagerHelper.getGoogleAccountNames();
105         return accountNames.toArray(new String[accountNames.size()]);
106     }
107
108     /**
109      * Called by native to list the accounts with OAuth2 refresh tokens.
110      * This can differ from getSystemAccounts as the user add/remove accounts
111      * from the OS. validateAccounts should be called to keep these two
112      * in sync.
113      */
114     @CalledByNative
115     public static String[] getAccounts(Context context) {
116         return getStoredAccounts(context);
117     }
118
119     /**
120      * Called by native to retrieve OAuth2 tokens.
121      *
122      * @param username The native username (full address).
123      * @param scope The scope to get an auth token for (without Android-style 'oauth2:' prefix).
124      * @param nativeCallback The pointer to the native callback that should be run upon completion.
125      */
126     @CalledByNative
127     public static void getOAuth2AuthToken(
128             Context context, String username, String scope, final long nativeCallback) {
129         Account account = getAccountOrNullFromUsername(context, username);
130         if (account == null) {
131             nativeOAuth2TokenFetched(null, false, nativeCallback);
132             return;
133         }
134         String oauth2Scope = OAUTH2_SCOPE_PREFIX + scope;
135
136         AccountManagerHelper accountManagerHelper = AccountManagerHelper.get(context);
137         accountManagerHelper.getAuthTokenFromForeground(
138             null, account, oauth2Scope, new AccountManagerHelper.GetAuthTokenCallback() {
139                 @Override
140                 public void tokenAvailable(String token) {
141                     nativeOAuth2TokenFetched(
142                         token, token != null, nativeCallback);
143                 }
144             });
145     }
146
147     /**
148      * Call this method to retrieve an OAuth2 access token for the given account and scope.
149      *
150      * @param activity the current activity. May be null.
151      * @param account the account to get the access token for.
152      * @param scope The scope to get an auth token for (without Android-style 'oauth2:' prefix).
153      * @param callback called on successful and unsuccessful fetching of auth token.
154      */
155     public static void getOAuth2AccessToken(Context context, @Nullable Activity activity,
156                                             Account account, String scope,
157                                             AccountManagerHelper.GetAuthTokenCallback callback) {
158         String oauth2Scope = OAUTH2_SCOPE_PREFIX + scope;
159         AccountManagerHelper.get(context).getAuthTokenFromForeground(
160                 activity, account, oauth2Scope, callback);
161     }
162
163     /**
164      * Call this method to retrieve an OAuth2 access token for the given account and scope. This
165      * method times out after the specified timeout, and will return null if that happens.
166      *
167      * Given that this is a blocking method call, this should never be called from the UI thread.
168      *
169      * @param activity the current activity. May be null.
170      * @param account the account to get the access token for.
171      * @param scope The scope to get an auth token for (without Android-style 'oauth2:' prefix).
172      * @param timeout the timeout.
173      * @param unit the unit for |timeout|.
174      */
175     public static String getOAuth2AccessTokenWithTimeout(
176             Context context, @Nullable Activity activity, Account account, String scope,
177             long timeout, TimeUnit unit) {
178         assert !ThreadUtils.runningOnUiThread();
179         final AtomicReference<String> result = new AtomicReference<String>();
180         final Semaphore semaphore = new Semaphore(0);
181         getOAuth2AccessToken(
182                 context, activity, account, scope,
183                 new AccountManagerHelper.GetAuthTokenCallback() {
184                     @Override
185                     public void tokenAvailable(String token) {
186                         result.set(token);
187                         semaphore.release();
188                     }
189                 });
190         try {
191             if (semaphore.tryAcquire(timeout, unit)) {
192                 return result.get();
193             } else {
194                 Log.d(TAG, "Failed to retrieve auth token within timeout (" +
195                         timeout + " + " + unit.name() + ")");
196                 return null;
197             }
198         } catch (InterruptedException e) {
199             Log.w(TAG, "Got interrupted while waiting for auth token");
200             return null;
201         }
202     }
203
204     /**
205      * Called by native to check wether the account has an OAuth2 refresh token.
206      */
207     @CalledByNative
208     public static boolean hasOAuth2RefreshToken(Context context, String accountName) {
209         return AccountManagerHelper.get(context).hasAccountForName(accountName);
210     }
211
212     /**
213     * Called by native to invalidate an OAuth2 token.
214     */
215     @CalledByNative
216     public static void invalidateOAuth2AuthToken(Context context, String accessToken) {
217         if (accessToken != null) {
218             AccountManagerHelper.get(context).invalidateAuthToken(accessToken);
219         }
220     }
221
222     public void validateAccounts(Context context) {
223         ThreadUtils.assertOnUiThread();
224         String currentlySignedInAccount =
225                 ChromeSigninController.get(context).getSignedInAccountName();
226         nativeValidateAccounts(mNativeProfileOAuth2TokenService, currentlySignedInAccount);
227     }
228
229     /**
230      * Triggers a notification to all observers of the native and Java instance of the
231      * OAuth2TokenService that a refresh token is now available. This may cause observers to retry
232      * operations that require authentication.
233      */
234     public void fireRefreshTokenAvailable(Account account) {
235         ThreadUtils.assertOnUiThread();
236         assert account != null;
237         nativeFireRefreshTokenAvailableFromJava(mNativeProfileOAuth2TokenService, account.name);
238     }
239
240     @CalledByNative
241     public void notifyRefreshTokenAvailable(String accountName) {
242         assert accountName != null;
243         Account account = AccountManagerHelper.createAccountFromName(accountName);
244         for (OAuth2TokenServiceObserver observer : mObservers) {
245             observer.onRefreshTokenAvailable(account);
246         }
247     }
248
249     /**
250      * Triggers a notification to all observers of the native and Java instance of the
251      * OAuth2TokenService that a refresh token is now revoked.
252      */
253     public void fireRefreshTokenRevoked(Account account) {
254         ThreadUtils.assertOnUiThread();
255         assert account != null;
256         nativeFireRefreshTokenRevokedFromJava(mNativeProfileOAuth2TokenService, account.name);
257     }
258
259     @CalledByNative
260     public void notifyRefreshTokenRevoked(String accountName) {
261         assert accountName != null;
262         Account account = AccountManagerHelper.createAccountFromName(accountName);
263         for (OAuth2TokenServiceObserver observer : mObservers) {
264             observer.onRefreshTokenRevoked(account);
265         }
266     }
267
268     /**
269      * Triggers a notification to all observers of the native and Java instance of the
270      * OAuth2TokenService that all refresh tokens now have been loaded.
271      */
272     public void fireRefreshTokensLoaded() {
273         ThreadUtils.assertOnUiThread();
274         nativeFireRefreshTokensLoadedFromJava(mNativeProfileOAuth2TokenService);
275     }
276
277     @CalledByNative
278     public void notifyRefreshTokensLoaded() {
279         for (OAuth2TokenServiceObserver observer : mObservers) {
280             observer.onRefreshTokensLoaded();
281         }
282     }
283
284     private static String[] getStoredAccounts(Context context) {
285         Set<String> accounts =
286                 PreferenceManager.getDefaultSharedPreferences(context)
287                         .getStringSet(STORED_ACCOUNTS_KEY, null);
288         return accounts == null ? new String[]{} : accounts.toArray(new String[accounts.size()]);
289     }
290
291     @CalledByNative
292     private static void saveStoredAccounts(Context context, String[] accounts) {
293         Set<String> set = new HashSet<String>(Arrays.asList(accounts));
294         PreferenceManager.getDefaultSharedPreferences(context).edit().
295                 putStringSet(STORED_ACCOUNTS_KEY, set).apply();
296     }
297
298     private static native Object nativeGetForProfile(Profile profile);
299     private static native void nativeOAuth2TokenFetched(
300             String authToken, boolean result, long nativeCallback);
301     private native void nativeValidateAccounts(
302             long nativeAndroidProfileOAuth2TokenService,
303             String currentlySignedInAccount);
304     private native void nativeFireRefreshTokenAvailableFromJava(
305             long nativeAndroidProfileOAuth2TokenService, String accountName);
306     private native void nativeFireRefreshTokenRevokedFromJava(
307             long nativeAndroidProfileOAuth2TokenService, String accountName);
308     private native void nativeFireRefreshTokensLoadedFromJava(
309             long nativeAndroidProfileOAuth2TokenService);
310 }