Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: sync/android/java/src/org/chromium/sync/signin/AccountManagerHelper.java

Issue 2130453004: [Sync] Move //sync to //components/sync. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase. Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.Manifest;
9 import android.accounts.Account;
10 import android.accounts.AuthenticatorDescription;
11 import android.content.Context;
12 import android.content.pm.PackageManager;
13 import android.os.AsyncTask;
14 import android.os.Process;
15
16 import org.chromium.base.Callback;
17 import org.chromium.base.Log;
18 import org.chromium.base.VisibleForTesting;
19 import org.chromium.net.NetworkChangeNotifier;
20
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.Locale;
24 import java.util.concurrent.atomic.AtomicBoolean;
25 import java.util.concurrent.atomic.AtomicInteger;
26 import java.util.regex.Pattern;
27
28 /**
29 * AccountManagerHelper wraps our access of AccountManager in Android.
30 *
31 * Use the AccountManagerHelper.get(someContext) to instantiate it
32 */
33 public class AccountManagerHelper {
34 private static final String TAG = "Sync_Signin";
35
36 private static final Pattern AT_SYMBOL = Pattern.compile("@");
37
38 private static final String GMAIL_COM = "gmail.com";
39
40 private static final String GOOGLEMAIL_COM = "googlemail.com";
41
42 public static final String GOOGLE_ACCOUNT_TYPE = "com.google";
43
44 /**
45 * An account feature (corresponding to a Gaia service flag) that specifies whether the account
46 * is a child account.
47 */
48 @VisibleForTesting public static final String FEATURE_IS_CHILD_ACCOUNT_KEY = "service_uca";
49
50 private static final Object sLock = new Object();
51
52 private static AccountManagerHelper sAccountManagerHelper;
53
54 private final AccountManagerDelegate mAccountManager;
55
56 private Context mApplicationContext;
57
58 /**
59 * A simple callback for getAuthToken.
60 */
61 public interface GetAuthTokenCallback {
62 /**
63 * Invoked on the UI thread if a token is provided by the AccountManager .
64 *
65 * @param token Auth token, guaranteed not to be null.
66 */
67 void tokenAvailable(String token);
68
69 /**
70 * Invoked on the UI thread if no token is available.
71 *
72 * @param isTransientError Indicates if the error is transient (network timeout or
73 * unavailable, etc) or persistent (bad credentials, permission denied, etc).
74 */
75 void tokenUnavailable(boolean isTransientError);
76 }
77
78 /**
79 * @param context the Android context
80 * @param accountManager the account manager to use as a backend service
81 */
82 private AccountManagerHelper(Context context, AccountManagerDelegate account Manager) {
83 mApplicationContext = context.getApplicationContext();
84 mAccountManager = accountManager;
85 }
86
87 /**
88 * Initialize AccountManagerHelper with a custom AccountManagerDelegate.
89 * Ensures that the singleton AccountManagerHelper hasn't been created yet.
90 * This can be overriden in tests using the overrideAccountManagerHelperForT ests method.
91 *
92 * @param context the applicationContext is retrieved from the context used as an argument.
93 * @param delegate the custom AccountManagerDelegate to use.
94 */
95 public static void initializeAccountManagerHelper(
96 Context context, AccountManagerDelegate delegate) {
97 synchronized (sLock) {
98 assert sAccountManagerHelper == null;
99 sAccountManagerHelper = new AccountManagerHelper(context, delegate);
100 }
101 }
102
103 /**
104 * A getter method for AccountManagerHelper singleton which also initializes it if not wasn't
105 * already initialized.
106 *
107 * @param context the applicationContext is retrieved from the context used as an argument.
108 * @return a singleton instance of the AccountManagerHelper
109 */
110 public static AccountManagerHelper get(Context context) {
111 synchronized (sLock) {
112 if (sAccountManagerHelper == null) {
113 sAccountManagerHelper = new AccountManagerHelper(
114 context, new SystemAccountManagerDelegate(context));
115 }
116 }
117 return sAccountManagerHelper;
118 }
119
120 /**
121 * Override AccountManagerHelper with a custom AccountManagerDelegate in tes ts.
122 * Unlike initializeAccountManagerHelper, this will override the existing in stance of
123 * AccountManagerHelper if any. Only for use in Tests.
124 *
125 * @param context the applicationContext is retrieved from the context used as an argument.
126 * @param delegate the custom AccountManagerDelegate to use.
127 */
128 @VisibleForTesting
129 public static void overrideAccountManagerHelperForTests(
130 Context context, AccountManagerDelegate delegate) {
131 synchronized (sLock) {
132 sAccountManagerHelper = new AccountManagerHelper(context, delegate);
133 }
134 }
135
136 /**
137 * Creates an Account object for the given name.
138 */
139 public static Account createAccountFromName(String name) {
140 return new Account(name, GOOGLE_ACCOUNT_TYPE);
141 }
142
143 /**
144 * This method is deprecated; please use the asynchronous version below inst ead.
145 *
146 * See http://crbug.com/517697 for details.
147 */
148 public List<String> getGoogleAccountNames() {
149 List<String> accountNames = new ArrayList<String>();
150 for (Account account : getGoogleAccounts()) {
151 accountNames.add(account.name);
152 }
153 return accountNames;
154 }
155
156 /**
157 * Retrieves a list of the Google account names on the device asynchronously .
158 */
159 public void getGoogleAccountNames(final Callback<List<String>> callback) {
160 getGoogleAccounts(new Callback<Account[]>() {
161 @Override
162 public void onResult(Account[] accounts) {
163 List<String> accountNames = new ArrayList<String>();
164 for (Account account : accounts) {
165 accountNames.add(account.name);
166 }
167 callback.onResult(accountNames);
168 }
169 });
170 }
171
172 /**
173 * This method is deprecated; please use the asynchronous version below inst ead.
174 *
175 * See http://crbug.com/517697 for details.
176 */
177 public Account[] getGoogleAccounts() {
178 return mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
179 }
180
181 /**
182 * Retrieves all Google accounts on the device asynchronously.
183 */
184 public void getGoogleAccounts(Callback<Account[]> callback) {
185 mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE, callback);
186 }
187
188 /**
189 * This method is deprecated; please use the asynchronous version below inst ead.
190 *
191 * See http://crbug.com/517697 for details.
192 */
193 public boolean hasGoogleAccounts() {
194 return getGoogleAccounts().length > 0;
195 }
196
197 /**
198 * Asynchronously determine whether any Google accounts have been added.
199 */
200 public void hasGoogleAccounts(final Callback<Boolean> callback) {
201 getGoogleAccounts(new Callback<Account[]>() {
202 @Override
203 public void onResult(Account[] accounts) {
204 callback.onResult(accounts.length > 0);
205 }
206 });
207 }
208
209 private String canonicalizeName(String name) {
210 String[] parts = AT_SYMBOL.split(name);
211 if (parts.length != 2) return name;
212
213 if (GOOGLEMAIL_COM.equalsIgnoreCase(parts[1])) {
214 parts[1] = GMAIL_COM;
215 }
216 if (GMAIL_COM.equalsIgnoreCase(parts[1])) {
217 parts[0] = parts[0].replace(".", "");
218 }
219 return (parts[0] + "@" + parts[1]).toLowerCase(Locale.US);
220 }
221
222 /**
223 * This method is deprecated; please use the asynchronous version below inst ead.
224 *
225 * See http://crbug.com/517697 for details.
226 */
227 public Account getAccountFromName(String accountName) {
228 String canonicalName = canonicalizeName(accountName);
229 Account[] accounts = getGoogleAccounts();
230 for (Account account : accounts) {
231 if (canonicalizeName(account.name).equals(canonicalName)) {
232 return account;
233 }
234 }
235 return null;
236 }
237
238 /**
239 * Asynchronously returns the account if it exists; null otherwise.
240 */
241 public void getAccountFromName(String accountName, final Callback<Account> c allback) {
242 final String canonicalName = canonicalizeName(accountName);
243 getGoogleAccounts(new Callback<Account[]>() {
244 @Override
245 public void onResult(Account[] accounts) {
246 Account accountForName = null;
247 for (Account account : accounts) {
248 if (canonicalizeName(account.name).equals(canonicalName)) {
249 accountForName = account;
250 break;
251 }
252 }
253 callback.onResult(accountForName);
254 }
255 });
256 }
257
258 /**
259 * This method is deprecated; please use the asynchronous version below inst ead.
260 *
261 * See http://crbug.com/517697 for details.
262 */
263 public boolean hasAccountForName(String accountName) {
264 return getAccountFromName(accountName) != null;
265 }
266
267 /**
268 * Asynchronously returns whether an account exists with the given name.
269 */
270 // TODO(maxbogue): Remove once this function is used outside of tests.
271 @VisibleForTesting
272 public void hasAccountForName(String accountName, final Callback<Boolean> ca llback) {
273 getAccountFromName(accountName, new Callback<Account>() {
274 @Override
275 public void onResult(Account account) {
276 callback.onResult(account != null);
277 }
278 });
279 }
280
281 /**
282 * @return Whether or not there is an account authenticator for Google accou nts.
283 */
284 public boolean hasGoogleAccountAuthenticator() {
285 AuthenticatorDescription[] descs = mAccountManager.getAuthenticatorTypes ();
286 for (AuthenticatorDescription desc : descs) {
287 if (GOOGLE_ACCOUNT_TYPE.equals(desc.type)) return true;
288 }
289 return false;
290 }
291
292 /**
293 * Gets the auth token and returns the response asynchronously.
294 * This should be called when we have a foreground activity that needs an au th token.
295 * If encountered an IO error, it will attempt to retry when the network is back.
296 *
297 * - Assumes that the account is a valid account.
298 */
299 public void getAuthToken(final Account account, final String authTokenType,
300 final GetAuthTokenCallback callback) {
301 ConnectionRetry.runAuthTask(new AuthTask<String>() {
302 @Override
303 public String run() throws AuthException {
304 return mAccountManager.getAuthToken(account, authTokenType);
305 }
306 @Override
307 public void onSuccess(String token) {
308 callback.tokenAvailable(token);
309 }
310 @Override
311 public void onFailure(boolean isTransientError) {
312 callback.tokenUnavailable(isTransientError);
313 }
314 });
315 }
316
317 public boolean hasGetAccountsPermission() {
318 return mApplicationContext.checkPermission(Manifest.permission.GET_ACCOU NTS,
319 Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_G RANTED;
320 }
321
322 /**
323 * Invalidates the old token (if non-null/non-empty) and asynchronously gene rates a new one.
324 *
325 * - Assumes that the account is a valid account.
326 */
327 public void getNewAuthToken(Account account, String authToken, String authTo kenType,
328 GetAuthTokenCallback callback) {
329 invalidateAuthToken(authToken);
330 getAuthToken(account, authTokenType, callback);
331 }
332
333 /**
334 * Clear an auth token from the local cache with respect to the ApplicationC ontext.
335 */
336 public void invalidateAuthToken(final String authToken) {
337 if (authToken == null || authToken.isEmpty()) {
338 return;
339 }
340 ConnectionRetry.runAuthTask(new AuthTask<Boolean>() {
341 @Override
342 public Boolean run() throws AuthException {
343 mAccountManager.invalidateAuthToken(authToken);
344 return true;
345 }
346 @Override
347 public void onSuccess(Boolean result) {}
348 @Override
349 public void onFailure(boolean isTransientError) {
350 Log.e(TAG, "Failed to invalidate auth token: " + authToken);
351 }
352 });
353 }
354
355 public void checkChildAccount(Account account, Callback<Boolean> callback) {
356 String[] features = {FEATURE_IS_CHILD_ACCOUNT_KEY};
357 mAccountManager.hasFeatures(account, features, callback);
358 }
359
360 private interface AuthTask<T> {
361 T run() throws AuthException;
362 void onSuccess(T result);
363 void onFailure(boolean isTransientError);
364 }
365
366 /**
367 * A helper class to encapsulate network connection retry logic for AuthTask s.
368 *
369 * The task will be run on the background thread. If it encounters a transie nt error, it will
370 * wait for a network change and retry up to MAX_TRIES times.
371 */
372 private static class ConnectionRetry<T>
373 implements NetworkChangeNotifier.ConnectionTypeObserver {
374 private static final int MAX_TRIES = 3;
375
376 private final AuthTask<T> mAuthTask;
377 private final AtomicInteger mNumTries;
378 private final AtomicBoolean mIsTransientError;
379
380 public static <T> void runAuthTask(AuthTask<T> authTask) {
381 new ConnectionRetry<T>(authTask).attempt();
382 }
383
384 private ConnectionRetry(AuthTask<T> authTask) {
385 mAuthTask = authTask;
386 mNumTries = new AtomicInteger(0);
387 mIsTransientError = new AtomicBoolean(false);
388 }
389
390 /**
391 * Tries running the {@link AuthTask} in the background. This object is never registered
392 * as a {@link ConnectionTypeObserver} when this method is called.
393 */
394 private void attempt() {
395 // Clear any transient error.
396 mIsTransientError.set(false);
397 new AsyncTask<Void, Void, T>() {
398 @Override
399 public T doInBackground(Void... params) {
400 try {
401 return mAuthTask.run();
402 } catch (AuthException ex) {
403 Log.w(TAG, "Failed to perform auth task", ex);
404 mIsTransientError.set(ex.isTransientError());
405 }
406 return null;
407 }
408 @Override
409 public void onPostExecute(T result) {
410 if (result != null) {
411 mAuthTask.onSuccess(result);
412 } else if (!mIsTransientError.get()
413 || mNumTries.incrementAndGet() >= MAX_TRIES
414 || !NetworkChangeNotifier.isInitialized()) {
415 // Permanent error, ran out of tries, or we can't listen for network
416 // change events; give up.
417 mAuthTask.onFailure(mIsTransientError.get());
418 } else {
419 // Transient error with tries left; register for another attempt.
420 NetworkChangeNotifier.addConnectionTypeObserver(Connecti onRetry.this);
421 }
422 }
423 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
424 }
425
426 @Override
427 public void onConnectionTypeChanged(int connectionType) {
428 assert mNumTries.get() < MAX_TRIES;
429 if (NetworkChangeNotifier.isOnline()) {
430 // The network is back; stop listening and try again.
431 NetworkChangeNotifier.removeConnectionTypeObserver(this);
432 attempt();
433 }
434 }
435 }
436 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698