PageRenderTime 51ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/sync/android/java/src/org/chromium/sync/signin/AccountManagerHelper.java

https://gitlab.com/jonnialva90/iridium-browser
Java | 381 lines | 249 code | 45 blank | 87 comment | 37 complexity | e37fb93d6a5dedcb450500287dec1695 MD5 | raw file
  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. package org.chromium.sync.signin;
  5. import android.Manifest;
  6. import android.accounts.Account;
  7. import android.accounts.AccountManager;
  8. import android.accounts.AccountManagerFuture;
  9. import android.accounts.AuthenticatorDescription;
  10. import android.accounts.AuthenticatorException;
  11. import android.accounts.OperationCanceledException;
  12. import android.content.Context;
  13. import android.content.pm.PackageManager;
  14. import android.os.AsyncTask;
  15. import android.os.Bundle;
  16. import android.os.Process;
  17. import android.util.Log;
  18. import org.chromium.base.BuildInfo;
  19. import org.chromium.base.ThreadUtils;
  20. import org.chromium.base.VisibleForTesting;
  21. import org.chromium.net.NetworkChangeNotifier;
  22. import java.io.IOException;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.Locale;
  26. import java.util.concurrent.atomic.AtomicBoolean;
  27. import java.util.concurrent.atomic.AtomicInteger;
  28. import java.util.regex.Pattern;
  29. /**
  30. * AccountManagerHelper wraps our access of AccountManager in Android.
  31. *
  32. * Use the AccountManagerHelper.get(someContext) to instantiate it
  33. */
  34. public class AccountManagerHelper {
  35. private static final String TAG = "cr.Sync.Signin";
  36. private static final Pattern AT_SYMBOL = Pattern.compile("@");
  37. private static final String GMAIL_COM = "gmail.com";
  38. private static final String GOOGLEMAIL_COM = "googlemail.com";
  39. public static final String GOOGLE_ACCOUNT_TYPE = "com.google";
  40. /**
  41. * An account feature (corresponding to a Gaia service flag) that specifies whether the account
  42. * is a child account.
  43. */
  44. @VisibleForTesting public static final String FEATURE_IS_CHILD_ACCOUNT_KEY = "service_uca";
  45. private static final Object sLock = new Object();
  46. private static final int MAX_TRIES = 3;
  47. private static AccountManagerHelper sAccountManagerHelper;
  48. private final AccountManagerDelegate mAccountManager;
  49. private Context mApplicationContext;
  50. /**
  51. * A simple callback for getAuthToken.
  52. */
  53. public interface GetAuthTokenCallback {
  54. /**
  55. * Invoked on the UI thread if a token is provided by the AccountManager.
  56. *
  57. * @param token Auth token, guaranteed not to be null.
  58. */
  59. void tokenAvailable(String token);
  60. /**
  61. * Invoked on the UI thread if no token is available.
  62. *
  63. * @param isTransientError Indicates if the error is transient (network timeout or
  64. * unavailable, etc) or persistent (bad credentials, permission denied, etc).
  65. */
  66. void tokenUnavailable(boolean isTransientError);
  67. }
  68. /**
  69. * @param context the Android context
  70. * @param accountManager the account manager to use as a backend service
  71. */
  72. private AccountManagerHelper(Context context, AccountManagerDelegate accountManager) {
  73. mApplicationContext = context.getApplicationContext();
  74. mAccountManager = accountManager;
  75. }
  76. /**
  77. * Initialize AccountManagerHelper with a custom AccountManagerDelegate.
  78. * Ensures that the singleton AccountManagerHelper hasn't been created yet.
  79. * This can be overriden in tests using the overrideAccountManagerHelperForTests method.
  80. *
  81. * @param context the applicationContext is retrieved from the context used as an argument.
  82. * @param delegate the custom AccountManagerDelegate to use.
  83. */
  84. public static void initializeAccountManagerHelper(
  85. Context context, AccountManagerDelegate delegate) {
  86. synchronized (sLock) {
  87. assert sAccountManagerHelper == null;
  88. sAccountManagerHelper = new AccountManagerHelper(context, delegate);
  89. }
  90. }
  91. /**
  92. * A getter method for AccountManagerHelper singleton which also initializes it if not wasn't
  93. * already initialized.
  94. *
  95. * @param context the applicationContext is retrieved from the context used as an argument.
  96. * @return a singleton instance of the AccountManagerHelper
  97. */
  98. public static AccountManagerHelper get(Context context) {
  99. synchronized (sLock) {
  100. if (sAccountManagerHelper == null) {
  101. sAccountManagerHelper = new AccountManagerHelper(
  102. context, new SystemAccountManagerDelegate(context));
  103. }
  104. }
  105. return sAccountManagerHelper;
  106. }
  107. /**
  108. * Override AccountManagerHelper with a custom AccountManagerDelegate in tests.
  109. * Unlike initializeAccountManagerHelper, this will override the existing instance of
  110. * AccountManagerHelper if any. Only for use in Tests.
  111. *
  112. * @param context the applicationContext is retrieved from the context used as an argument.
  113. * @param delegate the custom AccountManagerDelegate to use.
  114. */
  115. @VisibleForTesting
  116. public static void overrideAccountManagerHelperForTests(
  117. Context context, AccountManagerDelegate delegate) {
  118. synchronized (sLock) {
  119. sAccountManagerHelper = new AccountManagerHelper(context, delegate);
  120. }
  121. }
  122. /**
  123. * Creates an Account object for the given name.
  124. */
  125. public static Account createAccountFromName(String name) {
  126. return new Account(name, GOOGLE_ACCOUNT_TYPE);
  127. }
  128. public List<String> getGoogleAccountNames() {
  129. List<String> accountNames = new ArrayList<String>();
  130. for (Account account : getGoogleAccounts()) {
  131. accountNames.add(account.name);
  132. }
  133. return accountNames;
  134. }
  135. /**
  136. * Returns all Google accounts on the device.
  137. * @return an array of accounts.
  138. */
  139. public Account[] getGoogleAccounts() {
  140. return mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE);
  141. }
  142. public void getGoogleAccounts(AccountManagerDelegate.Callback<Account[]> callback) {
  143. mAccountManager.getAccountsByType(GOOGLE_ACCOUNT_TYPE, callback);
  144. }
  145. public boolean hasGoogleAccounts() {
  146. return getGoogleAccounts().length > 0;
  147. }
  148. private String canonicalizeName(String name) {
  149. String[] parts = AT_SYMBOL.split(name);
  150. if (parts.length != 2) return name;
  151. if (GOOGLEMAIL_COM.equalsIgnoreCase(parts[1])) {
  152. parts[1] = GMAIL_COM;
  153. }
  154. if (GMAIL_COM.equalsIgnoreCase(parts[1])) {
  155. parts[0] = parts[0].replace(".", "");
  156. }
  157. return (parts[0] + "@" + parts[1]).toLowerCase(Locale.US);
  158. }
  159. /**
  160. * Returns the account if it exists, null otherwise.
  161. */
  162. public Account getAccountFromName(String accountName) {
  163. String canonicalName = canonicalizeName(accountName);
  164. Account[] accounts = getGoogleAccounts();
  165. for (Account account : accounts) {
  166. if (canonicalizeName(account.name).equals(canonicalName)) {
  167. return account;
  168. }
  169. }
  170. return null;
  171. }
  172. /**
  173. * Returns whether the accounts exists.
  174. */
  175. public boolean hasAccountForName(String accountName) {
  176. return getAccountFromName(accountName) != null;
  177. }
  178. /**
  179. * @return Whether or not there is an account authenticator for Google accounts.
  180. */
  181. public boolean hasGoogleAccountAuthenticator() {
  182. AuthenticatorDescription[] descs = mAccountManager.getAuthenticatorTypes();
  183. for (AuthenticatorDescription desc : descs) {
  184. if (GOOGLE_ACCOUNT_TYPE.equals(desc.type)) return true;
  185. }
  186. return false;
  187. }
  188. /**
  189. * Gets the auth token and returns the response asynchronously.
  190. * This should be called when we have a foreground activity that needs an auth token.
  191. * If encountered an IO error, it will attempt to retry when the network is back.
  192. *
  193. * - Assumes that the account is a valid account.
  194. */
  195. public void getAuthToken(Account account, String authTokenType, GetAuthTokenCallback callback) {
  196. AtomicInteger numTries = new AtomicInteger(0);
  197. AtomicBoolean isTransientError = new AtomicBoolean(false);
  198. getAuthTokenAsynchronously(
  199. account, authTokenType, callback, numTries, isTransientError, null);
  200. }
  201. private class ConnectionRetry implements NetworkChangeNotifier.ConnectionTypeObserver {
  202. private final Account mAccount;
  203. private final String mAuthTokenType;
  204. private final GetAuthTokenCallback mCallback;
  205. private final AtomicInteger mNumTries;
  206. private final AtomicBoolean mIsTransientError;
  207. ConnectionRetry(Account account, String authTokenType, GetAuthTokenCallback callback,
  208. AtomicInteger numTries, AtomicBoolean isTransientError) {
  209. mAccount = account;
  210. mAuthTokenType = authTokenType;
  211. mCallback = callback;
  212. mNumTries = numTries;
  213. mIsTransientError = isTransientError;
  214. }
  215. @Override
  216. public void onConnectionTypeChanged(int connectionType) {
  217. assert mNumTries.get() <= MAX_TRIES;
  218. if (mNumTries.get() == MAX_TRIES) {
  219. NetworkChangeNotifier.removeConnectionTypeObserver(this);
  220. return;
  221. }
  222. if (NetworkChangeNotifier.isOnline()) {
  223. NetworkChangeNotifier.removeConnectionTypeObserver(this);
  224. getAuthTokenAsynchronously(
  225. mAccount, mAuthTokenType, mCallback, mNumTries, mIsTransientError, this);
  226. }
  227. }
  228. }
  229. private boolean hasUseCredentialsPermission() {
  230. return BuildInfo.isMncOrLater()
  231. || mApplicationContext.checkPermission("android.permission.USE_CREDENTIALS",
  232. Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;
  233. }
  234. public boolean hasGetAccountsPermission() {
  235. return mApplicationContext.checkPermission(Manifest.permission.GET_ACCOUNTS,
  236. Process.myPid(), Process.myUid()) == PackageManager.PERMISSION_GRANTED;
  237. }
  238. // Gets the auth token synchronously
  239. private String getAuthTokenInner(AccountManagerFuture<Bundle> future,
  240. AtomicBoolean isTransientError) {
  241. try {
  242. Bundle result = future.getResult();
  243. if (result != null) {
  244. return result.getString(AccountManager.KEY_AUTHTOKEN);
  245. } else {
  246. Log.w(TAG, "Auth token - getAuthToken returned null");
  247. }
  248. } catch (OperationCanceledException e) {
  249. Log.w(TAG, "Auth token - operation cancelled", e);
  250. } catch (AuthenticatorException e) {
  251. Log.w(TAG, "Auth token - authenticator exception", e);
  252. } catch (IOException e) {
  253. Log.w(TAG, "Auth token - IO exception", e);
  254. isTransientError.set(true);
  255. }
  256. return null;
  257. }
  258. private void getAuthTokenAsynchronously(final Account account, final String authTokenType,
  259. final GetAuthTokenCallback callback, final AtomicInteger numTries,
  260. final AtomicBoolean isTransientError, final ConnectionRetry retry) {
  261. // Return null token for no USE_CREDENTIALS permission.
  262. if (!hasUseCredentialsPermission()) {
  263. ThreadUtils.runOnUiThread(new Runnable() {
  264. @Override
  265. public void run() {
  266. callback.tokenUnavailable(false);
  267. }
  268. });
  269. return;
  270. }
  271. final AccountManagerFuture<Bundle> future = mAccountManager.getAuthToken(
  272. account, authTokenType, true, null, null);
  273. isTransientError.set(false);
  274. new AsyncTask<Void, Void, String>() {
  275. @Override
  276. public String doInBackground(Void... params) {
  277. return getAuthTokenInner(future, isTransientError);
  278. }
  279. @Override
  280. public void onPostExecute(String authToken) {
  281. onGotAuthTokenResult(account, authTokenType, authToken, callback, numTries,
  282. isTransientError, retry);
  283. }
  284. }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  285. }
  286. private void onGotAuthTokenResult(Account account, String authTokenType, String authToken,
  287. GetAuthTokenCallback callback, AtomicInteger numTries, AtomicBoolean isTransientError,
  288. ConnectionRetry retry) {
  289. if (authToken != null) {
  290. callback.tokenAvailable(authToken);
  291. return;
  292. } else if (!isTransientError.get()
  293. || numTries.incrementAndGet() == MAX_TRIES
  294. || !NetworkChangeNotifier.isInitialized()) {
  295. callback.tokenUnavailable(isTransientError.get());
  296. return;
  297. }
  298. if (retry == null) {
  299. ConnectionRetry newRetry = new ConnectionRetry(account, authTokenType, callback,
  300. numTries, isTransientError);
  301. NetworkChangeNotifier.addConnectionTypeObserver(newRetry);
  302. } else {
  303. NetworkChangeNotifier.addConnectionTypeObserver(retry);
  304. }
  305. }
  306. /**
  307. * Invalidates the old token (if non-null/non-empty) and asynchronously generates a new one.
  308. *
  309. * - Assumes that the account is a valid account.
  310. */
  311. public void getNewAuthToken(Account account, String authToken, String authTokenType,
  312. GetAuthTokenCallback callback) {
  313. invalidateAuthToken(authToken);
  314. AtomicInteger numTries = new AtomicInteger(0);
  315. AtomicBoolean isTransientError = new AtomicBoolean(false);
  316. getAuthTokenAsynchronously(
  317. account, authTokenType, callback, numTries, isTransientError, null);
  318. }
  319. /**
  320. * Removes an auth token from the AccountManager's cache.
  321. */
  322. public void invalidateAuthToken(String authToken) {
  323. // Cancel operation for no USE_CREDENTIALS permission.
  324. if (!hasUseCredentialsPermission()) {
  325. return;
  326. }
  327. if (authToken != null && !authToken.isEmpty()) {
  328. mAccountManager.invalidateAuthToken(GOOGLE_ACCOUNT_TYPE, authToken);
  329. }
  330. }
  331. public void checkChildAccount(
  332. Account account, AccountManagerDelegate.Callback<Boolean> callback) {
  333. String[] features = {FEATURE_IS_CHILD_ACCOUNT_KEY};
  334. mAccountManager.hasFeatures(account, features, callback);
  335. }
  336. }