1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.accounts; 18 19 import android.app.Activity; 20 import android.content.Intent; 21 import android.content.Context; 22 import android.content.IntentFilter; 23 import android.content.BroadcastReceiver; 24 import android.database.SQLException; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.Looper; 28 import android.os.RemoteException; 29 import android.os.Parcelable; 30 import android.os.Build; 31 import android.util.Log; 32 import android.text.TextUtils; 33 34 import java.io.IOException; 35 import java.util.concurrent.Callable; 36 import java.util.concurrent.CancellationException; 37 import java.util.concurrent.ExecutionException; 38 import java.util.concurrent.FutureTask; 39 import java.util.concurrent.TimeoutException; 40 import java.util.concurrent.TimeUnit; 41 import java.util.HashMap; 42 import java.util.Map; 43 44 import com.google.android.collect.Maps; 45 46 /** 47 * This class provides access to a centralized registry of the user's 48 * online accounts. The user enters credentials (username and password) once 49 * per account, granting applications access to online resources with 50 * "one-click" approval. 51 * 52 * <p>Different online services have different ways of handling accounts and 53 * authentication, so the account manager uses pluggable <em>authenticator</em> 54 * modules for different <em>account types</em>. Authenticators (which may be 55 * written by third parties) handle the actual details of validating account 56 * credentials and storing account information. For example, Google, Facebook, 57 * and Microsoft Exchange each have their own authenticator. 58 * 59 * <p>Many servers support some notion of an <em>authentication token</em>, 60 * which can be used to authenticate a request to the server without sending 61 * the user's actual password. (Auth tokens are normally created with a 62 * separate request which does include the user's credentials.) AccountManager 63 * can generate auth tokens for applications, so the application doesn't need to 64 * handle passwords directly. Auth tokens are normally reusable and cached by 65 * AccountManager, but must be refreshed periodically. It's the responsibility 66 * of applications to <em>invalidate</em> auth tokens when they stop working so 67 * the AccountManager knows it needs to regenerate them. 68 * 69 * <p>Applications accessing a server normally go through these steps: 70 * 71 * <ul> 72 * <li>Get an instance of AccountManager using {@link #get(Context)}. 73 * 74 * <li>List the available accounts using {@link #getAccountsByType} or 75 * {@link #getAccountsByTypeAndFeatures}. Normally applications will only 76 * be interested in accounts with one particular <em>type</em>, which 77 * identifies the authenticator. Account <em>features</em> are used to 78 * identify particular account subtypes and capabilities. Both the account 79 * type and features are authenticator-specific strings, and must be known by 80 * the application in coordination with its preferred authenticators. 81 * 82 * <li>Select one or more of the available accounts, possibly by asking the 83 * user for their preference. If no suitable accounts are available, 84 * {@link #addAccount} may be called to prompt the user to create an 85 * account of the appropriate type. 86 * 87 * <li><b>Important:</b> If the application is using a previously remembered 88 * account selection, it must make sure the account is still in the list 89 * of accounts returned by {@link #getAccountsByType}. Requesting an auth token 90 * for an account no longer on the device results in an undefined failure. 91 * 92 * <li>Request an auth token for the selected account(s) using one of the 93 * {@link #getAuthToken} methods or related helpers. Refer to the description 94 * of each method for exact usage and error handling details. 95 * 96 * <li>Make the request using the auth token. The form of the auth token, 97 * the format of the request, and the protocol used are all specific to the 98 * service you are accessing. The application may use whatever network and 99 * protocol libraries are useful. 100 * 101 * <li><b>Important:</b> If the request fails with an authentication error, 102 * it could be that a cached auth token is stale and no longer honored by 103 * the server. The application must call {@link #invalidateAuthToken} to remove 104 * the token from the cache, otherwise requests will continue failing! After 105 * invalidating the auth token, immediately go back to the "Request an auth 106 * token" step above. If the process fails the second time, then it can be 107 * treated as a "genuine" authentication failure and the user notified or other 108 * appropriate actions taken. 109 * </ul> 110 * 111 * <p>Some AccountManager methods may need to interact with the user to 112 * prompt for credentials, present options, or ask the user to add an account. 113 * The caller may choose whether to allow AccountManager to directly launch the 114 * necessary user interface and wait for the user, or to return an Intent which 115 * the caller may use to launch the interface, or (in some cases) to install a 116 * notification which the user can select at any time to launch the interface. 117 * To have AccountManager launch the interface directly, the caller must supply 118 * the current foreground {@link Activity} context. 119 * 120 * <p>Many AccountManager methods take {@link AccountManagerCallback} and 121 * {@link Handler} as parameters. These methods return immediately and 122 * run asynchronously. If a callback is provided then 123 * {@link AccountManagerCallback#run} will be invoked on the Handler's 124 * thread when the request completes, successfully or not. 125 * The result is retrieved by calling {@link AccountManagerFuture#getResult()} 126 * on the {@link AccountManagerFuture} returned by the method (and also passed 127 * to the callback). This method waits for the operation to complete (if 128 * necessary) and either returns the result or throws an exception if an error 129 * occurred during the operation. To make the request synchronously, call 130 * {@link AccountManagerFuture#getResult()} immediately on receiving the 131 * future from the method; no callback need be supplied. 132 * 133 * <p>Requests which may block, including 134 * {@link AccountManagerFuture#getResult()}, must never be called on 135 * the application's main event thread. These operations throw 136 * {@link IllegalStateException} if they are used on the main thread. 137 */ 138 public class AccountManager { 139 private static final String TAG = "AccountManager"; 140 141 public static final int ERROR_CODE_REMOTE_EXCEPTION = 1; 142 public static final int ERROR_CODE_NETWORK_ERROR = 3; 143 public static final int ERROR_CODE_CANCELED = 4; 144 public static final int ERROR_CODE_INVALID_RESPONSE = 5; 145 public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6; 146 public static final int ERROR_CODE_BAD_ARGUMENTS = 7; 147 public static final int ERROR_CODE_BAD_REQUEST = 8; 148 149 /** 150 * Bundle key used for the {@link String} account name in results 151 * from methods which return information about a particular account. 152 */ 153 public static final String KEY_ACCOUNT_NAME = "authAccount"; 154 155 /** 156 * Bundle key used for the {@link String} account type in results 157 * from methods which return information about a particular account. 158 */ 159 public static final String KEY_ACCOUNT_TYPE = "accountType"; 160 161 /** 162 * Bundle key used for the auth token value in results 163 * from {@link #getAuthToken} and friends. 164 */ 165 public static final String KEY_AUTHTOKEN = "authtoken"; 166 167 /** 168 * Bundle key used for an {@link Intent} in results from methods that 169 * may require the caller to interact with the user. The Intent can 170 * be used to start the corresponding user interface activity. 171 */ 172 public static final String KEY_INTENT = "intent"; 173 174 /** 175 * Bundle key used to supply the password directly in options to 176 * {@link #confirmCredentials}, rather than prompting the user with 177 * the standard password prompt. 178 */ 179 public static final String KEY_PASSWORD = "password"; 180 181 public static final String KEY_ACCOUNTS = "accounts"; 182 public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse"; 183 public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse"; 184 public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types"; 185 public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage"; 186 public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey"; 187 public static final String KEY_BOOLEAN_RESULT = "booleanResult"; 188 public static final String KEY_ERROR_CODE = "errorCode"; 189 public static final String KEY_ERROR_MESSAGE = "errorMessage"; 190 public static final String KEY_USERDATA = "userdata"; 191 /** 192 * Authenticators using 'customTokens' option will also get the UID of the 193 * caller 194 * @hide 195 */ 196 public static final String KEY_CALLER_UID = "callerUid"; 197 198 /** 199 * @hide 200 */ 201 public static final String KEY_CALLER_PID = "callerPid"; 202 203 /** 204 * Boolean, if set and 'customTokens' the authenticator is responsible for 205 * notifications. 206 * @hide 207 */ 208 public static final String KEY_NOTIFY_ON_FAILURE = "notifyOnAuthFailure"; 209 210 public static final String ACTION_AUTHENTICATOR_INTENT = 211 "android.accounts.AccountAuthenticator"; 212 public static final String AUTHENTICATOR_META_DATA_NAME = 213 "android.accounts.AccountAuthenticator"; 214 public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator"; 215 216 private final Context mContext; 217 private final IAccountManager mService; 218 private final Handler mMainHandler; 219 220 /** 221 * Action sent as a broadcast Intent by the AccountsService 222 * when accounts are added, accounts are removed, or an 223 * account's credentials (saved password, etc) are changed. 224 * 225 * @see #addOnAccountsUpdatedListener 226 */ 227 public static final String LOGIN_ACCOUNTS_CHANGED_ACTION = 228 "android.accounts.LOGIN_ACCOUNTS_CHANGED"; 229 230 /** 231 * @hide 232 */ 233 public AccountManager(Context context, IAccountManager service) { 234 mContext = context; 235 mService = service; 236 mMainHandler = new Handler(mContext.getMainLooper()); 237 } 238 239 /** 240 * @hide used for testing only 241 */ 242 public AccountManager(Context context, IAccountManager service, Handler handler) { 243 mContext = context; 244 mService = service; 245 mMainHandler = handler; 246 } 247 248 /** 249 * @hide for internal use only 250 */ 251 public static Bundle sanitizeResult(Bundle result) { 252 if (result != null) { 253 if (result.containsKey(KEY_AUTHTOKEN) 254 && !TextUtils.isEmpty(result.getString(KEY_AUTHTOKEN))) { 255 final Bundle newResult = new Bundle(result); 256 newResult.putString(KEY_AUTHTOKEN, "<omitted for logging purposes>"); 257 return newResult; 258 } 259 } 260 return result; 261 } 262 263 /** 264 * Gets an AccountManager instance associated with a Context. 265 * The {@link Context} will be used as long as the AccountManager is 266 * active, so make sure to use a {@link Context} whose lifetime is 267 * commensurate with any listeners registered to 268 * {@link #addOnAccountsUpdatedListener} or similar methods. 269 * 270 * <p>It is safe to call this method from the main thread. 271 * 272 * <p>No permission is required to call this method. 273 * 274 * @param context The {@link Context} to use when necessary 275 * @return An {@link AccountManager} instance 276 */ 277 public static AccountManager get(Context context) { 278 if (context == null) throw new IllegalArgumentException("context is null"); 279 return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); 280 } 281 282 /** 283 * Gets the saved password associated with the account. 284 * This is intended for authenticators and related code; applications 285 * should get an auth token instead. 286 * 287 * <p>It is safe to call this method from the main thread. 288 * 289 * <p>This method requires the caller to hold the permission 290 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} 291 * and to have the same UID as the account's authenticator. 292 * 293 * @param account The account to query for a password 294 * @return The account's password, null if none or if the account doesn't exist 295 */ 296 public String getPassword(final Account account) { 297 if (account == null) throw new IllegalArgumentException("account is null"); 298 try { 299 return mService.getPassword(account); 300 } catch (RemoteException e) { 301 // will never happen 302 throw new RuntimeException(e); 303 } 304 } 305 306 /** 307 * Gets the user data named by "key" associated with the account. 308 * This is intended for authenticators and related code to store 309 * arbitrary metadata along with accounts. The meaning of the keys 310 * and values is up to the authenticator for the account. 311 * 312 * <p>It is safe to call this method from the main thread. 313 * 314 * <p>This method requires the caller to hold the permission 315 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} 316 * and to have the same UID as the account's authenticator. 317 * 318 * @param account The account to query for user data 319 * @return The user data, null if the account or key doesn't exist 320 */ 321 public String getUserData(final Account account, final String key) { 322 if (account == null) throw new IllegalArgumentException("account is null"); 323 if (key == null) throw new IllegalArgumentException("key is null"); 324 try { 325 return mService.getUserData(account, key); 326 } catch (RemoteException e) { 327 // will never happen 328 throw new RuntimeException(e); 329 } 330 } 331 332 /** 333 * Lists the currently registered authenticators. 334 * 335 * <p>It is safe to call this method from the main thread. 336 * 337 * <p>No permission is required to call this method. 338 * 339 * @return An array of {@link AuthenticatorDescription} for every 340 * authenticator known to the AccountManager service. Empty (never 341 * null) if no authenticators are known. 342 */ 343 public AuthenticatorDescription[] getAuthenticatorTypes() { 344 try { 345 return mService.getAuthenticatorTypes(); 346 } catch (RemoteException e) { 347 // will never happen 348 throw new RuntimeException(e); 349 } 350 } 351 352 /** 353 * Lists all accounts of any type registered on the device. 354 * Equivalent to getAccountsByType(null). 355 * 356 * <p>It is safe to call this method from the main thread. 357 * 358 * <p>This method requires the caller to hold the permission 359 * {@link android.Manifest.permission#GET_ACCOUNTS}. 360 * 361 * @return An array of {@link Account}, one for each account. Empty 362 * (never null) if no accounts have been added. 363 */ 364 public Account[] getAccounts() { 365 try { 366 return mService.getAccounts(null); 367 } catch (RemoteException e) { 368 // won't ever happen 369 throw new RuntimeException(e); 370 } 371 } 372 373 /** 374 * Lists all accounts of a particular type. The account type is a 375 * string token corresponding to the authenticator and useful domain 376 * of the account. For example, there are types corresponding to Google 377 * and Facebook. The exact string token to use will be published somewhere 378 * associated with the authenticator in question. 379 * 380 * <p>It is safe to call this method from the main thread. 381 * 382 * <p>This method requires the caller to hold the permission 383 * {@link android.Manifest.permission#GET_ACCOUNTS}. 384 * 385 * @param type The type of accounts to return, null to retrieve all accounts 386 * @return An array of {@link Account}, one per matching account. Empty 387 * (never null) if no accounts of the specified type have been added. 388 */ 389 public Account[] getAccountsByType(String type) { 390 try { 391 return mService.getAccounts(type); 392 } catch (RemoteException e) { 393 // won't ever happen 394 throw new RuntimeException(e); 395 } 396 } 397 398 /** 399 * Finds out whether a particular account has all the specified features. 400 * Account features are authenticator-specific string tokens identifying 401 * boolean account properties. For example, features are used to tell 402 * whether Google accounts have a particular service (such as Google 403 * Calendar or Google Talk) enabled. The feature names and their meanings 404 * are published somewhere associated with the authenticator in question. 405 * 406 * <p>This method may be called from any thread, but the returned 407 * {@link AccountManagerFuture} must not be used on the main thread. 408 * 409 * <p>This method requires the caller to hold the permission 410 * {@link android.Manifest.permission#GET_ACCOUNTS}. 411 * 412 * @param account The {@link Account} to test 413 * @param features An array of the account features to check 414 * @param callback Callback to invoke when the request completes, 415 * null for no callback 416 * @param handler {@link Handler} identifying the callback thread, 417 * null for the main thread 418 * @return An {@link AccountManagerFuture} which resolves to a Boolean, 419 * true if the account exists and has all of the specified features. 420 */ 421 public AccountManagerFuture<Boolean> hasFeatures(final Account account, 422 final String[] features, 423 AccountManagerCallback<Boolean> callback, Handler handler) { 424 if (account == null) throw new IllegalArgumentException("account is null"); 425 if (features == null) throw new IllegalArgumentException("features is null"); 426 return new Future2Task<Boolean>(handler, callback) { 427 public void doWork() throws RemoteException { 428 mService.hasFeatures(mResponse, account, features); 429 } 430 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { 431 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { 432 throw new AuthenticatorException("no result in response"); 433 } 434 return bundle.getBoolean(KEY_BOOLEAN_RESULT); 435 } 436 }.start(); 437 } 438 439 /** 440 * Lists all accounts of a type which have certain features. The account 441 * type identifies the authenticator (see {@link #getAccountsByType}). 442 * Account features are authenticator-specific string tokens identifying 443 * boolean account properties (see {@link #hasFeatures}). 444 * 445 * <p>Unlike {@link #getAccountsByType}, this method calls the authenticator, 446 * which may contact the server or do other work to check account features, 447 * so the method returns an {@link AccountManagerFuture}. 448 * 449 * <p>This method may be called from any thread, but the returned 450 * {@link AccountManagerFuture} must not be used on the main thread. 451 * 452 * <p>This method requires the caller to hold the permission 453 * {@link android.Manifest.permission#GET_ACCOUNTS}. 454 * 455 * @param type The type of accounts to return, must not be null 456 * @param features An array of the account features to require, 457 * may be null or empty 458 * @param callback Callback to invoke when the request completes, 459 * null for no callback 460 * @param handler {@link Handler} identifying the callback thread, 461 * null for the main thread 462 * @return An {@link AccountManagerFuture} which resolves to an array of 463 * {@link Account}, one per account of the specified type which 464 * matches the requested features. 465 */ 466 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( 467 final String type, final String[] features, 468 AccountManagerCallback<Account[]> callback, Handler handler) { 469 if (type == null) throw new IllegalArgumentException("type is null"); 470 return new Future2Task<Account[]>(handler, callback) { 471 public void doWork() throws RemoteException { 472 mService.getAccountsByFeatures(mResponse, type, features); 473 } 474 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { 475 if (!bundle.containsKey(KEY_ACCOUNTS)) { 476 throw new AuthenticatorException("no result in response"); 477 } 478 final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS); 479 Account[] descs = new Account[parcelables.length]; 480 for (int i = 0; i < parcelables.length; i++) { 481 descs[i] = (Account) parcelables[i]; 482 } 483 return descs; 484 } 485 }.start(); 486 } 487 488 /** 489 * Adds an account directly to the AccountManager. Normally used by sign-up 490 * wizards associated with authenticators, not directly by applications. 491 * 492 * <p>It is safe to call this method from the main thread. 493 * 494 * <p>This method requires the caller to hold the permission 495 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} 496 * and to have the same UID as the added account's authenticator. 497 * 498 * @param account The {@link Account} to add 499 * @param password The password to associate with the account, null for none 500 * @param userdata String values to use for the account's userdata, null for none 501 * @return True if the account was successfully added, false if the account 502 * already exists, the account is null, or another error occurs. 503 */ 504 public boolean addAccountExplicitly(Account account, String password, Bundle userdata) { 505 if (account == null) throw new IllegalArgumentException("account is null"); 506 try { 507 return mService.addAccount(account, password, userdata); 508 } catch (RemoteException e) { 509 // won't ever happen 510 throw new RuntimeException(e); 511 } 512 } 513 514 /** 515 * Removes an account from the AccountManager. Does nothing if the account 516 * does not exist. Does not delete the account from the server. 517 * The authenticator may have its own policies preventing account 518 * deletion, in which case the account will not be deleted. 519 * 520 * <p>This method may be called from any thread, but the returned 521 * {@link AccountManagerFuture} must not be used on the main thread. 522 * 523 * <p>This method requires the caller to hold the permission 524 * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 525 * 526 * @param account The {@link Account} to remove 527 * @param callback Callback to invoke when the request completes, 528 * null for no callback 529 * @param handler {@link Handler} identifying the callback thread, 530 * null for the main thread 531 * @return An {@link AccountManagerFuture} which resolves to a Boolean, 532 * true if the account has been successfully removed, 533 * false if the authenticator forbids deleting this account. 534 */ 535 public AccountManagerFuture<Boolean> removeAccount(final Account account, 536 AccountManagerCallback<Boolean> callback, Handler handler) { 537 if (account == null) throw new IllegalArgumentException("account is null"); 538 return new Future2Task<Boolean>(handler, callback) { 539 public void doWork() throws RemoteException { 540 mService.removeAccount(mResponse, account); 541 } 542 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { 543 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { 544 throw new AuthenticatorException("no result in response"); 545 } 546 return bundle.getBoolean(KEY_BOOLEAN_RESULT); 547 } 548 }.start(); 549 } 550 551 /** 552 * Removes an auth token from the AccountManager's cache. Does nothing if 553 * the auth token is not currently in the cache. Applications must call this 554 * method when the auth token is found to have expired or otherwise become 555 * invalid for authenticating requests. The AccountManager does not validate 556 * or expire cached auth tokens otherwise. 557 * 558 * <p>It is safe to call this method from the main thread. 559 * 560 * <p>This method requires the caller to hold the permission 561 * {@link android.Manifest.permission#MANAGE_ACCOUNTS} or 562 * {@link android.Manifest.permission#USE_CREDENTIALS} 563 * 564 * @param accountType The account type of the auth token to invalidate, must not be null 565 * @param authToken The auth token to invalidate, may be null 566 */ 567 public void invalidateAuthToken(final String accountType, final String authToken) { 568 if (accountType == null) throw new IllegalArgumentException("accountType is null"); 569 try { 570 if (authToken != null) { 571 mService.invalidateAuthToken(accountType, authToken); 572 } 573 } catch (RemoteException e) { 574 // won't ever happen 575 throw new RuntimeException(e); 576 } 577 } 578 579 /** 580 * Gets an auth token from the AccountManager's cache. If no auth 581 * token is cached for this account, null will be returned -- a new 582 * auth token will not be generated, and the server will not be contacted. 583 * Intended for use by the authenticator, not directly by applications. 584 * 585 * <p>It is safe to call this method from the main thread. 586 * 587 * <p>This method requires the caller to hold the permission 588 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} 589 * and to have the same UID as the account's authenticator. 590 * 591 * @param account The account to fetch an auth token for 592 * @param authTokenType The type of auth token to fetch, see {#getAuthToken} 593 * @return The cached auth token for this account and type, or null if 594 * no auth token is cached or the account does not exist. 595 */ 596 public String peekAuthToken(final Account account, final String authTokenType) { 597 if (account == null) throw new IllegalArgumentException("account is null"); 598 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 599 try { 600 return mService.peekAuthToken(account, authTokenType); 601 } catch (RemoteException e) { 602 // won't ever happen 603 throw new RuntimeException(e); 604 } 605 } 606 607 /** 608 * Sets or forgets a saved password. This modifies the local copy of the 609 * password used to automatically authenticate the user; it does 610 * not change the user's account password on the server. Intended for use 611 * by the authenticator, not directly by applications. 612 * 613 * <p>It is safe to call this method from the main thread. 614 * 615 * <p>This method requires the caller to hold the permission 616 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} 617 * and have the same UID as the account's authenticator. 618 * 619 * @param account The account to set a password for 620 * @param password The password to set, null to clear the password 621 */ 622 public void setPassword(final Account account, final String password) { 623 if (account == null) throw new IllegalArgumentException("account is null"); 624 try { 625 mService.setPassword(account, password); 626 } catch (RemoteException e) { 627 // won't ever happen 628 throw new RuntimeException(e); 629 } 630 } 631 632 /** 633 * Forgets a saved password. This erases the local copy of the password; 634 * it does not change the user's account password on the server. 635 * Has the same effect as setPassword(account, null) but requires fewer 636 * permissions, and may be used by applications or management interfaces 637 * to "sign out" from an account. 638 * 639 * <p>It is safe to call this method from the main thread. 640 * 641 * <p>This method requires the caller to hold the permission 642 * {@link android.Manifest.permission#MANAGE_ACCOUNTS} 643 * 644 * @param account The account whose password to clear 645 */ 646 public void clearPassword(final Account account) { 647 if (account == null) throw new IllegalArgumentException("account is null"); 648 try { 649 mService.clearPassword(account); 650 } catch (RemoteException e) { 651 // won't ever happen 652 throw new RuntimeException(e); 653 } 654 } 655 656 /** 657 * Sets one userdata key for an account. Intended by use for the 658 * authenticator to stash state for itself, not directly by applications. 659 * The meaning of the keys and values is up to the authenticator. 660 * 661 * <p>It is safe to call this method from the main thread. 662 * 663 * <p>This method requires the caller to hold the permission 664 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} 665 * and to have the same UID as the account's authenticator. 666 * 667 * @param account The account to set the userdata for 668 * @param key The userdata key to set. Must not be null 669 * @param value The value to set, null to clear this userdata key 670 */ 671 public void setUserData(final Account account, final String key, final String value) { 672 if (account == null) throw new IllegalArgumentException("account is null"); 673 if (key == null) throw new IllegalArgumentException("key is null"); 674 try { 675 mService.setUserData(account, key, value); 676 } catch (RemoteException e) { 677 // won't ever happen 678 throw new RuntimeException(e); 679 } 680 } 681 682 /** 683 * Adds an auth token to the AccountManager cache for an account. 684 * If the account does not exist then this call has no effect. 685 * Replaces any previous auth token for this account and auth token type. 686 * Intended for use by the authenticator, not directly by applications. 687 * 688 * <p>It is safe to call this method from the main thread. 689 * 690 * <p>This method requires the caller to hold the permission 691 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} 692 * and to have the same UID as the account's authenticator. 693 * 694 * @param account The account to set an auth token for 695 * @param authTokenType The type of the auth token, see {#getAuthToken} 696 * @param authToken The auth token to add to the cache 697 */ 698 public void setAuthToken(Account account, final String authTokenType, final String authToken) { 699 if (account == null) throw new IllegalArgumentException("account is null"); 700 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 701 try { 702 mService.setAuthToken(account, authTokenType, authToken); 703 } catch (RemoteException e) { 704 // won't ever happen 705 throw new RuntimeException(e); 706 } 707 } 708 709 /** 710 * This convenience helper synchronously gets an auth token with 711 * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}. 712 * 713 * <p>This method may block while a network request completes, and must 714 * never be made from the main thread. 715 * 716 * <p>This method requires the caller to hold the permission 717 * {@link android.Manifest.permission#USE_CREDENTIALS}. 718 * 719 * @param account The account to fetch an auth token for 720 * @param authTokenType The auth token type, see {#link getAuthToken} 721 * @param notifyAuthFailure If true, display a notification and return null 722 * if authentication fails; if false, prompt and wait for the user to 723 * re-enter correct credentials before returning 724 * @return An auth token of the specified type for this account, or null 725 * if authentication fails or none can be fetched. 726 * @throws AuthenticatorException if the authenticator failed to respond 727 * @throws OperationCanceledException if the request was canceled for any 728 * reason, including the user canceling a credential request 729 * @throws java.io.IOException if the authenticator experienced an I/O problem 730 * creating a new auth token, usually because of network trouble 731 */ 732 public String blockingGetAuthToken(Account account, String authTokenType, 733 boolean notifyAuthFailure) 734 throws OperationCanceledException, IOException, AuthenticatorException { 735 if (account == null) throw new IllegalArgumentException("account is null"); 736 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 737 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */, 738 null /* handler */).getResult(); 739 if (bundle == null) { 740 // This should never happen, but it does, occasionally. If it does return null to 741 // signify that we were not able to get the authtoken. 742 // TODO: remove this when the bug is found that sometimes causes a null bundle to be 743 // returned 744 Log.e(TAG, "blockingGetAuthToken: null was returned from getResult() for " 745 + account + ", authTokenType " + authTokenType); 746 return null; 747 } 748 return bundle.getString(KEY_AUTHTOKEN); 749 } 750 751 /** 752 * Gets an auth token of the specified type for a particular account, 753 * prompting the user for credentials if necessary. This method is 754 * intended for applications running in the foreground where it makes 755 * sense to ask the user directly for a password. 756 * 757 * <p>If a previously generated auth token is cached for this account and 758 * type, then it is returned. Otherwise, if a saved password is 759 * available, it is sent to the server to generate a new auth token. 760 * Otherwise, the user is prompted to enter a password. 761 * 762 * <p>Some authenticators have auth token <em>types</em>, whose value 763 * is authenticator-dependent. Some services use different token types to 764 * access different functionality -- for example, Google uses different auth 765 * tokens to access Gmail and Google Calendar for the same account. 766 * 767 * <p>This method may be called from any thread, but the returned 768 * {@link AccountManagerFuture} must not be used on the main thread. 769 * 770 * <p>This method requires the caller to hold the permission 771 * {@link android.Manifest.permission#USE_CREDENTIALS}. 772 * 773 * @param account The account to fetch an auth token for 774 * @param authTokenType The auth token type, an authenticator-dependent 775 * string token, must not be null 776 * @param options Authenticator-specific options for the request, 777 * may be null or empty 778 * @param activity The {@link Activity} context to use for launching a new 779 * authenticator-defined sub-Activity to prompt the user for a password 780 * if necessary; used only to call startActivity(); must not be null. 781 * @param callback Callback to invoke when the request completes, 782 * null for no callback 783 * @param handler {@link Handler} identifying the callback thread, 784 * null for the main thread 785 * @return An {@link AccountManagerFuture} which resolves to a Bundle with 786 * at least the following fields: 787 * <ul> 788 * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied 789 * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account 790 * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted 791 * </ul> 792 * 793 * (Other authenticator-specific values may be returned.) If an auth token 794 * could not be fetched, {@link AccountManagerFuture#getResult()} throws: 795 * <ul> 796 * <li> {@link AuthenticatorException} if the authenticator failed to respond 797 * <li> {@link OperationCanceledException} if the operation is canceled for 798 * any reason, incluidng the user canceling a credential request 799 * <li> {@link IOException} if the authenticator experienced an I/O problem 800 * creating a new auth token, usually because of network trouble 801 * </ul> 802 * If the account is no longer present on the device, the return value is 803 * authenticator-dependent. The caller should verify the validity of the 804 * account before requesting an auth token. 805 */ 806 public AccountManagerFuture<Bundle> getAuthToken( 807 final Account account, final String authTokenType, final Bundle options, 808 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { 809 if (account == null) throw new IllegalArgumentException("account is null"); 810 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 811 return new AmsTask(activity, handler, callback) { 812 public void doWork() throws RemoteException { 813 mService.getAuthToken(mResponse, account, authTokenType, 814 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */, 815 options); 816 } 817 }.start(); 818 } 819 820 /** 821 * Gets an auth token of the specified type for a particular account, 822 * optionally raising a notification if the user must enter credentials. 823 * This method is intended for background tasks and services where the 824 * user should not be immediately interrupted with a password prompt. 825 * 826 * <p>If a previously generated auth token is cached for this account and 827 * type, then it is returned. Otherwise, if a saved password is 828 * available, it is sent to the server to generate a new auth token. 829 * Otherwise, an {@link Intent} is returned which, when started, will 830 * prompt the user for a password. If the notifyAuthFailure parameter is 831 * set, a status bar notification is also created with the same Intent, 832 * alerting the user that they need to enter a password at some point. 833 * 834 * <p>In that case, you may need to wait until the user responds, which 835 * could take hours or days or forever. When the user does respond and 836 * supply a new password, the account manager will broadcast the 837 * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent, which applications can 838 * use to try again. 839 * 840 * <p>If notifyAuthFailure is not set, it is the application's 841 * responsibility to launch the returned Intent at some point. 842 * Either way, the result from this call will not wait for user action. 843 * 844 * <p>Some authenticators have auth token <em>types</em>, whose value 845 * is authenticator-dependent. Some services use different token types to 846 * access different functionality -- for example, Google uses different auth 847 * tokens to access Gmail and Google Calendar for the same account. 848 * 849 * <p>This method may be called from any thread, but the returned 850 * {@link AccountManagerFuture} must not be used on the main thread. 851 * 852 * <p>This method requires the caller to hold the permission 853 * {@link android.Manifest.permission#USE_CREDENTIALS}. 854 * 855 * @param account The account to fetch an auth token for 856 * @param authTokenType The auth token type, an authenticator-dependent 857 * string token, must not be null 858 * @param notifyAuthFailure True to add a notification to prompt the 859 * user for a password if necessary, false to leave that to the caller 860 * @param callback Callback to invoke when the request completes, 861 * null for no callback 862 * @param handler {@link Handler} identifying the callback thread, 863 * null for the main thread 864 * @return An {@link AccountManagerFuture} which resolves to a Bundle with 865 * at least the following fields on success: 866 * <ul> 867 * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied 868 * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account 869 * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted 870 * </ul> 871 * 872 * (Other authenticator-specific values may be returned.) If the user 873 * must enter credentials, the returned Bundle contains only 874 * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt. 875 * 876 * If an error occurred, {@link AccountManagerFuture#getResult()} throws: 877 * <ul> 878 * <li> {@link AuthenticatorException} if the authenticator failed to respond 879 * <li> {@link OperationCanceledException} if the operation is canceled for 880 * any reason, incluidng the user canceling a credential request 881 * <li> {@link IOException} if the authenticator experienced an I/O problem 882 * creating a new auth token, usually because of network trouble 883 * </ul> 884 * If the account is no longer present on the device, the return value is 885 * authenticator-dependent. The caller should verify the validity of the 886 * account before requesting an auth token. 887 */ 888 public AccountManagerFuture<Bundle> getAuthToken( 889 final Account account, final String authTokenType, final boolean notifyAuthFailure, 890 AccountManagerCallback<Bundle> callback, Handler handler) { 891 if (account == null) throw new IllegalArgumentException("account is null"); 892 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 893 return new AmsTask(null, handler, callback) { 894 public void doWork() throws RemoteException { 895 mService.getAuthToken(mResponse, account, authTokenType, 896 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */); 897 } 898 }.start(); 899 } 900 901 /** 902 * Asks the user to add an account of a specified type. The authenticator 903 * for this account type processes this request with the appropriate user 904 * interface. If the user does elect to create a new account, the account 905 * name is returned. 906 * 907 * <p>This method may be called from any thread, but the returned 908 * {@link AccountManagerFuture} must not be used on the main thread. 909 * 910 * <p>This method requires the caller to hold the permission 911 * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 912 * 913 * @param accountType The type of account to add; must not be null 914 * @param authTokenType The type of auth token (see {@link #getAuthToken}) 915 * this account will need to be able to generate, null for none 916 * @param requiredFeatures The features (see {@link #hasFeatures}) this 917 * account must have, null for none 918 * @param addAccountOptions Authenticator-specific options for the request, 919 * may be null or empty 920 * @param activity The {@link Activity} context to use for launching a new 921 * authenticator-defined sub-Activity to prompt the user to create an 922 * account; used only to call startActivity(); if null, the prompt 923 * will not be launched directly, but the necessary {@link Intent} 924 * will be returned to the caller instead 925 * @param callback Callback to invoke when the request completes, 926 * null for no callback 927 * @param handler {@link Handler} identifying the callback thread, 928 * null for the main thread 929 * @return An {@link AccountManagerFuture} which resolves to a Bundle with 930 * these fields if activity was specified and an account was created: 931 * <ul> 932 * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created 933 * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account 934 * </ul> 935 * 936 * If no activity was specified, the returned Bundle contains only 937 * {@link #KEY_INTENT} with the {@link Intent} needed to launch the 938 * actual account creation process. If an error occurred, 939 * {@link AccountManagerFuture#getResult()} throws: 940 * <ul> 941 * <li> {@link AuthenticatorException} if no authenticator was registered for 942 * this account type or the authenticator failed to respond 943 * <li> {@link OperationCanceledException} if the operation was canceled for 944 * any reason, including the user canceling the creation process 945 * <li> {@link IOException} if the authenticator experienced an I/O problem 946 * creating a new account, usually because of network trouble 947 * </ul> 948 */ 949 public AccountManagerFuture<Bundle> addAccount(final String accountType, 950 final String authTokenType, final String[] requiredFeatures, 951 final Bundle addAccountOptions, 952 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { 953 if (accountType == null) throw new IllegalArgumentException("accountType is null"); 954 return new AmsTask(activity, handler, callback) { 955 public void doWork() throws RemoteException { 956 mService.addAcount(mResponse, accountType, authTokenType, 957 requiredFeatures, activity != null, addAccountOptions); 958 } 959 }.start(); 960 } 961 962 /** 963 * Confirms that the user knows the password for an account to make extra 964 * sure they are the owner of the account. The user-entered password can 965 * be supplied directly, otherwise the authenticator for this account type 966 * prompts the user with the appropriate interface. This method is 967 * intended for applications which want extra assurance; for example, the 968 * phone lock screen uses this to let the user unlock the phone with an 969 * account password if they forget the lock pattern. 970 * 971 * <p>If the user-entered password matches a saved password for this 972 * account, the request is considered valid; otherwise the authenticator 973 * verifies the password (usually by contacting the server). 974 * 975 * <p>This method may be called from any thread, but the returned 976 * {@link AccountManagerFuture} must not be used on the main thread. 977 * 978 * <p>This method requires the caller to hold the permission 979 * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 980 * 981 * @param account The account to confirm password knowledge for 982 * @param options Authenticator-specific options for the request; 983 * if the {@link #KEY_PASSWORD} string field is present, the 984 * authenticator may use it directly rather than prompting the user; 985 * may be null or empty 986 * @param activity The {@link Activity} context to use for launching a new 987 * authenticator-defined sub-Activity to prompt the user to enter a 988 * password; used only to call startActivity(); if null, the prompt 989 * will not be launched directly, but the necessary {@link Intent} 990 * will be returned to the caller instead 991 * @param callback Callback to invoke when the request completes, 992 * null for no callback 993 * @param handler {@link Handler} identifying the callback thread, 994 * null for the main thread 995 * @return An {@link AccountManagerFuture} which resolves to a Bundle 996 * with these fields if activity or password was supplied and 997 * the account was successfully verified: 998 * <ul> 999 * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created 1000 * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account 1001 * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success 1002 * </ul> 1003 * 1004 * If no activity or password was specified, the returned Bundle contains 1005 * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the 1006 * password prompt. If an error occurred, 1007 * {@link AccountManagerFuture#getResult()} throws: 1008 * <ul> 1009 * <li> {@link AuthenticatorException} if the authenticator failed to respond 1010 * <li> {@link OperationCanceledException} if the operation was canceled for 1011 * any reason, including the user canceling the password prompt 1012 * <li> {@link IOException} if the authenticator experienced an I/O problem 1013 * verifying the password, usually because of network trouble 1014 * </ul> 1015 */ 1016 public AccountManagerFuture<Bundle> confirmCredentials(final Account account, 1017 final Bundle options, 1018 final Activity activity, 1019 final AccountManagerCallback<Bundle> callback, 1020 final Handler handler) { 1021 if (account == null) throw new IllegalArgumentException("account is null"); 1022 return new AmsTask(activity, handler, callback) { 1023 public void doWork() throws RemoteException { 1024 mService.confirmCredentials(mResponse, account, options, activity != null); 1025 } 1026 }.start(); 1027 } 1028 1029 /** 1030 * Asks the user to enter a new password for an account, updating the 1031 * saved credentials for the account. Normally this happens automatically 1032 * when the server rejects credentials during an auth token fetch, but this 1033 * can be invoked directly to ensure we have the correct credentials stored. 1034 * 1035 * <p>This method may be called from any thread, but the returned 1036 * {@link AccountManagerFuture} must not be used on the main thread. 1037 * 1038 * <p>This method requires the caller to hold the permission 1039 * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 1040 * 1041 * @param account The account to update credentials for 1042 * @param authTokenType The credentials entered must allow an auth token 1043 * of this type to be created (but no actual auth token is returned); 1044 * may be null 1045 * @param options Authenticator-specific options for the request; 1046 * may be null or empty 1047 * @param activity The {@link Activity} context to use for launching a new 1048 * authenticator-defined sub-Activity to prompt the user to enter a 1049 * password; used only to call startActivity(); if null, the prompt 1050 * will not be launched directly, but the necessary {@link Intent} 1051 * will be returned to the caller instead 1052 * @param callback Callback to invoke when the request completes, 1053 * null for no callback 1054 * @param handler {@link Handler} identifying the callback thread, 1055 * null for the main thread 1056 * @return An {@link AccountManagerFuture} which resolves to a Bundle 1057 * with these fields if an activity was supplied and the account 1058 * credentials were successfully updated: 1059 * <ul> 1060 * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created 1061 * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account 1062 * </ul> 1063 * 1064 * If no activity was specified, the returned Bundle contains only 1065 * {@link #KEY_INTENT} with the {@link Intent} needed to launch the 1066 * password prompt. If an error occurred, 1067 * {@link AccountManagerFuture#getResult()} throws: 1068 * <ul> 1069 * <li> {@link AuthenticatorException} if the authenticator failed to respond 1070 * <li> {@link OperationCanceledException} if the operation was canceled for 1071 * any reason, including the user canceling the password prompt 1072 * <li> {@link IOException} if the authenticator experienced an I/O problem 1073 * verifying the password, usually because of network trouble 1074 * </ul> 1075 */ 1076 public AccountManagerFuture<Bundle> updateCredentials(final Account account, 1077 final String authTokenType, 1078 final Bundle options, final Activity activity, 1079 final AccountManagerCallback<Bundle> callback, 1080 final Handler handler) { 1081 if (account == null) throw new IllegalArgumentException("account is null"); 1082 return new AmsTask(activity, handler, callback) { 1083 public void doWork() throws RemoteException { 1084 mService.updateCredentials(mResponse, account, authTokenType, activity != null, 1085 options); 1086 } 1087 }.start(); 1088 } 1089 1090 /** 1091 * Offers the user an opportunity to change an authenticator's settings. 1092 * These properties are for the authenticator in general, not a particular 1093 * account. Not all authenticators support this method. 1094 * 1095 * <p>This method may be called from any thread, but the returned 1096 * {@link AccountManagerFuture} must not be used on the main thread. 1097 * 1098 * <p>This method requires the caller to hold the permission 1099 * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 1100 * 1101 * @param accountType The account type associated with the authenticator 1102 * to adjust 1103 * @param activity The {@link Activity} context to use for launching a new 1104 * authenticator-defined sub-Activity to adjust authenticator settings; 1105 * used only to call startActivity(); if null, the settings dialog will 1106 * not be launched directly, but the necessary {@link Intent} will be 1107 * returned to the caller instead 1108 * @param callback Callback to invoke when the request completes, 1109 * null for no callback 1110 * @param handler {@link Handler} identifying the callback thread, 1111 * null for the main thread 1112 * @return An {@link AccountManagerFuture} which resolves to a Bundle 1113 * which is empty if properties were edited successfully, or 1114 * if no activity was specified, contains only {@link #KEY_INTENT} 1115 * needed to launch the authenticator's settings dialog. 1116 * If an error occurred, {@link AccountManagerFuture#getResult()} 1117 * throws: 1118 * <ul> 1119 * <li> {@link AuthenticatorException} if no authenticator was registered for 1120 * this account type or the authenticator failed to respond 1121 * <li> {@link OperationCanceledException} if the operation was canceled for 1122 * any reason, including the user canceling the settings dialog 1123 * <li> {@link IOException} if the authenticator experienced an I/O problem 1124 * updating settings, usually because of network trouble 1125 * </ul> 1126 */ 1127 public AccountManagerFuture<Bundle> editProperties(final String accountType, 1128 final Activity activity, final AccountManagerCallback<Bundle> callback, 1129 final Handler handler) { 1130 if (accountType == null) throw new IllegalArgumentException("accountType is null"); 1131 return new AmsTask(activity, handler, callback) { 1132 public void doWork() throws RemoteException { 1133 mService.editProperties(mResponse, accountType, activity != null); 1134 } 1135 }.start(); 1136 } 1137 1138 private void ensureNotOnMainThread() { 1139 final Looper looper = Looper.myLooper(); 1140 if (looper != null && looper == mContext.getMainLooper()) { 1141 final IllegalStateException exception = new IllegalStateException( 1142 "calling this from your main thread can lead to deadlock"); 1143 Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", 1144 exception); 1145 if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.FROYO) { 1146 throw exception; 1147 } 1148 } 1149 } 1150 1151 private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback, 1152 final AccountManagerFuture<Bundle> future) { 1153 handler = handler == null ? mMainHandler : handler; 1154 handler.post(new Runnable() { 1155 public void run() { 1156 callback.run(future); 1157 } 1158 }); 1159 } 1160 1161 private void postToHandler(Handler handler, final OnAccountsUpdateListener listener, 1162 final Account[] accounts) { 1163 final Account[] accountsCopy = new Account[accounts.length]; 1164 // send a copy to make sure that one doesn't 1165 // change what another sees 1166 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length); 1167 handler = (handler == null) ? mMainHandler : handler; 1168 handler.post(new Runnable() { 1169 public void run() { 1170 try { 1171 listener.onAccountsUpdated(accountsCopy); 1172 } catch (SQLException e) { 1173 // Better luck next time. If the problem was disk-full, 1174 // the STORAGE_OK intent will re-trigger the update. 1175 Log.e(TAG, "Can't update accounts", e); 1176 } 1177 } 1178 }); 1179 } 1180 1181 private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> { 1182 final IAccountManagerResponse mResponse; 1183 final Handler mHandler; 1184 final AccountManagerCallback<Bundle> mCallback; 1185 final Activity mActivity; 1186 public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) { 1187 super(new Callable<Bundle>() { 1188 public Bundle call() throws Exception { 1189 throw new IllegalStateException("this should never be called"); 1190 } 1191 }); 1192 1193 mHandler = handler; 1194 mCallback = callback; 1195 mActivity = activity; 1196 mResponse = new Response(); 1197 } 1198 1199 public final AccountManagerFuture<Bundle> start() { 1200 try { 1201 doWork(); 1202 } catch (RemoteException e) { 1203 setException(e); 1204 } 1205 return this; 1206 } 1207 1208 protected void set(Bundle bundle) { 1209 // TODO: somehow a null is being set as the result of the Future. Log this 1210 // case to help debug where this is occurring. When this bug is fixed this 1211 // condition statement should be removed. 1212 if (bundle == null) { 1213 Log.e(TAG, "the bundle must not be null", new Exception()); 1214 } 1215 super.set(bundle); 1216 } 1217 1218 public abstract void doWork() throws RemoteException; 1219 1220 private Bundle internalGetResult(Long timeout, TimeUnit unit) 1221 throws OperationCanceledException, IOException, AuthenticatorException { 1222 if (!isDone()) { 1223 ensureNotOnMainThread(); 1224 } 1225 try { 1226 if (timeout == null) { 1227 return get(); 1228 } else { 1229 return get(timeout, unit); 1230 } 1231 } catch (CancellationException e) { 1232 throw new OperationCanceledException(); 1233 } catch (TimeoutException e) { 1234 // fall through and cancel 1235 } catch (InterruptedException e) { 1236 // fall through and cancel 1237 } catch (ExecutionException e) { 1238 final Throwable cause = e.getCause(); 1239 if (cause instanceof IOException) { 1240 throw (IOException) cause; 1241 } else if (cause instanceof UnsupportedOperationException) { 1242 throw new AuthenticatorException(cause); 1243 } else if (cause instanceof AuthenticatorException) { 1244 throw (AuthenticatorException) cause; 1245 } else if (cause instanceof RuntimeException) { 1246 throw (RuntimeException) cause; 1247 } else if (cause instanceof Error) { 1248 throw (Error) cause; 1249 } else { 1250 throw new IllegalStateException(cause); 1251 } 1252 } finally { 1253 cancel(true /* interrupt if running */); 1254 } 1255 throw new OperationCanceledException(); 1256 } 1257 1258 public Bundle getResult() 1259 throws OperationCanceledException, IOException, AuthenticatorException { 1260 return internalGetResult(null, null); 1261 } 1262 1263 public Bundle getResult(long timeout, TimeUnit unit) 1264 throws OperationCanceledException, IOException, AuthenticatorException { 1265 return internalGetResult(timeout, unit); 1266 } 1267 1268 protected void done() { 1269 if (mCallback != null) { 1270 postToHandler(mHandler, mCallback, this); 1271 } 1272 } 1273 1274 /** Handles the responses from the AccountManager */ 1275 private class Response extends IAccountManagerResponse.Stub { 1276 public void onResult(Bundle bundle) { 1277 Intent intent = bundle.getParcelable("intent"); 1278 if (intent != null && mActivity != null) { 1279 // since the user provided an Activity we will silently start intents 1280 // that we see 1281 mActivity.startActivity(intent); 1282 // leave the Future running to wait for the real response to this request 1283 } else if (bundle.getBoolean("retry")) { 1284 try { 1285 doWork(); 1286 } catch (RemoteException e) { 1287 // this will only happen if the system process is dead, which means 1288 // we will be dying ourselves 1289 } 1290 } else { 1291 set(bundle); 1292 } 1293 } 1294 1295 public void onError(int code, String message) { 1296 if (code == ERROR_CODE_CANCELED) { 1297 // the authenticator indicated that this request was canceled, do so now 1298 cancel(true /* mayInterruptIfRunning */); 1299 return; 1300 } 1301 setException(convertErrorToException(code, message)); 1302 } 1303 } 1304 1305 } 1306 1307 private abstract class BaseFutureTask<T> extends FutureTask<T> { 1308 final public IAccountManagerResponse mResponse; 1309 final Handler mHandler; 1310 1311 public BaseFutureTask(Handler handler) { 1312 super(new Callable<T>() { 1313 public T call() throws Exception { 1314 throw new IllegalStateException("this should never be called"); 1315 } 1316 }); 1317 mHandler = handler; 1318 mResponse = new Response(); 1319 } 1320 1321 public abstract void doWork() throws RemoteException; 1322 1323 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException; 1324 1325 protected void postRunnableToHandler(Runnable runnable) { 1326 Handler handler = (mHandler == null) ? mMainHandler : mHandler; 1327 handler.post(runnable); 1328 } 1329 1330 protected void startTask() { 1331 try { 1332 doWork(); 1333 } catch (RemoteException e) { 1334 setException(e); 1335 } 1336 } 1337 1338 protected class Response extends IAccountManagerResponse.Stub { 1339 public void onResult(Bundle bundle) { 1340 try { 1341 T result = bundleToResult(bundle); 1342 if (result == null) { 1343 return; 1344 } 1345 set(result); 1346 return; 1347 } catch (ClassCastException e) { 1348 // we will set the exception below 1349 } catch (AuthenticatorException e) { 1350 // we will set the exception below 1351 } 1352 onError(ERROR_CODE_INVALID_RESPONSE, "no result in response"); 1353 } 1354 1355 public void onError(int code, String message) { 1356 if (code == ERROR_CODE_CANCELED) { 1357 cancel(true /* mayInterruptIfRunning */); 1358 return; 1359 } 1360 setException(convertErrorToException(code, message)); 1361 } 1362 } 1363 } 1364 1365 private abstract class Future2Task<T> 1366 extends BaseFutureTask<T> implements AccountManagerFuture<T> { 1367 final AccountManagerCallback<T> mCallback; 1368 public Future2Task(Handler handler, AccountManagerCallback<T> callback) { 1369 super(handler); 1370 mCallback = callback; 1371 } 1372 1373 protected void done() { 1374 if (mCallback != null) { 1375 postRunnableToHandler(new Runnable() { 1376 public void run() { 1377 mCallback.run(Future2Task.this); 1378 } 1379 }); 1380 } 1381 } 1382 1383 public Future2Task<T> start() { 1384 startTask(); 1385 return this; 1386 } 1387 1388 private T internalGetResult(Long timeout, TimeUnit unit) 1389 throws OperationCanceledException, IOException, AuthenticatorException { 1390 if (!isDone()) { 1391 ensureNotOnMainThread(); 1392 } 1393 try { 1394 if (timeout == null) { 1395 return get(); 1396 } else { 1397 return get(timeout, unit); 1398 } 1399 } catch (InterruptedException e) { 1400 // fall through and cancel 1401 } catch (TimeoutException e) { 1402 // fall through and cancel 1403 } catch (CancellationException e) { 1404 // fall through and cancel 1405 } catch (ExecutionException e) { 1406 final Throwable cause = e.getCause(); 1407 if (cause instanceof IOException) { 1408 throw (IOException) cause; 1409 } else if (cause instanceof UnsupportedOperationException) { 1410 throw new AuthenticatorException(cause); 1411 } else if (cause instanceof AuthenticatorException) { 1412 throw (AuthenticatorException) cause; 1413 } else if (cause instanceof RuntimeException) { 1414 throw (RuntimeException) cause; 1415 } else if (cause instanceof Error) { 1416 throw (Error) cause; 1417 } else { 1418 throw new IllegalStateException(cause); 1419 } 1420 } finally { 1421 cancel(true /* interrupt if running */); 1422 } 1423 throw new OperationCanceledException(); 1424 } 1425 1426 public T getResult() 1427 throws OperationCanceledException, IOException, AuthenticatorException { 1428 return internalGetResult(null, null); 1429 } 1430 1431 public T getResult(long timeout, TimeUnit unit) 1432 throws OperationCanceledException, IOException, AuthenticatorException { 1433 return internalGetResult(timeout, unit); 1434 } 1435 1436 } 1437 1438 private Exception convertErrorToException(int code, String message) { 1439 if (code == ERROR_CODE_NETWORK_ERROR) { 1440 return new IOException(message); 1441 } 1442 1443 if (code == ERROR_CODE_UNSUPPORTED_OPERATION) { 1444 return new UnsupportedOperationException(message); 1445 } 1446 1447 if (code == ERROR_CODE_INVALID_RESPONSE) { 1448 return new AuthenticatorException(message); 1449 } 1450 1451 if (code == ERROR_CODE_BAD_ARGUMENTS) { 1452 return new IllegalArgumentException(message); 1453 } 1454 1455 return new AuthenticatorException(message); 1456 } 1457 1458 private class GetAuthTokenByTypeAndFeaturesTask 1459 extends AmsTask implements AccountManagerCallback<Bundle> { 1460 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType, 1461 final String[] features, Activity activityForPrompting, 1462 final Bundle addAccountOptions, final Bundle loginOptions, 1463 AccountManagerCallback<Bundle> callback, Handler handler) { 1464 super(activityForPrompting, handler, callback); 1465 if (accountType == null) throw new IllegalArgumentException("account type is null"); 1466 mAccountType = accountType; 1467 mAuthTokenType = authTokenType; 1468 mFeatures = features; 1469 mAddAccountOptions = addAccountOptions; 1470 mLoginOptions = loginOptions; 1471 mMyCallback = this; 1472 } 1473 volatile AccountManagerFuture<Bundle> mFuture = null; 1474 final String mAccountType; 1475 final String mAuthTokenType; 1476 final String[] mFeatures; 1477 final Bundle mAddAccountOptions; 1478 final Bundle mLoginOptions; 1479 final AccountManagerCallback<Bundle> mMyCallback; 1480 private volatile int mNumAccounts = 0; 1481 1482 public void doWork() throws RemoteException { 1483 getAccountsByTypeAndFeatures(mAccountType, mFeatures, 1484 new AccountManagerCallback<Account[]>() { 1485 public void run(AccountManagerFuture<Account[]> future) { 1486 Account[] accounts; 1487 try { 1488 accounts = future.getResult(); 1489 } catch (OperationCanceledException e) { 1490 setException(e); 1491 return; 1492 } catch (IOException e) { 1493 setException(e); 1494 return; 1495 } catch (AuthenticatorException e) { 1496 setException(e); 1497 return; 1498 } 1499 1500 mNumAccounts = accounts.length; 1501 1502 if (accounts.length == 0) { 1503 if (mActivity != null) { 1504 // no accounts, add one now. pretend that the user directly 1505 // made this request 1506 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures, 1507 mAddAccountOptions, mActivity, mMyCallback, mHandler); 1508 } else { 1509 // send result since we can't prompt to add an account 1510 Bundle result = new Bundle(); 1511 result.putString(KEY_ACCOUNT_NAME, null); 1512 result.putString(KEY_ACCOUNT_TYPE, null); 1513 result.putString(KEY_AUTHTOKEN, null); 1514 try { 1515 mResponse.onResult(result); 1516 } catch (RemoteException e) { 1517 // this will never happen 1518 } 1519 // we are done 1520 } 1521 } else if (accounts.length == 1) { 1522 // have a single account, return an authtoken for it 1523 if (mActivity == null) { 1524 mFuture = getAuthToken(accounts[0], mAuthTokenType, 1525 false /* notifyAuthFailure */, mMyCallback, mHandler); 1526 } else { 1527 mFuture = getAuthToken(accounts[0], 1528 mAuthTokenType, mLoginOptions, 1529 mActivity, mMyCallback, mHandler); 1530 } 1531 } else { 1532 if (mActivity != null) { 1533 IAccountManagerResponse chooseResponse = 1534 new IAccountManagerResponse.Stub() { 1535 public void onResult(Bundle value) throws RemoteException { 1536 Account account = new Account( 1537 value.getString(KEY_ACCOUNT_NAME), 1538 value.getString(KEY_ACCOUNT_TYPE)); 1539 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, 1540 mActivity, mMyCallback, mHandler); 1541 } 1542 1543 public void onError(int errorCode, String errorMessage) 1544 throws RemoteException { 1545 mResponse.onError(errorCode, errorMessage); 1546 } 1547 }; 1548 // have many accounts, launch the chooser 1549 Intent intent = new Intent(); 1550 intent.setClassName("android", 1551 "android.accounts.ChooseAccountActivity"); 1552 intent.putExtra(KEY_ACCOUNTS, accounts); 1553 intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE, 1554 new AccountManagerResponse(chooseResponse)); 1555 mActivity.startActivity(intent); 1556 // the result will arrive via the IAccountManagerResponse 1557 } else { 1558 // send result since we can't prompt to select an account 1559 Bundle result = new Bundle(); 1560 result.putString(KEY_ACCOUNTS, null); 1561 try { 1562 mResponse.onResult(result); 1563 } catch (RemoteException e) { 1564 // this will never happen 1565 } 1566 // we are done 1567 } 1568 } 1569 }}, mHandler); 1570 } 1571 1572 public void run(AccountManagerFuture<Bundle> future) { 1573 try { 1574 final Bundle result = future.getResult(); 1575 if (mNumAccounts == 0) { 1576 final String accountName = result.getString(KEY_ACCOUNT_NAME); 1577 final String accountType = result.getString(KEY_ACCOUNT_TYPE); 1578 if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(accountType)) { 1579 setException(new AuthenticatorException("account not in result")); 1580 return; 1581 } 1582 final Account account = new Account(accountName, accountType); 1583 mNumAccounts = 1; 1584 getAuthToken(account, mAuthTokenType, null /* options */, mActivity, 1585 mMyCallback, mHandler); 1586 return; 1587 } 1588 set(result); 1589 } catch (OperationCanceledException e) { 1590 cancel(true /* mayInterruptIfRUnning */); 1591 } catch (IOException e) { 1592 setException(e); 1593 } catch (AuthenticatorException e) { 1594 setException(e); 1595 } 1596 } 1597 } 1598 1599 /** 1600 * This convenience helper combines the functionality of 1601 * {@link #getAccountsByTypeAndFeatures}, {@link #getAuthToken}, and 1602 * {@link #addAccount}. 1603 * 1604 * <p>This method gets a list of the accounts matching the 1605 * specified type and feature set; if there is exactly one, it is 1606 * used; if there are more than one, the user is prompted to pick one; 1607 * if there are none, the user is prompted to add one. Finally, 1608 * an auth token is acquired for the chosen account. 1609 * 1610 * <p>This method may be called from any thread, but the returned 1611 * {@link AccountManagerFuture} must not be used on the main thread. 1612 * 1613 * <p>This method requires the caller to hold the permission 1614 * {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 1615 * 1616 * @param accountType The account type required 1617 * (see {@link #getAccountsByType}), must not be null 1618 * @param authTokenType The desired auth token type 1619 * (see {@link #getAuthToken}), must not be null 1620 * @param features Required features for the account 1621 * (see {@link #getAccountsByTypeAndFeatures}), may be null or empty 1622 * @param activity The {@link Activity} context to use for launching new 1623 * sub-Activities to prompt to add an account, select an account, 1624 * and/or enter a password, as necessary; used only to call 1625 * startActivity(); should not be null 1626 * @param addAccountOptions Authenticator-specific options to use for 1627 * adding new accounts; may be null or empty 1628 * @param getAuthTokenOptions Authenticator-specific options to use for 1629 * getting auth tokens; may be null or empty 1630 * @param callback Callback to invoke when the request completes, 1631 * null for no callback 1632 * @param handler {@link Handler} identifying the callback thread, 1633 * null for the main thread 1634 * @return An {@link AccountManagerFuture} which resolves to a Bundle with 1635 * at least the following fields: 1636 * <ul> 1637 * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account 1638 * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account 1639 * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted 1640 * </ul> 1641 * 1642 * If an error occurred, {@link AccountManagerFuture#getResult()} throws: 1643 * <ul> 1644 * <li> {@link AuthenticatorException} if no authenticator was registered for 1645 * this account type or the authenticator failed to respond 1646 * <li> {@link OperationCanceledException} if the operation was canceled for 1647 * any reason, including the user canceling any operation 1648 * <li> {@link IOException} if the authenticator experienced an I/O problem 1649 * updating settings, usually because of network trouble 1650 * </ul> 1651 */ 1652 public AccountManagerFuture<Bundle> getAuthTokenByFeatures( 1653 final String accountType, final String authTokenType, final String[] features, 1654 final Activity activity, final Bundle addAccountOptions, 1655 final Bundle getAuthTokenOptions, 1656 final AccountManagerCallback<Bundle> callback, final Handler handler) { 1657 if (accountType == null) throw new IllegalArgumentException("account type is null"); 1658 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 1659 final GetAuthTokenByTypeAndFeaturesTask task = 1660 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, 1661 activity, addAccountOptions, getAuthTokenOptions, callback, handler); 1662 task.start(); 1663 return task; 1664 } 1665 1666 private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners = 1667 Maps.newHashMap(); 1668 1669 /** 1670 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent 1671 * so that it can read the updated list of accounts and send them to the listener 1672 * in mAccountsUpdatedListeners. 1673 */ 1674 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() { 1675 public void onReceive(final Context context, final Intent intent) { 1676 final Account[] accounts = getAccounts(); 1677 // send the result to the listeners 1678 synchronized (mAccountsUpdatedListeners) { 1679 for (Map.Entry<OnAccountsUpdateListener, Handler> entry : 1680 mAccountsUpdatedListeners.entrySet()) { 1681 postToHandler(entry.getValue(), entry.getKey(), accounts); 1682 } 1683 } 1684 } 1685 }; 1686 1687 /** 1688 * Adds an {@link OnAccountsUpdateListener} to this instance of the 1689 * {@link AccountManager}. This listener will be notified whenever the 1690 * list of accounts on the device changes. 1691 * 1692 * <p>As long as this listener is present, the AccountManager instance 1693 * will not be garbage-collected, and neither will the {@link Context} 1694 * used to retrieve it, which may be a large Activity instance. To avoid 1695 * memory leaks, you must remove this listener before then. Normally 1696 * listeners are added in an Activity or Service's {@link Activity#onCreate} 1697 * and removed in {@link Activity#onDestroy}. 1698 * 1699 * <p>It is safe to call this method from the main thread. 1700 * 1701 * <p>No permission is required to call this method. 1702 * 1703 * @param listener The listener to send notifications to 1704 * @param handler {@link Handler} identifying the thread to use 1705 * for notifications, null for the main thread 1706 * @param updateImmediately If true, the listener will be invoked 1707 * (on the handler thread) right away with the current account list 1708 * @throws IllegalArgumentException if listener is null 1709 * @throws IllegalStateException if listener was already added 1710 */ 1711 public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener, 1712 Handler handler, boolean updateImmediately) { 1713 if (listener == null) { 1714 throw new IllegalArgumentException("the listener is null"); 1715 } 1716 synchronized (mAccountsUpdatedListeners) { 1717 if (mAccountsUpdatedListeners.containsKey(listener)) { 1718 throw new IllegalStateException("this listener is already added"); 1719 } 1720 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty(); 1721 1722 mAccountsUpdatedListeners.put(listener, handler); 1723 1724 if (wasEmpty) { 1725 // Register a broadcast receiver to monitor account changes 1726 IntentFilter intentFilter = new IntentFilter(); 1727 intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION); 1728 // To recover from disk-full. 1729 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 1730 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter); 1731 } 1732 } 1733 1734 if (updateImmediately) { 1735 postToHandler(handler, listener, getAccounts()); 1736 } 1737 } 1738 1739 /** 1740 * Removes an {@link OnAccountsUpdateListener} previously registered with 1741 * {@link #addOnAccountsUpdatedListener}. The listener will no longer 1742 * receive notifications of account changes. 1743 * 1744 * <p>It is safe to call this method from the main thread. 1745 * 1746 * <p>No permission is required to call this method. 1747 * 1748 * @param listener The previously added listener to remove 1749 * @throws IllegalArgumentException if listener is null 1750 * @throws IllegalStateException if listener was not already added 1751 */ 1752 public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) { 1753 if (listener == null) throw new IllegalArgumentException("listener is null"); 1754 synchronized (mAccountsUpdatedListeners) { 1755 if (!mAccountsUpdatedListeners.containsKey(listener)) { 1756 Log.e(TAG, "Listener was not previously added"); 1757 return; 1758 } 1759 mAccountsUpdatedListeners.remove(listener); 1760 if (mAccountsUpdatedListeners.isEmpty()) { 1761 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver); 1762 } 1763 } 1764 } 1765 } 1766