Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / sync / android / java / src / org / chromium / sync / signin / AccountManagerHelper.java
1 // Copyright 2011 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.sync.signin;
6
7
8 import android.accounts.Account;
9 import android.accounts.AccountManager;
10 import android.accounts.AccountManagerFuture;
11 import android.accounts.AuthenticatorDescription;
12 import android.accounts.AuthenticatorException;
13 import android.accounts.OperationCanceledException;
14 import android.app.Activity;
15 import android.content.Context;
16 import android.os.AsyncTask;
17 import android.os.Bundle;
18 import android.util.Log;
19
20 import org.chromium.base.ThreadUtils;
21 import org.chromium.base.VisibleForTesting;
22 import org.chromium.net.NetworkChangeNotifier;
23
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.Locale;
28 import java.util.concurrent.atomic.AtomicBoolean;
29 import java.util.concurrent.atomic.AtomicInteger;
30 import java.util.regex.Pattern;
31
32 import javax.annotation.Nullable;
33
34 /**
35  * AccountManagerHelper wraps our access of AccountManager in Android.
36  *
37  * Use the AccountManagerHelper.get(someContext) to instantiate it
38  */
39 public class AccountManagerHelper {
40
41     private static final String TAG = "AccountManagerHelper";
42
43     private static final Pattern AT_SYMBOL = Pattern.compile("@");
44
45     private static final String GMAIL_COM = "gmail.com";
46
47     private static final String GOOGLEMAIL_COM = "googlemail.com";
48
49     public static final String GOOGLE_ACCOUNT_TYPE = "com.google";
50
51     private static final Object sLock = new Object();
52
53     private static final int MAX_TRIES = 3;
54
55     private static AccountManagerHelper sAccountManagerHelper;
56
57     private final AccountManagerDelegate mAccountManager;
58
59     private Context mApplicationContext;
60
61     /**
62      * A simple callback for getAuthToken.
63      */
64     public interface GetAuthTokenCallback {
65         /**
66          * Invoked on the UI thread once a token has been provided by the AccountManager.
67          * @param token Auth token, or null if no token is available (bad credentials,
68          *      permission denied, etc).
69          */
70         void tokenAvailable(String token);
71     }
72
73     /**
74      * @param context the Android context
75      * @param accountManager the account manager to use as a backend service
76      */
77     private AccountManagerHelper(Context context,
78                                  AccountManagerDelegate accountManager) {
79         mApplicationContext = context.getApplicationContext();
80         mAccountManager = accountManager;
81     }
82
83     /**
84      * A factory method for the AccountManagerHelper.
85      *
86      * It is possible to override the AccountManager to use in tests for the instance of the
87      * AccountManagerHelper by calling overrideAccountManagerHelperForTests(...) with
88      * your MockAccountManager.
89      *
90      * @param context the applicationContext is retrieved from the context used as an argument.
91      * @return a singleton instance of the AccountManagerHelper
92      */
93     public static AccountManagerHelper get(Context context) {
94         synchronized (sLock) {
95             if (sAccountManagerHelper == null) {
96                 sAccountManagerHelper = new AccountManagerHelper(context,
97                         new SystemAccountManagerDelegate(context));
98             }
99         }
100         return sAccountManagerHelper;
101     }
102
103     @VisibleForTesting
104     public static void overrideAccountManagerHelperForTests(Context context,
105             AccountManagerDelegate accountManager) {
106         synchronized (sLock) {
107             sAccountManagerHelper = new AccountManagerHelper(context, accountManager);
108         }
109     }
110
111     /**
112      * Creates an Account object for the given name.
113      */
114     public static Account createAccountFromName(String name) {
115         return new Account(name, GOOGLE_ACCOUNT_TYPE);
116     }
117
118     public List<String> getGoogleAccountNames() {
119         List<String> accountNames = new ArrayList<String>();
120         for (Account account : getGoogleAccounts()) {
121             accountNames.add(account.name);
122         }
123         return accountNames;
124     }
125
126     public Account[] getGoogleAccounts() {
127         return mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
128     }
129
130     public boolean hasGoogleAccounts() {
131         return getGoogleAccounts().length > 0;
132     }
133
134     private String canonicalizeName(String name) {
135         String[] parts = AT_SYMBOL.split(name);
136         if (parts.length != 2) return name;
137
138         if (GOOGLEMAIL_COM.equalsIgnoreCase(parts[1])) {
139             parts[1] = GMAIL_COM;
140         }
141         if (GMAIL_COM.equalsIgnoreCase(parts[1])) {
142             parts[0] = parts[0].replace(".", "");
143         }
144         return (parts[0] + "@" + parts[1]).toLowerCase(Locale.US);
145     }
146
147     /**
148      * Returns the account if it exists, null otherwise.
149      */
150     public Account getAccountFromName(String accountName) {
151         String canonicalName = canonicalizeName(accountName);
152         Account[] accounts = getGoogleAccounts();
153         for (Account account : accounts) {
154             if (canonicalizeName(account.name).equals(canonicalName)) {
155                 return account;
156             }
157         }
158         return null;
159     }
160
161     /**
162      * Returns whether the accounts exists.
163      */
164     public boolean hasAccountForName(String accountName) {
165         return getAccountFromName(accountName) != null;
166     }
167
168     /**
169      * @return Whether or not there is an account authenticator for Google accounts.
170      */
171     public boolean hasGoogleAccountAuthenticator() {
172         AuthenticatorDescription[] descs = mAccountManager.getAuthenticatorTypes();
173         for (AuthenticatorDescription desc : descs) {
174             if (GOOGLE_ACCOUNT_TYPE.equals(desc.type)) return true;
175         }
176         return false;
177     }
178
179     /**
180      * Gets the auth token synchronously.
181      *
182      * - Assumes that the account is a valid account.
183      * - Should not be called on the main thread.
184      */
185     @Deprecated
186     public String getAuthTokenFromBackground(Account account, String authTokenType) {
187         AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(
188                 account, authTokenType, true, null, null);
189         AtomicBoolean errorEncountered = new AtomicBoolean(false);
190         return getAuthTokenInner(future, errorEncountered);
191     }
192
193     /**
194      * Gets the auth token and returns the response asynchronously.
195      * This should be called when we have a foreground activity that needs an auth token.
196      * If encountered an IO error, it will attempt to retry when the network is back.
197      *
198      * - Assumes that the account is a valid account.
199      */
200     public void getAuthTokenFromForeground(Activity activity, Account account, String authTokenType,
201                 GetAuthTokenCallback callback) {
202         AtomicInteger numTries = new AtomicInteger(0);
203         AtomicBoolean errorEncountered = new AtomicBoolean(false);
204         getAuthTokenAsynchronously(activity, account, authTokenType, callback, numTries,
205                 errorEncountered, null);
206     }
207
208     private class ConnectionRetry implements NetworkChangeNotifier.ConnectionTypeObserver {
209         private final Account mAccount;
210         private final String mAuthTokenType;
211         private final GetAuthTokenCallback mCallback;
212         private final AtomicInteger mNumTries;
213         private final AtomicBoolean mErrorEncountered;
214
215         ConnectionRetry(Account account, String authTokenType, GetAuthTokenCallback callback,
216                 AtomicInteger numTries, AtomicBoolean errorEncountered) {
217             mAccount = account;
218             mAuthTokenType = authTokenType;
219             mCallback = callback;
220             mNumTries = numTries;
221             mErrorEncountered = errorEncountered;
222         }
223
224         @Override
225         public void onConnectionTypeChanged(int connectionType) {
226             assert mNumTries.get() <= MAX_TRIES;
227             if (mNumTries.get() == MAX_TRIES) {
228                 NetworkChangeNotifier.removeConnectionTypeObserver(this);
229                 return;
230             }
231             if (NetworkChangeNotifier.isOnline()) {
232                 NetworkChangeNotifier.removeConnectionTypeObserver(this);
233                 getAuthTokenAsynchronously(null, mAccount, mAuthTokenType, mCallback, mNumTries,
234                         mErrorEncountered, this);
235             }
236         }
237     }
238
239     // Gets the auth token synchronously
240     private String getAuthTokenInner(AccountManagerFuture<Bundle> future,
241             AtomicBoolean errorEncountered) {
242         try {
243             Bundle result = future.getResult();
244             if (result != null) {
245                 return result.getString(AccountManager.KEY_AUTHTOKEN);
246             } else {
247                 Log.w(TAG, "Auth token - getAuthToken returned null");
248             }
249         } catch (OperationCanceledException e) {
250             Log.w(TAG, "Auth token - operation cancelled", e);
251         } catch (AuthenticatorException e) {
252             Log.w(TAG, "Auth token - authenticator exception", e);
253         } catch (IOException e) {
254             Log.w(TAG, "Auth token - IO exception", e);
255             errorEncountered.set(true);
256         }
257         return null;
258     }
259
260     private void getAuthTokenAsynchronously(@Nullable Activity activity, final Account account,
261             final String authTokenType, final GetAuthTokenCallback callback,
262             final AtomicInteger numTries, final AtomicBoolean errorEncountered,
263             final ConnectionRetry retry) {
264         final AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(
265                 account, authTokenType, true, null, null);
266         errorEncountered.set(false);
267
268         // On ICS onPostExecute is never called when running an AsyncTask from a different thread
269         // than the UI thread.
270         if (ThreadUtils.runningOnUiThread()) {
271             new AsyncTask<Void, Void, String>() {
272                 @Override
273                 public String doInBackground(Void... params) {
274                     return getAuthTokenInner(future, errorEncountered);
275                 }
276                 @Override
277                 public void onPostExecute(String authToken) {
278                     onGotAuthTokenResult(account, authTokenType, authToken, callback, numTries,
279                             errorEncountered, retry);
280                 }
281             }.execute();
282         } else {
283             String authToken = getAuthTokenInner(future, errorEncountered);
284             onGotAuthTokenResult(account, authTokenType, authToken, callback, numTries,
285                     errorEncountered, retry);
286         }
287     }
288
289     private void onGotAuthTokenResult(Account account, String authTokenType, String authToken,
290             GetAuthTokenCallback callback, AtomicInteger numTries, AtomicBoolean errorEncountered,
291             ConnectionRetry retry) {
292         if (authToken != null || !errorEncountered.get() ||
293                 numTries.incrementAndGet() == MAX_TRIES ||
294                 !NetworkChangeNotifier.isInitialized()) {
295             callback.tokenAvailable(authToken);
296             return;
297         }
298         if (retry == null) {
299             ConnectionRetry newRetry = new ConnectionRetry(account, authTokenType, callback,
300                     numTries, errorEncountered);
301             NetworkChangeNotifier.addConnectionTypeObserver(newRetry);
302         } else {
303             NetworkChangeNotifier.addConnectionTypeObserver(retry);
304         }
305     }
306
307     /**
308      * Invalidates the old token (if non-null/non-empty) and synchronously generates a new one.
309      * Also notifies the user (via status bar) if any user action is required. The method will
310      * return null if any user action is required to generate the new token.
311      *
312      * - Assumes that the account is a valid account.
313      * - Should not be called on the main thread.
314      */
315     @Deprecated
316     public String getNewAuthToken(Account account, String authToken, String authTokenType) {
317         invalidateAuthToken(authToken);
318
319         // TODO(dsmyers): consider reimplementing using an AccountManager function with an
320         // explicit timeout.
321         // Bug: https://code.google.com/p/chromium/issues/detail?id=172394.
322         try {
323             return mAccountManager.blockingGetAuthToken(account, authTokenType, true);
324         } catch (OperationCanceledException e) {
325             Log.w(TAG, "Auth token - operation cancelled", e);
326         } catch (AuthenticatorException e) {
327             Log.w(TAG, "Auth token - authenticator exception", e);
328         } catch (IOException e) {
329             Log.w(TAG, "Auth token - IO exception", e);
330         }
331         return null;
332     }
333
334     /**
335      * Invalidates the old token (if non-null/non-empty) and asynchronously generates a new one.
336      *
337      * - Assumes that the account is a valid account.
338      */
339     public void getNewAuthTokenFromForeground(Account account, String authToken,
340                 String authTokenType, GetAuthTokenCallback callback) {
341         invalidateAuthToken(authToken);
342         AtomicInteger numTries = new AtomicInteger(0);
343         AtomicBoolean errorEncountered = new AtomicBoolean(false);
344         getAuthTokenAsynchronously(
345                 null, account, authTokenType, callback, numTries, errorEncountered, null);
346     }
347
348     /**
349      * Removes an auth token from the AccountManager's cache.
350      */
351     public void invalidateAuthToken(String authToken) {
352         if (authToken != null && !authToken.isEmpty()) {
353             mAccountManager.invalidateAuthToken(GOOGLE_ACCOUNT_TYPE, authToken);
354         }
355     }
356 }