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