AccountManager.java revision 31957f1badbb900bbfe211317e1ea992d650a72d
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.util.Log; 31 32import java.io.IOException; 33import java.util.concurrent.Callable; 34import java.util.concurrent.CancellationException; 35import java.util.concurrent.ExecutionException; 36import java.util.concurrent.FutureTask; 37import java.util.concurrent.TimeoutException; 38import java.util.concurrent.TimeUnit; 39import java.util.HashMap; 40import java.util.Map; 41 42import com.google.android.collect.Maps; 43 44/** 45 * A class that helps with interactions with the AccountManager Service. It provides 46 * methods to allow for account, password, and authtoken management for all accounts on the 47 * device. One accesses the {@link AccountManager} by calling: 48 * <pre> 49 * AccountManager accountManager = AccountManager.get(context); 50 * </pre> 51 * 52 * <p> 53 * The AccountManager Service provides storage for the accounts known to the system, 54 * provides methods to manage them, and allows the registration of authenticators to 55 * which operations such as addAccount and getAuthToken are delegated. 56 * <p> 57 * Many of the calls take an {@link AccountManagerCallback} and {@link Handler} as parameters. 58 * These calls return immediately but run asynchronously. If a callback is provided then 59 * {@link AccountManagerCallback#run} will be invoked wen the request completes, successfully 60 * or not. An {@link AccountManagerFuture} is returned by these requests and also passed into the 61 * callback. The result if retrieved by calling {@link AccountManagerFuture#getResult()} which 62 * either returns the result or throws an exception as appropriate. 63 * <p> 64 * The asynchronous request can be made blocking by not providing a callback and instead 65 * calling {@link AccountManagerFuture#getResult()} on the future that is returned. This will 66 * cause the running thread to block until the result is returned. Keep in mind that one 67 * should not block the main thread in this way. Instead one should either use a callback, 68 * thus making the call asynchronous, or make the blocking call on a separate thread. 69 * <p> 70 * If one wants to ensure that the callback is invoked from a specific handler then they should 71 * pass the handler to the request. This makes it easier to ensure thread-safety by running 72 * all of one's logic from a single handler. 73 */ 74public class AccountManager { 75 private static final String TAG = "AccountManager"; 76 77 public static final int ERROR_CODE_REMOTE_EXCEPTION = 1; 78 public static final int ERROR_CODE_NETWORK_ERROR = 3; 79 public static final int ERROR_CODE_CANCELED = 4; 80 public static final int ERROR_CODE_INVALID_RESPONSE = 5; 81 public static final int ERROR_CODE_UNSUPPORTED_OPERATION = 6; 82 public static final int ERROR_CODE_BAD_ARGUMENTS = 7; 83 public static final int ERROR_CODE_BAD_REQUEST = 8; 84 85 public static final String KEY_ACCOUNTS = "accounts"; 86 public static final String KEY_AUTHENTICATOR_TYPES = "authenticator_types"; 87 public static final String KEY_USERDATA = "userdata"; 88 public static final String KEY_AUTHTOKEN = "authtoken"; 89 public static final String KEY_PASSWORD = "password"; 90 public static final String KEY_ACCOUNT_NAME = "authAccount"; 91 public static final String KEY_ACCOUNT_TYPE = "accountType"; 92 public static final String KEY_ERROR_CODE = "errorCode"; 93 public static final String KEY_ERROR_MESSAGE = "errorMessage"; 94 public static final String KEY_INTENT = "intent"; 95 public static final String KEY_BOOLEAN_RESULT = "booleanResult"; 96 public static final String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "accountAuthenticatorResponse"; 97 public static final String KEY_ACCOUNT_MANAGER_RESPONSE = "accountManagerResponse"; 98 public static final String KEY_AUTH_FAILED_MESSAGE = "authFailedMessage"; 99 public static final String KEY_AUTH_TOKEN_LABEL = "authTokenLabelKey"; 100 public static final String ACTION_AUTHENTICATOR_INTENT = 101 "android.accounts.AccountAuthenticator"; 102 public static final String AUTHENTICATOR_META_DATA_NAME = 103 "android.accounts.AccountAuthenticator"; 104 public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator"; 105 106 private final Context mContext; 107 private final IAccountManager mService; 108 private final Handler mMainHandler; 109 /** 110 * Action sent as a broadcast Intent by the AccountsService 111 * when accounts are added to and/or removed from the device's 112 * database. 113 */ 114 public static final String LOGIN_ACCOUNTS_CHANGED_ACTION = 115 "android.accounts.LOGIN_ACCOUNTS_CHANGED"; 116 117 /** 118 * @hide 119 */ 120 public AccountManager(Context context, IAccountManager service) { 121 mContext = context; 122 mService = service; 123 mMainHandler = new Handler(mContext.getMainLooper()); 124 } 125 126 /** 127 * @hide used for testing only 128 */ 129 public AccountManager(Context context, IAccountManager service, Handler handler) { 130 mContext = context; 131 mService = service; 132 mMainHandler = handler; 133 } 134 135 /** 136 * Retrieve an AccountManager instance that is associated with the context that is passed in. 137 * Certain calls such as {@link #addOnAccountsUpdatedListener} use this context internally, 138 * so the caller must take care to use a {@link Context} whose lifetime is associated with 139 * the listener registration. 140 * @param context The {@link Context} to use when necessary 141 * @return an {@link AccountManager} instance that is associated with context 142 */ 143 public static AccountManager get(Context context) { 144 return (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); 145 } 146 147 /** 148 * Get the password that is associated with the account. Returns null if the account does 149 * not exist. 150 * <p> 151 * Requires that the caller has permission 152 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 153 * with the same UID as the Authenticator for the account. 154 */ 155 public String getPassword(final Account account) { 156 try { 157 return mService.getPassword(account); 158 } catch (RemoteException e) { 159 // will never happen 160 throw new RuntimeException(e); 161 } 162 } 163 164 /** 165 * Get the user data named by "key" that is associated with the account. 166 * Returns null if the account does not exist or if it does not have a value for key. 167 * <p> 168 * Requires that the caller has permission 169 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 170 * with the same UID as the Authenticator for the account. 171 */ 172 public String getUserData(final Account account, final String key) { 173 try { 174 return mService.getUserData(account, key); 175 } catch (RemoteException e) { 176 // will never happen 177 throw new RuntimeException(e); 178 } 179 } 180 181 /** 182 * Query the AccountManager Service for an array that contains a 183 * {@link AuthenticatorDescription} for each registered authenticator. 184 * @return an array that contains all the authenticators known to the AccountManager service. 185 * This array will be empty if there are no authenticators and will never return null. 186 * <p> 187 * No permission is required to make this call. 188 */ 189 public AuthenticatorDescription[] getAuthenticatorTypes() { 190 try { 191 return mService.getAuthenticatorTypes(); 192 } catch (RemoteException e) { 193 // will never happen 194 throw new RuntimeException(e); 195 } 196 } 197 198 /** 199 * Query the AccountManager Service for all accounts. 200 * @return an array that contains all the accounts known to the AccountManager service. 201 * This array will be empty if there are no accounts and will never return null. 202 * <p> 203 * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS} 204 */ 205 public Account[] getAccounts() { 206 try { 207 return mService.getAccounts(null); 208 } catch (RemoteException e) { 209 // won't ever happen 210 throw new RuntimeException(e); 211 } 212 } 213 214 /** 215 * Query the AccountManager for the set of accounts that have a given type. If null 216 * is passed as the type than all accounts are returned. 217 * @param type the account type by which to filter, or null to get all accounts 218 * @return an array that contains the accounts that match the specified type. This array 219 * will be empty if no accounts match. It will never return null. 220 * <p> 221 * Requires that the caller has permission {@link android.Manifest.permission#GET_ACCOUNTS} 222 */ 223 public Account[] getAccountsByType(String type) { 224 try { 225 return mService.getAccounts(type); 226 } catch (RemoteException e) { 227 // won't ever happen 228 throw new RuntimeException(e); 229 } 230 } 231 232 /** 233 * Add an account to the AccountManager's set of known accounts. 234 * <p> 235 * Requires that the caller has permission 236 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 237 * with the same UID as the Authenticator for the account. 238 * @param account The account to add 239 * @param password The password to associate with the account. May be null. 240 * @param userdata A bundle of key/value pairs to set as the account's userdata. May be null. 241 * @return true if the account was sucessfully added, false otherwise, for example, 242 * if the account already exists or if the account is null 243 */ 244 public boolean addAccountExplicitly(Account account, String password, Bundle userdata) { 245 try { 246 return mService.addAccount(account, password, userdata); 247 } catch (RemoteException e) { 248 // won't ever happen 249 throw new RuntimeException(e); 250 } 251 } 252 253 /** 254 * Removes the given account. If this account does not exist then this call has no effect. 255 * <p> 256 * This call returns immediately but runs asynchronously and the result is accessed via the 257 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 258 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 259 * method asynchronously then they will generally pass in a callback object that will get 260 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 261 * they will generally pass null for the callback and instead call 262 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 263 * which will then block until the request completes. 264 * <p> 265 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 266 * 267 * @param account The {@link Account} to remove 268 * @param callback A callback to invoke when the request completes. If null then 269 * no callback is invoked. 270 * @param handler The {@link Handler} to use to invoke the callback. If null then the 271 * main thread's {@link Handler} is used. 272 * @return an {@link AccountManagerFuture} that represents the future result of the call. 273 * The future result is a {@link Boolean} that is true if the account is successfully removed 274 * or false if the authenticator refuses to remove the account. 275 */ 276 public AccountManagerFuture<Boolean> removeAccount(final Account account, 277 AccountManagerCallback<Boolean> callback, Handler handler) { 278 return new Future2Task<Boolean>(handler, callback) { 279 public void doWork() throws RemoteException { 280 mService.removeAccount(mResponse, account); 281 } 282 public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { 283 if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { 284 throw new AuthenticatorException("no result in response"); 285 } 286 return bundle.getBoolean(KEY_BOOLEAN_RESULT); 287 } 288 }.start(); 289 } 290 291 /** 292 * Removes the given authtoken. If this authtoken does not exist for the given account type 293 * then this call has no effect. 294 * <p> 295 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 296 * @param accountType the account type of the authtoken to invalidate 297 * @param authToken the authtoken to invalidate 298 */ 299 public void invalidateAuthToken(final String accountType, final String authToken) { 300 try { 301 mService.invalidateAuthToken(accountType, authToken); 302 } catch (RemoteException e) { 303 // won't ever happen 304 throw new RuntimeException(e); 305 } 306 } 307 308 /** 309 * Gets the authtoken named by "authTokenType" for the specified account if it is cached 310 * by the AccountManager. If no authtoken is cached then null is returned rather than 311 * asking the authenticaticor to generate one. If the account or the 312 * authtoken do not exist then null is returned. 313 * <p> 314 * Requires that the caller has permission 315 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 316 * with the same UID as the Authenticator for the account. 317 * @param account the account whose authtoken is to be retrieved, must not be null 318 * @param authTokenType the type of authtoken to retrieve 319 * @return an authtoken for the given account and authTokenType, if one is cached by the 320 * AccountManager, null otherwise. 321 */ 322 public String peekAuthToken(final Account account, final String authTokenType) { 323 if (account == null) { 324 throw new IllegalArgumentException("the account must not be null"); 325 } 326 if (authTokenType == null) { 327 return null; 328 } 329 try { 330 return mService.peekAuthToken(account, authTokenType); 331 } catch (RemoteException e) { 332 // won't ever happen 333 throw new RuntimeException(e); 334 } 335 } 336 337 /** 338 * Sets the password for the account. The password may be null. If the account does not exist 339 * then this call has no affect. 340 * <p> 341 * Requires that the caller has permission 342 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 343 * with the same UID as the Authenticator for the account. 344 * @param account the account whose password is to be set. Must not be null. 345 * @param password the password to set for the account. May be null. 346 */ 347 public void setPassword(final Account account, final String password) { 348 if (account == null) { 349 throw new IllegalArgumentException("the account must not be null"); 350 } 351 try { 352 mService.setPassword(account, password); 353 } catch (RemoteException e) { 354 // won't ever happen 355 throw new RuntimeException(e); 356 } 357 } 358 359 /** 360 * Sets the password for account to null. If the account does not exist then this call 361 * has no effect. 362 * <p> 363 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 364 * @param account the account whose password is to be cleared. Must not be null. 365 */ 366 public void clearPassword(final Account account) { 367 if (account == null) { 368 throw new IllegalArgumentException("the account must not be null"); 369 } 370 try { 371 mService.clearPassword(account); 372 } catch (RemoteException e) { 373 // won't ever happen 374 throw new RuntimeException(e); 375 } 376 } 377 378 /** 379 * Sets account's userdata named "key" to the specified value. If the account does not 380 * exist then this call has no effect. 381 * <p> 382 * Requires that the caller has permission 383 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 384 * with the same UID as the Authenticator for the account. 385 * @param account the account whose userdata is to be set. Must not be null. 386 * @param key the key of the userdata to set. Must not be null. 387 * @param value the value to set. May be null. 388 */ 389 public void setUserData(final Account account, final String key, final String value) { 390 if (account == null) { 391 throw new IllegalArgumentException("the account must not be null"); 392 } 393 if (key == null) { 394 throw new IllegalArgumentException("the key must not be null"); 395 } 396 try { 397 mService.setUserData(account, key, value); 398 } catch (RemoteException e) { 399 // won't ever happen 400 throw new RuntimeException(e); 401 } 402 } 403 404 /** 405 * Sets the authtoken named by "authTokenType" to the value specified by authToken. 406 * If the account does not exist then this call has no effect. 407 * <p> 408 * Requires that the caller has permission 409 * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and is running 410 * with the same UID as the Authenticator for the account. 411 * @param account the account whose authtoken is to be set. Must not be null. 412 * @param authTokenType the type of the authtoken to set. Must not be null. 413 * @param authToken the authToken to set. May be null. 414 */ 415 public void setAuthToken(Account account, final String authTokenType, final String authToken) { 416 try { 417 mService.setAuthToken(account, authTokenType, authToken); 418 } catch (RemoteException e) { 419 // won't ever happen 420 throw new RuntimeException(e); 421 } 422 } 423 424 /** 425 * Convenience method that makes a blocking call to 426 * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)} 427 * then extracts and returns the value of {@link #KEY_AUTHTOKEN} from its result. 428 * <p> 429 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. 430 * @param account the account whose authtoken is to be retrieved, must not be null 431 * @param authTokenType the type of authtoken to retrieve 432 * @param notifyAuthFailure if true, cause the AccountManager to put up a "sign-on" notification 433 * for the account if no authtoken is cached by the AccountManager and the the authenticator 434 * does not have valid credentials to get an authtoken. 435 * @return an authtoken for the given account and authTokenType, if one is cached by the 436 * AccountManager, null otherwise. 437 * @throws AuthenticatorException if the authenticator is not present, unreachable or returns 438 * an invalid response. 439 * @throws OperationCanceledException if the request is canceled for any reason 440 * @throws java.io.IOException if the authenticator experiences an IOException while attempting 441 * to communicate with its backend server. 442 */ 443 public String blockingGetAuthToken(Account account, String authTokenType, 444 boolean notifyAuthFailure) 445 throws OperationCanceledException, IOException, AuthenticatorException { 446 Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */, 447 null /* handler */).getResult(); 448 return bundle.getString(KEY_AUTHTOKEN); 449 } 450 451 /** 452 * Request that an authtoken of the specified type be returned for an account. 453 * If the Account Manager has a cached authtoken of the requested type then it will 454 * service the request itself. Otherwise it will pass the request on to the authenticator. 455 * The authenticator can try to service this request with information it already has stored 456 * in the AccountManager but may need to launch an activity to prompt the 457 * user to enter credentials. If it is able to retrieve the authtoken it will be returned 458 * in the result. 459 * <p> 460 * If the authenticator needs to prompt the user for credentials it will return an intent to 461 * the activity that will do the prompting. If an activity is supplied then that activity 462 * will be used to launch the intent and the result will come from it. Otherwise a result will 463 * be returned that contains the intent. 464 * <p> 465 * This call returns immediately but runs asynchronously and the result is accessed via the 466 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 467 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 468 * method asynchronously then they will generally pass in a callback object that will get 469 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 470 * they will generally pass null for the callback and instead call 471 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 472 * which will then block until the request completes. 473 * <p> 474 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. 475 * 476 * @param account The account whose credentials are to be updated. 477 * @param authTokenType the auth token to retrieve as part of updating the credentials. 478 * May be null. 479 * @param options authenticator specific options for the request 480 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 481 * the intent will be started with this activity. If activity is null then the result will 482 * be returned as-is. 483 * @param callback A callback to invoke when the request completes. If null then 484 * no callback is invoked. 485 * @param handler The {@link Handler} to use to invoke the callback. If null then the 486 * main thread's {@link Handler} is used. 487 * @return an {@link AccountManagerFuture} that represents the future result of the call. 488 * The future result is a {@link Bundle} that contains: 489 * <ul> 490 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} 491 * </ul> 492 * If the user presses "back" then the request will be canceled. 493 */ 494 public AccountManagerFuture<Bundle> getAuthToken( 495 final Account account, final String authTokenType, final Bundle options, 496 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { 497 if (activity == null) throw new IllegalArgumentException("activity is null"); 498 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 499 return new AmsTask(activity, handler, callback) { 500 public void doWork() throws RemoteException { 501 mService.getAuthToken(mResponse, account, authTokenType, 502 false /* notifyOnAuthFailure */, true /* expectActivityLaunch */, 503 options); 504 } 505 }.start(); 506 } 507 508 /** 509 * Request that an authtoken of the specified type be returned for an account. 510 * If the Account Manager has a cached authtoken of the requested type then it will 511 * service the request itself. Otherwise it will pass the request on to the authenticator. 512 * The authenticator can try to service this request with information it already has stored 513 * in the AccountManager but may need to launch an activity to prompt the 514 * user to enter credentials. If it is able to retrieve the authtoken it will be returned 515 * in the result. 516 * <p> 517 * If the authenticator needs to prompt the user for credentials it will return an intent for 518 * an activity that will do the prompting. If an intent is returned and notifyAuthFailure 519 * is true then a notification will be created that launches this intent. 520 * <p> 521 * This call returns immediately but runs asynchronously and the result is accessed via the 522 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 523 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 524 * method asynchronously then they will generally pass in a callback object that will get 525 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 526 * they will generally pass null for the callback and instead call 527 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 528 * which will then block until the request completes. 529 * <p> 530 * Requires that the caller has permission {@link android.Manifest.permission#USE_CREDENTIALS}. 531 * 532 * @param account The account whose credentials are to be updated. 533 * @param authTokenType the auth token to retrieve as part of updating the credentials. 534 * May be null. 535 * @param notifyAuthFailure if true and the authenticator returns a {@link #KEY_INTENT} in the 536 * result then a "sign-on needed" notification will be created that will launch this intent. 537 * @param callback A callback to invoke when the request completes. If null then 538 * no callback is invoked. 539 * @param handler The {@link Handler} to use to invoke the callback. If null then the 540 * main thread's {@link Handler} is used. 541 * @return an {@link AccountManagerFuture} that represents the future result of the call. 542 * The future result is a {@link Bundle} that contains either: 543 * <ul> 544 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 545 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} 546 * if the authenticator is able to retrieve the auth token 547 * </ul> 548 * If the user presses "back" then the request will be canceled. 549 */ 550 public AccountManagerFuture<Bundle> getAuthToken( 551 final Account account, final String authTokenType, final boolean notifyAuthFailure, 552 AccountManagerCallback<Bundle> callback, Handler handler) { 553 if (account == null) throw new IllegalArgumentException("account is null"); 554 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 555 return new AmsTask(null, handler, callback) { 556 public void doWork() throws RemoteException { 557 mService.getAuthToken(mResponse, account, authTokenType, 558 notifyAuthFailure, false /* expectActivityLaunch */, null /* options */); 559 } 560 }.start(); 561 } 562 563 /** 564 * Request that an account be added with the given accountType. This request 565 * is processed by the authenticator for the account type. If no authenticator is registered 566 * in the system then {@link AuthenticatorException} is thrown. 567 * <p> 568 * This call returns immediately but runs asynchronously and the result is accessed via the 569 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 570 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 571 * method asynchronously then they will generally pass in a callback object that will get 572 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 573 * they will generally pass null for the callback and instead call 574 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 575 * which will then block until the request completes. 576 * <p> 577 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 578 * 579 * @param accountType The type of account to add. This must not be null. 580 * @param authTokenType The account that is added should be able to service this auth token 581 * type. This may be null. 582 * @param requiredFeatures The account that is added should support these features. 583 * This array may be null or empty. 584 * @param addAccountOptions A bundle of authenticator-specific options that is passed on 585 * to the authenticator. This may be null. 586 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 587 * the intent will be started with this activity. If activity is null then the result will 588 * be returned as-is. 589 * @param callback A callback to invoke when the request completes. If null then 590 * no callback is invoked. 591 * @param handler The {@link Handler} to use to invoke the callback. If null then the 592 * main thread's {@link Handler} is used. 593 * @return an {@link AccountManagerFuture} that represents the future result of the call. 594 * The future result is a {@link Bundle} that contains either: 595 * <ul> 596 * <li> {@link #KEY_INTENT}, or 597 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} 598 * and {@link #KEY_AUTHTOKEN} (if an authTokenType was specified). 599 * </ul> 600 */ 601 public AccountManagerFuture<Bundle> addAccount(final String accountType, 602 final String authTokenType, final String[] requiredFeatures, 603 final Bundle addAccountOptions, 604 final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) { 605 if (accountType == null) { 606 throw new IllegalArgumentException(); 607 } 608 return new AmsTask(activity, handler, callback) { 609 public void doWork() throws RemoteException { 610 mService.addAcount(mResponse, accountType, authTokenType, 611 requiredFeatures, activity != null, addAccountOptions); 612 } 613 }.start(); 614 } 615 616 public AccountManagerFuture<Account[]> getAccountsByTypeAndFeatures( 617 final String type, final String[] features, 618 AccountManagerCallback<Account[]> callback, Handler handler) { 619 if (type == null) throw new IllegalArgumentException("type is null"); 620 return new Future2Task<Account[]>(handler, callback) { 621 public void doWork() throws RemoteException { 622 mService.getAccountsByFeatures(mResponse, type, features); 623 } 624 public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { 625 if (!bundle.containsKey(KEY_ACCOUNTS)) { 626 throw new AuthenticatorException("no result in response"); 627 } 628 final Parcelable[] parcelables = bundle.getParcelableArray(KEY_ACCOUNTS); 629 Account[] descs = new Account[parcelables.length]; 630 for (int i = 0; i < parcelables.length; i++) { 631 descs[i] = (Account) parcelables[i]; 632 } 633 return descs; 634 } 635 }.start(); 636 } 637 638 /** 639 * Requests that the authenticator checks that the user knows the credentials for the account. 640 * This is typically done by returning an intent to an activity that prompts the user to 641 * enter the credentials. This request 642 * is processed by the authenticator for the account. If no matching authenticator is 643 * registered in the system then {@link AuthenticatorException} is thrown. 644 * <p> 645 * This call returns immediately but runs asynchronously and the result is accessed via the 646 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 647 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 648 * method asynchronously then they will generally pass in a callback object that will get 649 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 650 * they will generally pass null for the callback and instead call 651 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 652 * which will then block until the request completes. 653 * <p> 654 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 655 * 656 * @param account The account whose credentials are to be checked 657 * @param options authenticator specific options for the request 658 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 659 * the intent will be started with this activity. If activity is null then the result will 660 * be returned as-is. 661 * @param callback A callback to invoke when the request completes. If null then 662 * no callback is invoked. 663 * @param handler The {@link Handler} to use to invoke the callback. If null then the 664 * main thread's {@link Handler} is used. 665 * @return an {@link AccountManagerFuture} that represents the future result of the call. 666 * The future result is a {@link Bundle} that contains either: 667 * <ul> 668 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 669 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct 670 * credentials 671 * </ul> 672 * If the user presses "back" then the request will be canceled. 673 */ 674 public AccountManagerFuture<Bundle> confirmCredentials(final Account account, 675 final Bundle options, 676 final Activity activity, 677 final AccountManagerCallback<Bundle> callback, 678 final Handler handler) { 679 return new AmsTask(activity, handler, callback) { 680 public void doWork() throws RemoteException { 681 mService.confirmCredentials(mResponse, account, options, activity != null); 682 } 683 }.start(); 684 } 685 686 /** 687 * Requests that the authenticator update the the credentials for a user. This is typically 688 * done by returning an intent to an activity that will prompt the user to update the stored 689 * credentials for the account. This request 690 * is processed by the authenticator for the account. If no matching authenticator is 691 * registered in the system then {@link AuthenticatorException} is thrown. 692 * <p> 693 * This call returns immediately but runs asynchronously and the result is accessed via the 694 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 695 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 696 * method asynchronously then they will generally pass in a callback object that will get 697 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 698 * they will generally pass null for the callback and instead call 699 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 700 * which will then block until the request completes. 701 * <p> 702 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 703 * 704 * @param account The account whose credentials are to be updated. 705 * @param authTokenType the auth token to retrieve as part of updating the credentials. 706 * May be null. 707 * @param options authenticator specific options for the request 708 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 709 * the intent will be started with this activity. If activity is null then the result will 710 * be returned as-is. 711 * @param callback A callback to invoke when the request completes. If null then 712 * no callback is invoked. 713 * @param handler The {@link Handler} to use to invoke the callback. If null then the 714 * main thread's {@link Handler} is used. 715 * @return an {@link AccountManagerFuture} that represents the future result of the call. 716 * The future result is a {@link Bundle} that contains either: 717 * <ul> 718 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 719 * <li> {@link #KEY_ACCOUNT_NAME} and {@link #KEY_ACCOUNT_TYPE} if the user enters the correct 720 * credentials, and optionally a {@link #KEY_AUTHTOKEN} if an authTokenType was provided. 721 * </ul> 722 * If the user presses "back" then the request will be canceled. 723 */ 724 public AccountManagerFuture<Bundle> updateCredentials(final Account account, 725 final String authTokenType, 726 final Bundle options, final Activity activity, 727 final AccountManagerCallback<Bundle> callback, 728 final Handler handler) { 729 return new AmsTask(activity, handler, callback) { 730 public void doWork() throws RemoteException { 731 mService.updateCredentials(mResponse, account, authTokenType, activity != null, 732 options); 733 } 734 }.start(); 735 } 736 737 /** 738 * Request that the properties for an authenticator be updated. This is typically done by 739 * returning an intent to an activity that will allow the user to make changes. This request 740 * is processed by the authenticator for the account. If no matching authenticator is 741 * registered in the system then {@link AuthenticatorException} is thrown. 742 * <p> 743 * This call returns immediately but runs asynchronously and the result is accessed via the 744 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 745 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 746 * method asynchronously then they will generally pass in a callback object that will get 747 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 748 * they will generally pass null for the callback and instead call 749 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 750 * which will then block until the request completes. 751 * <p> 752 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 753 * 754 * @param accountType The account type of the authenticator whose properties are to be edited. 755 * @param activity If the authenticator returns a {@link #KEY_INTENT} in the result then 756 * the intent will be started with this activity. If activity is null then the result will 757 * be returned as-is. 758 * @param callback A callback to invoke when the request completes. If null then 759 * no callback is invoked. 760 * @param handler The {@link Handler} to use to invoke the callback. If null then the 761 * main thread's {@link Handler} is used. 762 * @return an {@link AccountManagerFuture} that represents the future result of the call. 763 * The future result is a {@link Bundle} that contains either: 764 * <ul> 765 * <li> {@link #KEY_INTENT}, which is to be used to prompt the user for the credentials 766 * <li> nothing, returned if the edit completes successfully 767 * </ul> 768 * If the user presses "back" then the request will be canceled. 769 */ 770 public AccountManagerFuture<Bundle> editProperties(final String accountType, 771 final Activity activity, final AccountManagerCallback<Bundle> callback, 772 final Handler handler) { 773 return new AmsTask(activity, handler, callback) { 774 public void doWork() throws RemoteException { 775 mService.editProperties(mResponse, accountType, activity != null); 776 } 777 }.start(); 778 } 779 780 private void ensureNotOnMainThread() { 781 final Looper looper = Looper.myLooper(); 782 if (looper != null && looper == mContext.getMainLooper()) { 783 // We really want to throw an exception here, but GTalkService exercises this 784 // path quite a bit and needs some serious rewrite in order to work properly. 785 //noinspection ThrowableInstanceNeverThrow 786// Log.e(TAG, "calling this from your main thread can lead to deadlock and/or ANRs", 787// new Exception()); 788 // TODO(fredq) remove the log and throw this exception when the callers are fixed 789// throw new IllegalStateException( 790// "calling this from your main thread can lead to deadlock"); 791 } 792 } 793 794 private void postToHandler(Handler handler, final AccountManagerCallback<Bundle> callback, 795 final AccountManagerFuture<Bundle> future) { 796 handler = handler == null ? mMainHandler : handler; 797 handler.post(new Runnable() { 798 public void run() { 799 callback.run(future); 800 } 801 }); 802 } 803 804 private void postToHandler(Handler handler, final OnAccountsUpdateListener listener, 805 final Account[] accounts) { 806 final Account[] accountsCopy = new Account[accounts.length]; 807 // send a copy to make sure that one doesn't 808 // change what another sees 809 System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length); 810 handler = (handler == null) ? mMainHandler : handler; 811 handler.post(new Runnable() { 812 public void run() { 813 try { 814 listener.onAccountsUpdated(accountsCopy); 815 } catch (SQLException e) { 816 // Better luck next time. If the problem was disk-full, 817 // the STORAGE_OK intent will re-trigger the update. 818 Log.e(TAG, "Can't update accounts", e); 819 } 820 } 821 }); 822 } 823 824 private abstract class AmsTask extends FutureTask<Bundle> implements AccountManagerFuture<Bundle> { 825 final IAccountManagerResponse mResponse; 826 final Handler mHandler; 827 final AccountManagerCallback<Bundle> mCallback; 828 final Activity mActivity; 829 public AmsTask(Activity activity, Handler handler, AccountManagerCallback<Bundle> callback) { 830 super(new Callable<Bundle>() { 831 public Bundle call() throws Exception { 832 throw new IllegalStateException("this should never be called"); 833 } 834 }); 835 836 mHandler = handler; 837 mCallback = callback; 838 mActivity = activity; 839 mResponse = new Response(); 840 } 841 842 public final AccountManagerFuture<Bundle> start() { 843 try { 844 doWork(); 845 } catch (RemoteException e) { 846 setException(e); 847 } 848 return this; 849 } 850 851 public abstract void doWork() throws RemoteException; 852 853 private Bundle internalGetResult(Long timeout, TimeUnit unit) 854 throws OperationCanceledException, IOException, AuthenticatorException { 855 ensureNotOnMainThread(); 856 try { 857 if (timeout == null) { 858 return get(); 859 } else { 860 return get(timeout, unit); 861 } 862 } catch (CancellationException e) { 863 throw new OperationCanceledException(); 864 } catch (TimeoutException e) { 865 // fall through and cancel 866 } catch (InterruptedException e) { 867 // fall through and cancel 868 } catch (ExecutionException e) { 869 final Throwable cause = e.getCause(); 870 if (cause instanceof IOException) { 871 throw (IOException) cause; 872 } else if (cause instanceof UnsupportedOperationException) { 873 throw new AuthenticatorException(cause); 874 } else if (cause instanceof AuthenticatorException) { 875 throw (AuthenticatorException) cause; 876 } else if (cause instanceof RuntimeException) { 877 throw (RuntimeException) cause; 878 } else if (cause instanceof Error) { 879 throw (Error) cause; 880 } else { 881 throw new IllegalStateException(cause); 882 } 883 } finally { 884 cancel(true /* interrupt if running */); 885 } 886 throw new OperationCanceledException(); 887 } 888 889 public Bundle getResult() 890 throws OperationCanceledException, IOException, AuthenticatorException { 891 return internalGetResult(null, null); 892 } 893 894 public Bundle getResult(long timeout, TimeUnit unit) 895 throws OperationCanceledException, IOException, AuthenticatorException { 896 return internalGetResult(timeout, unit); 897 } 898 899 protected void done() { 900 if (mCallback != null) { 901 postToHandler(mHandler, mCallback, this); 902 } 903 } 904 905 /** Handles the responses from the AccountManager */ 906 private class Response extends IAccountManagerResponse.Stub { 907 public void onResult(Bundle bundle) { 908 Intent intent = bundle.getParcelable("intent"); 909 if (intent != null && mActivity != null) { 910 // since the user provided an Activity we will silently start intents 911 // that we see 912 mActivity.startActivity(intent); 913 // leave the Future running to wait for the real response to this request 914 } else if (bundle.getBoolean("retry")) { 915 try { 916 doWork(); 917 } catch (RemoteException e) { 918 // this will only happen if the system process is dead, which means 919 // we will be dying ourselves 920 } 921 } else { 922 set(bundle); 923 } 924 } 925 926 public void onError(int code, String message) { 927 if (code == ERROR_CODE_CANCELED) { 928 // the authenticator indicated that this request was canceled, do so now 929 cancel(true /* mayInterruptIfRunning */); 930 return; 931 } 932 setException(convertErrorToException(code, message)); 933 } 934 } 935 936 } 937 938 private abstract class BaseFutureTask<T> extends FutureTask<T> { 939 final public IAccountManagerResponse mResponse; 940 final Handler mHandler; 941 942 public BaseFutureTask(Handler handler) { 943 super(new Callable<T>() { 944 public T call() throws Exception { 945 throw new IllegalStateException("this should never be called"); 946 } 947 }); 948 mHandler = handler; 949 mResponse = new Response(); 950 } 951 952 public abstract void doWork() throws RemoteException; 953 954 public abstract T bundleToResult(Bundle bundle) throws AuthenticatorException; 955 956 protected void postRunnableToHandler(Runnable runnable) { 957 Handler handler = (mHandler == null) ? mMainHandler : mHandler; 958 handler.post(runnable); 959 } 960 961 protected void startTask() { 962 try { 963 doWork(); 964 } catch (RemoteException e) { 965 setException(e); 966 } 967 } 968 969 protected class Response extends IAccountManagerResponse.Stub { 970 public void onResult(Bundle bundle) { 971 try { 972 T result = bundleToResult(bundle); 973 if (result == null) { 974 return; 975 } 976 set(result); 977 return; 978 } catch (ClassCastException e) { 979 // we will set the exception below 980 } catch (AuthenticatorException e) { 981 // we will set the exception below 982 } 983 onError(ERROR_CODE_INVALID_RESPONSE, "no result in response"); 984 } 985 986 public void onError(int code, String message) { 987 if (code == ERROR_CODE_CANCELED) { 988 cancel(true /* mayInterruptIfRunning */); 989 return; 990 } 991 setException(convertErrorToException(code, message)); 992 } 993 } 994 } 995 996 private abstract class Future2Task<T> 997 extends BaseFutureTask<T> implements AccountManagerFuture<T> { 998 final AccountManagerCallback<T> mCallback; 999 public Future2Task(Handler handler, AccountManagerCallback<T> callback) { 1000 super(handler); 1001 mCallback = callback; 1002 } 1003 1004 protected void done() { 1005 if (mCallback != null) { 1006 postRunnableToHandler(new Runnable() { 1007 public void run() { 1008 mCallback.run(Future2Task.this); 1009 } 1010 }); 1011 } 1012 } 1013 1014 public Future2Task<T> start() { 1015 startTask(); 1016 return this; 1017 } 1018 1019 private T internalGetResult(Long timeout, TimeUnit unit) 1020 throws OperationCanceledException, IOException, AuthenticatorException { 1021 ensureNotOnMainThread(); 1022 try { 1023 if (timeout == null) { 1024 return get(); 1025 } else { 1026 return get(timeout, unit); 1027 } 1028 } catch (InterruptedException e) { 1029 // fall through and cancel 1030 } catch (TimeoutException e) { 1031 // fall through and cancel 1032 } catch (CancellationException e) { 1033 // fall through and cancel 1034 } catch (ExecutionException e) { 1035 final Throwable cause = e.getCause(); 1036 if (cause instanceof IOException) { 1037 throw (IOException) cause; 1038 } else if (cause instanceof UnsupportedOperationException) { 1039 throw new AuthenticatorException(cause); 1040 } else if (cause instanceof AuthenticatorException) { 1041 throw (AuthenticatorException) cause; 1042 } else if (cause instanceof RuntimeException) { 1043 throw (RuntimeException) cause; 1044 } else if (cause instanceof Error) { 1045 throw (Error) cause; 1046 } else { 1047 throw new IllegalStateException(cause); 1048 } 1049 } finally { 1050 cancel(true /* interrupt if running */); 1051 } 1052 throw new OperationCanceledException(); 1053 } 1054 1055 public T getResult() 1056 throws OperationCanceledException, IOException, AuthenticatorException { 1057 return internalGetResult(null, null); 1058 } 1059 1060 public T getResult(long timeout, TimeUnit unit) 1061 throws OperationCanceledException, IOException, AuthenticatorException { 1062 return internalGetResult(timeout, unit); 1063 } 1064 1065 } 1066 1067 private Exception convertErrorToException(int code, String message) { 1068 if (code == ERROR_CODE_NETWORK_ERROR) { 1069 return new IOException(message); 1070 } 1071 1072 if (code == ERROR_CODE_UNSUPPORTED_OPERATION) { 1073 return new UnsupportedOperationException(message); 1074 } 1075 1076 if (code == ERROR_CODE_INVALID_RESPONSE) { 1077 return new AuthenticatorException(message); 1078 } 1079 1080 if (code == ERROR_CODE_BAD_ARGUMENTS) { 1081 return new IllegalArgumentException(message); 1082 } 1083 1084 return new AuthenticatorException(message); 1085 } 1086 1087 private class GetAuthTokenByTypeAndFeaturesTask 1088 extends AmsTask implements AccountManagerCallback<Bundle> { 1089 GetAuthTokenByTypeAndFeaturesTask(final String accountType, final String authTokenType, 1090 final String[] features, Activity activityForPrompting, 1091 final Bundle addAccountOptions, final Bundle loginOptions, 1092 AccountManagerCallback<Bundle> callback, Handler handler) { 1093 super(activityForPrompting, handler, callback); 1094 if (accountType == null) throw new IllegalArgumentException("account type is null"); 1095 mAccountType = accountType; 1096 mAuthTokenType = authTokenType; 1097 mFeatures = features; 1098 mAddAccountOptions = addAccountOptions; 1099 mLoginOptions = loginOptions; 1100 mMyCallback = this; 1101 } 1102 volatile AccountManagerFuture<Bundle> mFuture = null; 1103 final String mAccountType; 1104 final String mAuthTokenType; 1105 final String[] mFeatures; 1106 final Bundle mAddAccountOptions; 1107 final Bundle mLoginOptions; 1108 final AccountManagerCallback<Bundle> mMyCallback; 1109 1110 public void doWork() throws RemoteException { 1111 getAccountsByTypeAndFeatures(mAccountType, mFeatures, 1112 new AccountManagerCallback<Account[]>() { 1113 public void run(AccountManagerFuture<Account[]> future) { 1114 Account[] accounts; 1115 try { 1116 accounts = future.getResult(); 1117 } catch (OperationCanceledException e) { 1118 setException(e); 1119 return; 1120 } catch (IOException e) { 1121 setException(e); 1122 return; 1123 } catch (AuthenticatorException e) { 1124 setException(e); 1125 return; 1126 } 1127 1128 if (accounts.length == 0) { 1129 if (mActivity != null) { 1130 // no accounts, add one now. pretend that the user directly 1131 // made this request 1132 mFuture = addAccount(mAccountType, mAuthTokenType, mFeatures, 1133 mAddAccountOptions, mActivity, mMyCallback, mHandler); 1134 } else { 1135 // send result since we can't prompt to add an account 1136 Bundle result = new Bundle(); 1137 result.putString(KEY_ACCOUNT_NAME, null); 1138 result.putString(KEY_ACCOUNT_TYPE, null); 1139 result.putString(KEY_AUTHTOKEN, null); 1140 try { 1141 mResponse.onResult(result); 1142 } catch (RemoteException e) { 1143 // this will never happen 1144 } 1145 // we are done 1146 } 1147 } else if (accounts.length == 1) { 1148 // have a single account, return an authtoken for it 1149 if (mActivity == null) { 1150 mFuture = getAuthToken(accounts[0], mAuthTokenType, 1151 false /* notifyAuthFailure */, mMyCallback, mHandler); 1152 } else { 1153 mFuture = getAuthToken(accounts[0], 1154 mAuthTokenType, mLoginOptions, 1155 mActivity, mMyCallback, mHandler); 1156 } 1157 } else { 1158 if (mActivity != null) { 1159 IAccountManagerResponse chooseResponse = 1160 new IAccountManagerResponse.Stub() { 1161 public void onResult(Bundle value) throws RemoteException { 1162 Account account = new Account( 1163 value.getString(KEY_ACCOUNT_NAME), 1164 value.getString(KEY_ACCOUNT_TYPE)); 1165 mFuture = getAuthToken(account, mAuthTokenType, mLoginOptions, 1166 mActivity, mMyCallback, mHandler); 1167 } 1168 1169 public void onError(int errorCode, String errorMessage) 1170 throws RemoteException { 1171 mResponse.onError(errorCode, errorMessage); 1172 } 1173 }; 1174 // have many accounts, launch the chooser 1175 Intent intent = new Intent(); 1176 intent.setClassName("android", 1177 "android.accounts.ChooseAccountActivity"); 1178 intent.putExtra(KEY_ACCOUNTS, accounts); 1179 intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE, 1180 new AccountManagerResponse(chooseResponse)); 1181 mActivity.startActivity(intent); 1182 // the result will arrive via the IAccountManagerResponse 1183 } else { 1184 // send result since we can't prompt to select an account 1185 Bundle result = new Bundle(); 1186 result.putString(KEY_ACCOUNTS, null); 1187 try { 1188 mResponse.onResult(result); 1189 } catch (RemoteException e) { 1190 // this will never happen 1191 } 1192 // we are done 1193 } 1194 } 1195 }}, mHandler); 1196 } 1197 1198 public void run(AccountManagerFuture<Bundle> future) { 1199 try { 1200 set(future.getResult()); 1201 } catch (OperationCanceledException e) { 1202 cancel(true /* mayInterruptIfRUnning */); 1203 } catch (IOException e) { 1204 setException(e); 1205 } catch (AuthenticatorException e) { 1206 setException(e); 1207 } 1208 } 1209 } 1210 1211 /** 1212 * Convenience method that combines the functionality of {@link #getAccountsByTypeAndFeatures}, 1213 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)}, 1214 * and {@link #addAccount}. It first gets the list of accounts that match accountType and the 1215 * feature set. If there are none then {@link #addAccount} is invoked with the authTokenType 1216 * feature set, and addAccountOptions. If there is exactly one then 1217 * {@link #getAuthToken(Account, String, Bundle, Activity, AccountManagerCallback, Handler)} is 1218 * called with that account. If there are more than one then a chooser activity is launched 1219 * to prompt the user to select one of them and then the authtoken is retrieved for it, 1220 * <p> 1221 * This call returns immediately but runs asynchronously and the result is accessed via the 1222 * {@link AccountManagerFuture} that is returned. This future is also passed as the sole 1223 * parameter to the {@link AccountManagerCallback}. If the caller wished to use this 1224 * method asynchronously then they will generally pass in a callback object that will get 1225 * invoked with the {@link AccountManagerFuture}. If they wish to use it synchronously then 1226 * they will generally pass null for the callback and instead call 1227 * {@link android.accounts.AccountManagerFuture#getResult()} on this method's return value, 1228 * which will then block until the request completes. 1229 * <p> 1230 * Requires that the caller has permission {@link android.Manifest.permission#MANAGE_ACCOUNTS}. 1231 * 1232 * @param accountType the accountType to query; this must be non-null 1233 * @param authTokenType the type of authtoken to retrieve; this must be non-null 1234 * @param features a filter for the accounts. See {@link #getAccountsByTypeAndFeatures}. 1235 * @param activityForPrompting The activity used to start any account management 1236 * activities that are required to fulfill this request. This may be null. 1237 * @param addAccountOptions authenticator-specific options used if an account needs to be added 1238 * @param getAuthTokenOptions authenticator-specific options passed to getAuthToken 1239 * @param callback A callback to invoke when the request completes. If null then 1240 * no callback is invoked. 1241 * @param handler The {@link Handler} to use to invoke the callback. If null then the 1242 * main thread's {@link Handler} is used. 1243 * @return an {@link AccountManagerFuture} that represents the future result of the call. 1244 * The future result is a {@link Bundle} that contains either: 1245 * <ul> 1246 * <li> {@link #KEY_INTENT}, if no activity is supplied yet an activity needs to launched to 1247 * fulfill the request. 1248 * <li> {@link #KEY_ACCOUNT_NAME}, {@link #KEY_ACCOUNT_TYPE} and {@link #KEY_AUTHTOKEN} if the 1249 * request completes successfully. 1250 * </ul> 1251 * If the user presses "back" then the request will be canceled. 1252 */ 1253 public AccountManagerFuture<Bundle> getAuthTokenByFeatures( 1254 final String accountType, final String authTokenType, final String[] features, 1255 final Activity activityForPrompting, final Bundle addAccountOptions, 1256 final Bundle getAuthTokenOptions, 1257 final AccountManagerCallback<Bundle> callback, final Handler handler) { 1258 if (accountType == null) throw new IllegalArgumentException("account type is null"); 1259 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 1260 final GetAuthTokenByTypeAndFeaturesTask task = 1261 new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType, features, 1262 activityForPrompting, addAccountOptions, getAuthTokenOptions, callback, handler); 1263 task.start(); 1264 return task; 1265 } 1266 1267 private final HashMap<OnAccountsUpdateListener, Handler> mAccountsUpdatedListeners = 1268 Maps.newHashMap(); 1269 1270 /** 1271 * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent 1272 * so that it can read the updated list of accounts and send them to the listener 1273 * in mAccountsUpdatedListeners. 1274 */ 1275 private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() { 1276 public void onReceive(final Context context, final Intent intent) { 1277 final Account[] accounts = getAccounts(); 1278 // send the result to the listeners 1279 synchronized (mAccountsUpdatedListeners) { 1280 for (Map.Entry<OnAccountsUpdateListener, Handler> entry : 1281 mAccountsUpdatedListeners.entrySet()) { 1282 postToHandler(entry.getValue(), entry.getKey(), accounts); 1283 } 1284 } 1285 } 1286 }; 1287 1288 /** 1289 * Add a {@link OnAccountsUpdateListener} to this instance of the {@link AccountManager}. 1290 * The listener is guaranteed to be invoked on the thread of the Handler that is passed 1291 * in or the main thread's Handler if handler is null. 1292 * <p> 1293 * You must remove this listener before the context that was used to retrieve this 1294 * {@link AccountManager} instance goes away. This generally means when the Activity 1295 * or Service you are running is stopped. 1296 * @param listener the listener to add 1297 * @param handler the Handler whose thread will be used to invoke the listener. If null 1298 * the AccountManager context's main thread will be used. 1299 * @param updateImmediately if true then the listener will be invoked as a result of this 1300 * call. 1301 * @throws IllegalArgumentException if listener is null 1302 * @throws IllegalStateException if listener was already added 1303 */ 1304 public void addOnAccountsUpdatedListener(final OnAccountsUpdateListener listener, 1305 Handler handler, boolean updateImmediately) { 1306 if (listener == null) { 1307 throw new IllegalArgumentException("the listener is null"); 1308 } 1309 synchronized (mAccountsUpdatedListeners) { 1310 if (mAccountsUpdatedListeners.containsKey(listener)) { 1311 throw new IllegalStateException("this listener is already added"); 1312 } 1313 final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty(); 1314 1315 mAccountsUpdatedListeners.put(listener, handler); 1316 1317 if (wasEmpty) { 1318 // Register a broadcast receiver to monitor account changes 1319 IntentFilter intentFilter = new IntentFilter(); 1320 intentFilter.addAction(LOGIN_ACCOUNTS_CHANGED_ACTION); 1321 // To recover from disk-full. 1322 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); 1323 mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter); 1324 } 1325 } 1326 1327 if (updateImmediately) { 1328 postToHandler(handler, listener, getAccounts()); 1329 } 1330 } 1331 1332 /** 1333 * Remove an {@link OnAccountsUpdateListener} that was previously registered with 1334 * {@link #addOnAccountsUpdatedListener}. 1335 * @param listener the listener to remove 1336 * @throws IllegalArgumentException if listener is null 1337 * @throws IllegalStateException if listener was not already added 1338 */ 1339 public void removeOnAccountsUpdatedListener(OnAccountsUpdateListener listener) { 1340 if (listener == null) { 1341 throw new IllegalArgumentException("the listener is null"); 1342 } 1343 synchronized (mAccountsUpdatedListeners) { 1344 if (!mAccountsUpdatedListeners.containsKey(listener)) { 1345 throw new IllegalStateException("this listener was not previously added"); 1346 } 1347 mAccountsUpdatedListeners.remove(listener); 1348 if (mAccountsUpdatedListeners.isEmpty()) { 1349 mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver); 1350 } 1351 } 1352 } 1353} 1354